From ad8d7a6954b9553689658ec95308773ab63b3eb0 Mon Sep 17 00:00:00 2001 From: Yevhen Shyshkin Date: Fri, 5 Aug 2016 17:01:55 +0200 Subject: [PATCH 01/10] OC-632: "Maximum function nesting level of '300' reached" running oro:cron:imap-sync - removed useless flushes --- src/Oro/Bundle/EmailBundle/Resources/config/process.yml | 2 -- src/Oro/Bundle/ImapBundle/Resources/config/process.yml | 9 +++------ .../Bundle/ImapBundle/Workflow/Action/DependencyJob.php | 4 +--- src/Oro/Component/Action/Resources/doc/actions.md | 2 ++ 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Oro/Bundle/EmailBundle/Resources/config/process.yml b/src/Oro/Bundle/EmailBundle/Resources/config/process.yml index c299f19803e..9441e6ad9bf 100644 --- a/src/Oro/Bundle/EmailBundle/Resources/config/process.yml +++ b/src/Oro/Bundle/EmailBundle/Resources/config/process.yml @@ -24,7 +24,6 @@ definitions: - '@create_entity': class: JMS\JobQueueBundle\Entity\Job attribute: $.syncJob - flush: true arguments: - 'oro:email:flag-sync' - ['--seen=false', $.arguments] @@ -43,7 +42,6 @@ definitions: - '@create_entity': class: JMS\JobQueueBundle\Entity\Job attribute: $.syncJob - flush: true arguments: - 'oro:email:flag-sync' - ['--seen=true', $.arguments] diff --git a/src/Oro/Bundle/ImapBundle/Resources/config/process.yml b/src/Oro/Bundle/ImapBundle/Resources/config/process.yml index 1b5e461a90c..97f924c517b 100644 --- a/src/Oro/Bundle/ImapBundle/Resources/config/process.yml +++ b/src/Oro/Bundle/ImapBundle/Resources/config/process.yml @@ -37,7 +37,7 @@ definitions: - '@create_entity': class: JMS\JobQueueBundle\Entity\Job attribute: $.syncJob - flush: true + flush: true # flush is used make sure that only one pending job will be created arguments: - $.command clear_imap_email_folder_on_sync_enabled_change: @@ -80,7 +80,7 @@ definitions: - '@create_entity': class: JMS\JobQueueBundle\Entity\Job attribute: $.syncJob - flush: true + flush: true # flush is used make sure that only one pending job will be created arguments: - $.command - '@tree': @@ -92,12 +92,9 @@ definitions: - '@create_entity': class: JMS\JobQueueBundle\Entity\Job attribute: $.syncJob - flush: true arguments: - $.command - - [] - - false - - '@job_add_dependency': + - '@job_add_dependency': # called flush to make sure that only one pending job will be created job: $.syncJob dependency: $.runningJob diff --git a/src/Oro/Bundle/ImapBundle/Workflow/Action/DependencyJob.php b/src/Oro/Bundle/ImapBundle/Workflow/Action/DependencyJob.php index a20354020a6..9cb26e57c2c 100644 --- a/src/Oro/Bundle/ImapBundle/Workflow/Action/DependencyJob.php +++ b/src/Oro/Bundle/ImapBundle/Workflow/Action/DependencyJob.php @@ -51,10 +51,8 @@ protected function executeAction($context) $dependency = $this->contextAccessor->getValue($context, $this->dependency); $job->addDependency($dependency); - $job->setState(Job::STATE_PENDING); - $this->doctrine->getManager()->persist($job); - $this->doctrine->getManager()->flush(); + $this->doctrine->getManager()->flush(); // flush is required to allow following processes track this job } /** diff --git a/src/Oro/Component/Action/Resources/doc/actions.md b/src/Oro/Component/Action/Resources/doc/actions.md index c010ccc73f4..bba9d756eaa 100644 --- a/src/Oro/Component/Action/Resources/doc/actions.md +++ b/src/Oro/Component/Action/Resources/doc/actions.md @@ -151,6 +151,8 @@ Create Entity - attribute - attribute that will contain the created entity instance; - flush - (optional) when flush in DB should be performed. Immediately after entity creation if ``true`` or later if ``false`` (default value: false); + _Note: This option might significantly slow down an application, + so it should be set to "true" only if entity really must be flushed to DB during this action_; - data - (optional) array of data that should be set to entity. **Configuration Example** From 6d3dccbb6fa1b9cf9add665b4bcf06abf36badfb Mon Sep 17 00:00:00 2001 From: Aleksey Solonenko Date: Wed, 8 Feb 2017 11:57:07 +0200 Subject: [PATCH 02/10] BAP-12454: SwiftMailer SMTP transport exception (#7355) - added logger for transport exception for DirectMail --- .../Compiler/OverrideServiceSwiftMailer.php | 16 ++++ .../EmailBundle/Mailer/DirectMailer.php | 30 ++++++ src/Oro/Bundle/EmailBundle/OroEmailBundle.php | 2 + .../EmailBundle/Resources/config/services.yml | 9 ++ .../Bundle/EmailBundle/Util/MailerWrapper.php | 93 +++++++++++++++++++ .../Resources/config/services.yml | 1 + 6 files changed, 151 insertions(+) create mode 100644 src/Oro/Bundle/EmailBundle/DependencyInjection/Compiler/OverrideServiceSwiftMailer.php create mode 100644 src/Oro/Bundle/EmailBundle/Util/MailerWrapper.php diff --git a/src/Oro/Bundle/EmailBundle/DependencyInjection/Compiler/OverrideServiceSwiftMailer.php b/src/Oro/Bundle/EmailBundle/DependencyInjection/Compiler/OverrideServiceSwiftMailer.php new file mode 100644 index 00000000000..a098f053933 --- /dev/null +++ b/src/Oro/Bundle/EmailBundle/DependencyInjection/Compiler/OverrideServiceSwiftMailer.php @@ -0,0 +1,16 @@ +findDefinition('mailer'); + $definition->addArgument(new Reference('oro_email.logger.link')); + } +} diff --git a/src/Oro/Bundle/EmailBundle/Mailer/DirectMailer.php b/src/Oro/Bundle/EmailBundle/Mailer/DirectMailer.php index 86884a7e1ba..3d62f25a500 100644 --- a/src/Oro/Bundle/EmailBundle/Mailer/DirectMailer.php +++ b/src/Oro/Bundle/EmailBundle/Mailer/DirectMailer.php @@ -2,6 +2,8 @@ namespace Oro\Bundle\EmailBundle\Mailer; +use Monolog\Logger; + use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\IntrospectableContainerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -9,6 +11,7 @@ use Oro\Bundle\EmailBundle\Entity\EmailOrigin; use Oro\Bundle\EmailBundle\Event\SendEmailTransport; use Oro\Bundle\EmailBundle\Exception\NotSupportedException; +use Oro\Component\DependencyInjection\ServiceLink; /** * The goal of this class is to send an email directly, not using a mail spool @@ -25,6 +28,9 @@ class DirectMailer extends \Swift_Mailer /** @var ContainerInterface */ protected $container; + /** @var ServiceLink */ + protected $loggerLink; + /** * Constructor * @@ -55,6 +61,21 @@ public function __construct(\Swift_Mailer $baseMailer, ContainerInterface $conta parent::__construct($transport); } + /** + * @param ServiceLink $loggerLink + */ + public function setLogger(ServiceLink $loggerLink) + { + $this->loggerLink = $loggerLink; + } + /** + * @return Logger + */ + protected function getLogger() + { + return $this->loggerLink->getService(); + } + /** * Set SmtpTransport instance or create a new if default mailer transport is not smtp * @@ -130,6 +151,15 @@ public function send(\Swift_Mime_Message $message, &$failedRecipients = null) } else { $result = parent::send($message, $failedRecipients); } + } catch (\Swift_TransportException $transportException) { + $logger = $this->getLogger(); + $logger->crit(sprintf("Mail message: %s", $message)); + $logger->crit(sprintf("Mail recipients: %s", $failedRecipients)); + $logger->crit( + sprintf("Error message: %s", $transportException->getMessage()), + ['exception' => $transportException] + ); + $sendException = $transportException; } catch (\Exception $unexpectedEx) { $sendException = $unexpectedEx; } diff --git a/src/Oro/Bundle/EmailBundle/OroEmailBundle.php b/src/Oro/Bundle/EmailBundle/OroEmailBundle.php index 90b8d16f627..29034188aa5 100644 --- a/src/Oro/Bundle/EmailBundle/OroEmailBundle.php +++ b/src/Oro/Bundle/EmailBundle/OroEmailBundle.php @@ -19,6 +19,7 @@ use Oro\Bundle\EmailBundle\DependencyInjection\Compiler\EmailFlagManagerLoaderPass; use Oro\Bundle\EmailBundle\DependencyInjection\Compiler\EmailFolderLoaderPass; use Oro\Bundle\EmailBundle\DependencyInjection\Compiler\EmailRecipientsProviderPass; +use Oro\Bundle\EmailBundle\DependencyInjection\Compiler\OverrideServiceSwiftMailer; class OroEmailBundle extends Bundle { @@ -59,6 +60,7 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new TwigSandboxConfigurationPass()); $container->addCompilerPass(new EmailRecipientsProviderPass()); $container->addCompilerPass(new MailboxProcessPass()); + $container->addCompilerPass(new OverrideServiceSwiftMailer()); } /** diff --git a/src/Oro/Bundle/EmailBundle/Resources/config/services.yml b/src/Oro/Bundle/EmailBundle/Resources/config/services.yml index 678b1078d31..743cfc306f8 100644 --- a/src/Oro/Bundle/EmailBundle/Resources/config/services.yml +++ b/src/Oro/Bundle/EmailBundle/Resources/config/services.yml @@ -140,6 +140,9 @@ parameters: oro_email.listener.activity_list_pre_query_build_listener.class: Oro\Bundle\EmailBundle\EventListener\ActivityListPreQueryBuildListener oro_email.model.email_activity_updates.class: Oro\Bundle\EmailBundle\Model\EmailActivityUpdates + #decorate swiftmailer class + swiftmailer.class: Oro\Bundle\EmailBundle\Util\MailerWrapper + services: oro_email.entity.cache.warmer: class: %oro_email.entity.cache.warmer.class% @@ -351,6 +354,8 @@ services: arguments: - '@mailer' - '@service_container' + calls: + - [setLogger, ['@oro_email.logger.link']] # Email template API oro_email.manager.emailtemplate.api: @@ -1066,3 +1071,7 @@ services: - '@oro_email.email.activity.manager' - '@oro_email.provider.emailowners.provider' - '@oro_email.email.manager' + + oro_email.logger.link: + tags: + - { name: oro_service_link, service: logger } diff --git a/src/Oro/Bundle/EmailBundle/Util/MailerWrapper.php b/src/Oro/Bundle/EmailBundle/Util/MailerWrapper.php new file mode 100644 index 00000000000..55c16f14a3d --- /dev/null +++ b/src/Oro/Bundle/EmailBundle/Util/MailerWrapper.php @@ -0,0 +1,93 @@ +mailer = parent::newInstance($transport); + $this->loggerLink = $loggerLink; + } + + /** + * @return Logger + */ + protected function getLogger() + { + return $this->loggerLink->getService(); + } + + /** + * @param \Swift_Mime_Message $message + * @param array|null $failedRecipients + * @return int + * + * @throws \Swift_TransportException + */ + public function send(\Swift_Mime_Message $message, &$failedRecipients = null) + { + try { + $result = $this->mailer->send($message, $failedRecipients); + } catch (\Swift_TransportException $transportException) { + if (is_array($failedRecipients)) { + $failedRecipients = implode(',', $failedRecipients); + } + $logger = $this->getLogger(); + + $logger->crit(sprintf("Mail message: %s", $message)); + $logger->crit(sprintf("Mail recipients: %s", $failedRecipients)); + $logger->crit( + sprintf("Error message: %s", $transportException->getMessage()), + ['exception' => $transportException] + ); + + throw $transportException; + } + + return $result; + } + + /** + * @param \Swift_Transport $transport + * @return MailerWrapper + */ + public static function newInstance(\Swift_Transport $transport) + { + return new self($transport); + } + + /** + * @return \Swift_Transport + */ + public function getTransport() + { + return $this->mailer->getTransport(); + } + + /** + * @param \Swift_Events_EventListener $plugin + */ + public function registerPlugin(\Swift_Events_EventListener $plugin) + { + $this->getTransport()->registerPlugin($plugin); + } +} diff --git a/src/Oro/Bundle/NotificationBundle/Resources/config/services.yml b/src/Oro/Bundle/NotificationBundle/Resources/config/services.yml index e708b271e8f..40883861f82 100644 --- a/src/Oro/Bundle/NotificationBundle/Resources/config/services.yml +++ b/src/Oro/Bundle/NotificationBundle/Resources/config/services.yml @@ -189,6 +189,7 @@ services: class: %swiftmailer.class% arguments: - '@oro_notification.mailer.transport' + - '@oro_email.logger.link' swiftmailer.mailer.db_spool_mailer.transport.real: alias: swiftmailer.transport.real From 0754888e24809ba91ca0e4f984243fed7971bfc0 Mon Sep 17 00:00:00 2001 From: Makar Date: Wed, 8 Feb 2017 14:17:12 +0200 Subject: [PATCH 03/10] BAP-13517: Deleted Extended Entity causes Cache Warmup crash (#7282) --- .../Bundle/EntityExtendBundle/Extend/EntityProxyGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Oro/Bundle/EntityExtendBundle/Extend/EntityProxyGenerator.php b/src/Oro/Bundle/EntityExtendBundle/Extend/EntityProxyGenerator.php index ac8095c236d..1a4168552bc 100644 --- a/src/Oro/Bundle/EntityExtendBundle/Extend/EntityProxyGenerator.php +++ b/src/Oro/Bundle/EntityExtendBundle/Extend/EntityProxyGenerator.php @@ -87,7 +87,7 @@ protected function generateEntityManagerProxies(EntityManager $em) if (!$extendConfig->is('is_extend')) { continue; } - if ($extendConfig->in('state', [ExtendScope::STATE_NEW])) { + if ($extendConfig->in('state', [ExtendScope::STATE_NEW, ExtendScope::STATE_DELETE])) { continue; } From 650b119e8810a235f4cfe636cfb613f5efc1dfd2 Mon Sep 17 00:00:00 2001 From: Mykhailo Date: Thu, 9 Feb 2017 16:45:56 +0200 Subject: [PATCH 04/10] BAP-13715: Need to fix True and False conditions --- src/Oro/Bundle/ActionBundle/Resources/config/conditions.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Oro/Bundle/ActionBundle/Resources/config/conditions.yml b/src/Oro/Bundle/ActionBundle/Resources/config/conditions.yml index d792e7c1bd6..7bac137ad5a 100644 --- a/src/Oro/Bundle/ActionBundle/Resources/config/conditions.yml +++ b/src/Oro/Bundle/ActionBundle/Resources/config/conditions.yml @@ -52,12 +52,12 @@ services: - { name: oro_action.condition, alias: not } oro_action.expression.true: - class: Oro\Component\ConfigExpression\Condition\True + class: Oro\Component\ConfigExpression\Condition\TrueCondition tags: - { name: oro_action.condition, alias: "true" } oro_action.expression.false: - class: Oro\Component\ConfigExpression\Condition\False + class: Oro\Component\ConfigExpression\Condition\FalseCondition tags: - { name: oro_action.condition, alias: "false" } From b22093590ea33fd30104eea3e5237cd45d273c57 Mon Sep 17 00:00:00 2001 From: Mykhailo Date: Thu, 9 Feb 2017 17:08:27 +0200 Subject: [PATCH 05/10] BAP-13721: Remove unused oro_calendar.exception.form.type --- src/Oro/Bundle/CalendarBundle/Resources/config/form.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Oro/Bundle/CalendarBundle/Resources/config/form.yml b/src/Oro/Bundle/CalendarBundle/Resources/config/form.yml index 2735ba4aafb..ffa6d5ccc7e 100644 --- a/src/Oro/Bundle/CalendarBundle/Resources/config/form.yml +++ b/src/Oro/Bundle/CalendarBundle/Resources/config/form.yml @@ -16,7 +16,6 @@ parameters: oro_calendar.attendees_to_view_transformer.class: Oro\Bundle\CalendarBundle\Form\DataTransformer\AttendeesToViewTransformer oro_calendar.user_ids_to_users.tranformer.class: Oro\Bundle\FormBundle\Form\DataTransformer\EntitiesToIdsTransformer oro_calendar.recurrence.form.type.class: Oro\Bundle\CalendarBundle\Form\Type\RecurrenceFormType - oro_calendar.exception.form.type.class: Oro\Bundle\CalendarBundle\Form\Type\ExceptionFormType services: oro_calendar.calendar_event.form.type: @@ -213,8 +212,3 @@ services: - '@oro_calendar.model.recurrence' tags: - { name: form.type, alias: oro_calendar_event_recurrence } - - oro_calendar.exception.form.type: - class: '%oro_calendar.exception.form.type.class%' - tags: - - { name: form.type, alias: oro_calendar_event_exception } From 5d11ee93e463d64641e93eaa1b6f22e67aad8801 Mon Sep 17 00:00:00 2001 From: Dmitriy Krushelnitskiy Date: Thu, 9 Feb 2017 18:43:46 +0200 Subject: [PATCH 06/10] BAP-13644: Cannot configure system mail box (#7378) --- .../Validators/MailboxOriginValidatorTest.php | 119 ++++++++++++++++++ .../Validator/MailboxOriginValidator.php | 35 +++++- .../Bundle/ImapBundle/Mail/Storage/Folder.php | 2 +- 3 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 src/Oro/Bundle/EmailBundle/Tests/Unit/Validators/MailboxOriginValidatorTest.php diff --git a/src/Oro/Bundle/EmailBundle/Tests/Unit/Validators/MailboxOriginValidatorTest.php b/src/Oro/Bundle/EmailBundle/Tests/Unit/Validators/MailboxOriginValidatorTest.php new file mode 100644 index 00000000000..f6ee8b7a57d --- /dev/null +++ b/src/Oro/Bundle/EmailBundle/Tests/Unit/Validators/MailboxOriginValidatorTest.php @@ -0,0 +1,119 @@ +constraint = new MailboxOrigin(); + + $this->context = $this + ->getMockBuilder('Symfony\Component\Validator\ExecutionContextInterface') + ->getMock(); + + $this->translator = $this->getMockBuilder('Symfony\Component\Translation\Translator') + ->disableOriginalConstructor() + ->getMock(); + + $this->validator = new MailboxOriginValidator( + $this->translator + ); + $this->validator->initialize($this->context); + } + + /** + * Test for case: $value has folder with type Sent + */ + public function testValueWithFolderSentOnRootLevel() + { + $this->context->expects($this->never()) + ->method('addViolation'); + $this->translator->expects($this->never()) + ->method('trans'); + + $folderSent = new EmailFolder(); + $folderSent->setType('sent'); + + $value = new UserEmailOrigin(); + $value->addFolder($folderSent); + + $this->validator->validate($value, $this->constraint); + } + + /** + * Test for case: $value is not EmailOrigin + */ + public function testValueIsNotEmailOrigin() + { + $this->context->expects($this->never()) + ->method('addViolation'); + $this->translator->expects($this->never()) + ->method('trans'); + + $value = new EmailFolder(); + + $this->validator->validate($value, $this->constraint); + } + + /** + * Test for case: $value has folder with type "Inbox" which has folder with type "Sent" + */ + public function testValueWithFolderSentInFolderInbox() + { + $this->context->expects($this->never()) + ->method('addViolation'); + $this->translator->expects($this->never()) + ->method('trans'); + + $folderSent = new EmailFolder(); + $folderSent->setType('sent'); + + $folderInbox = new EmailFolder(); + $folderInbox->setType('inbox'); + $folderInbox->addSubFolder($folderSent); + + $value = new UserEmailOrigin(); + $value->addFolder($folderInbox); + + $this->validator->validate($value, $this->constraint); + } + + /** + * Test for case: $value is EmailOrigin but does not have folder with type "Sent" + */ + public function testValueWithoutFolderSent() + { + $this->context->expects($this->once()) + ->method('addViolation'); + $this->translator->expects($this->once()) + ->method('trans') + ->with('oro.imap.configuration.connect_and_retrieve_folders') + ->will($this->returnArgument(0)); + + $folderInbox = new EmailFolder(); + $folderInbox->setType('inbox'); + + $value = new UserEmailOrigin(); + $value->addFolder($folderInbox); + + $this->validator->validate($value, $this->constraint); + } +} diff --git a/src/Oro/Bundle/EmailBundle/Validator/MailboxOriginValidator.php b/src/Oro/Bundle/EmailBundle/Validator/MailboxOriginValidator.php index d09bba2c9ba..1a625cb826f 100644 --- a/src/Oro/Bundle/EmailBundle/Validator/MailboxOriginValidator.php +++ b/src/Oro/Bundle/EmailBundle/Validator/MailboxOriginValidator.php @@ -2,12 +2,14 @@ namespace Oro\Bundle\EmailBundle\Validator; -use Oro\Bundle\EmailBundle\Entity\EmailOrigin; -use Oro\Bundle\EmailBundle\Model\FolderType; use Symfony\Component\Translation\TranslatorInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; +use Oro\Bundle\EmailBundle\Entity\EmailFolder; +use Oro\Bundle\EmailBundle\Entity\EmailOrigin; +use Oro\Bundle\EmailBundle\Model\FolderType; + class MailboxOriginValidator extends ConstraintValidator { /** @var TranslatorInterface */ @@ -34,6 +36,10 @@ public function validate($value, Constraint $constraint) return; } + if ($this->inboxHasSubFolderWithType($value, FolderType::SENT)) { + return; + } + $this->context->addViolation( $constraint->message, [ @@ -41,4 +47,29 @@ public function validate($value, Constraint $constraint) ] ); } + + /** + * @param $value + * @param $folderType + * + * @return bool + */ + protected function inboxHasSubFolderWithType($value, $folderType) + { + /** @var EmailFolder $folder */ + $folder = $value->getFolder(FolderType::INBOX); + + if ($folder) { + $subFolders = $folder->getSubFolders(); + if ($subFolders->count() > 0) { + foreach ($subFolders as $subFolder) { + if ($subFolder->getType() === $folderType) { + return true; + } + } + } + } + + return false; + } } diff --git a/src/Oro/Bundle/ImapBundle/Mail/Storage/Folder.php b/src/Oro/Bundle/ImapBundle/Mail/Storage/Folder.php index bf6c02e8b3f..4035d7b221c 100644 --- a/src/Oro/Bundle/ImapBundle/Mail/Storage/Folder.php +++ b/src/Oro/Bundle/ImapBundle/Mail/Storage/Folder.php @@ -26,7 +26,7 @@ class Folder extends BaseFolder /** @var array */ protected $possibleSentFolderNameMap = [ - 'SentBox', 'Sent' + 'SentBox', 'Sent', 'INBOX.Sent' ]; /** @var string[] */ From c77118135d535dc0f18ae7f6d692780feb9b557f Mon Sep 17 00:00:00 2001 From: Alexander Nezdoiminoga Date: Fri, 10 Feb 2017 16:08:42 +0200 Subject: [PATCH 07/10] CRM-7471: System mailbox thread disappeared (#7332) --- .../Controller/EmailController.php | 53 ++++++ .../Datagrid/EmailQueryFactory.php | 158 +++++++++++++++++- .../Entity/Provider/EmailThreadProvider.php | 47 ++++++ .../Datagrid/EmailGridListener.php | 48 +++--- .../EmailBundle/Resources/config/datagrid.yml | 11 +- .../views/Email/Thread/userEmails.html.twig | 23 +++ .../Tests/Functional/Grid/EmailGridTest.php | 4 +- .../Unit/Datagrid/EmailQueryFactoryTest.php | 4 +- 8 files changed, 308 insertions(+), 40 deletions(-) create mode 100644 src/Oro/Bundle/EmailBundle/Resources/views/Email/Thread/userEmails.html.twig diff --git a/src/Oro/Bundle/EmailBundle/Controller/EmailController.php b/src/Oro/Bundle/EmailBundle/Controller/EmailController.php index 9ae58f7893d..a561ca646c8 100644 --- a/src/Oro/Bundle/EmailBundle/Controller/EmailController.php +++ b/src/Oro/Bundle/EmailBundle/Controller/EmailController.php @@ -215,6 +215,59 @@ public function threadWidgetAction(Email $entity) ]; } + /** + * Used on `My Emails` page to show emails thread with only emails being related to currently logged user. + * + * @Route("/view/user-thread/{id}", name="oro_email_user_thread_view", requirements={"id"="\d+"}) + * @AclAncestor("oro_email_email_view") + * @Template("OroEmailBundle:Email/Thread:userEmails.html.twig") + */ + public function viewUserThreadAction(Email $entity) + { + $this->getEmailManager()->setSeenStatus($entity, true, true); + + return ['entity' => $entity]; + } + + /** + * Used on `My Emails` page to show emails thread with only emails being related to currently logged user. + * + * @Route("/widget/user-thread/{id}", name="oro_email_user_thread_widget", requirements={"id"="\d+"}) + * @Template("OroEmailBundle:Email/widget:thread.html.twig") + */ + public function userThreadWidgetAction(Email $entity) + { + $emails = []; + if ($this->getRequest()->get('showSingleEmail', false)) { + $emails[] = $entity; + } else { + $emails = $this->get('oro_email.email.thread.provider')->getUserThreadEmails( + $this->get('doctrine')->getManager(), + $entity, + $this->getUser(), + $this->get('oro_email.mailbox.manager')->findAvailableMailboxes( + $this->getUser(), + $this->get('security.token_storage')->getToken()->getOrganizationContext() + ) + ); + } + + $emails = array_filter($emails, function ($email) { + return $this->get('security.context')->isGranted('VIEW', $email); + }); + $this->loadEmailBody($emails); + + return [ + 'entity' => $entity, + 'thread' => $emails, + 'target' => $this->getTargetEntity(), + 'hasGrantReattach' => $this->isAttachmentCreationGranted(), + 'routeParameters' => $this->getTargetEntityConfig(), + 'renderContexts' => $this->getRequest()->get('renderContexts', true), + 'defaultReplyButton' => $this->get('oro_config.user')->get('oro_email.default_button_reply') + ]; + } + /** * @Route("/view-items", name="oro_email_items_view") * @Template diff --git a/src/Oro/Bundle/EmailBundle/Datagrid/EmailQueryFactory.php b/src/Oro/Bundle/EmailBundle/Datagrid/EmailQueryFactory.php index e13a92b9baf..bfdcc9bf2a7 100644 --- a/src/Oro/Bundle/EmailBundle/Datagrid/EmailQueryFactory.php +++ b/src/Oro/Bundle/EmailBundle/Datagrid/EmailQueryFactory.php @@ -3,6 +3,7 @@ namespace Oro\Bundle\EmailBundle\Datagrid; use Doctrine\Bundle\DoctrineBundle\Registry; +use Doctrine\ORM\Query\Expr; use Doctrine\ORM\QueryBuilder; use Oro\Bundle\EmailBundle\Entity\Manager\MailboxManager; @@ -67,18 +68,16 @@ public function prepareQuery(QueryBuilder $qb, $emailFromTableAlias = 'a') */ public function applyAcl(QueryBuilder $qb) { - $user = $this->securityFacade->getLoggedUser(); - $organization = $this->getOrganization(); - - $mailboxIds = $this->mailboxManager->findAvailableMailboxIds($user, $organization); - $exprs = [$qb->expr()->eq('eu.owner', ':owner')]; + + $organization = $this->getOrganization(); if ($organization) { $exprs[] = $qb->expr()->eq('eu.organization ', ':organization'); $qb->setParameter('organization', $organization->getId()); } $uoCheck = call_user_func_array([$qb->expr(), 'andX'], $exprs); + $mailboxIds = $this->getAvailableMailboxIds(); if (!empty($mailboxIds)) { $qb->andWhere( $qb->expr()->orX( @@ -90,7 +89,154 @@ public function applyAcl(QueryBuilder $qb) } else { $qb->andWhere($uoCheck); } - $qb->setParameter('owner', $user->getId()); + + $qb->setParameter('owner', $this->securityFacade->getLoggedUserId()); + } + + /** + * Apply custom ACL checks in case emails should be shown in threads view + * Adds additional WHERE condition: + * + * o0_.id IN ( + * SELECT max(u.id) + * FROM OroEmailBundle:EmailUser as u + * INNER JOIN OroEmailBundle:Email as m on u.email_id = m.id + * WHERE + * m.thread_id is not null + * AND ( + * ( + * u.user_owner_id = {owner_id} + * AND u.organization_id = {organization_id} + * ) + * OR u.mailbox_owner_id IN ( {allowed_mailboxes_ids} ) + * ) + * GROUP BY m.thread_id + * ) + * OR (o3_.is_head = 1 AND o3_.thread_id is null) + * + * @param QueryBuilder $qb + */ + public function applyAclThreadsGrouping(QueryBuilder $qb) + { + $innerQb = $qb->getEntityManager()->createQueryBuilder(); + $innerQb + ->select('MAX(u.id)') + ->from('OroEmailBundle:EmailUser', 'u') + ->innerJoin('OroEmailBundle:Email', 'm', 'WITH', 'u.email = m.id') + ->where( + $innerQb->expr()->andX( + $innerQb->expr()->isNotNull('m.thread'), + $this->getOwningExpression($innerQb->expr(), 'u') + ) + ) + ->groupBy('m.thread'); + + $qb->andWhere( + $qb->expr()->orX( + $qb->expr()->in('eu.id', $innerQb->getDQL()), + $qb->expr()->andX( + $qb->expr()->isNull('e.thread'), + $qb->expr()->eq('e.head', 'TRUE') + ) + ) + ); + } + + /** + * In case threaded view, adds email counting (SELECT COUNT) expression. + * + * SELECT COUNT(eu.id) + * FROM oro_email_user AS eu + * INNER JOIN oro_email e ON (eu.email_id = e.id) + * WHERE + * e.thread_id = o3_.thread_id + * AND ( + * ( + * eu.user_owner_id = {owner_id} + * AND eu.organization_id = {organization_id} + * ) + * OR eu.mailbox_owner_id IN ( {allowed_mailboxes_ids} ) + * ) + * AND o3_.thread_id IS NOT NULL -- `o3_` is alias for `oro_email` table from base query + * as thread_email_count + * + * @param QueryBuilder $qb + * @param bool $isThreadGroupingEnabled + */ + public function addEmailsCount(QueryBuilder $qb, $isThreadGroupingEnabled) + { + // in case threading view is disabled the default value for counting is `0` + $selectExpression = '0 AS thread_email_count'; + + if ($isThreadGroupingEnabled) { + $innerQb = $qb->getEntityManager()->createQueryBuilder(); + $innerQb + ->select('COUNT(emailUser.id)') + ->from('OroEmailBundle:EmailUser', 'emailUser') + ->innerJoin('OroEmailBundle:Email', 'email', 'WITH', 'emailUser.email = email.id') + ->where( + $innerQb->expr()->andX( + $innerQb->expr()->isNotNull('e.thread'), + $innerQb->expr()->eq('email.thread', 'e.thread'), + $this->getOwningExpression($innerQb->expr(), 'emailUser') + ) + ); + + $selectExpression = '(' . $innerQb->getDQL() . ') AS thread_email_count'; + } + + $qb->addSelect($selectExpression); + } + + /** + * Builds owning expression part, being used in case of threaded emails view enabled. + * + * ( + * eu.user_owner_id = {owner_id} + * AND eu.organization_id = {organization_id} + * ) + * OR eu.mailbox_owner_id IN ( {allowed_mailboxes_ids} ) + * + * @param Expr $expr + * @param string $tableAlias + * + * @return Expr\Andx|Expr\Comparison|Expr\Orx + */ + protected function getOwningExpression($expr, $tableAlias) + { + $user = $this->securityFacade->getLoggedUser(); + $organization = $this->getOrganization(); + + if ($organization === null) { + $ownerExpression = + $expr->eq($tableAlias . '.owner', $user->getId()); + } else { + $ownerExpression = $expr->andX( + $expr->eq($tableAlias . '.owner', $user->getId()), + $expr->eq($tableAlias . '.organization', $organization->getId()) + ); + } + + $availableMailboxIds = $this->getAvailableMailboxIds(); + if ($availableMailboxIds) { + return $expr->orX( + $ownerExpression, + $expr->in($tableAlias . '.mailboxOwner', $this->getAvailableMailboxIds()) + ); + } else { + return $ownerExpression; + } + } + + /** + * @return array + */ + protected function getAvailableMailboxIds() + { + return $this->mailboxManager->findAvailableMailboxIds( + $this->securityFacade->getLoggedUser(), + $this->getOrganization() + ); } /** diff --git a/src/Oro/Bundle/EmailBundle/Entity/Provider/EmailThreadProvider.php b/src/Oro/Bundle/EmailBundle/Entity/Provider/EmailThreadProvider.php index 307d21cbad6..933eea8958b 100644 --- a/src/Oro/Bundle/EmailBundle/Entity/Provider/EmailThreadProvider.php +++ b/src/Oro/Bundle/EmailBundle/Entity/Provider/EmailThreadProvider.php @@ -9,6 +9,7 @@ use Oro\Bundle\EmailBundle\Entity\Email; use Oro\Bundle\EmailBundle\Entity\EmailThread; +use Oro\Bundle\UserBundle\Entity\User; class EmailThreadProvider { @@ -118,4 +119,50 @@ public function getThreadEmails(EntityManager $entityManager, Email $entity) return $result; } + + /** + * Get emails in thread by given email. + * Used on `My Emails` page to show emails thread with only emails being related to currently logged user. + * + * @param EntityManager $entityManager + * @param Email $entity + * @param User $user + * @param array $mailboxes + * @return array + */ + public function getUserThreadEmails(EntityManager $entityManager, Email $entity, User $user, $mailboxes = []) + { + $thread = $entity->getThread(); + if ($thread) { + /** @var QueryBuilder $queryBuilder */ + $queryBuilder = $entityManager->getRepository('OroEmailBundle:Email')->createQueryBuilder('e'); + $queryBuilder->join('e.emailUsers', 'eu'); + + $criteria = new Criteria(); + $criteria->where($criteria->expr()->eq('thread', $thread)); + $criteria->orderBy(['sentAt' => Criteria::DESC]); + + if ($mailboxes) { + $criteria->andWhere( + $criteria->expr()->orX( + $criteria->expr()->in('eu.mailboxOwner', $mailboxes), + $criteria->expr()->eq('eu.owner', $user) + ) + ); + } else { + $criteria->andWhere( + $criteria->expr()->eq('eu.owner', $user) + ); + } + + $result = $queryBuilder + ->addCriteria($criteria) + ->getQuery() + ->getResult(); + } else { + $result = [$entity]; + } + + return $result; + } } diff --git a/src/Oro/Bundle/EmailBundle/EventListener/Datagrid/EmailGridListener.php b/src/Oro/Bundle/EmailBundle/EventListener/Datagrid/EmailGridListener.php index d4d658e7924..ef562a20f57 100644 --- a/src/Oro/Bundle/EmailBundle/EventListener/Datagrid/EmailGridListener.php +++ b/src/Oro/Bundle/EmailBundle/EventListener/Datagrid/EmailGridListener.php @@ -6,6 +6,7 @@ use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; +use Oro\Bundle\ConfigBundle\Config\ConfigManager; use Oro\Bundle\DataGridBundle\Datagrid\ParameterBag; use Oro\Bundle\DataGridBundle\Datasource\Orm\OrmDatasource; use Oro\Bundle\DataGridBundle\Entity\GridView; @@ -14,28 +15,19 @@ use Oro\Bundle\DataGridBundle\Event\OrmResultBeforeQuery; use Oro\Bundle\EmailBundle\Datagrid\EmailQueryFactory; use Oro\Bundle\SecurityBundle\SecurityFacade; -use Oro\Bundle\ConfigBundle\Config\ConfigManager; class EmailGridListener { - /** - * @var EmailQueryFactory - */ + /** @var EmailQueryFactory */ protected $factory; - /** - * @var SecurityFacade - */ + /** @var SecurityFacade */ protected $securityFacade; - /** - * @var GridViewManager - */ + /** @var GridViewManager */ protected $gridViewManager; - /** - * @var ConfigManager - */ + /** @var ConfigManager */ protected $configManager; /** @@ -47,8 +39,8 @@ class EmailGridListener /** * @param EmailQueryFactory $factory - * @param SecurityFacade $securityFacade - * @param GridViewManager $gridViewManager + * @param SecurityFacade $securityFacade + * @param GridViewManager $gridViewManager */ public function __construct( EmailQueryFactory $factory, @@ -93,9 +85,20 @@ public function onBuildAfter(BuildAfter $event) $countQb = $ormDataSource->getCountQb(); $parameters = $event->getDatagrid()->getParameters(); - $this->factory->applyAcl($queryBuilder); - if ($countQb) { - $this->factory->applyAcl($countQb); + $isThreadGroupingEnabled = $this->configManager && $this->configManager->get('oro_email.threads_grouping'); + + $this->factory->addEmailsCount($queryBuilder, $isThreadGroupingEnabled); + + if ($isThreadGroupingEnabled) { + $this->factory->applyAclThreadsGrouping($queryBuilder); + if ($countQb) { + $this->factory->applyAclThreadsGrouping($countQb); + } + } else { + $this->factory->applyAcl($queryBuilder); + if ($countQb) { + $this->factory->applyAcl($countQb); + } } if ($parameters->has('emailIds')) { @@ -106,11 +109,6 @@ public function onBuildAfter(BuildAfter $event) $queryBuilder->andWhere($queryBuilder->expr()->in('e.id', $emailIds)); } - if ($this->configManager && $this->configManager->get('oro_email.threads_grouping')) { - $queryBuilder->andWhere('e.head = :enabled')->setParameter('enabled', true); - $countQb->andWhere('e.head = :enabled')->setParameter('enabled', true); - } - $this->prepareQueryToFilter($parameters, $queryBuilder, $countQb); } @@ -195,9 +193,9 @@ protected function removeGroupByPart(QueryBuilder $qb, $part) $groupByParts = $qb->getDQLPart('groupBy'); $qb->resetDQLPart('groupBy'); /** @var GroupBy $groupByPart */ - foreach ($groupByParts as $i => $groupByPart) { + foreach ($groupByParts as $groupByPart) { $newGroupByPart = []; - foreach ($groupByPart->getParts() as $j => $val) { + foreach ($groupByPart->getParts() as $val) { if ($val !== $part) { $newGroupByPart[] = $val; } diff --git a/src/Oro/Bundle/EmailBundle/Resources/config/datagrid.yml b/src/Oro/Bundle/EmailBundle/Resources/config/datagrid.yml index a0ac5aebe66..c1519a78523 100644 --- a/src/Oro/Bundle/EmailBundle/Resources/config/datagrid.yml +++ b/src/Oro/Bundle/EmailBundle/Resources/config/datagrid.yml @@ -102,10 +102,11 @@ datagrid: - e - eu.receivedAt as receivedAt - eb.textBody AS body_content - - > - (SELECT COUNT(_ec.id) - FROM OroEmailBundle:Email _ec - WHERE _ec.thread = e.thread) AS thread_email_count +# counting logic moved to EmailQueryFactory:addEmailsCount +# - > +# (SELECT COUNT(_ec.id) +# FROM OroEmailBundle:Email _ec +# WHERE _ec.thread = e.thread) AS thread_email_count - > CASE WHEN e.thread IS NULL THEN eb.hasAttachments @@ -262,7 +263,7 @@ datagrid: email.id: ~ view_thread_link: type: url - route: oro_email_thread_view + route: oro_email_user_thread_view params: { id: email.id } toggle_seen_link: type: url diff --git a/src/Oro/Bundle/EmailBundle/Resources/views/Email/Thread/userEmails.html.twig b/src/Oro/Bundle/EmailBundle/Resources/views/Email/Thread/userEmails.html.twig new file mode 100644 index 00000000000..8281ca8686e --- /dev/null +++ b/src/Oro/Bundle/EmailBundle/Resources/views/Email/Thread/userEmails.html.twig @@ -0,0 +1,23 @@ +{% extends "OroEmailBundle:Email/Thread:view.html.twig" %} + +{% block content_data %} +
+
+ {{ oro_widget_render({ + 'widgetType': 'block', + 'wid': 'thread-view', + 'url': path('oro_email_user_thread_widget', {'id': entity.id, 'renderContexts': false, + 'showSingleEmail': not oro_config_value('oro_email.threads_grouping')}), + 'alias': 'thread-view', + 'contextsRendered': true + }) }} +
+ +
+{% endblock content_data %} diff --git a/src/Oro/Bundle/EmailBundle/Tests/Functional/Grid/EmailGridTest.php b/src/Oro/Bundle/EmailBundle/Tests/Functional/Grid/EmailGridTest.php index 938e0fb0bb4..76b9763f8c0 100644 --- a/src/Oro/Bundle/EmailBundle/Tests/Functional/Grid/EmailGridTest.php +++ b/src/Oro/Bundle/EmailBundle/Tests/Functional/Grid/EmailGridTest.php @@ -54,7 +54,7 @@ public function gridProvider() ], 'gridFilters' => [], 'assert' => [], - 'expectedResultCount' => 9, + 'expectedResultCount' => 10, ], ], 'Email grid filtered by from (admin)' => [ @@ -78,7 +78,7 @@ public function gridProvider() 'user-email-grid[_filter][to][value]' => 'simple_user@example.com', ], 'assert' => [], - 'expectedResultCount' => 9, + 'expectedResultCount' => 10, ], ], ]; diff --git a/src/Oro/Bundle/EmailBundle/Tests/Unit/Datagrid/EmailQueryFactoryTest.php b/src/Oro/Bundle/EmailBundle/Tests/Unit/Datagrid/EmailQueryFactoryTest.php index e3124589bbc..1e4a5232b0f 100644 --- a/src/Oro/Bundle/EmailBundle/Tests/Unit/Datagrid/EmailQueryFactoryTest.php +++ b/src/Oro/Bundle/EmailBundle/Tests/Unit/Datagrid/EmailQueryFactoryTest.php @@ -123,7 +123,7 @@ public function testFilterQueryByUserIdWhenMailboxesAreFound() ->method('getLoggedUser') ->will($this->returnValue($user)); - $this->securityFacade->expects($this->once()) + $this->securityFacade->expects($this->exactly(2)) ->method('getOrganization') ->will($this->returnValue($organization)); @@ -156,7 +156,7 @@ public function testFilterQueryByUserIdWhenNoMailboxesFound() ->method('getLoggedUser') ->will($this->returnValue($user)); - $this->securityFacade->expects($this->once()) + $this->securityFacade->expects($this->exactly(2)) ->method('getOrganization') ->will($this->returnValue($organization)); From 2e268ba71c3297438b953617eb297a14cf85835a Mon Sep 17 00:00:00 2001 From: Makar Date: Mon, 13 Feb 2017 16:58:39 +0200 Subject: [PATCH 08/10] BAP-11756: Corrupted installation on replicated mysql server (#7504) --- .../EmailBundle/EventListener/RoleSubscriber.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Oro/Bundle/EmailBundle/EventListener/RoleSubscriber.php b/src/Oro/Bundle/EmailBundle/EventListener/RoleSubscriber.php index c7e4d232b0c..91082aaffd9 100644 --- a/src/Oro/Bundle/EmailBundle/EventListener/RoleSubscriber.php +++ b/src/Oro/Bundle/EmailBundle/EventListener/RoleSubscriber.php @@ -73,14 +73,16 @@ public function postFlush(PostFlushEventArgs $args) $oid = $aclManager->getOid('entity:Oro\Bundle\EmailBundle\Entity\Email'); foreach ($this->insertedRoles as $role) { $sid = $aclManager->getSid($role); - $maskBuilder = $aclManager->getMaskBuilder($oid) - ->add('VIEW_SYSTEM') - ->add('CREATE_SYSTEM') - ->add('EDIT_SYSTEM'); - $aclManager->setPermission($sid, $oid, $maskBuilder->get()); + $mask = 0; + + foreach (['VIEW', 'CREATE', 'EDIT'] as $permission) { + $maskBuilder = $aclManager->getMaskBuilder($oid, $permission); + $maskBuilder->add($permission . '_SYSTEM'); + $mask |= $maskBuilder->get(); + } + $aclManager->setPermission($sid, $oid, $mask); } $this->insertedRoles = []; - $aclManager->flush(); } From 62b7438e046697b7db7f73f23fdc7c05ff6e54d5 Mon Sep 17 00:00:00 2001 From: Makar Date: Wed, 15 Feb 2017 09:12:14 +0200 Subject: [PATCH 09/10] BAP-13566: Custom field is not sychronized for old entities. Redis configuration (#7434) --- .../Bundle/EntityExtendBundle/Tools/ExtendConfigDumper.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Oro/Bundle/EntityExtendBundle/Tools/ExtendConfigDumper.php b/src/Oro/Bundle/EntityExtendBundle/Tools/ExtendConfigDumper.php index ccb85147667..b87598fb772 100644 --- a/src/Oro/Bundle/EntityExtendBundle/Tools/ExtendConfigDumper.php +++ b/src/Oro/Bundle/EntityExtendBundle/Tools/ExtendConfigDumper.php @@ -3,6 +3,7 @@ namespace Oro\Bundle\EntityExtendBundle\Tools; use Doctrine\Common\Cache\ClearableCache; +use Doctrine\Common\Cache\FlushableCache; use Doctrine\ORM\Mapping\ClassMetadataFactory; use Symfony\Component\Filesystem\Filesystem; @@ -112,6 +113,8 @@ public function addExtension(AbstractEntityConfigDumperExtension $extension, $pr * * @param callable|null $filter function (ConfigInterface $config) : bool * @param bool $updateCustom + * + * @SuppressWarnings(PHPMD.NPathComplexity) */ public function updateConfig($filter = null, $updateCustom = false) { @@ -259,7 +262,9 @@ public function clear($keepEntityProxies = false) /** @var ClassMetadataFactory $metadataFactory */ $metadataFactory = $em->getMetadataFactory(); $metadataCache = $metadataFactory->getCacheDriver(); - if ($metadataCache instanceof ClearableCache) { + if ($metadataCache instanceof FlushableCache) { + $metadataCache->flushAll(); + } elseif ($metadataCache instanceof ClearableCache) { $metadataCache->deleteAll(); } } From 7d549b3e352e870dcf445885e46443d07faead5a Mon Sep 17 00:00:00 2001 From: Vadim Tokarchuk Date: Tue, 21 Feb 2017 15:20:17 +0200 Subject: [PATCH 10/10] HRM-240: Fixed update action error in dev mode (#7777) --- .../Bundle/UIBundle/Resources/views/actions/update.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Oro/Bundle/UIBundle/Resources/views/actions/update.html.twig b/src/Oro/Bundle/UIBundle/Resources/views/actions/update.html.twig index b48a0eecd65..0d5c88859ac 100644 --- a/src/Oro/Bundle/UIBundle/Resources/views/actions/update.html.twig +++ b/src/Oro/Bundle/UIBundle/Resources/views/actions/update.html.twig @@ -114,7 +114,7 @@ 'entity': entity|default(form.vars.value), 'entity_class': audit_entity_class|default(null), 'id': audit_entity_id, - 'title': audit_title|default(form.vars.value.__toString is defined ? form.vars.value : null), + 'title': audit_title|default(form.vars.value.__toString is defined ? form.vars.value.__toString : null), 'audit_path': audit_path|default('oro_dataaudit_history'), 'audit_show_change_history': audit_show_change_history|default(false) } %}