<?php namespace Common\Channels; use App\Models\Channel; use BadMethodCallException; use Common\Core\Prerender\Actions\ReplacePlaceholders; use Common\Database\Datasource\Datasource; use Illuminate\Database\Eloquent\Model; use Illuminate\Pagination\AbstractPaginator; use Illuminate\Pagination\Paginator; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Str; class LoadChannelContent { public function execute( Channel $channel, array $params = [], Channel $parent = null, ): ?AbstractPaginator { $params['perPage'] = $params['perPage'] ?? 50; $params['paginate'] = $this->resolvePaginateType($channel, $params); if (!isset($params['orderBy']) && !isset($params['order'])) { $params['order'] = Arr::get($channel->config, 'contentOrder'); } if ( $channel->shouldRestrictContent() // Arr::get($params, 'loader') !== 'editChannelPage' ) { $this->applyRestriction($channel, $params, $parent); // If restriction could not be loaded bail. This is used to cancel content loading and return 404, // if, for example, loading genre channel, but specified genre does not exist. if (!$channel->restriction) { return new Paginator([], 50); } } $contentType = Arr::get($channel->config, 'contentType'); if ($contentType === 'listAll') { return $this->paginateAllContentFromDatabase( $channel, $params, $parent, ); } else { // only cache channels that have other nested channels as content $shouldCache = Arr::get($channel->config, 'contentModel') === Channel::MODEL_TYPE; if (!$shouldCache) { return $this->loadCuratedContent($channel, $params); } else { $paramsKey = json_encode($params); return Cache::remember( // use "updated at" so channel changes from admin area will automatically // cause new cache item, without having to clear cache manually "channels.$channel->id.$channel->updated_at.$paramsKey", now()->addHours(24), fn() => $this->loadCuratedContent($channel, $params), ); } } } private function paginateAllContentFromDatabase( Channel $channel, array $params, Channel $parent = null, ): AbstractPaginator { $contentModel = Arr::get($channel->config, 'contentModel'); $methodName = sprintf('all%s', ucfirst(Str::plural($contentModel))); // prevent channel from erroring when trying to sort // by channelables table when paginating all content if ( isset($params['order']) && Str::startsWith($params['order'], 'channelables') ) { unset($params['order']); } // if channel specifies a method to load this model, use that if (method_exists($channel, $methodName)) { return $channel->{$methodName}($params, null, $parent); // otherwise do a basic pagination for the model } else { $namespace = modelTypeToNamespace($contentModel); $datasource = new Datasource(app($namespace)::query(), $params); return $datasource->paginate(); } } private function loadCuratedContent( Channel $channel, array $params, ): AbstractPaginator { $contentModel = Arr::get($channel, 'config.contentModel'); $methodName = sprintf('all%s', ucfirst(Str::plural($contentModel))); $builder = $channel->{Str::plural($contentModel)}(); // if channel specifies a method to load this model, use that if (method_exists($channel, $methodName)) { $pagination = $channel->{$methodName}($params, $builder); } else { $datasource = new Datasource($builder, $params); $order = $datasource->getOrder(); // get only column name, in case it's prefixed with table name if (last(explode('.', $order['col'])) === 'popularity') { $datasource->order = false; try { $builder->orderByPopularity($order['dir']); } catch (BadMethodCallException $e) { // } } $pagination = $datasource->paginate(); } $pagination ->filter(fn($model) => $model->pivot) ->transform(function (Model $model) use ($channel, $params) { $model['channelable_id'] = $model->pivot->id; $model['channelable_order'] = $model->pivot->order; if ($model instanceof Channel) { $model->loadContent( array_merge($params, [ // clear parent channel pagination params and only load 15 items per nested channel 'perPage' => 15, 'page' => 1, 'paginate' => 'simple', // clear this so nested channel always uses sorting order set in that channel's config 'order' => null, ]), $channel, ); } return $model; }); return $pagination; } private function applyRestriction( Channel $channel, array $params = [], Channel $parent = null, ): void { $urlParam = Arr::get($params, 'restriction'); $restriction = $parent->restriction ?? $channel->loadRestrictionModel($urlParam); if ($restriction) { $channel->setAttribute('restriction', $restriction); $channel->name = app(ReplacePlaceholders::class)->execute($channel->name, [ 'channel' => $channel, ]) ?: $channel->name; } } protected function resolvePaginateType( Channel $channel, array $params, ): string { if (isset($params['paginate'])) { return $params['paginate']; } if (isset($channel->config['paginationType'])) { return match ($channel->config['paginationType']) { 'lengthAware' => 'lengthAware', // simple and infinite scroll default => 'simple', }; } return 'simple'; } }