. // }}} /** * Attachments Albums for GNU social * * @package GNUsocial * @category Plugin * * @author Phablulo * @copyright 2018-2019, 2021 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ namespace Plugin\AttachmentCollections; use App\Core\DB\DB; use App\Core\Event; use App\Core\Form; use function App\Core\I18n\_m; use App\Core\Modules\Plugin; use App\Core\Router\RouteLoader; use App\Core\Router\Router; use App\Entity\Feed; use App\Entity\LocalUser; use App\Util\Common; use App\Util\Exception\RedirectException; use App\Util\Formatting; use App\Util\Nickname; use Component\Collection\Entity\Collection; use Plugin\AttachmentCollections\Controller as C; use Plugin\AttachmentCollections\Entity\AttachmentCollectionEntry; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\HttpFoundation\Request; class AttachmentCollections extends Plugin { public function onAddRoute(RouteLoader $r): bool { // View all collections by actor id and nickname $r->connect( id: 'collections_view_by_actor_id', uri_path: '/actor/{id<\d+>}/collections', target: [C\Controller::class, 'collectionsViewByActorId'], ); $r->connect( id: 'collections_view_by_nickname', uri_path: '/@{nickname<' . Nickname::DISPLAY_FMT . '>}/collections', target: [C\Controller::class, 'collectionsByActorNickname'], ); // View notes from a collection by actor id and nickname $r->connect( id: 'collection_notes_view_by_actor_id', uri_path: '/actor/{id<\d+>}/collections/{cid<\d+>}', target: [C\Controller::class, 'collectionNotesViewByActorId'], ); $r->connect( id: 'collection_notes_view_by_nickname', uri_path: '/@{nickname<' . Nickname::DISPLAY_FMT . '>}/collections/{cid<\d+>}', target: [C\Controller::class, 'collectionNotesByNickname'], ); return Event::next; } public function onCreateDefaultFeeds(int $actor_id, LocalUser $user, int &$ordering) { DB::persist(Feed::create([ 'actor_id' => $actor_id, 'url' => Router::url($route = 'collections_view_by_nickname', ['nickname' => $user->getNickname()]), 'route' => $route, 'title' => _m('Attachment Collections'), 'ordering' => $ordering++, ])); return Event::next; } /** * Append Attachment Collections widget to the right panel. * It's compose of two forms: one to select collections to add * the current attachment to, and another to create a new collection. */ public function onAppendRightPanelBlock($vars, Request $request, &$res): bool { if ($vars['path'] !== 'note_attachment_show') { return Event::next; } $user = Common::user(); if (\is_null($user)) { return Event::next; } $collections = DB::findBy(Collection::class, ['actor_id' => $user->getId()]); // add to collection form $attachment_id = $vars['vars']['attachment_id']; $note_id = $vars['vars']['note_id']; $choices = []; foreach ($collections as $col) { $choices[$col->getName()] = $col->getId(); } $already_selected = DB::dql( <<<'EOF' SELECT entry.collection_id FROM \Plugin\AttachmentCollections\Entity\AttachmentCollectionEntry AS entry INNER JOIN \Component\Collection\Entity\Collection AS attachment_collection WITH attachment_collection.id = entry.collection_id WHERE entry.attachment_id = :attach_id AND entry.note_id = :note_id AND attachment_collection.actor_id = :id EOF, ['attach_id' => $attachment_id, 'note_id' => $note_id, 'id' => $user->getId()], ); $already_selected = array_map(fn ($x) => $x['collection_id'], $already_selected); $add_form = Form::create([ ['collections', ChoiceType::class, [ 'choices' => $choices, 'multiple' => true, 'required' => false, 'choice_attr' => function ($id) use ($already_selected) { if (\in_array($id, $already_selected)) { return ['selected' => 'selected']; } return []; }, ]], ['add', SubmitType::class, [ 'label' => _m('Add to collections'), 'attr' => [ 'title' => _m('Add to collection'), ], ]], ]); $add_form->handleRequest($request); if ($add_form->isSubmitted() && $add_form->isValid()) { $collections = $add_form->getData()['collections']; $removed = array_filter($already_selected, fn ($x) => !\in_array($x, $collections)); $added = array_filter($collections, fn ($x) => !\in_array($x, $already_selected)); if (\count($removed) > 0) { DB::dql(<<<'EOF' DELETE FROM \Plugin\AttachmentCollections\Entity\AttachmentCollectionEntry AS entry WHERE entry.attachment_id = :attach_id AND entry.note_id = :note_id AND entry.collection_id IN ( SELECT album.id FROM \Component\Collection\Entity\Collection AS album WHERE album.actor_id = :user_id AND album.id IN (:ids) ) EOF, ['attach_id' => $attachment_id, 'note_id' => $note_id, 'user_id' => $user->getId(), 'ids' => $removed]); } foreach ($added as $cid) { // prevent user from putting something in a collection (s)he doesn't own: if (\in_array($cid, $collections)) { DB::persist(AttachmentCollectionEntry::create([ 'attachment_id' => $attachment_id, 'note_id' => $note_id, 'collection_id' => $cid, ])); } } DB::flush(); throw new RedirectException(); } // add to new collection form $create_form = Form::create([ ['name', TextType::class, [ 'label' => _m('Add to a new collection'), 'attr' => [ 'placeholder' => _m('New collection name'), 'required' => 'required', ], 'data' => '', ]], ['create', SubmitType::class, [ 'label' => _m('Create a new collection'), 'attr' => [ 'title' => _m('Create a new collection'), ], ]], ]); $create_form->handleRequest($request); if ($create_form->isSubmitted() && $create_form->isValid()) { $name = $create_form->getData()['name']; $coll = Collection::create([ 'name' => $name, 'actor_id' => $user->getId(), ]); DB::persist($coll); DB::persist(AttachmentCollectionEntry::create([ 'attachment_id' => $attachment_id, 'note_id' => $note_id, 'collection_id' => $coll->getId(), ])); DB::flush(); throw new RedirectException(); } $res[] = Formatting::twigRenderFile( 'AttachmentCollections/widget.html.twig', [ 'has_collections' => $collections, 'add_form' => $add_form->createView(), 'create_form' => $create_form->createView(), ], ); return Event::next; } /** * Output our dedicated stylesheet * * @param array $styles stylesheets path * * @return bool hook value; true means continue processing, false means stop */ public function onEndShowStyles(array &$styles, string $route): bool { $styles[] = 'plugins/AttachmentCollections/assets/css/widget.css'; $styles[] = 'plugins/AttachmentCollections/assets/css/pages.css'; return Event::next; } }