暂无描述

SubscribersLastEngagement.php 4.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. <?php declare(strict_types=1);
  2. namespace MailPoet\Cron\Workers;
  3. if (!defined('ABSPATH')) exit;
  4. use MailPoet\Entities\StatisticsClickEntity;
  5. use MailPoet\Entities\StatisticsOpenEntity;
  6. use MailPoet\Entities\SubscriberEntity;
  7. use MailPoet\Models\ScheduledTask;
  8. use MailPoet\Util\DBCollationChecker;
  9. use MailPoet\WooCommerce\Helper as WooCommerceHelper;
  10. use MailPoetVendor\Doctrine\ORM\EntityManager;
  11. class SubscribersLastEngagement extends SimpleWorker {
  12. const AUTOMATIC_SCHEDULING = false;
  13. const SUPPORT_MULTIPLE_INSTANCES = false;
  14. const BATCH_SIZE = 2000;
  15. const TASK_TYPE = 'subscribers_last_engagement';
  16. /** @var EntityManager */
  17. private $entityManager;
  18. /** @var DBCollationChecker */
  19. private $dbCollationChecker;
  20. /** @var WooCommerceHelper */
  21. private $wooCommereHelper;
  22. /** @var null|string */
  23. private $emailCollationCorrection;
  24. public function __construct(
  25. EntityManager $entityManager,
  26. DBCollationChecker $dbCollationChecker,
  27. WooCommerceHelper $wooCommereHelper
  28. ) {
  29. parent::__construct();
  30. $this->entityManager = $entityManager;
  31. $this->dbCollationChecker = $dbCollationChecker;
  32. $this->wooCommereHelper = $wooCommereHelper;
  33. }
  34. public function processTaskStrategy(ScheduledTask $task, $timer): bool {
  35. $meta = $task->getMeta();
  36. $minId = $meta['nextId'] ?? 1;
  37. $highestId = $this->getHighestSubscriberId();
  38. while ($minId <= $highestId) {
  39. $maxId = $minId + self::BATCH_SIZE;
  40. $this->processBatch($minId, $maxId);
  41. $task->meta = ['nextId' => $maxId];
  42. $task->save();
  43. $this->cronHelper->enforceExecutionLimit($timer); // Throws exception and interrupts process if over execution limit
  44. $minId = $maxId;
  45. }
  46. return true;
  47. }
  48. private function processBatch(int $minSubscriberId, int $maxSubscriberId): void {
  49. global $wpdb;
  50. $statisticsClicksTable = $this->entityManager->getClassMetadata(StatisticsClickEntity::class)->getTableName();
  51. $statisticsOpensTable = $this->entityManager->getClassMetadata(StatisticsOpenEntity::class)->getTableName();
  52. $subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
  53. $postsTable = $wpdb->posts;
  54. $postsmetaTable = $wpdb->postmeta;
  55. if (is_null($this->emailCollationCorrection)) {
  56. $this->emailCollationCorrection = $this->dbCollationChecker->getCollateIfNeeded(
  57. $subscribersTable,
  58. 'email',
  59. $postsmetaTable,
  60. 'meta_value'
  61. );
  62. }
  63. $emailCollate = $this->emailCollationCorrection;
  64. $query = "
  65. UPDATE $subscribersTable as mps
  66. LEFT JOIN (SELECT max(created_at) as created_at, subscriber_id FROM $statisticsOpensTable as mpsoinner GROUP BY mpsoinner.subscriber_id) as mpso ON mpso.subscriber_id = mps.id
  67. LEFT JOIN (SELECT max(created_at) as created_at, subscriber_id FROM $statisticsClicksTable as mpscinner GROUP BY mpscinner.subscriber_id) as mpsc ON mpsc.subscriber_id = mps.id
  68. SET mps.last_engagement_at = NULLIF(GREATEST(COALESCE(mpso.created_at, 0), COALESCE(mpsc.created_at,0)), 0)
  69. WHERE mps.last_engagement_at IS NULL AND mps.id >= $minSubscriberId AND mps.id < $maxSubscriberId;
  70. ";
  71. // Use more complex query that takes into the account also subscriber's latest WooCommerce order
  72. if ($this->wooCommereHelper->isWooCommerceActive()) {
  73. $query = "
  74. UPDATE $subscribersTable as mps
  75. LEFT JOIN (SELECT max(created_at) as created_at, subscriber_id FROM $statisticsOpensTable as mpsoinner GROUP BY mpsoinner.subscriber_id) as mpso ON mpso.subscriber_id = mps.id
  76. LEFT JOIN (SELECT max(created_at) as created_at, subscriber_id FROM $statisticsClicksTable as mpscinner GROUP BY mpscinner.subscriber_id) as mpsc ON mpsc.subscriber_id = mps.id
  77. LEFT JOIN (SELECT MAX(post_id) AS post_id, meta_value as email FROM $postsmetaTable WHERE meta_key = '_billing_email' GROUP BY email) AS newestOrderIds ON newestOrderIds.email $emailCollate = mps.email
  78. LEFT JOIN (SELECT ID, post_date FROM $postsTable WHERE post_type = 'shop_order') AS shopOrders ON newestOrderIds.post_id = shopOrders.ID
  79. SET mps.last_engagement_at = NULLIF(GREATEST(COALESCE(mpso.created_at, '0'), COALESCE(mpsc.created_at, '0'), COALESCE(shopOrders.post_date, '0')), '0')
  80. WHERE mps.last_engagement_at IS NULL AND mps.id >= $minSubscriberId AND mps.id < $maxSubscriberId;
  81. ";
  82. }
  83. $this->entityManager->getConnection()->executeStatement($query);
  84. }
  85. private function getHighestSubscriberId(): int {
  86. $subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
  87. $result = $this->entityManager->getConnection()->executeQuery("SELECT MAX(id) FROM $subscribersTable LIMIT 1;")->fetchNumeric();
  88. return is_array($result) && isset($result[0]) ? (int)$result[0] : 0;
  89. }
  90. }