diff --git a/lib/Alchemy/Phrasea/Controller/Prod/FeedController.php b/lib/Alchemy/Phrasea/Controller/Prod/FeedController.php index 2bc333f45c..39a0a8f48d 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/FeedController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/FeedController.php @@ -19,8 +19,10 @@ use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Feed\Aggregate; use Alchemy\Phrasea\Feed\Link\AggregateLinkGenerator; use Alchemy\Phrasea\Feed\Link\FeedLinkGenerator; +use Alchemy\Phrasea\Model\Entities\Feed; use Alchemy\Phrasea\Model\Entities\FeedEntry; use Alchemy\Phrasea\Model\Entities\FeedItem; +use Alchemy\Phrasea\Model\Entities\FeedPublisher; use Alchemy\Phrasea\Model\Repositories\FeedEntryRepository; use Alchemy\Phrasea\Model\Repositories\FeedItemRepository; use Alchemy\Phrasea\Model\Repositories\FeedPublisherRepository; @@ -46,6 +48,7 @@ class FeedController extends Controller } public function createFeedEntryAction(Request $request) { + /** @var Feed $feed */ $feed = $this->getFeedRepository()->find($request->request->get('feed_id')); if (null === $feed) { @@ -53,6 +56,8 @@ class FeedController extends Controller } $user = $this->getAuthenticatedUser(); + + /** @var FeedPublisher $publisher */ $publisher = $this->getFeedPublisherRepository()->findOneBy([ 'feed' => $feed, 'user' => $user, diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/FeedEntrySubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/FeedEntrySubscriber.php index 03a72a95b4..8bc7cb2b52 100644 --- a/lib/Alchemy/Phrasea/Core/Event/Subscriber/FeedEntrySubscriber.php +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/FeedEntrySubscriber.php @@ -13,7 +13,9 @@ namespace Alchemy\Phrasea\Core\Event\Subscriber; use Alchemy\Phrasea\Core\Event\FeedEntryEvent; use Alchemy\Phrasea\Core\PhraseaEvents; +use Alchemy\Phrasea\Model\Entities\User; use Alchemy\Phrasea\Model\Entities\WebhookEvent; +use Alchemy\Phrasea\Model\Manipulator\TokenManipulator; use Alchemy\Phrasea\Notification\Receiver; use Alchemy\Phrasea\Notification\Mail\MailInfoNewPublication; @@ -53,36 +55,43 @@ class FeedEntrySubscriber extends AbstractNotificationSubscriber do { $results = $Query->limit($start, $perLoop)->execute()->get_results(); - foreach ($results as $user_to_notif) { - $mailed = false; + $users_emailed = []; // for all users + $users_to_email = []; // list only users who must be emailed (=create tokens) - if ($params['notify_email'] && $this->shouldSendNotificationFor($user_to_notif, 'eventsmanager_notify_feed')) { - $readyToSend = false; - try { - $token = $this->app['manipulator.token']->createFeedEntryToken($user_to_notif, $entry); - $url = $this->app->url('lightbox', ['LOG' => $token->getValue()]); - - $receiver = Receiver::fromUser($user_to_notif); - $readyToSend = true; - } catch (\Exception $e) { - - } - - if ($readyToSend) { - $mail = MailInfoNewPublication::create($this->app, $receiver); - $mail->setButtonUrl($url); - $mail->setAuthor($entry->getAuthorName()); - $mail->setTitle($entry->getTitle()); - - $this->deliver($mail); - $mailed = true; - } + /** @var User $user */ + foreach ($results as $user) { + $users_emailed[$user->getId()] = false; + if ($params['notify_email'] && $this->shouldSendNotificationFor($user, 'eventsmanager_notify_feed')) { + $users_to_email[$user->getId()] = $user; } - - $this->app['events-manager']->notify($user_to_notif->getId(), 'eventsmanager_notify_feed', $datas, $mailed); } + + // get many tokens in one shot + $tokens = $this->getTokenManipulator()->createFeedEntryTokens($users_to_email, $entry); + foreach($tokens as $token) { + try { + $url = $this->app->url('lightbox', ['LOG' => $token->getValue()]); + $receiver = Receiver::fromUser($token->getUser()); + + $mail = MailInfoNewPublication::create($this->app, $receiver); + $mail->setButtonUrl($url); + $mail->setAuthor($entry->getAuthorName()); + $mail->setTitle($entry->getTitle()); + + $this->deliver($mail); + $users_emailed[$token->getUser()->getId()] = true; + } + catch (\Exception $e) { + // no-op + } + } + foreach($users_emailed as $id => $emailed) { + $this->app['events-manager']->notify($id, 'eventsmanager_notify_feed', $datas, $emailed); + } + $start += $perLoop; - } while (count($results) > 0); + } + while (count($results) > 0); } public static function getSubscribedEvents() @@ -91,4 +100,12 @@ class FeedEntrySubscriber extends AbstractNotificationSubscriber PhraseaEvents::FEED_ENTRY_CREATE => 'onCreate', ]; } + + /** + * @return TokenManipulator + */ + private function getTokenManipulator() + { + return $this->app['manipulator.token']; + } } diff --git a/lib/Alchemy/Phrasea/Core/Version.php b/lib/Alchemy/Phrasea/Core/Version.php index 3a3ff63afc..d17062de4c 100644 --- a/lib/Alchemy/Phrasea/Core/Version.php +++ b/lib/Alchemy/Phrasea/Core/Version.php @@ -16,7 +16,7 @@ class Version /** * @var string */ - private $number = '4.1.0-alpha.21a'; + private $number = '4.1.0-alpha.22a'; /** * @var string diff --git a/lib/Alchemy/Phrasea/Model/Entities/Token.php b/lib/Alchemy/Phrasea/Model/Entities/Token.php index 576cf05d36..81c632f97d 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/Token.php +++ b/lib/Alchemy/Phrasea/Model/Entities/Token.php @@ -15,7 +15,14 @@ use Doctrine\ORM\Mapping as ORM; use Gedmo\Mapping\Annotation as Gedmo; /** - * @ORM\Table(name="Tokens") + * @ORM\Table(name="Tokens", + * indexes={ + * @ORM\index(name="type", columns={"type"}), + * @ORM\index(name="created", columns={"created"}), + * @ORM\index(name="updated", columns={"updated"}), + * @ORM\index(name="expiration", columns={"expiration"}) + * } + * ) * @ORM\Entity(repositoryClass="Alchemy\Phrasea\Model\Repositories\TokenRepository") */ class Token diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/TokenManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/TokenManipulator.php index 2a43363693..56c4e9fc36 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/TokenManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/TokenManipulator.php @@ -22,7 +22,6 @@ use RandomLib\Generator; class TokenManipulator implements ManipulatorInterface { const LETTERS_AND_NUMBERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - const TYPE_FEED_ENTRY = 'FEED_ENTRY'; const TYPE_PASSWORD = 'password'; const TYPE_ACCOUNT_UNLOCK = 'account-unlock'; @@ -126,6 +125,38 @@ class TokenManipulator implements ManipulatorInterface return $this->create($user, self::TYPE_FEED_ENTRY, null, $entry->getId()); } + /** + * Create feedEntryTokens for many users in one shot + * + * @param User[] $users + * @param FeedEntry $entry + * @return Token[] + * @throws \Doctrine\DBAL\DBALException + */ + public function createFeedEntryTokens($users, FeedEntry $entry) + { + // $this->removeExpiredTokens(); + + $tokens = []; + foreach ($users as $user) { + $value = $this->random->generateString(32, self::LETTERS_AND_NUMBERS) . $user->getId(); + + $token = new Token(); + $token->setUser($user) + ->setType(self::TYPE_FEED_ENTRY) + ->setValue($value) + ->setExpiration(null) + ->setData($entry->getId()); + $tokens[] = $token; + + $this->om->persist($token); + } + $this->om->flush(); + $this->om->clear(); + + return $tokens; + } + /** * @param User $user * @param $data diff --git a/lib/Alchemy/Phrasea/Model/Repositories/TokenRepository.php b/lib/Alchemy/Phrasea/Model/Repositories/TokenRepository.php index ad45425f7c..8e40f9ebd8 100644 --- a/lib/Alchemy/Phrasea/Model/Repositories/TokenRepository.php +++ b/lib/Alchemy/Phrasea/Model/Repositories/TokenRepository.php @@ -72,4 +72,9 @@ class TokenRepository extends EntityRepository return $query->getResult(); } + + public function getEntityManager() + { + return parent::getEntityManager(); + } } diff --git a/lib/classes/patch/410alpha22a.php b/lib/classes/patch/410alpha22a.php new file mode 100644 index 0000000000..6a80bd058e --- /dev/null +++ b/lib/classes/patch/410alpha22a.php @@ -0,0 +1,75 @@ +release; + } + + /** + * {@inheritdoc} + */ + public function concern() + { + return $this->concern; + } + + /** + * {@inheritdoc} + */ + public function require_all_upgrades() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function getDoctrineMigrations() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function apply(base $appbox, Application $app) + { + foreach(['type', 'created', 'updated', 'expiration'] as $t) { + $sql = "ALTER TABLE `Tokens` ADD INDEX `".$t."` (`".$t."`);"; + try { + $stmt = $appbox->get_connection()->prepare($sql); + $stmt->execute(); + $stmt->closeCursor(); + } + catch (\Exception $e) { + // the inex already exists ? + } + } + + return true; + } +} diff --git a/templates/web/admin/fields/index.html.twig b/templates/web/admin/fields/index.html.twig index e8198a324f..531ef168eb 100644 --- a/templates/web/admin/fields/index.html.twig +++ b/templates/web/admin/fields/index.html.twig @@ -27,3 +27,17 @@ {# bootstrap admin field backbone application #} + + diff --git a/templates/web/admin/fields/templates.html.twig b/templates/web/admin/fields/templates.html.twig index 016534fd52..73106dbafb 100644 --- a/templates/web/admin/fields/templates.html.twig +++ b/templates/web/admin/fields/templates.html.twig @@ -259,11 +259,14 @@ - - - - - /> + + +
> + +
+ diff --git a/templates/web/prod/results/help.html.twig b/templates/web/prod/results/help.html.twig index 633ebeac40..bc6406cb5b 100644 --- a/templates/web/prod/results/help.html.twig +++ b/templates/web/prod/results/help.html.twig @@ -30,7 +30,11 @@
  • {{ 'help::help-section-bullet: search-in-a-specific-field' | trans }}

  • -

    - {{ 'help::help-search: relaunch search without filter' | trans }}

    +
    +

    {{ 'help::help-search: OR' | trans }}

    +
    +

    + {{ 'help::help-search: relaunch search without filter' | trans }} +

    {% endblock %}