. // }}} namespace Plugin\Directory\Controller; use App\Core\DB\DB; use function App\Core\I18n\_m; use App\Entity\Actor; use App\Util\Exception\BugFoundException; use App\Util\Exception\ClientException; use Component\Collection\Util\Controller\CircleController; use Symfony\Component\HttpFoundation\Request; class Directory extends FeedController { public const PER_PAGE = 32; public const ALLOWED_FIELDS = ['nickname', 'created', 'modified', 'activity', 'subscribers']; /** * Function responsible for displaying a list of actors of a given * $actor_type, sorted by the `order_by` GET parameter, if given */ private function impl(Request $request, int $actor_type, string $title, string $empty_message): array { if ($actor_type !== Actor::PERSON && $actor_type !== Actor::GROUP) { throw new BugFoundException("Unimplemented for actor type: {$actor_type}"); } $page = $this->int('page') ?? 1; $limit = self::PER_PAGE; $offset = self::PER_PAGE * ($page - 1); // -------- Figure out the order by field and operator -------- $order_by_qs = $this->string('order_by'); if (!\is_null($order_by_qs) && mb_detect_encoding($order_by_qs, 'ASCII', strict: true) !== false) { $order_by_op = mb_substr($order_by_qs, -1); if (\in_array($order_by_op, ['^', '<'])) { $order_by_field = mb_substr($order_by_qs, 0, -1); $order_by_op = 'DESC'; } elseif (\in_array($order_by_op, ['v', '>'])) { $order_by_field = mb_substr($order_by_qs, 0, -1); $order_by_op = 'ASC'; } else { $order_by_field = $order_by_qs; $order_by_op = 'ASC'; } if (!\in_array($order_by_field, self::ALLOWED_FIELDS)) { throw new ClientException(_m('Invalid order by given: {order_by}', ['{order_by}' => $order_by_field])); } } else { $order_by_field = 'nickname'; $order_by_op = 'ASC'; } $order_by = [$order_by_field => $order_by_op]; // -------- *** -------- // -------- Query builder for selecting actors joined with another table, namely activity and group_inbox -------- $general_query_fn_fn = function (string $func, string $order) use ($limit, $offset) { return fn (string $table, string $join_field, string $aggregate_field) => fn (int $actor_type) => DB::sql( << $actor_type, 'limit' => $limit, 'offset' => $offset, ], ['actr' => Actor::class], ); }; // -------- *** -------- // -------- Start setting up the queries -------- $actor_query_fn = fn (int $actor_type) => DB::findBy(Actor::class, ['type' => $actor_type], order_by: $order_by, limit: $limit, offset: $offset); $minmax_query_fn = $general_query_fn_fn(func: $order_by_op === 'ASC' ? 'MAX' : 'MIN', order: $order_by_op); $count_query_fn = $general_query_fn_fn(func: 'COUNT', order: $order_by_op); // -------- *** -------- // -------- Figure out the final query -------- $query_fn = match ($order_by_field) { 'nickname', 'created' => $actor_query_fn, // select only from actors 'modified' => match ($actor_type) { // select by most/least recent activity Actor::PERSON => $minmax_query_fn(table: 'activity', join_field: 'actor_id', aggregate_field: 'created'), Actor::GROUP => $minmax_query_fn(table: 'group_inbox', join_field: 'group_id', aggregate_field: 'created'), }, 'activity' => match ($actor_type) { // select by most/least activity amount Actor::PERSON => $count_query_fn(table: 'activity', join_field: 'actor_id', aggregate_field: 'created'), Actor::GROUP => $count_query_fn(table: 'group_inbox', join_field: 'group_id', aggregate_field: 'created'), }, 'subscribers' => match ($actor_type) { // select by actors with most/least subscribers/members Actor::PERSON => $count_query_fn(table: 'subscription', join_field: 'subscribed_id', aggregate_field: 'subscriber_id'), Actor::GROUP => $count_query_fn(table: 'group_member', join_field: 'group_id', aggregate_field: 'actor_id'), }, default => throw new BugFoundException("Unkown order by found, but should have been validated: {$order_by_field}"), }; // -------- *** -------- $sort_options = []; foreach (self::ALLOWED_FIELDS as $al) { $sort_options[] = [ 'active' => false, 'url' => '?order_by=' . $al . '^', 'label' => _m('{order_by} ascending', ['{order_by}' => ucfirst($al)]), ]; $sort_options[] = [ 'active' => false, 'url' => '?order_by=' . $al . 'v', 'label' => _m('{order_by} descending', ['{order_by}' => ucfirst($al)]), ]; } return [ '_template' => 'collection/actors.html.twig', 'actors' => $query_fn($actor_type), 'title' => $title, 'empty_message' => $empty_message, 'sort_options' => $sort_options, 'page' => $page, ]; } public function people(Request $request): array { return $this->impl($request, Actor::PERSON, title: _m('People'), empty_message: _m('No people here')); } public function groups(Request $request): array { return $this->impl($request, Actor::GROUP, title: _m('Groups'), empty_message: _m('No groups here')); } }