diff --git a/plugins/Directory/Controller/Directory.php b/plugins/Directory/Controller/Directory.php index b96cf072c6..0ce2e3a2ae 100644 --- a/plugins/Directory/Controller/Directory.php +++ b/plugins/Directory/Controller/Directory.php @@ -25,11 +25,113 @@ namespace Plugin\Directory\Controller; use App\Core\DB\DB; use App\Entity\Actor; +use App\Util\Exception\BugFoundException; +use App\Util\Exception\ClientException; use Component\Feed\Util\FeedController; +use function App\Core\I18n\_m; use Symfony\Component\HttpFoundation\Request; class Directory extends FeedController { + + const PER_PAGE = 32; + const ALLOWED_FIELDS = ['nickname', 'created', 'modified', 'activity', 'subscribers']; + + private function impl(Request $request, string $template, int $actor_type): array + { + $page = $this->int('page') ?? 1; + $limit = self::PER_PAGE; + $offset = self::PER_PAGE * ($page - 1); + + $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 = substr($order_by_qs, -1); + if (\in_array($order_by_op, ['^', '<'])) { + $order_by_field = substr($order_by_qs, 0, -1); + $order_by_op = 'ASC'; + } else if (\in_array($order_by_op, ['v', '>'])) { + $order_by_field = substr($order_by_qs, 0, -1); + $order_by_op = 'DESC'; + } 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]; + $route = $request->get('_route'); + + $query_fn = function (int $actor_type, string $table, string $join_field) use ($limit, $offset) { + return function (string $func, string $order) use ($actor_type, $table, $join_field, $limit, $offset) { + return DB::sql( + << $actor_type, + 'limit' => $limit, + 'offset' => $offset, + ], + ['actr' => Actor::class] + ); + }; + }; + + $person_activity_query = $query_fn(actor_type: Actor::PERSON, table: 'activity', join_field: 'actor_id'); + $group_activity_query = $query_fn(actor_type: Actor::GROUP, table: 'group_inbox', join_field: 'group_id'); + + switch ($order_by_field) { + case 'nickname': + case 'created': + $actors = DB::findBy(Actor::class, ['type' => $actor_type], order_by: $order_by, limit: $limit, offset: $offset); + break; + + case 'modified': + $query = match ($actor_type) { + Actor::PERSON => $person_activity_query, + Actor::GROUP => $group_activity_query, + default => throw new BugFoundException("Unimplemented for actor type: {$actor_type}"), + }; + $actors = $query(func: $order_by_op === 'ASC' ? 'MAX' : 'MIN', order: "created {$order_by_op}"); + break; + + case 'activity': + $query = match ($actor_type) { + Actor::PERSON => $person_activity_query, + Actor::GROUP => $group_activity_query, + default => throw new BugFoundException("Unimplemented for actor type: {$actor_type}"), + }; + $actors = $query(func: 'COUNT', order: "created {$order_by_op}"); + break; + + default: + throw new BugFoundException("Unkown order by found, but should have been validated: {$order_by_field}"); + } + + return [ + '_template' => $template, + 'actors' => $actors, + 'page' => $page, + ]; + } + /** * people stream * @@ -37,10 +139,7 @@ class Directory extends FeedController */ public function people(Request $request): array { - return [ - '_template' => 'directory/people.html.twig', - 'actors' => DB::findBy(Actor::class, ['type' => Actor::PERSON], order_by: ['created' => 'DESC', 'nickname' => 'ASC']), - ]; + return $this->impl($request, 'directory/people.html.twig', Actor::PERSON); } /** @@ -50,9 +149,6 @@ class Directory extends FeedController */ public function groups(Request $request): array { - return [ - '_template' => 'directory/groups.html.twig', - 'groups' => DB::findBy(Actor::class, ['type' => Actor::GROUP], order_by: ['created' => 'DESC', 'nickname' => 'ASC']), - ]; + return $this->impl($request, 'directory/groups.html.twig', Actor::GROUP); } }