| 
									
										
										
										
											2020-09-04 18:44:20 +00:00
										 |  |  | <?php | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-10 09:26:18 +01:00
										 |  |  | declare(strict_types = 1); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-04 18:44:20 +00:00
										 |  |  | // {{{ License
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // This file is part of GNU social - https://www.gnu.org/software/social
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // GNU social is free software: you can redistribute it and/or modify
 | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by
 | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or
 | 
					
						
							|  |  |  | // (at your option) any later version.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // GNU social is distributed in the hope that it will be useful,
 | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					
						
							|  |  |  | // GNU Affero General Public License for more details.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License
 | 
					
						
							|  |  |  | // along with GNU social.  If not, see <http://www.gnu.org/licenses/>.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // }}}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace Plugin\Directory\Controller; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | use App\Core\DB\DB; | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  | use function App\Core\I18n\_m; | 
					
						
							| 
									
										
										
										
											2021-12-27 17:10:58 +00:00
										 |  |  | use App\Entity\Actor; | 
					
						
							| 
									
										
										
										
											2022-01-01 20:49:17 +00:00
										 |  |  | use App\Util\Exception\BugFoundException; | 
					
						
							|  |  |  | use App\Util\Exception\ClientException; | 
					
						
							| 
									
										
										
										
											2021-12-23 13:27:31 +00:00
										 |  |  | use Component\Feed\Util\FeedController; | 
					
						
							| 
									
										
										
										
											2020-09-04 18:44:20 +00:00
										 |  |  | use Symfony\Component\HttpFoundation\Request; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-07 23:31:20 +00:00
										 |  |  | class Directory extends FeedController | 
					
						
							| 
									
										
										
										
											2020-09-04 18:44:20 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  |     public const PER_PAGE       = 32; | 
					
						
							|  |  |  |     public const ALLOWED_FIELDS = ['nickname', 'created', 'modified', 'activity', 'subscribers']; | 
					
						
							| 
									
										
										
										
											2022-01-01 20:49:17 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Function responsible for displaying a list of actors of a given | 
					
						
							|  |  |  |      * $actor_type, sorted by the `order_by` GET parameter, if given | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-01-01 21:52:30 +00:00
										 |  |  |     private function impl(Request $request, int $actor_type, string $title, string $empty_message): array | 
					
						
							| 
									
										
										
										
											2022-01-01 20:49:17 +00:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  |         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; | 
					
						
							| 
									
										
										
										
											2022-01-01 20:49:17 +00:00
										 |  |  |         $offset = self::PER_PAGE * ($page - 1); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  |         // -------- Figure out the order by field and operator --------
 | 
					
						
							| 
									
										
										
										
											2022-01-01 20:49:17 +00:00
										 |  |  |         $order_by_qs = $this->string('order_by'); | 
					
						
							|  |  |  |         if (!\is_null($order_by_qs) && mb_detect_encoding($order_by_qs, 'ASCII', strict: true) !== false) { | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  |             $order_by_op = mb_substr($order_by_qs, -1); | 
					
						
							| 
									
										
										
										
											2022-01-01 20:49:17 +00:00
										 |  |  |             if (\in_array($order_by_op, ['^', '<'])) { | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  |                 $order_by_field = mb_substr($order_by_qs, 0, -1); | 
					
						
							| 
									
										
										
										
											2022-01-01 21:24:24 +00:00
										 |  |  |                 $order_by_op    = 'DESC'; | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  |             } elseif (\in_array($order_by_op, ['v', '>'])) { | 
					
						
							|  |  |  |                 $order_by_field = mb_substr($order_by_qs, 0, -1); | 
					
						
							| 
									
										
										
										
											2022-01-01 21:24:24 +00:00
										 |  |  |                 $order_by_op    = 'ASC'; | 
					
						
							| 
									
										
										
										
											2022-01-01 20:49:17 +00:00
										 |  |  |             } else { | 
					
						
							|  |  |  |                 $order_by_field = $order_by_qs; | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  |                 $order_by_op    = 'ASC'; | 
					
						
							| 
									
										
										
										
											2022-01-01 20:49:17 +00:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             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'; | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  |             $order_by_op    = 'ASC'; | 
					
						
							| 
									
										
										
										
											2022-01-01 20:49:17 +00:00
										 |  |  |         } | 
					
						
							|  |  |  |         $order_by = [$order_by_field => $order_by_op]; | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  |         // -------- *** --------
 | 
					
						
							| 
									
										
										
										
											2022-01-01 20:49:17 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  |         // -------- 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) { | 
					
						
							| 
									
										
										
										
											2022-01-01 21:25:30 +00:00
										 |  |  |             return fn (string $table, string $join_field, string $aggregate_field) => fn (int $actor_type) => DB::sql( | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  |                 <<<EOQ | 
					
						
							| 
									
										
										
										
											2022-01-01 20:49:17 +00:00
										 |  |  |                     select {select} | 
					
						
							|  |  |  |                     from actor actr | 
					
						
							|  |  |  |                     join ( | 
					
						
							| 
									
										
										
										
											2022-01-01 21:25:30 +00:00
										 |  |  |                         select tbl.{$join_field}, {$func}(tbl.{$aggregate_field}) as aggr | 
					
						
							| 
									
										
										
										
											2022-01-01 20:49:17 +00:00
										 |  |  |                         from {$table} tbl | 
					
						
							|  |  |  |                         group by tbl.{$join_field} | 
					
						
							|  |  |  |                     ) actor_activity on actr.id = actor_activity.{$join_field} | 
					
						
							|  |  |  |                     where actr.type = :type | 
					
						
							| 
									
										
										
										
											2022-01-01 21:25:30 +00:00
										 |  |  |                     order by actor_activity.aggr {$order} | 
					
						
							| 
									
										
										
										
											2022-01-01 20:49:17 +00:00
										 |  |  |                     limit :limit offset :offset | 
					
						
							|  |  |  |                     EOQ, | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  |                 [ | 
					
						
							|  |  |  |                     'type'   => $actor_type, | 
					
						
							|  |  |  |                     'limit'  => $limit, | 
					
						
							|  |  |  |                     'offset' => $offset, | 
					
						
							|  |  |  |                 ], | 
					
						
							|  |  |  |                 ['actr' => Actor::class], | 
					
						
							|  |  |  |             ); | 
					
						
							| 
									
										
										
										
											2022-01-01 20:49:17 +00:00
										 |  |  |         }; | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  |         // -------- *** --------
 | 
					
						
							| 
									
										
										
										
											2022-01-01 20:49:17 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  |         // -------- Start setting up the queries --------
 | 
					
						
							| 
									
										
										
										
											2022-01-01 21:25:30 +00:00
										 |  |  |         $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); | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  |         // -------- *** --------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // -------- 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
 | 
					
						
							| 
									
										
										
										
											2022-01-01 21:25:30 +00:00
										 |  |  |                 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'), | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  |             }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             'activity'        => match ($actor_type) { // select by most/least activity amount
 | 
					
						
							| 
									
										
										
										
											2022-01-01 21:25:30 +00:00
										 |  |  |                 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', aggregate_field: 'subscriber'), | 
					
						
							|  |  |  |                 Actor::GROUP  => $count_query_fn(table: 'group_member', join_field: 'group_id', aggregate_field: 'actor_id'), | 
					
						
							| 
									
										
										
										
											2022-01-01 20:50:04 +00:00
										 |  |  |             }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             default => throw new BugFoundException("Unkown order by found, but should have been validated: {$order_by_field}"), | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |         // -------- *** --------
 | 
					
						
							| 
									
										
										
										
											2022-01-01 20:49:17 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return [ | 
					
						
							| 
									
										
										
										
											2022-01-01 21:52:30 +00:00
										 |  |  |             '_template'     => 'directory/actors.html.twig', | 
					
						
							|  |  |  |             'actors'        => $query_fn($actor_type), | 
					
						
							|  |  |  |             'title'         => $title, | 
					
						
							|  |  |  |             'empty_message' => $empty_message, | 
					
						
							|  |  |  |             'page'          => $page, | 
					
						
							| 
									
										
										
										
											2022-01-01 20:49:17 +00:00
										 |  |  |         ]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-01 20:02:32 +00:00
										 |  |  |     public function people(Request $request): array | 
					
						
							| 
									
										
										
										
											2020-09-04 18:44:20 +00:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2022-01-01 21:52:30 +00:00
										 |  |  |         return $this->impl($request, Actor::PERSON, title: _m('People'), empty_message: _m('No people here')); | 
					
						
							| 
									
										
										
										
											2020-09-04 18:44:20 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-10 09:26:18 +01:00
										 |  |  |     public function groups(Request $request): array | 
					
						
							| 
									
										
										
										
											2020-09-04 18:44:20 +00:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2022-01-01 21:52:30 +00:00
										 |  |  |         return $this->impl($request, Actor::GROUP, title: _m('Groups'), empty_message: _m('No groups here')); | 
					
						
							| 
									
										
										
										
											2020-09-04 18:44:20 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | } |