From bf6c0346d8d35a719dd1bff1cb4d573d422f99ff Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Wed, 31 May 2017 09:31:18 +0200 Subject: WIP Signed-off-by: Thomas Citharel --- app/AppKernel.php | 3 +- app/DoctrineMigrations/Version20170328185535.php | 99 +++++ .../static/themes/material/css/index.scss | 1 + .../static/themes/material/css/layout.scss | 6 + .../static/themes/material/css/profile.scss | 54 +++ app/config/config.yml | 3 +- app/config/parameters.yml.dist | 3 + app/config/routing.yml | 5 + app/config/security.yml | 1 + .../Controller/WallabagAnnotationController.php | 8 + .../ApiBundle/Controller/EntryRestController.php | 42 +- src/Wallabag/CoreBundle/Command/InstallCommand.php | 2 +- .../CoreBundle/Controller/ConfigController.php | 53 +++ .../CoreBundle/Controller/EntryController.php | 13 +- .../CoreBundle/Controller/ShareController.php | 165 ++++++++ .../CoreBundle/Controller/TagController.php | 5 + src/Wallabag/CoreBundle/Entity/Activity.php | 295 ++++++++++++++ src/Wallabag/CoreBundle/Entity/Config.php | 2 +- src/Wallabag/CoreBundle/Entity/Entry.php | 26 +- src/Wallabag/CoreBundle/Entity/Notification.php | 1 + src/Wallabag/CoreBundle/Entity/Share.php | 140 +++++++ .../Actions/Annotation/AnnotationCreatedEvent.php | 8 + .../Actions/Annotation/AnnotationDeletedEvent.php | 8 + .../Actions/Annotation/AnnotationEditedEvent.php | 8 + .../Actions/Annotation/AnnotationEvent.php | 39 ++ .../Activity/Actions/Entry/EntryDeletedEvent.php | 11 + .../Activity/Actions/Entry/EntryEditedEvent.php | 11 + .../Event/Activity/Actions/Entry/EntryEvent.php | 41 ++ .../Activity/Actions/Entry/EntryFavouriteEvent.php | 14 + .../Activity/Actions/Entry/EntryReadEvent.php | 14 + .../Activity/Actions/Entry/EntrySavedEvent.php | 11 + .../Activity/Actions/Entry/EntryTaggedEvent.php | 55 +++ .../Actions/Federation/FederationEvent.php | 40 ++ .../Activity/Actions/Federation/FollowEvent.php | 39 ++ .../Actions/Federation/RecommendedEntryEvent.php | 43 +++ .../Activity/Actions/Federation/UnfollowEvent.php | 39 ++ .../Activity/Actions/Share/ShareAcceptedEvent.php | 11 + .../Activity/Actions/Share/ShareCancelledEvent.php | 11 + .../Activity/Actions/Share/ShareCreatedEvent.php | 11 + .../Activity/Actions/Share/ShareDeniedEvent.php | 11 + .../Event/Activity/Actions/Share/ShareEvent.php | 39 ++ .../Activity/Actions/User/UserDeletedEvent.php | 8 + .../Activity/Actions/User/UserEditedEvent.php | 8 + .../Event/Activity/Actions/User/UserEvent.php | 41 ++ .../Event/Activity/ActivitySubscriber.php | 224 +++++++++++ .../CoreBundle/Event/EntryDeletedEvent.php | 26 -- src/Wallabag/CoreBundle/Event/EntrySavedEvent.php | 26 -- .../Event/Subscriber/DownloadImagesSubscriber.php | 4 +- .../CoreBundle/Repository/ChangeRepository.php | 26 ++ .../CoreBundle/Repository/EntryRepository.php | 14 +- .../CoreBundle/Resources/config/services.yml | 8 + .../Resources/translations/messages.en.yml | 11 +- .../Resources/translations/messages.fr.yml | 5 + .../Resources/translations/messages.pt.yml | 2 +- .../views/themes/material/Config/index.html.twig | 45 +++ .../views/themes/material/layout.html.twig | 5 + .../Command/CreateAccountsCommand.php | 126 ++++++ .../Controller/InboxController.php | 43 +++ .../Controller/LikedController.php | 19 + .../Controller/MetadataController.php | 69 ++++ .../Controller/OutboxController.php | 23 ++ .../Controller/ProfileController.php | 425 +++++++++++++++++++++ .../Controller/RecommandController.php | 34 ++ .../DependencyInjection/Configuration.php | 25 ++ .../WallabagFederationExtension.php | 26 ++ src/Wallabag/FederationBundle/Entity/Account.php | 307 +++++++++++++++ src/Wallabag/FederationBundle/Entity/Instance.php | 111 ++++++ .../EventListener/CreateAccountListener.php | 54 +++ .../FederationBundle/Federation/CloudId.php | 78 ++++ .../FederationBundle/Form/Type/AccountType.php | 46 +++ .../Repository/AccountRepository.php | 48 +++ .../Repository/InstanceRepository.php | 10 + .../FederationBundle/Resources/config/services.yml | 8 + .../views/themes/material/User/followers.html.twig | 88 +++++ .../views/themes/material/User/profile.html.twig | 77 ++++ .../themes/material/User/profile_header.html.twig | 35 ++ .../themes/material/User/recommendations.html.twig | 4 + .../FederationBundle/WallabagFederationBundle.php | 9 + src/Wallabag/UserBundle/Entity/User.php | 27 ++ .../CoreBundle/Command/InstallCommandTest.php | 12 +- 80 files changed, 3468 insertions(+), 80 deletions(-) create mode 100644 app/DoctrineMigrations/Version20170328185535.php create mode 100644 app/Resources/static/themes/material/css/profile.scss create mode 100644 src/Wallabag/CoreBundle/Controller/ShareController.php create mode 100644 src/Wallabag/CoreBundle/Entity/Activity.php create mode 100644 src/Wallabag/CoreBundle/Entity/Share.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Annotation/AnnotationCreatedEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Annotation/AnnotationDeletedEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Annotation/AnnotationEditedEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Annotation/AnnotationEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Entry/EntryDeletedEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Entry/EntryEditedEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Entry/EntryEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Entry/EntryFavouriteEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Entry/EntryReadEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Entry/EntrySavedEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Entry/EntryTaggedEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Federation/FederationEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Federation/FollowEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Federation/RecommendedEntryEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Federation/UnfollowEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Share/ShareAcceptedEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Share/ShareCancelledEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Share/ShareCreatedEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Share/ShareDeniedEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/Share/ShareEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/User/UserDeletedEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/User/UserEditedEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/Actions/User/UserEvent.php create mode 100644 src/Wallabag/CoreBundle/Event/Activity/ActivitySubscriber.php delete mode 100644 src/Wallabag/CoreBundle/Event/EntryDeletedEvent.php delete mode 100644 src/Wallabag/CoreBundle/Event/EntrySavedEvent.php create mode 100644 src/Wallabag/CoreBundle/Repository/ChangeRepository.php create mode 100644 src/Wallabag/FederationBundle/Command/CreateAccountsCommand.php create mode 100644 src/Wallabag/FederationBundle/Controller/InboxController.php create mode 100644 src/Wallabag/FederationBundle/Controller/LikedController.php create mode 100644 src/Wallabag/FederationBundle/Controller/MetadataController.php create mode 100644 src/Wallabag/FederationBundle/Controller/OutboxController.php create mode 100644 src/Wallabag/FederationBundle/Controller/ProfileController.php create mode 100644 src/Wallabag/FederationBundle/Controller/RecommandController.php create mode 100644 src/Wallabag/FederationBundle/DependencyInjection/Configuration.php create mode 100644 src/Wallabag/FederationBundle/DependencyInjection/WallabagFederationExtension.php create mode 100644 src/Wallabag/FederationBundle/Entity/Account.php create mode 100644 src/Wallabag/FederationBundle/Entity/Instance.php create mode 100644 src/Wallabag/FederationBundle/EventListener/CreateAccountListener.php create mode 100644 src/Wallabag/FederationBundle/Federation/CloudId.php create mode 100644 src/Wallabag/FederationBundle/Form/Type/AccountType.php create mode 100644 src/Wallabag/FederationBundle/Repository/AccountRepository.php create mode 100644 src/Wallabag/FederationBundle/Repository/InstanceRepository.php create mode 100644 src/Wallabag/FederationBundle/Resources/config/services.yml create mode 100644 src/Wallabag/FederationBundle/Resources/views/themes/material/User/followers.html.twig create mode 100644 src/Wallabag/FederationBundle/Resources/views/themes/material/User/profile.html.twig create mode 100644 src/Wallabag/FederationBundle/Resources/views/themes/material/User/profile_header.html.twig create mode 100644 src/Wallabag/FederationBundle/Resources/views/themes/material/User/recommendations.html.twig create mode 100644 src/Wallabag/FederationBundle/WallabagFederationBundle.php diff --git a/app/AppKernel.php b/app/AppKernel.php index c50783a6..c5109866 100644 --- a/app/AppKernel.php +++ b/app/AppKernel.php @@ -32,6 +32,7 @@ class AppKernel extends Kernel new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(), new FOS\JsRoutingBundle\FOSJsRoutingBundle(), new BD\GuzzleSiteAuthenticatorBundle\BDGuzzleSiteAuthenticatorBundle(), + new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(), // wallabag bundles new Wallabag\CoreBundle\WallabagCoreBundle(), @@ -39,7 +40,7 @@ class AppKernel extends Kernel new Wallabag\UserBundle\WallabagUserBundle(), new Wallabag\ImportBundle\WallabagImportBundle(), new Wallabag\AnnotationBundle\WallabagAnnotationBundle(), - new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(), + new Wallabag\FederationBundle\WallabagFederationBundle(), ]; if (in_array($this->getEnvironment(), ['dev', 'test'], true)) { diff --git a/app/DoctrineMigrations/Version20170328185535.php b/app/DoctrineMigrations/Version20170328185535.php new file mode 100644 index 00000000..f0afb7b9 --- /dev/null +++ b/app/DoctrineMigrations/Version20170328185535.php @@ -0,0 +1,99 @@ +container = $container; + } + + private function getTable($tableName) + { + return $this->container->getParameter('database_table_prefix').$tableName; + } + + /** + * @param Schema $schema + */ + public function up(Schema $schema) + { + try { + $schema->getTable($this->getTable('change')); + } catch (SchemaException $e) { + // The Change table doesn't exist, we need to create it + if (10 == $e->getCode()) { + if ($this->connection->getDatabasePlatform()->getName() == 'sqlite') { + $this->addSql('CREATE TABLE '.$this->getTable('change').' (id INTEGER NOT NULL, entry_id INTEGER DEFAULT NULL, type INTEGER NOT NULL, created_at DATETIME NOT NULL, PRIMARY KEY(id), CONSTRAINT FK_133B9D0FBA364942 FOREIGN KEY (entry_id) REFERENCES '.$this->getTable('entry').' (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + + return true; + } + + $changeTable = $schema->createTable($this->getTable('change')); + $changeTable->addColumn( + 'id', + 'integer', + ['autoincrement' => true] + ); + $changeTable->addColumn( + 'type', + 'integer', + ['notnull' => false] + ); + $changeTable->addColumn( + 'created_at', + 'datetime', + ['notnull' => false] + ); + $changeTable->addColumn( + 'entry_id', + 'integer', + ['notnull' => false] + ); + + $changeTable->setPrimaryKey(['id']); + + $changeTable->addForeignKeyConstraint( + $this->getTable('entry'), + ['entry_id'], + ['id'], + ['onDelete' => 'CASCADE'], + 'IDX_change_entry' + ); + + return true; + } + } + + throw new SkipMigrationException('It seems that you already played this migration.'); + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema) + { + try { + $changeTable = $schema->getTable($this->getTable('change')); + $schema->dropTable($changeTable->getName()); + } catch (SchemaException $e) { + throw new SkipMigrationException('It seems that you already played this migration.'); + } + } +} diff --git a/app/Resources/static/themes/material/css/index.scss b/app/Resources/static/themes/material/css/index.scss index 8300e430..a360b99b 100644 --- a/app/Resources/static/themes/material/css/index.scss +++ b/app/Resources/static/themes/material/css/index.scss @@ -9,6 +9,7 @@ @import 'nav'; @import 'sidenav'; @import 'notifications'; +@import 'profile'; @import 'various'; /* Tools */ diff --git a/app/Resources/static/themes/material/css/layout.scss b/app/Resources/static/themes/material/css/layout.scss index cfdbf2b3..6fd14335 100755 --- a/app/Resources/static/themes/material/css/layout.scss +++ b/app/Resources/static/themes/material/css/layout.scss @@ -18,6 +18,12 @@ body { border-bottom: 1px solid #ddd; } +nav, +body:not(.reset-left) main, +footer { + padding-left: 240px; +} + main, #content, .valign-wrapper { diff --git a/app/Resources/static/themes/material/css/profile.scss b/app/Resources/static/themes/material/css/profile.scss new file mode 100644 index 00000000..d5b9a5ff --- /dev/null +++ b/app/Resources/static/themes/material/css/profile.scss @@ -0,0 +1,54 @@ +.profile { + .details { + display: flex; + flex-direction: row; + + .bio { + flex: 1; + font-size: 14px; + line-height: 18px; + padding: 5px 10px; + order: 1; + + p { + font-size: 14px; + font-weight: 400; + overflow: hidden; + word-break: normal; + word-wrap: break-word; + } + } + + .details-counters { + order: 0; + display: flex; + flex-direction: row; + } + + .counter { + width: 80px; + color: #9baec8; + padding: 5px 10px 0; + margin-bottom: 10px; + border-right: 1px solid #9baec8; + cursor: default; + position: relative; + + .counter-label { + font-size: 12px; + text-transform: uppercase; + display: block; + margin-bottom: 5px; + } + + .counter-number { + font-weight: 500; + font-size: 18px; + color: #00bcd4; + } + } + } + img.avatar { + width: 10em; + } +} diff --git a/app/config/config.yml b/app/config/config.yml index 2bc5e3b3..4a935dfb 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -6,8 +6,9 @@ imports: parameters: # Allows to use the live reload feature for changes in assets - use_webpack_dev_server: false + use_webpack_dev_server: true craue_config.cache_adapter.class: Craue\ConfigBundle\CacheAdapter\SymfonyCacheComponentAdapter + media_directory: '%kernel.root_dir%/../web/uploads/media' framework: #esi: ~ diff --git a/app/config/parameters.yml.dist b/app/config/parameters.yml.dist index b3fe11c8..d342c72a 100644 --- a/app/config/parameters.yml.dist +++ b/app/config/parameters.yml.dist @@ -25,6 +25,9 @@ parameters: domain_name: https://your-wallabag-url-instance.com + # Instance URL + domain_name: wallabag.tld + mailer_transport: smtp mailer_host: 127.0.0.1 mailer_user: ~ diff --git a/app/config/routing.yml b/app/config/routing.yml index 0bd2d130..f1bbbda9 100644 --- a/app/config/routing.yml +++ b/app/config/routing.yml @@ -17,6 +17,11 @@ wallabag_api: type: annotation prefix: / +wallabag_federation: + resource: "@WallabagFederationBundle/Controller/" + type: annotation + prefix: / + app: resource: "@WallabagCoreBundle/Controller/" type: annotation diff --git a/app/config/security.yml b/app/config/security.yml index e14a0bd1..a8a0a6ae 100644 --- a/app/config/security.yml +++ b/app/config/security.yml @@ -66,4 +66,5 @@ security: - { path: ^/settings, roles: ROLE_SUPER_ADMIN } - { path: ^/annotations, roles: ROLE_USER } - { path: ^/users, roles: ROLE_SUPER_ADMIN } + - { path: ^/profile, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/, roles: ROLE_USER } diff --git a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php index 2b4b0e8d..45fd9cf3 100644 --- a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php +++ b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php @@ -10,6 +10,9 @@ use Wallabag\AnnotationBundle\Entity\Annotation; use Wallabag\AnnotationBundle\Form\EditAnnotationType; use Wallabag\AnnotationBundle\Form\NewAnnotationType; use Wallabag\CoreBundle\Entity\Entry; +use Wallabag\CoreBundle\Event\Activity\Actions\Annotation\AnnotationCreatedEvent; +use Wallabag\CoreBundle\Event\Activity\Actions\Annotation\AnnotationDeletedEvent; +use Wallabag\CoreBundle\Event\Activity\Actions\Annotation\AnnotationEditedEvent; class WallabagAnnotationController extends FOSRestController { @@ -64,6 +67,8 @@ class WallabagAnnotationController extends FOSRestController $em->persist($annotation); $em->flush(); + $this->get('event_dispatcher')->dispatch(AnnotationCreatedEvent::NAME, new AnnotationCreatedEvent($annotation)); + $json = $this->get('serializer')->serialize($annotation, 'json'); return JsonResponse::fromJsonString($json); @@ -100,6 +105,7 @@ class WallabagAnnotationController extends FOSRestController $em->flush(); $json = $this->get('serializer')->serialize($annotation, 'json'); + $this->get('event_dispatcher')->dispatch(AnnotationEditedEvent::NAME, new AnnotationEditedEvent($annotation)); return JsonResponse::fromJsonString($json); } @@ -124,6 +130,8 @@ class WallabagAnnotationController extends FOSRestController $em->remove($annotation); $em->flush(); + $this->get('event_dispatcher')->dispatch(AnnotationDeletedEvent::NAME, new AnnotationDeletedEvent($annotation)); + $json = $this->get('serializer')->serialize($annotation, 'json'); return (new JsonResponse())->setJson($json); diff --git a/src/Wallabag/ApiBundle/Controller/EntryRestController.php b/src/Wallabag/ApiBundle/Controller/EntryRestController.php index 768c4fdc..d1c95da6 100644 --- a/src/Wallabag/ApiBundle/Controller/EntryRestController.php +++ b/src/Wallabag/ApiBundle/Controller/EntryRestController.php @@ -11,8 +11,10 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Wallabag\CoreBundle\Entity\Entry; use Wallabag\CoreBundle\Entity\Tag; -use Wallabag\CoreBundle\Event\EntrySavedEvent; use Wallabag\CoreBundle\Event\EntryDeletedEvent; +use Wallabag\CoreBundle\Event\EntrySavedEvent; +use Wallabag\CoreBundle\Event\EntryTaggedEvent; +use Wallabag\CoreBundle\Event\EntryUpdatedEvent; class EntryRestController extends WallabagRestController { @@ -356,6 +358,8 @@ class EntryRestController extends WallabagRestController $this->upsertEntry($entry, $request, true); + $this->get('event_dispatcher')->dispatch(EntryUpdatedEvent::NAME, new EntryUpdatedEvent($entry)); + return $this->sendResponse($entry); } @@ -397,6 +401,7 @@ class EntryRestController extends WallabagRestController $em->flush(); // entry saved, dispatch event about it! + $this->get('event_dispatcher')->dispatch(EntryUpdatedEvent::NAME, new EntryUpdatedEvent($entry)); $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry)); return $this->sendResponse($entry); @@ -467,6 +472,7 @@ class EntryRestController extends WallabagRestController $this->validateUserAccess($entry->getUser()->getId()); $tags = $request->request->get('tags', ''); + $tagsEntries = []; if (!empty($tags)) { $this->get('wallabag_core.tags_assigner')->assignTagsToEntry($entry, $tags); } @@ -475,6 +481,8 @@ class EntryRestController extends WallabagRestController $em->persist($entry); $em->flush(); + $this->get('event_dispatcher')->dispatch(EntryTaggedEvent::NAME, new EntryTaggedEvent($entry, $tagsEntries)); + return $this->sendResponse($entry); } @@ -668,11 +676,11 @@ class EntryRestController extends WallabagRestController } if (!is_null($isArchived)) { - $entry->setArchived((bool) $isArchived); + $entry->setArchived((bool)$isArchived); } if (!is_null($isStarred)) { - $entry->setStarred((bool) $isStarred); + $entry->setStarred((bool)$isStarred); } if (!empty($tags)) { @@ -680,9 +688,9 @@ class EntryRestController extends WallabagRestController } if (!is_null($isPublic)) { - if (true === (bool) $isPublic && null === $entry->getUid()) { + if (true === (bool)$isPublic && null === $entry->getUid()) { $entry->generateUid(); - } elseif (false === (bool) $isPublic) { + } elseif (false === (bool)$isPublic) { $entry->cleanUid(); } } @@ -694,4 +702,28 @@ class EntryRestController extends WallabagRestController // entry saved, dispatch event about it! $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry)); } + + /** + * Gets history since a date. + * + * @ApiDoc( + * parameters={ + * {"name"="since", "dataType"="integer", "required"=true, "format"="A timestamp", "description"="Timestamp of the history's start"}, + * } + * ) + * + * @return JsonResponse + */ + public function getEntriesHistoryAction(Request $request) + { + $this->validateAuthentication(); + + $res = $this->getDoctrine() + ->getRepository('WallabagCoreBundle:Change') + ->findChangesSinceDate($request->query->get('since')); + + $json = $this->get('serializer')->serialize($res, 'json'); + + return (new JsonResponse())->setJson($json); + } } diff --git a/src/Wallabag/CoreBundle/Command/InstallCommand.php b/src/Wallabag/CoreBundle/Command/InstallCommand.php index eb725a59..e327bbe5 100644 --- a/src/Wallabag/CoreBundle/Command/InstallCommand.php +++ b/src/Wallabag/CoreBundle/Command/InstallCommand.php @@ -286,7 +286,7 @@ class InstallCommand extends ContainerAwareCommand protected function setupConfig() { - $this->defaultOutput->writeln('Step 4 of 5. Config setup.'); + $this->defaultOutput->writeln('Step 4 of 5. config setup.'); $em = $this->getContainer()->get('doctrine.orm.entity_manager'); // cleanup before insert new stuff diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php index d4170d39..23af98e5 100644 --- a/src/Wallabag/CoreBundle/Controller/ConfigController.php +++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php @@ -10,12 +10,16 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Wallabag\CoreBundle\Entity\Config; use Wallabag\CoreBundle\Entity\TaggingRule; +use Wallabag\CoreBundle\Event\Activity\Actions\User\UserDeletedEvent; +use Wallabag\CoreBundle\Event\Activity\Actions\User\UserEditedEvent; use Wallabag\CoreBundle\Form\Type\ConfigType; use Wallabag\CoreBundle\Form\Type\ChangePasswordType; use Wallabag\CoreBundle\Form\Type\RssType; use Wallabag\CoreBundle\Form\Type\TaggingRuleType; use Wallabag\CoreBundle\Form\Type\UserInformationType; use Wallabag\CoreBundle\Tools\Utils; +use Wallabag\FederationBundle\Form\Type\AccountType; +use Wallabag\UserBundle\Entity\User; class ConfigController extends Controller { @@ -82,6 +86,50 @@ class ConfigController extends Controller if ($userForm->isSubmitted() && $userForm->isValid()) { $userManager->updateUser($user, true); + $this->get('event_dispatcher')->dispatch(UserEditedEvent::NAME, new UserEditedEvent($user->getAccount())); + + $this->get('session')->getFlashBag()->add( + 'notice', + 'flashes.config.notice.user_updated' + ); + + return $this->redirect($this->generateUrl('config').'#set3'); + } + + // handle account information + $account = $user->getAccount(); + $accountForm = $this->createForm(AccountType::class, $account, [ + 'action' => $this->generateUrl('config').'#set3', + ]); + $accountForm->handleRequest($request); + + if ($accountForm->isSubmitted() && $accountForm->isValid()) { + + $avatar = $account->getAvatar(); + $banner = $account->getBanner(); + + if (null !== $avatar) { + $avatarFileName = md5(uniqid('', true)) . '.' . $avatar->guessExtension(); + + $avatar->move( + $this->getParameter('media_directory') . '/avatar', + $avatarFileName + ); + $account->setAvatar($avatarFileName); + } + + if (null != $banner) { + $bannerFileName = md5(uniqid('', true)) . '.' . $banner->guessExtension(); + + $banner->move( + $this->get('media_directory') . '/banner', + $bannerFileName + ); + $account->setBanner($bannerFileName); + } + + $this->get('event_dispatcher')->dispatch(UserEditedEvent::NAME, new UserEditedEvent($user)); + $this->get('session')->getFlashBag()->add( 'notice', 'flashes.config.notice.user_updated' @@ -145,6 +193,7 @@ class ConfigController extends Controller 'pwd' => $pwdForm->createView(), 'user' => $userForm->createView(), 'new_tagging_rule' => $newTaggingRule->createView(), + 'account' => $accountForm->createView(), ], 'rss' => [ 'username' => $user->getUsername(), @@ -400,9 +449,13 @@ class ConfigController extends Controller $this->get('security.token_storage')->setToken(null); $request->getSession()->invalidate(); + $account = $user->getAccount(); + $em = $this->get('fos_user.user_manager'); $em->deleteUser($user); + $this->get('event_dispatcher')->dispatch(UserDeletedEvent::NAME, new UserDeletedEvent($account)); + return $this->redirect($this->generateUrl('fos_user_security_login')); } diff --git a/src/Wallabag/CoreBundle/Controller/EntryController.php b/src/Wallabag/CoreBundle/Controller/EntryController.php index fafa49f1..5e4462ed 100644 --- a/src/Wallabag/CoreBundle/Controller/EntryController.php +++ b/src/Wallabag/CoreBundle/Controller/EntryController.php @@ -9,12 +9,14 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Wallabag\CoreBundle\Entity\Entry; +use Wallabag\CoreBundle\Event\Activity\Actions\Entry\EntryEditedEvent; +use Wallabag\CoreBundle\Event\Activity\Actions\Entry\EntryFavouriteEvent; +use Wallabag\CoreBundle\Event\Activity\Actions\Entry\EntryReadEvent; +use Wallabag\CoreBundle\Event\Activity\Actions\Entry\EntrySavedEvent; use Wallabag\CoreBundle\Form\Type\EntryFilterType; use Wallabag\CoreBundle\Form\Type\EditEntryType; use Wallabag\CoreBundle\Form\Type\NewEntryType; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache; -use Wallabag\CoreBundle\Event\EntrySavedEvent; -use Wallabag\CoreBundle\Event\EntryDeletedEvent; use Wallabag\CoreBundle\Form\Type\SearchEntryType; class EntryController extends Controller @@ -405,6 +407,8 @@ class EntryController extends Controller $entry->toggleArchive(); $this->getDoctrine()->getManager()->flush(); + $this->get('event_dispatcher')->dispatch(EntryReadEvent::NAME, new EntryReadEvent($entry)); + $message = 'flashes.entry.notice.entry_unarchived'; if ($entry->isArchived()) { $message = 'flashes.entry.notice.entry_archived'; @@ -437,6 +441,8 @@ class EntryController extends Controller $entry->toggleStar(); $this->getDoctrine()->getManager()->flush(); + $this->get('event_dispatcher')->dispatch(EntryFavouriteEvent::NAME, new EntryFavouriteEvent($entry)); + $message = 'flashes.entry.notice.entry_unstarred'; if ($entry->isStarred()) { $message = 'flashes.entry.notice.entry_starred'; @@ -473,9 +479,6 @@ class EntryController extends Controller UrlGeneratorInterface::ABSOLUTE_PATH ); - // entry deleted, dispatch event about it! - $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry)); - $em = $this->getDoctrine()->getManager(); $em->remove($entry); $em->flush(); diff --git a/src/Wallabag/CoreBundle/Controller/ShareController.php b/src/Wallabag/CoreBundle/Controller/ShareController.php new file mode 100644 index 00000000..d6f83ebc --- /dev/null +++ b/src/Wallabag/CoreBundle/Controller/ShareController.php @@ -0,0 +1,165 @@ +getUser() !== $this->getUser()) { + throw new AccessDeniedException("You can't share this entry"); + } + + if ($destination === $this->getUser()) { + throw new InvalidArgumentException("You can't share entries to yourself"); + } + + $share = new Share(); + $share->setUserOrigin($this->getUser()) + ->setEntry($entry) + ->setUserDestination($destination); + + $em = $this->getDoctrine()->getManager(); + $em->persist($share); + $em->flush(); + + $this->get('event_dispatcher')->dispatch(ShareCreatedEvent::NAME, new ShareCancelledEvent($share)); + + $accept = new YesAction($this->generateUrl('share-entry-user-accept', ['share' => $share->getId()])); + + $deny = new NoAction($this->generateUrl('share-entry-user-refuse', ['share' => $share->getId()])); + + $notification = new Notification($destination); + $notification->setType(Notification::TYPE_SHARE) + ->setTitle($this->get('translator')->trans('share.notification.new.title')) + ->addAction($accept) + ->addAction($deny); + + $em->persist($notification); + $em->flush(); + + $this->redirectToRoute('view', ['id' => $entry->getId()]); + } + + /** + * @Route("/share-user/accept/{share}", name="share-entry-user-accept") + * + * @param Share $share + * @return RedirectResponse + * @throws AccessDeniedException + */ + public function acceptShareAction(Share $share) + { + if ($share->getUserDestination() !== $this->getUser()) { + throw new AccessDeniedException("You can't accept this entry"); + } + + $entry = new Entry($this->getUser()); + $entry->setUrl($share->getEntry()->getUrl()); + + $em = $this->getDoctrine()->getManager(); + + if (false === $this->checkIfEntryAlreadyExists($entry)) { + $this->updateEntry($entry); + + $em->persist($entry); + $em->flush(); + + $this->get('event_dispatcher')->dispatch(ShareAcceptedEvent::NAME, new ShareAcceptedEvent($share)); + + // entry saved, dispatch event about it! + $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry)); + } + + $em->remove($share); + $em->flush(); // we keep the previous flush above in case the event dispatcher would lead in using the saved entry + + return $this->redirect($this->generateUrl('homepage')); + } + + /** + * @Route("/share-user/refuse/{share}", name="share-entry-user-refuse") + * + * @param Share $share + * @return RedirectResponse + */ + public function refuseShareAction(Share $share) + { + $em = $this->getDoctrine()->getManager(); + $em->remove($share); + $em->flush(); + + $this->get('event_dispatcher')->dispatch(ShareDeniedEvent::NAME, new ShareDeniedEvent($share)); + + return $this->redirect($this->generateUrl('homepage')); + } + + /** + * Fetch content and update entry. + * In case it fails, entry will return to avod loosing the data. + * + * @param Entry $entry + * @param string $prefixMessage Should be the translation key: entry_saved or entry_reloaded + * + * @return Entry + */ + private function updateEntry(Entry $entry, $prefixMessage = 'entry_saved') + { + // put default title in case of fetching content failed + $entry->setTitle('No title found'); + + $message = 'flashes.entry.notice.'.$prefixMessage; + + try { + $entry = $this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl()); + } catch (\Exception $e) { + $this->get('logger')->error('Error while saving an entry', [ + 'exception' => $e, + 'entry' => $entry, + ]); + + $message = 'flashes.entry.notice.'.$prefixMessage.'_failed'; + } + + $this->get('session')->getFlashBag()->add('notice', $message); + + return $entry; + } + + /** + * Check for existing entry, if it exists, redirect to it with a message. + * + * @param Entry $entry + * + * @return Entry|bool + */ + private function checkIfEntryAlreadyExists(Entry $entry) + { + return $this->get('wallabag_core.entry_repository')->findByUrlAndUserId($entry->getUrl(), $this->getUser()->getId()); + } +} diff --git a/src/Wallabag/CoreBundle/Controller/TagController.php b/src/Wallabag/CoreBundle/Controller/TagController.php index a8b1eadd..6cc78458 100644 --- a/src/Wallabag/CoreBundle/Controller/TagController.php +++ b/src/Wallabag/CoreBundle/Controller/TagController.php @@ -9,6 +9,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Wallabag\CoreBundle\Entity\Entry; use Wallabag\CoreBundle\Entity\Tag; +use Wallabag\CoreBundle\Event\Activity\Actions\Entry\EntryTaggedEvent; use Wallabag\CoreBundle\Form\Type\NewTagType; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; @@ -37,6 +38,8 @@ class TagController extends Controller $em->persist($entry); $em->flush(); + $this->get('event_dispatcher')->dispatch(EntryTaggedEvent::NAME, new EntryTaggedEvent($entry, $tags)); + $this->get('session')->getFlashBag()->add( 'notice', 'flashes.tag.notice.tag_added' @@ -64,6 +67,8 @@ class TagController extends Controller $em = $this->getDoctrine()->getManager(); $em->flush(); + $this->get('event_dispatcher')->dispatch(EntryTaggedEvent::NAME, new EntryTaggedEvent($entry, $tag), true); + // remove orphan tag in case no entries are associated to it if (count($tag->getEntries()) === 0) { $em->remove($tag); diff --git a/src/Wallabag/CoreBundle/Entity/Activity.php b/src/Wallabag/CoreBundle/Entity/Activity.php new file mode 100644 index 00000000..08a3f1fb --- /dev/null +++ b/src/Wallabag/CoreBundle/Entity/Activity.php @@ -0,0 +1,295 @@ +activityType = $activityType; + $this->primaryObjectType = $primaryObjectType; + $this->primaryObjectId = $primaryObjectId; + $this->createdAt = new \DateTime(); + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @return int + */ + public function getActivityType() + { + return $this->activityType; + } + + /** + * @param int $activityType + * @return Activity + */ + public function setActivityType($activityType) + { + $this->activityType = $activityType; + return $this; + } + + /** + * @return \DateTime + */ + public function getCreatedAt() + { + return $this->createdAt; + } + + /** + * @param \DateTime $createdAt + * @return Activity + */ + public function setCreatedAt(\DateTime $createdAt) + { + $this->createdAt = $createdAt; + return $this; + } + + /** + * @return int + */ + public function getPrimaryObjectId() + { + return $this->primaryObjectId; + } + + /** + * @param $primaryObjectId + * @return Activity + */ + public function setPrimaryObjectId($primaryObjectId) + { + $this->primaryObjectId = $primaryObjectId; + return $this; + } + + /** + * @return Account + */ + public function getUser() + { + return $this->user; + } + + /** + * @param Account $user + * @return Activity + */ + public function setUser($user) + { + $this->user = $user; + return $this; + } + + /** + * @return int + */ + public function getPrimaryObjectType() + { + return $this->primaryObjectType; + } + + /** + * @param int $primaryObjectType + * @return Activity + */ + public function setPrimaryObjectType($primaryObjectType) + { + $this->primaryObjectType = $primaryObjectType; + return $this; + } + + /** + * @return int + */ + public function getSecondaryObjectType() + { + return $this->secondaryObjectType; + } + + /** + * @param int $secondaryObjectType + * @return Activity + */ + public function setSecondaryObjectType($secondaryObjectType) + { + $this->secondaryObjectType = $secondaryObjectType; + return $this; + } + + /** + * @return int + */ + public function getSecondaryObjectId() + { + return $this->secondaryObjectId; + } + + /** + * @param int $secondaryObjectId + * @return Activity + */ + public function setSecondaryObjectId($secondaryObjectId) + { + $this->secondaryObjectId = $secondaryObjectId; + return $this; + } +} diff --git a/src/Wallabag/CoreBundle/Entity/Config.php b/src/Wallabag/CoreBundle/Entity/Config.php index b902ae2c..f42a49b3 100644 --- a/src/Wallabag/CoreBundle/Entity/Config.php +++ b/src/Wallabag/CoreBundle/Entity/Config.php @@ -8,7 +8,7 @@ use Symfony\Component\Validator\Constraints as Assert; use Wallabag\UserBundle\Entity\User; /** - * Config. + * config. * * @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\ConfigRepository") * @ORM\Table(name="`config`") diff --git a/src/Wallabag/CoreBundle/Entity/Entry.php b/src/Wallabag/CoreBundle/Entity/Entry.php index a0503c39..6ca17126 100644 --- a/src/Wallabag/CoreBundle/Entity/Entry.php +++ b/src/Wallabag/CoreBundle/Entity/Entry.php @@ -233,13 +233,21 @@ class Entry */ private $tags; - /* + /** + * @var boolean + * + * @ORM\Column(name="recommended", type="boolean", nullable=true) + */ + private $recommended; + + /** * @param User $user */ public function __construct(User $user) { $this->user = $user; $this->tags = new ArrayCollection(); + $this->changes = new ArrayCollection(); } /** @@ -778,4 +786,20 @@ class Entry return $this; } + + /** + * @return bool + */ + public function isRecommended() + { + return $this->recommended; + } + + /** + * @param bool $recommended + */ + public function setRecommended($recommended) + { + $this->recommended = $recommended; + } } diff --git a/src/Wallabag/CoreBundle/Entity/Notification.php b/src/Wallabag/CoreBundle/Entity/Notification.php index aa4c03c3..d4304f39 100644 --- a/src/Wallabag/CoreBundle/Entity/Notification.php +++ b/src/Wallabag/CoreBundle/Entity/Notification.php @@ -91,6 +91,7 @@ class Notification implements NotificationInterface const TYPE_ADMIN = 0; const TYPE_USER = 1; const TYPE_RELEASE = 2; + const TYPE_SHARE = 3; public function __construct(User $user = null) { diff --git a/src/Wallabag/CoreBundle/Entity/Share.php b/src/Wallabag/CoreBundle/Entity/Share.php new file mode 100644 index 00000000..a55b4e67 --- /dev/null +++ b/src/Wallabag/CoreBundle/Entity/Share.php @@ -0,0 +1,140 @@ +accepted = false; + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @return Account + */ + public function getUserOrigin() + { + return $this->userOrigin; + } + + /** + * @param User $userOrigin + * @return Share + */ + public function setUserOrigin(User $userOrigin) + { + $this->userOrigin = $userOrigin; + return $this; + } + + /** + * @return Account + */ + public function getUserDestination() + { + return $this->userDestination; + } + + /** + * @param User $userDestination + * @return Share + */ + public function setUserDestination(User $userDestination) + { + $this->userDestination = $userDestination; + return $this; + } + + /** + * @return bool + */ + public function isAccepted() + { + return $this->accepted; + } + + /** + * @param bool $accepted + * @return Share + */ + public function setAccepted($accepted) + { + $this->accepted = $accepted; + return $this; + } + + /** + * @return Entry + */ + public function getEntry() + { + return $this->entry; + } + + /** + * @param Entry $entry + * @return Share + */ + public function setEntry(Entry $entry) + { + $this->entry = $entry; + return $this; + } +} diff --git a/src/Wallabag/CoreBundle/Event/Activity/Actions/Annotation/AnnotationCreatedEvent.php b/src/Wallabag/CoreBundle/Event/Activity/Actions/Annotation/AnnotationCreatedEvent.php new file mode 100644 index 00000000..b3667703 --- /dev/null +++ b/src/Wallabag/CoreBundle/Event/Activity/Actions/Annotation/AnnotationCreatedEvent.php @@ -0,0 +1,8 @@ +annotation = $annotation; + } + + /** + * @return Annotation + */ + public function getAnnotation() + { + return $this->annotation; + } + + /** + * @param Annotation $annotation + */ + public function setAnnotation(Annotation $annotation) + { + $this->annotation = $annotation; + } +} diff --git a/src/Wallabag/CoreBundle/Event/Activity/Actions/Entry/EntryDeletedEvent.php b/src/Wallabag/CoreBundle/Event/Activity/Actions/Entry/EntryDeletedEvent.php new file mode 100644 index 00000000..1d413d41 --- /dev/null +++ b/src/Wallabag/CoreBundle/Event/Activity/Actions/Entry/EntryDeletedEvent.php @@ -0,0 +1,11 @@ +entry = $entry; + } + + /** + * @return Entry + */ + public function getEntry() + { + return $this->entry; + } + + /** + * @param Entry $entry + * @return EntryEvent + */ + public function setEntry(Entry $entry) + { + $this->entry = $entry; + return $this; + } +} diff --git a/src/Wallabag/CoreBundle/Event/Activity/Actions/Entry/EntryFavouriteEvent.php b/src/Wallabag/CoreBundle/Event/Activity/Actions/Entry/EntryFavouriteEvent.php new file mode 100644 index 00000000..98edb00d --- /dev/null +++ b/src/Wallabag/CoreBundle/Event/Activity/Actions/Entry/EntryFavouriteEvent.php @@ -0,0 +1,14 @@ +tags = $tags; + } + + /** + * @return Tag[] + */ + public function getTags() + { + return $this->tags; + } + + /** + * @return bool + */ + public function isRemove() + { + return $this->remove; + } +} diff --git a/src/Wallabag/CoreBundle/Event/Activity/Actions/Federation/FederationEvent.php b/src/Wallabag/CoreBundle/Event/Activity/Actions/Federation/FederationEvent.php new file mode 100644 index 00000000..da18330e --- /dev/null +++ b/src/Wallabag/CoreBundle/Event/Activity/Actions/Federation/FederationEvent.php @@ -0,0 +1,40 @@ +account = $account; + } + + /** + * @return Account + */ + public function getAccount() + { + return $this->account; + } + + /** + * @param Account $account + * @return FederationEvent + */ + public function setAccount(Account $account) + { + $this->account = $account; + return $this; + } + + +} diff --git a/src/Wallabag/CoreBundle/Event/Activity/Actions/Federation/FollowEvent.php b/src/Wallabag/CoreBundle/Event/Activity/Actions/Federation/FollowEvent.php new file mode 100644 index 00000000..4004932e --- /dev/null +++ b/src/Wallabag/CoreBundle/Event/Activity/Actions/Federation/FollowEvent.php @@ -0,0 +1,39 @@ +follower = $follower; + } + + /** + * @return Account + */ + public function getFollower() + { + return $this->follower; + } + + /** + * @param Account $follower + * @return FollowEvent + */ + public function setFollower(Account $follower) + { + $this->follower = $follower; + return $this; + } +} diff --git a/src/Wallabag/CoreBundle/Event/Activity/Actions/Federation/RecommendedEntryEvent.php b/src/Wallabag/CoreBundle/Event/Activity/Actions/Federation/RecommendedEntryEvent.php new file mode 100644 index 00000000..998b56d5 --- /dev/null +++ b/src/Wallabag/CoreBundle/Event/Activity/Actions/Federation/RecommendedEntryEvent.php @@ -0,0 +1,43 @@ +entry = $entry; + } + + /** + * @return Entry + */ + public function getEntry() + { + return $this->entry; + } + + /** + * @param Entry $entry + * @return RecommendedEntryEvent + */ + public function setEntry(Entry $entry) + { + $this->entry = $entry; + return $this; + } +} diff --git a/src/Wallabag/CoreBundle/Event/Activity/Actions/Federation/UnfollowEvent.php b/src/Wallabag/CoreBundle/Event/Activity/Actions/Federation/UnfollowEvent.php new file mode 100644 index 00000000..bf9a35f8 --- /dev/null +++ b/src/Wallabag/CoreBundle/Event/Activity/Actions/Federation/UnfollowEvent.php @@ -0,0 +1,39 @@ +follower = $follower; + } + + /** + * @return Account + */ + public function getFollower() + { + return $this->follower; + } + + /** + * @param Account $follower + * @return UnfollowEvent + */ + public function setFollower(Account $follower) + { + $this->follower = $follower; + return $this; + } +} diff --git a/src/Wallabag/CoreBundle/Event/Activity/Actions/Share/ShareAcceptedEvent.php b/src/Wallabag/CoreBundle/Event/Activity/Actions/Share/ShareAcceptedEvent.php new file mode 100644 index 00000000..e171ef04 --- /dev/null +++ b/src/Wallabag/CoreBundle/Event/Activity/Actions/Share/ShareAcceptedEvent.php @@ -0,0 +1,11 @@ +share = $share; + } + + /** + * @return Share + */ + public function getShare() + { + return $this->share; + } + + /** + * @param Share $share + */ + public function setShare(Share $share) + { + $this->share = $share; + } +} diff --git a/src/Wallabag/CoreBundle/Event/Activity/Actions/User/UserDeletedEvent.php b/src/Wallabag/CoreBundle/Event/Activity/Actions/User/UserDeletedEvent.php new file mode 100644 index 00000000..df06db8c --- /dev/null +++ b/src/Wallabag/CoreBundle/Event/Activity/Actions/User/UserDeletedEvent.php @@ -0,0 +1,8 @@ +user = $user; + } + + /** + * @return User + */ + public function getUser() + { + return $this->user; + } + + /** + * @param User $user + * @return UserEvent + */ + public function setUser(User $user) + { + $this->user = $user; + return $this; + } +} diff --git a/src/Wallabag/CoreBundle/Event/Activity/ActivitySubscriber.php b/src/Wallabag/CoreBundle/Event/Activity/ActivitySubscriber.php new file mode 100644 index 00000000..81379ff2 --- /dev/null +++ b/src/Wallabag/CoreBundle/Event/Activity/ActivitySubscriber.php @@ -0,0 +1,224 @@ +em = $em; + $this->logger = $logger; + } + + public static function getSubscribedEvents() + { + return [ + EntrySavedEvent::NAME => 'entryActivity', + EntryDeletedEvent::NAME => 'entryActivity', + EntryEditedEvent::NAME => 'entryActivity', + EntryTaggedEvent::NAME => 'taggedEntry', + EntryFavouriteEvent::NAME => 'entryActivity', + EntryReadEvent::NAME => 'entryActivity', + + AnnotationCreatedEvent::NAME => 'annotationActivity', + AnnotationEditedEvent::NAME => 'annotationActivity', + AnnotationDeletedEvent::NAME => 'annotationActivity', + + FollowEvent::NAME => 'followedAccount', + UnfollowEvent::NAME => 'unfollowedAccount', + RecommendedEntryEvent::NAME => 'recommendedEntry', + + ShareCreatedEvent::NAME => 'shareActivity', + ShareAcceptedEvent::NAME => 'shareActivity', + ShareDeniedEvent::NAME => 'shareActivity', + ShareCancelledEvent::NAME => 'shareActivity', + + // when a user register using the normal form + FOSUserEvents::REGISTRATION_COMPLETED => 'userActivity', + // when we manually create a user using the command line + // OR when we create it from the config UI + FOSUserEvents::USER_CREATED => 'userActivity', + UserEditedEvent::NAME => 'userActivity', + UserDeletedEvent::NAME => 'userActivity', + ]; + } + + public function userActivity(Event $event) + { + $activityType = 0; + if ($event instanceof UserEvent) { + $activityType = Activity::USER_CREATE; + } elseif ($event instanceof UserEditedEvent) { + $activityType = Activity::USER_EDIT; + } elseif ($event instanceof UserDeletedEvent) { + $activityType = Activity::USER_REMOVE; + } + + $user = $event->getUser(); + $activity = new Activity($activityType, Activity::USER_OBJECT, $user->getId()); + $activity->setUser($user->getAccount()); + $this->em->persist($activity); + $this->em->flush(); + } + + public function entryActivity(EntryEvent $event) + { + $entry = $event->getEntry(); + + $activityType = 0; + if ($event instanceof EntrySavedEvent) { + $activityType = Activity::ENTRY_ADD; + } elseif ($event instanceof EntryDeletedEvent) { + $activityType = Activity::ENTRY_DELETE; + } elseif ($event instanceof EntryEditedEvent) { + $activityType = Activity::ENTRY_EDIT; + } elseif ($event instanceof EntryFavouriteEvent) { + if ($entry->isStarred()) { + $activityType = Activity::ENTRY_FAVOURITE; + } else { + $activityType = Activity::ENTRY_UNFAVOURITE; + } + } elseif ($event instanceof EntryReadEvent) { + if ($entry->isArchived()) { + $activityType = Activity::ENTRY_READ; + } else { + $activityType = Activity::ENTRY_UNREAD; + } + } + + $activity = new Activity($activityType, Activity::ENTRY_OBJECT, $entry->getId()); + $activity->setUser($entry->getUser()->getAccount()); + $this->em->persist($activity); + $this->em->flush(); + } + + public function taggedEntry(EntryTaggedEvent $event) + { + $entry = $event->getEntry(); + $activity = new Activity($event->isRemove() ? Activity::ENTRY_REMOVE_TAG : Activity::ENTRY_ADD_TAG, Activity::ENTRY_OBJECT, $entry->getId()); + $activity->setUser($entry->getUser()->getAccount()); + $activity->setSecondaryObjectType(Activity::TAG_OBJECT) + ->setSecondaryObjectId($event->getTags()[0]->getId()); + $this->em->persist($activity); + $this->em->flush(); + } + + public function annotationActivity(AnnotationEvent $event) + { + $annotation = $event->getAnnotation(); + + $activityType = 0; + if ($event instanceof AnnotationCreatedEvent) { + $activityType = Activity::ANNOTATION_ADD; + } elseif ($event instanceof AnnotationEditedEvent) { + $activityType = Activity::ANNOTATION_EDIT; + } elseif ($event instanceof AnnotationDeletedEvent) { + $activityType = Activity::ANNOTATION_REMOVE; + } + + $activity = new Activity($activityType, Activity::ANNOTATION_OBJECT, $annotation->getId()); + $activity->setUser($annotation->getUser()->getAccount()); + $this->em->persist($activity); + $this->em->flush(); + } + + public function followedAccount(FollowEvent $event) + { + $activity = new Activity(Activity::FOLLOW_ACCOUNT, Activity::ACCOUNT_OBJECT, $event->getAccount()->getId()); + $activity->setUser($event->getAccount()); + $activity->setSecondaryObjectType(Activity::ACCOUNT_OBJECT) + ->setSecondaryObjectId($event->getFollower()->getId()); + $this->em->persist($activity); + $this->em->flush(); + } + + public function unfollowedAccount(UnfollowEvent $event) + { + $activity = new Activity(Activity::UNFOLLOW_ACCOUNT, Activity::ACCOUNT_OBJECT, $event->getAccount()->getId()); + $activity->setUser($event->getAccount()); + $activity->setSecondaryObjectType(Activity::ACCOUNT_OBJECT) + ->setSecondaryObjectId($event->getFollower()->getId()); + $this->em->persist($activity); + $this->em->flush(); + } + + public function recommendedEntry(RecommendedEntryEvent $event) + { + $entry = $event->getEntry(); + $account = $entry->getUser()->getAccount(); + $activity = new Activity(Activity::RECOMMEND_ENTRY, Activity::ACCOUNT_OBJECT, $account->getId()); + $activity->setUser($account); + $activity->setSecondaryObjectType(Activity::ENTRY_OBJECT) + ->setSecondaryObjectId($entry->getId()); + $this->em->persist($activity); + $this->em->flush(); + } + + public function shareActivity(ShareEvent $event) + { + $share = $event->getShare(); + + $activityType = 0; + if ($event instanceof ShareCreatedEvent) { + $activityType = Activity::USER_SHARE_CREATED; + } elseif ($event instanceof ShareAcceptedEvent) { + $activityType = Activity::USER_SHARE_ACCEPTED; + } elseif ($event instanceof ShareDeniedEvent) { + $activityType = Activity::USER_SHARE_REFUSED; + } elseif ($event instanceof ShareCancelledEvent) { + $activityType = Activity::USER_SHARE_CANCELLED; + } + + $activity = new Activity($activityType, Activity::SHARE_OBJECT, $share->getId()); + $activity->setUser($share->getUserOrigin()); + $activity->setSecondaryObjectType(Activity::ACCOUNT_OBJECT) + ->setSecondaryObjectId($share->getUserDestination()->getId()); + $this->em->persist($activity); + $this->em->flush(); + } +} diff --git a/src/Wallabag/CoreBundle/Event/EntryDeletedEvent.php b/src/Wallabag/CoreBundle/Event/EntryDeletedEvent.php deleted file mode 100644 index e9061d04..00000000 --- a/src/Wallabag/CoreBundle/Event/EntryDeletedEvent.php +++ /dev/null @@ -1,26 +0,0 @@ -entry = $entry; - } - - public function getEntry() - { - return $this->entry; - } -} diff --git a/src/Wallabag/CoreBundle/Event/EntrySavedEvent.php b/src/Wallabag/CoreBundle/Event/EntrySavedEvent.php deleted file mode 100644 index 5fdb5221..00000000 --- a/src/Wallabag/CoreBundle/Event/EntrySavedEvent.php +++ /dev/null @@ -1,26 +0,0 @@ -entry = $entry; - } - - public function getEntry() - { - return $this->entry; - } -} diff --git a/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php b/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php index 4ebe837b..5c3550c2 100644 --- a/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php +++ b/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php @@ -4,10 +4,10 @@ namespace Wallabag\CoreBundle\Event\Subscriber; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Psr\Log\LoggerInterface; +use Wallabag\CoreBundle\Event\Activity\Actions\Entry\EntryDeletedEvent; +use Wallabag\CoreBundle\Event\Activity\Actions\Entry\EntrySavedEvent; use Wallabag\CoreBundle\Helper\DownloadImages; use Wallabag\CoreBundle\Entity\Entry; -use Wallabag\CoreBundle\Event\EntrySavedEvent; -use Wallabag\CoreBundle\Event\EntryDeletedEvent; use Doctrine\ORM\EntityManager; class DownloadImagesSubscriber implements EventSubscriberInterface diff --git a/src/Wallabag/CoreBundle/Repository/ChangeRepository.php b/src/Wallabag/CoreBundle/Repository/ChangeRepository.php new file mode 100644 index 00000000..18d015a7 --- /dev/null +++ b/src/Wallabag/CoreBundle/Repository/ChangeRepository.php @@ -0,0 +1,26 @@ +setTimestamp($timestamp); + + return $this->createQueryBuilder('c') + ->where('c.createdAt >= :timestamp')->setParameter('timestamp', $date) + ->getQuery() + ->getResult(); + } +} diff --git a/src/Wallabag/CoreBundle/Repository/EntryRepository.php b/src/Wallabag/CoreBundle/Repository/EntryRepository.php index 9bda4e15..4bbd05ff 100644 --- a/src/Wallabag/CoreBundle/Repository/EntryRepository.php +++ b/src/Wallabag/CoreBundle/Repository/EntryRepository.php @@ -4,6 +4,7 @@ namespace Wallabag\CoreBundle\Repository; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query; +use Doctrine\ORM\QueryBuilder; use Pagerfanta\Adapter\DoctrineORMAdapter; use Pagerfanta\Pagerfanta; use Wallabag\CoreBundle\Entity\Tag; @@ -89,7 +90,7 @@ class EntryRepository extends EntityRepository * * @param int $userId * @param string $term - * @param strint $currentRoute + * @param string $currentRoute * * @return QueryBuilder */ @@ -414,4 +415,15 @@ class EntryRepository extends EntityRepository ->getQuery() ->getResult(); } + + /** + * @param $userId + * @return QueryBuilder + */ + public function getBuilderForRecommendationsByUser($userId) + { + return $this->getBuilderByUser($userId) + ->andWhere('e.recommended = true') + ; + } } diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml index 183b6690..6864993c 100644 --- a/src/Wallabag/CoreBundle/Resources/config/services.yml +++ b/src/Wallabag/CoreBundle/Resources/config/services.yml @@ -222,3 +222,11 @@ services: arguments: - "%wallabag_core.site_credentials.encryption_key_path%" - "@logger" + + wallabag_core.activity.subscriber: + class: Wallabag\CoreBundle\Event\Activity\ActivitySubscriber + arguments: + - "@doctrine.orm.default_entity_manager" + - "@logger" + tags: + - { name: kernel.event_subscriber } diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml index 98f1b48c..4f19556f 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml @@ -20,7 +20,7 @@ menu: starred: 'Starred' archive: 'Archive' all_articles: 'All entries' - config: 'Config' + config: 'config' tags: 'Tags' internal_settings: 'Internal Settings' import: 'Import' @@ -51,7 +51,7 @@ footer: stats: Since %user_creation% you read %nb_archives% articles. That is about %per_day% a day! config: - page_title: 'Config' + page_title: 'config' tab_menu: settings: 'Settings' rss: 'RSS' @@ -108,6 +108,11 @@ config: description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. confirm: Are you really sure? (THIS CAN'T BE UNDONE) button: Delete my account + form_account: + title: "Profile" + description_label: "Description de votre compte. Ce texte sera affiché sur votre page de profil." + avatar_label: "Avatar" + banner_label: "Bannière" reset: title: Reset area (a.k.a danger zone) description: By hitting buttons below you'll have ability to remove some information from your account. Be aware that these actions are IRREVERSIBLE. @@ -562,7 +567,7 @@ error: flashes: config: notice: - config_saved: 'Config saved.' + config_saved: 'config saved.' password_updated: 'Password updated' password_not_updated_demo: "In demonstration mode, you can't change password for this user." user_updated: 'Information updated' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml index 277f2f63..2c53c12b 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml @@ -108,6 +108,11 @@ config: description: "Si vous confirmez la suppression de votre compte, TOUS les articles, TOUS les tags, TOUTES les annotations et votre compte seront DÉFINITIVEMENT supprimé (c’est IRRÉVERSIBLE). Vous serez ensuite déconnecté." confirm: "Vous êtes vraiment sûr ? (C’EST IRRÉVERSIBLE)" button: "Supprimer mon compte" + form_account: + title: "Profil" + description_label: "Description de votre compte. Ce texte sera affiché sur votre page de profil." + avatar_label: "Avatar" + banner_label: "Bannière" reset: title: "Réinitialisation (attention danger !)" description: "En cliquant sur les boutons ci-dessous vous avez la possibilité de supprimer certaines informations de votre compte. Attention, ces actions sont IRRÉVERSIBLES !" diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml index 2d71cb9d..b77bfd7f 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml @@ -51,7 +51,7 @@ footer: stats: 'Desde %user_creation% você leu %nb_archives% artigos. Isso é %per_day% por dia!' config: - page_title: 'Config' + page_title: 'config' tab_menu: settings: 'Configurações' rss: 'RSS' diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig index bd5932b0..c2beb35a 100644 --- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig +++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig @@ -219,6 +219,51 @@ {{ form_widget(form.user._token) }} +


+
{{ 'config.form_account.title'|trans }}
+ + {{ form_start(form.account) }} + {{ form_errors(form.account) }} + +
+
+ {{ form_errors(form.account.description) }} + {{ form_widget(form.account.description, {'attr': {'class': 'materialize-textarea'}}) }} + {{ form_label(form.account.description) }} +
+
+ +
+
+ {{ form_errors(form.account.avatar) }} +
+ {{ 'config.form_account.avatar_label'|trans }} + {{ form_widget(form.account.avatar) }} +
+
+ +
+
+
+ +
+
+ {{ form_errors(form.account.banner) }} +
+ {{ 'config.form_account.banner_label'|trans }} + {{ form_widget(form.account.banner) }} +
+
+ +
+
+
+ + {{ form_widget(form.account.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }} + {{ 'config.form_account.view_profile' | trans }} + {{ form_widget(form.account._token) }} + +


diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig index ccc44931..f93b635f 100644 --- a/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig +++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig @@ -117,6 +117,11 @@ {% if unreadNotifs > 0 %}{{ unreadNotifs }}{% endif %} +
  • + + person + +
  • filter_list diff --git a/src/Wallabag/FederationBundle/Command/CreateAccountsCommand.php b/src/Wallabag/FederationBundle/Command/CreateAccountsCommand.php new file mode 100644 index 00000000..f2ca3b06 --- /dev/null +++ b/src/Wallabag/FederationBundle/Command/CreateAccountsCommand.php @@ -0,0 +1,126 @@ +setName('wallabag:federation:create-accounts') + ->setDescription('Creates missing federation accounts') + ->setHelp('This command creates accounts for federation for missing users') + ->addArgument( + 'username', + InputArgument::OPTIONAL, + 'User to create an account for' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->output = $output; + + $domainName = $this->getContainer()->getParameter('domain_name'); + $instance = $this->checkInstance($domainName); + + $username = $input->getArgument('username'); + + if ($username) { + try { + $user = $this->getUser($username); + $this->createAccount($user, $instance); + } catch (NoResultException $e) { + $output->writeln(sprintf('User "%s" not found.', $username)); + + return 1; + } + } else { + $users = $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findAll(); + + $output->writeln(sprintf('Creating through %d user federated accounts', count($users))); + + foreach ($users as $user) { + $output->writeln(sprintf('Processing user %s', $user->getUsername())); + $this->createAccount($user, $instance); + } + $output->writeln(sprintf('Creating user federated accounts. %d accounts created in total', $this->created)); + } + + return 0; + } + + /** + * @param User $user + * @param $instance + */ + private function createAccount(User $user, Instance $instance) + { + $em = $this->getContainer()->get('doctrine.orm.entity_manager'); + $repo = $em->getRepository('WallabagFederationBundle:Account'); + + if ($repo->findBy(['user' => $user->getId()])) { + return; + } + + $account = new Account(); + $account->setUsername($user->getUsername()) + ->setUser($user) + ->setServer($instance); + + $em->persist($account); + $em->flush(); + + $user->setAccount($account); + $em->persist($account); + $em->flush(); + + ++$this->created; + } + + private function checkInstance($domainName) + { + $em = $this->getContainer()->get('doctrine.orm.entity_manager'); + $repo = $em->getRepository('WallabagFederationBundle:Instance'); + + $instance = $repo->findOneByDomain($domainName); + if (!$instance) { + $instance = new Instance($domainName); + + $em->persist($instance); + $em->flush(); + } + return $instance; + } + + /** + * Fetches a user from its username. + * + * @param string $username + * + * @return \Wallabag\UserBundle\Entity\User + */ + private function getUser($username) + { + return $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findOneByUserName($username); + } + + private function getDoctrine() + { + return $this->getContainer()->get('doctrine'); + } +} diff --git a/src/Wallabag/FederationBundle/Controller/InboxController.php b/src/Wallabag/FederationBundle/Controller/InboxController.php new file mode 100644 index 00000000..99cfeadf --- /dev/null +++ b/src/Wallabag/FederationBundle/Controller/InboxController.php @@ -0,0 +1,43 @@ +getDoctrine()->getManager(); + $response = new Response(); + + if ($activity = json_decode($request->getContent())) { + if ($activity->type === 'Follow' && isset($activity->actor->id)) { + $cloudId = new CloudId($activity->actor->id); + $account = new Account(); + $account->setServer($cloudId->getRemote()) + ->setUsername($cloudId->getUser()); + $em->persist($account); + $em->flush(); + + $response->setStatusCode(201); + } else { + $response->setStatusCode(400); + } + } + return $response; + } +} diff --git a/src/Wallabag/FederationBundle/Controller/LikedController.php b/src/Wallabag/FederationBundle/Controller/LikedController.php new file mode 100644 index 00000000..3e86d50d --- /dev/null +++ b/src/Wallabag/FederationBundle/Controller/LikedController.php @@ -0,0 +1,19 @@ +setContent(' +generateUrl('webfinger') . '?resource={uri}\"/> +'); + $response->headers->set('Content-Type', 'application/xrd+xml'); + return $response; + } + + /** + * @Route("/.well-known/webfinger", name="webfinger") + * @param Request $request + * @return JsonResponse + */ + public function getWebfingerData(Request $request) + { + $subject = $request->query->get('resource'); + + if (!$subject || strlen($subject) < 12 || 0 !== strpos($subject, 'acct:') || false !== strpos($subject, '@')) { + return new JsonResponse([]); + } + $subjectId = substr($subject, 5); + + $cloudId = $this->getCloudId($subjectId); + + + $data = [ + 'subject' => $subject, + 'aliases' => [$this->generateUrl('profile', $cloudId->getUser())], + 'links' => [ + [ + 'rel' => 'http://webfinger.net/rel/profile-page', + 'type' => 'text/html', + 'href' => $this->generateUrl('profile', $cloudId->getUser()) + ], + ] + ]; + return new JsonResponse($data); + } + + private function getCloudId($subjectId) + { + return new CloudId($subjectId); + } + + + # + # {"subject":"acct:tcit@social.tcit.fr","aliases":["https://social.tcit.fr/@tcit","https://social.tcit.fr/users/tcit"],"links":[{"rel":"http://webfinger.net/rel/profile-page","type":"text/html","href":"https://social.tcit.fr/@tcit"},{"rel":"http://schemas.google.com/g/2010#updates-from","type":"application/atom+xml","href":"https://social.tcit.fr/users/tcit.atom"},{"rel":"self","type":"application/activity+json","href":"https://social.tcit.fr/@tcit"},{"rel":"salmon","href":"https://social.tcit.fr/api/salmon/1"},{"rel":"magic-public-key","href":"data:application/magic-public-key,RSA.pXwYMUdFg3XUd-bGsh8CyiMRGpRGAWuCdM5pDWx5uM4pW2pM3xbHbcI21j9h8BmlAiPg6hbZD73KGly2N8Rt5iIS0I-l6i8kA1JCCdlAaDTRd41RKMggZDoQvjVZQtsyE1VzMeU2kbqqTFN6ew7Hvbd6O0NhixoKoZ5f3jwuBDZoT0p1TAcaMdmG8oqHD97isizkDnRn8cOBA6wtI-xb5xP2zxZMsLpTDZLiKU8XcPKZCw4OfQfmDmKkHtrFb77jCAQj_s_FxjVnvxRwmfhNnWy0D-LUV_g63nHh_b5zXIeV92QZLvDYbgbezmzUzv9UeA1s70GGbaDqCIy85gw9-w==.AQAB"},{"rel":"http://ostatus.org/schema/1.0/subscribe","template":"https://social.tcit.fr/authorize_follow?acct={uri}"}]} + # +} diff --git a/src/Wallabag/FederationBundle/Controller/OutboxController.php b/src/Wallabag/FederationBundle/Controller/OutboxController.php new file mode 100644 index 00000000..87ebdba1 --- /dev/null +++ b/src/Wallabag/FederationBundle/Controller/OutboxController.php @@ -0,0 +1,23 @@ +getAcceptableContentTypes(), true)) { + $data = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Person', + 'id' => CloudId::getCloudIdFromAccount($user, $this->generateUrl('homepage', [], UrlGeneratorInterface::ABSOLUTE_URL))->getDisplayId(), + 'following' => $this->generateUrl('following', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), + 'followers' => $this->generateUrl('followers', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), + //'liked' => $this->generateUrl('recommended', ['user' => $user], UrlGeneratorInterface::ABSOLUTE_URL), + 'inbox' => $this->generateUrl('user-inbox', ['user' => $user], UrlGeneratorInterface::ABSOLUTE_URL), + 'outbox' => $this->generateUrl('user-outbox', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), + 'preferredUsername' => $user->getUser()->getName(), + 'name' => $user->getUsername(), + //'oauthAuthorizationEndpoint' => $this->generateUrl('fos_oauth_server_authorize', [], UrlGeneratorInterface::ABSOLUTE_URL), + 'oauthTokenEndpoint' => $this->generateUrl('fos_oauth_server_token', [], UrlGeneratorInterface::ABSOLUTE_URL), + //'publicInbox' => $this->generateUrl('public_inbox', [], UrlGeneratorInterface::ABSOLUTE_URL), + ]; + return new JsonResponse($data); + } + return $this->render( + 'WallabagFederationBundle:User:profile.html.twig', [ + 'user' => $user, + 'registration_enabled' => $this->getParameter('wallabag_user.registration_enabled'), + ] + ); + } + + /** + * @Route("/profile/@{user}/followings/{page}", name="following", defaults={"page" : 0}) + * @ParamConverter("user", class="WallabagFederationBundle:Account", options={ + * "repository_method" = "findOneByUsername"}) + * + * @param Request $request + * @param Account $user + * @param int $page + * @return JsonResponse|Response + */ + public function getUsersFollowing(Request $request, Account $user, $page = 0) + { + $qb = $this->getDoctrine()->getRepository('WallabagFederationBundle:Account')->getBuilderForFollowingsByAccount($user->getId()); + + $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false); + + $following = new Pagerfanta($pagerAdapter); + $totalFollowing = $following->getNbResults(); + + $activityStream = in_array('application/ld+json; profile="https://www.w3.org/ns/activitystreams', $request->getAcceptableContentTypes(), true); + + if ($page === 0 && $activityStream) { + /** Home page */ + $dataPrez = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'summary' => $user->getUsername() . " followings'", + 'type' => 'Collection', + 'id' => $this->generateUrl('following', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), + 'totalItems' => $totalFollowing, + 'first' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('following', ['user' => $user->getUsername(), 'page' => 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'First page of ' . $user->getUsername() . ' followings' + ], + 'last' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('following', ['user' => $user->getUsername(), 'page' => $following->getNbPages()], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Last page of ' . $user->getUsername() . ' followings' + ] + ]; + return new JsonResponse($dataPrez); + //} + } + + $following->setMaxPerPage(30); + $following->setCurrentPage($page); + + if (!$activityStream) { + return $this->render('WallabagFederationBundle:User:followers.html.twig', [ + 'users' => $following, + 'user' => $user, + 'registration_enabled' => $this->getParameter('wallabag_user.registration_enabled'), + ]); + } + + $items = []; + + foreach ($following->getCurrentPageResults() as $follow) { + /** @var Account $follow */ + /** Items in the page */ + $items[] = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Person', + 'name' => $follow->getUsername(), + 'id' => CloudId::getCloudIdFromAccount($follow), + ]; + } + + $data = [ + 'summary' => 'Page ' . $page . ' of ' . $user->getUsername() . ' followers', + 'partOf' => $this->generateUrl('following', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), + 'type' => 'OrderedCollectionPage', + 'startIndex' => ($page - 1) * 30, + 'orderedItems' => $items, + 'first' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('following', ['user' => $user->getUsername(), 'page' => 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'First page of ' . $user->getUsername() . ' followings' + ], + 'last' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('following', ['user' => $user->getUsername(), 'page' => $following->getNbPages()], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Last page of ' . $user->getUsername() . ' followings' + ], + ]; + + /** Previous page */ + if ($page > 1) { + $data['prev'] = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('following', ['user' => $user->getUsername(), 'page' => $page - 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Previous page of ' . $user->getUsername() . ' followings' + ]; + } + + /** Next page */ + if ($page < $following->getNbPages()) { + $data['next'] = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('following', ['user' => $user->getUsername(), 'page' => $page + 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Next page of ' . $user->getUsername() . ' followings' + ]; + } + + return new JsonResponse($data); + } + + /** + * @Route("/profile/@{user}/followers/{page}", name="followers", defaults={"page" : 0}) + * @ParamConverter("user", class="WallabagFederationBundle:Account", options={ + * "repository_method" = "findOneByUsername"}) + * + * @param Request $request + * @param Account $user + * @return JsonResponse + */ + public function getUsersFollowers(Request $request, Account $user, $page) + { + $qb = $this->getDoctrine()->getRepository('WallabagFederationBundle:Account')->getBuilderForFollowersByAccount($user->getId()); + + $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false); + + $followers = new Pagerfanta($pagerAdapter); + $totalFollowers = $followers->getNbResults(); + + $activityStream = in_array('application/ld+json; profile="https://www.w3.org/ns/activitystreams', $request->getAcceptableContentTypes(), true); + + if ($page === 0 && $activityStream) { + /** Home page */ + $dataPrez = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'summary' => $user->getUsername() . " followers'", + 'type' => 'Collection', + 'id' => $this->generateUrl('followers', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), + 'totalItems' => $totalFollowers, + 'first' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('followers', ['user' => $user->getUsername(), 'page' => 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'First page of ' . $user->getUsername() . ' followers' + ], + 'last' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('followers', ['user' => $user->getUsername(), 'page' => $followers->getNbPages()], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Last page of ' . $user->getUsername() . ' followers' + ] + ]; + return new JsonResponse($dataPrez); + } + + $followers->setMaxPerPage(30); + if (!$activityStream && $page === 0) { + $followers->setCurrentPage(1); + } else { + $followers->setCurrentPage($page); + } + + if (!$activityStream) { + return $this->render('WallabagFederationBundle:User:followers.html.twig', [ + 'users' => $followers, + 'user' => $user, + 'registration_enabled' => $this->getParameter('wallabag_user.registration_enabled'), + ]); + } + + $items = []; + + foreach ($followers->getCurrentPageResults() as $follow) { + /** @var Account $follow */ + /** Items in the page */ + $items[] = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Person', + 'name' => $follow->getUsername(), + 'id' => CloudId::getCloudIdFromAccount($follow)->getDisplayId(), + ]; + } + $data = [ + 'summary' => 'Page ' . $page . ' of ' . $user->getUsername() . ' followers', + 'partOf' => $this->generateUrl('followers', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), + 'type' => 'OrderedCollectionPage', + 'startIndex' => ($page - 1) * 30, + 'orderedItems' => $items, + 'first' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('followers', ['user' => $user->getUsername(), 'page' => 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'First page of ' . $user->getUsername() . ' followers' + ], + 'last' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('followers', ['user' => $user->getUsername(), 'page' => $followers->getNbPages()], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Last page of ' . $user->getUsername() . ' followers' + ], + ]; + + /** Previous page */ + if ($page > 1) { + $data['prev'] = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('followers', ['user' => $user->getUsername(), 'page' => $page - 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Previous page of ' . $user->getUsername() . ' followers' + ]; + } + + /** Next page */ + if ($page < $followers->getNbPages()) { + $data['next'] = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('followers', ['user' => $user->getUsername(), 'page' => $page + 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Next page of ' . $user->getUsername() . ' followers' + ]; + } + + return new JsonResponse($data); + } + + /** + * @Route("/profile/@{userToFollow}/follow", name="follow-user") + * @ParamConverter("userToFollow", class="WallabagFederationBundle:Account", options={ + * "repository_method" = "findOneByUsername"}) + * @param Account $userToFollow + */ + public function followAccountAction(Account $userToFollow) + { + // if we're on our own instance + if ($this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) { + + /** @var Account $userAccount */ + $userAccount = $this->getUser()->getAccount(); + + if ($userToFollow === $userAccount) { + $this->createAccessDeniedException("You can't follow yourself"); + } + + $em = $this->getDoctrine()->getManager(); + + $userAccount->addFollowing($userToFollow); + $userToFollow->addFollower($userAccount); + + $em->persist($userAccount); + $em->persist($userToFollow); + + $em->flush(); + } else { + // ask cloud id and redirect to instance + } + } + + /** + * @Route("/profile/@{user}/recommendations", name="user-recommendations", defaults={"page" : 0}) + * @ParamConverter("user", class="WallabagFederationBundle:Account", options={ + * "repository_method" = "findOneByUsername"}) + * + * @param Request $request + * @param Account $user + * @param int $page + * @return JsonResponse|Response + */ + public function getUsersRecommendationsAction(Request $request, Account $user, $page = 0) + { + $qb = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry')->getBuilderForRecommendationsByUser($user->getUser()->getId()); + + $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false); + + $recommendations = new Pagerfanta($pagerAdapter); + $totalRecommendations = $recommendations->getNbResults(); + + $activityStream = in_array('application/ld+json; profile="https://www.w3.org/ns/activitystreams', $request->getAcceptableContentTypes(), true); + + if ($page === 0 && $activityStream) { + $dataPrez = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'summary' => $user->getUsername() . " recommendations'", + 'type' => 'Collection', + 'id' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), + 'totalItems' => $totalRecommendations, + 'first' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername(), 'page' => 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'First page of ' . $user->getUsername() . ' followers' + ], + 'last' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername(), 'page' => $recommendations->getNbPages()], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Last page of ' . $user->getUsername() . ' followers' + ] + ]; + return new JsonResponse($dataPrez); + } + + $recommendations->setMaxPerPage(30); + if (!$activityStream && $page === 0) { + $recommendations->setCurrentPage(1); + } else { + $recommendations->setCurrentPage($page); + } + + if (!$activityStream) { + return $this->render('WallabagFederationBundle:User:recommendations.html.twig', [ + 'recommendations' => $recommendations, + 'registration_enabled' => $this->getParameter('wallabag_user.registration_enabled'), + ]); + } + + $items = []; + + foreach ($recommendations->getCurrentPageResults() as $recommendation) { + $items[] = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Person', + 'name' => $recommendation->getTitle(), + 'id' => $recommendation->getUrl(), + ]; + } + $data = [ + 'summary' => 'Page ' . $page . ' of ' . $user->getUsername() . ' recommendations', + 'partOf' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), + 'type' => 'OrderedCollectionPage', + 'startIndex' => ($page - 1) * 30, + 'orderedItems' => $items, + 'first' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername(), 'page' => 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'First page of ' . $user->getUsername() . ' recommendations' + ], + 'last' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername(), 'page' => $recommendations->getNbPages()], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Last page of ' . $user->getUsername() . ' recommendations' + ], + ]; + + if ($page > 1) { + $data['prev'] = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername(), 'page' => $page - 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Previous page of ' . $user->getUsername() . ' recommendations' + ]; + } + + if ($page < $recommendations->getNbPages()) { + $data['next'] = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername(), 'page' => $page + 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Next page of ' . $user->getUsername() . ' recommendations' + ]; + } + + return new JsonResponse($data); + } + +} diff --git a/src/Wallabag/FederationBundle/Controller/RecommandController.php b/src/Wallabag/FederationBundle/Controller/RecommandController.php new file mode 100644 index 00000000..ea5a6660 --- /dev/null +++ b/src/Wallabag/FederationBundle/Controller/RecommandController.php @@ -0,0 +1,34 @@ +getUser() !== $this->getUser()) { + $this->createAccessDeniedException("You can't recommend entries which are not your own"); + } + $em = $this->getDoctrine()->getManager(); + + $entry->setRecommended(true); + + $em->persist($entry); + $em->flush(); + + $this->get('event_dispatcher')->dispatch(RecommendedEntryEvent::NAME, new RecommendedEntryEvent($entry)); + + $this->redirectToRoute('view', ['id' => $entry->getId()]); + } +} diff --git a/src/Wallabag/FederationBundle/DependencyInjection/Configuration.php b/src/Wallabag/FederationBundle/DependencyInjection/Configuration.php new file mode 100644 index 00000000..7754e8e4 --- /dev/null +++ b/src/Wallabag/FederationBundle/DependencyInjection/Configuration.php @@ -0,0 +1,25 @@ +root('wallabag_user'); + + $rootNode + ->children() + ->booleanNode('registration_enabled') + ->defaultValue(true) + ->end() + ->end() + ; + + return $treeBuilder; + } +} diff --git a/src/Wallabag/FederationBundle/DependencyInjection/WallabagFederationExtension.php b/src/Wallabag/FederationBundle/DependencyInjection/WallabagFederationExtension.php new file mode 100644 index 00000000..ceeb8379 --- /dev/null +++ b/src/Wallabag/FederationBundle/DependencyInjection/WallabagFederationExtension.php @@ -0,0 +1,26 @@ +processConfiguration($configuration, $configs); + + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('services.yml'); + $container->setParameter('wallabag_user.registration_enabled', $config['registration_enabled']); + } + + public function getAlias() + { + return 'wallabag_federation'; + } +} diff --git a/src/Wallabag/FederationBundle/Entity/Account.php b/src/Wallabag/FederationBundle/Entity/Account.php new file mode 100644 index 00000000..c44050d9 --- /dev/null +++ b/src/Wallabag/FederationBundle/Entity/Account.php @@ -0,0 +1,307 @@ +followers = new ArrayCollection(); + $this->following = new ArrayCollection(); + $this->liked = new ArrayCollection(); + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @return string + */ + public function getUsername() + { + return $this->username; + } + + /** + * @param string $username + * @return Account + */ + public function setUsername($username) + { + $this->username = $username; + return $this; + } + + /** + * @return string + */ + public function getServer() + { + return $this->server; + } + + /** + * @param string $server + * @return Account + */ + public function setServer($server) + { + $this->server = $server; + return $this; + } + + /** + * @return User + */ + public function getUser() + { + return $this->user; + } + + /** + * @param User $user + * @return Account + */ + public function setUser(User $user) + { + $this->user = $user; + return $this; + } + + /** + * @return Collection + */ + public function getFollowers() + { + return $this->followers; + } + + /** + * @param Collection $followers + * @return Account + */ + public function setFollowers($followers) + { + $this->followers = $followers; + return $this; + } + + /** + * @param Account $account + * @return Account + */ + public function addFollower(Account $account) + { + $this->followers->add($account); + return $this; + } + + /** + * @return Collection + */ + public function getFollowing() + { + return $this->following; + } + + /** + * @param Collection $following + * @return Account + */ + public function setFollowing(Collection $following) + { + $this->following = $following; + return $this; + } + + /** + * @param Account $account + * @return Account + */ + public function addFollowing(Account $account) + { + $this->following->add($account); + return $this; + } + + /** + * @return Collection + */ + public function getLiked() + { + return $this->liked; + } + + /** + * @param Collection $liked + * @return Account + */ + public function setLiked(Collection $liked) + { + $this->liked = $liked; + return $this; + } + + /** + * @param Entry $entry + * @return Account + */ + public function addLiked(Entry $entry) + { + $this->liked->add($entry); + return $this; + } + + /** + * @return string + */ + public function getAvatar() + { + return $this->avatar; + } + + /** + * @param string $avatar + * @return Account + */ + public function setAvatar($avatar) + { + $this->avatar = $avatar; + return $this; + } + + /** + * @return string + */ + public function getBanner() + { + return $this->banner; + } + + /** + * @param string $banner + * @return Account + */ + public function setBanner($banner) + { + $this->banner = $banner; + return $this; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param string $description + * @return Account + */ + public function setDescription($description) + { + $this->description = $description; + return $this; + } + +} diff --git a/src/Wallabag/FederationBundle/Entity/Instance.php b/src/Wallabag/FederationBundle/Entity/Instance.php new file mode 100644 index 00000000..ff8960cd --- /dev/null +++ b/src/Wallabag/FederationBundle/Entity/Instance.php @@ -0,0 +1,111 @@ +domain = $domain; + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @return string + */ + public function getDomain() + { + return $this->domain; + } + + /** + * @param string $domain + */ + public function setDomain($domain) + { + $this->domain = $domain; + } + + /** + * @return float + */ + public function getScore() + { + return $this->score; + } + + /** + * @param float $score + */ + public function setScore($score) + { + $this->score = $score; + } + + /** + * @return array + */ + public function getUsers() + { + return $this->users; + } + + /** + * @param array $users + */ + public function setUsers($users) + { + $this->users = $users; + } +} diff --git a/src/Wallabag/FederationBundle/EventListener/CreateAccountListener.php b/src/Wallabag/FederationBundle/EventListener/CreateAccountListener.php new file mode 100644 index 00000000..92626b15 --- /dev/null +++ b/src/Wallabag/FederationBundle/EventListener/CreateAccountListener.php @@ -0,0 +1,54 @@ +em = $em; + $this->domainName = $domainName; + } + + public static function getSubscribedEvents() + { + return [ + // when a user register using the normal form + FOSUserEvents::REGISTRATION_COMPLETED => 'createAccount', + // when we manually create a user using the command line + // OR when we create it from the config UI + FOSUserEvents::USER_CREATED => 'createAccount', + ]; + } + + public function createAccount(UserEvent $event) + { + $user = $event->getUser(); + $account = new Account(); + $account->setUser($user) + ->setUsername($user->getUsername()) + ->setServer($this->domainName); + + $this->em->persist($account); + + $user->setAccount($account); + + $this->em->persist($user); + $this->em->flush(); + } +} diff --git a/src/Wallabag/FederationBundle/Federation/CloudId.php b/src/Wallabag/FederationBundle/Federation/CloudId.php new file mode 100644 index 00000000..038ea5e8 --- /dev/null +++ b/src/Wallabag/FederationBundle/Federation/CloudId.php @@ -0,0 +1,78 @@ +id = $id; + + $atPos = strpos($id, '@'); + $user = substr($id, 0, $atPos); + $remote = substr($id, $atPos + 1); + if (!empty($user) && !empty($remote)) { + $this->user = $user; + $this->remote = $remote; + } + } + + /** + * The full remote cloud id + * + * @return string + */ + public function getId() { + return $this->id; + } + + public function getDisplayId() { + return str_replace('https://', '', str_replace('http://', '', $this->getId())); + } + + /** + * The username on the remote server + * + * @return string + */ + public function getUser() { + return $this->user; + } + + /** + * The base address of the remote server + * + * @return string + */ + public function getRemote() { + return $this->remote; + } + + /** + * @param Account $account + * @param string $domain + * @return CloudId + */ + public static function getCloudIdFromAccount(Account $account, $domain = '') + { + if ($account->getServer() !== null) { + return new self($account->getUsername() . '@' . $account->getServer()); + } + return new self($account->getUsername() . '@' . $domain); + } +} diff --git a/src/Wallabag/FederationBundle/Form/Type/AccountType.php b/src/Wallabag/FederationBundle/Form/Type/AccountType.php new file mode 100644 index 00000000..af291ced --- /dev/null +++ b/src/Wallabag/FederationBundle/Form/Type/AccountType.php @@ -0,0 +1,46 @@ +add('description', TextareaType::class, ['label' => 'config.form_account.description_label']) + ->add('avatar', FileType::class, [ + 'label' => 'config.form_account.avatar_label', + 'required' => false, + 'data_class' => null, + ]) + ->add('banner', FileType::class, [ + 'label' => 'config.form_account.banner_label', + 'required' => false, + 'data_class' => null, + ]) + ->add('save', SubmitType::class, [ + 'label' => 'config.form.save', + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults(array( + 'data_class' => Account::class, + )); + } + + public function getBlockPrefix() + { + return 'update_account'; + } +} diff --git a/src/Wallabag/FederationBundle/Repository/AccountRepository.php b/src/Wallabag/FederationBundle/Repository/AccountRepository.php new file mode 100644 index 00000000..e39bc582 --- /dev/null +++ b/src/Wallabag/FederationBundle/Repository/AccountRepository.php @@ -0,0 +1,48 @@ +createQueryBuilder('a') + ->select('f.id, f.username') + ->innerJoin('a.following', 'f') + ->where('a.id = :accountId')->setParameter('accountId', $accountId) + ; + } + + /** + * @param $accountId + * @return QueryBuilder + */ + public function getBuilderForFollowersByAccount($accountId) + { + return $this->createQueryBuilder('a') + ->innerJoin('a.followers', 'f') + ->where('a.id = :accountId')->setParameter('accountId', $accountId) + ; + } + + /** + * @param $username + * @return QueryBuilder + * @throws \Doctrine\ORM\NonUniqueResultException + */ + public function findAccountByUsername($username) + { + return $this->createQueryBuilder('a') + ->where('a.username = :username')->setParameter('username', $username) + ->andWhere('a.server = null') + ->getQuery() + ->getOneOrNullResult(); + } +} diff --git a/src/Wallabag/FederationBundle/Repository/InstanceRepository.php b/src/Wallabag/FederationBundle/Repository/InstanceRepository.php new file mode 100644 index 00000000..6365d5c4 --- /dev/null +++ b/src/Wallabag/FederationBundle/Repository/InstanceRepository.php @@ -0,0 +1,10 @@ + + {% endif %} +{% endblock %} + +{% block scripts %} + {{ parent() }} + +{% endblock %} + +{% block header %} +{% endblock %} + +{% block body_class %}reset-left{% endblock %} + +{% block messages %} + {% for flashMessage in app.session.flashbag.get('notice') %} + + {% endfor %} +{% endblock %} + +{% block menu %} + + +{% endblock %} + +{% block content %} +
    +
    +
    + {{ include('@WallabagFederation/themes/material/User/profile_header.html.twig') }} +
      + {% for account in users %} +
    • + folder + {{ account.username }}@{{ account.server }} +

      First Line

      + grade +
    • + {% endfor %} +
    +
    +
    +
    +{% endblock %} + +{% block footer %} +
    + +
    +{% endblock %} diff --git a/src/Wallabag/FederationBundle/Resources/views/themes/material/User/profile.html.twig b/src/Wallabag/FederationBundle/Resources/views/themes/material/User/profile.html.twig new file mode 100644 index 00000000..c52d0ca4 --- /dev/null +++ b/src/Wallabag/FederationBundle/Resources/views/themes/material/User/profile.html.twig @@ -0,0 +1,77 @@ +{% extends "WallabagCoreBundle::base.html.twig" %} + +{% block css %} + {{ parent() }} + {% if not app.debug %} + + {% endif %} +{% endblock %} + +{% block scripts %} + {{ parent() }} + +{% endblock %} + +{% block header %} +{% endblock %} + +{% block body_class %}reset-left{% endblock %} + +{% block messages %} + {% for flashMessage in app.session.flashbag.get('notice') %} + + {% endfor %} +{% endblock %} + +{% block menu %} + + +{% endblock %} + +{% block content %} +
    +
    +
    + {{ include('@WallabagFederation/themes/material/User/profile_header.html.twig') }} +
    +
    +{% endblock %} + +{% block footer %} +
    + +
    +{% endblock %} diff --git a/src/Wallabag/FederationBundle/Resources/views/themes/material/User/profile_header.html.twig b/src/Wallabag/FederationBundle/Resources/views/themes/material/User/profile_header.html.twig new file mode 100644 index 00000000..703de79f --- /dev/null +++ b/src/Wallabag/FederationBundle/Resources/views/themes/material/User/profile_header.html.twig @@ -0,0 +1,35 @@ +{% set usershow = user.username %} +{% if user.user.name is not null %} + {% set usershow = user.user.name %} +{% endif %} + +

    + {{ usershow }} utilise wallabag pour lire et archiver son contenu. Vous pouvez le/la suivre et interagir si vous possédez un compte quelque part dans le "fediverse". Si ce n'est pas le cas, vous pouvez en créer un ici. +

    +
    +
    + +

    {{ usershow }}

    +
    @{{ user.username }}
    + person_add + +
    +
    diff --git a/src/Wallabag/FederationBundle/Resources/views/themes/material/User/recommendations.html.twig b/src/Wallabag/FederationBundle/Resources/views/themes/material/User/recommendations.html.twig new file mode 100644 index 00000000..b3d0d2cf --- /dev/null +++ b/src/Wallabag/FederationBundle/Resources/views/themes/material/User/recommendations.html.twig @@ -0,0 +1,4 @@ +{{ dump(recommendations) }} +{% for entry in recommendations %} +{{ include('@WallabagCore/themes/material/Entry/_card_list.html.twig') }} +{% endfor %} diff --git a/src/Wallabag/FederationBundle/WallabagFederationBundle.php b/src/Wallabag/FederationBundle/WallabagFederationBundle.php new file mode 100644 index 00000000..f9bd665a --- /dev/null +++ b/src/Wallabag/FederationBundle/WallabagFederationBundle.php @@ -0,0 +1,9 @@ +notifications = $notifications; } + + /** + * @return Account + */ + public function getAccount() + { + return $this->account; + } + + /** + * @param mixed $account + * @return User + */ + public function setAccount(Account $account) + { + $this->account = $account; + return $this; + } } diff --git a/tests/Wallabag/CoreBundle/Command/InstallCommandTest.php b/tests/Wallabag/CoreBundle/Command/InstallCommandTest.php index 94fc0b94..3f219b2f 100644 --- a/tests/Wallabag/CoreBundle/Command/InstallCommandTest.php +++ b/tests/Wallabag/CoreBundle/Command/InstallCommandTest.php @@ -97,7 +97,7 @@ class InstallCommandTest extends WallabagCoreTestCase $this->assertContains('Checking system requirements.', $tester->getDisplay()); $this->assertContains('Setting up database.', $tester->getDisplay()); $this->assertContains('Administration setup.', $tester->getDisplay()); - $this->assertContains('Config setup.', $tester->getDisplay()); + $this->assertContains('config setup.', $tester->getDisplay()); $this->assertContains('Run migrations.', $tester->getDisplay()); } @@ -124,7 +124,7 @@ class InstallCommandTest extends WallabagCoreTestCase $this->assertContains('Setting up database.', $tester->getDisplay()); $this->assertContains('Dropping database, creating database and schema, clearing the cache', $tester->getDisplay()); $this->assertContains('Administration setup.', $tester->getDisplay()); - $this->assertContains('Config setup.', $tester->getDisplay()); + $this->assertContains('config setup.', $tester->getDisplay()); $this->assertContains('Run migrations.', $tester->getDisplay()); // we force to reset everything @@ -170,7 +170,7 @@ class InstallCommandTest extends WallabagCoreTestCase $this->assertContains('Checking system requirements.', $tester->getDisplay()); $this->assertContains('Setting up database.', $tester->getDisplay()); $this->assertContains('Administration setup.', $tester->getDisplay()); - $this->assertContains('Config setup.', $tester->getDisplay()); + $this->assertContains('config setup.', $tester->getDisplay()); $this->assertContains('Run migrations.', $tester->getDisplay()); // the current database doesn't already exist @@ -197,7 +197,7 @@ class InstallCommandTest extends WallabagCoreTestCase $this->assertContains('Checking system requirements.', $tester->getDisplay()); $this->assertContains('Setting up database.', $tester->getDisplay()); $this->assertContains('Administration setup.', $tester->getDisplay()); - $this->assertContains('Config setup.', $tester->getDisplay()); + $this->assertContains('config setup.', $tester->getDisplay()); $this->assertContains('Run migrations.', $tester->getDisplay()); $this->assertContains('Dropping schema and creating schema', $tester->getDisplay()); @@ -241,7 +241,7 @@ class InstallCommandTest extends WallabagCoreTestCase $this->assertContains('Checking system requirements.', $tester->getDisplay()); $this->assertContains('Setting up database.', $tester->getDisplay()); $this->assertContains('Administration setup.', $tester->getDisplay()); - $this->assertContains('Config setup.', $tester->getDisplay()); + $this->assertContains('config setup.', $tester->getDisplay()); $this->assertContains('Run migrations.', $tester->getDisplay()); $this->assertContains('Creating schema', $tester->getDisplay()); @@ -264,7 +264,7 @@ class InstallCommandTest extends WallabagCoreTestCase $this->assertContains('Checking system requirements.', $tester->getDisplay()); $this->assertContains('Setting up database.', $tester->getDisplay()); $this->assertContains('Administration setup.', $tester->getDisplay()); - $this->assertContains('Config setup.', $tester->getDisplay()); + $this->assertContains('config setup.', $tester->getDisplay()); $this->assertContains('Run migrations.', $tester->getDisplay()); } } -- cgit v1.2.3