PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.21.2
MailPoet – Newsletters, Email Marketing, and Automation v5.21.2
5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / lib / Newsletter / DynamicProducts.php
mailpoet / lib / Newsletter Last commit date
Editor 9 months ago Links 2 years ago Listing 7 months ago Options 2 years ago Preview 3 months ago Renderer 3 months ago Scheduler 1 year ago Segment 11 months ago Sending 1 year ago Shortcodes 6 months ago Statistics 1 year ago ViewInBrowser 9 months ago ApiDataSanitizer.php 3 years ago AutomatedLatestContent.php 1 year ago AutomaticEmailsRepository.php 3 years ago BlockPostQuery.php 9 months ago DynamicProducts.php 9 months ago NewsletterCoupon.php 3 years ago NewsletterDeleteController.php 2 years ago NewsletterHtmlSanitizer.php 2 years ago NewsletterPostsRepository.php 2 years ago NewsletterSaveController.php 3 months ago NewsletterValidator.php 1 year ago NewslettersRepository.php 6 months ago Url.php 3 years ago index.php 3 years ago
DynamicProducts.php
191 lines
1 <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
2
3 namespace MailPoet\Newsletter;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\Logging\LoggerFactory;
9 use MailPoet\Newsletter\Editor\Transformer;
10 use MailPoet\WooCommerce\Helper as WCHelper;
11 use MailPoet\WP\Functions as WPFunctions;
12
13 class DynamicProducts {
14
15 /** @var LoggerFactory */
16 private $loggerFactory;
17
18 /** @var int|false */
19 private $newsletterId;
20
21 /** @var NewsletterPostsRepository */
22 private $newsletterPostsRepository;
23
24 /** @var WPFunctions */
25 private $wp;
26
27 /** @var WCHelper */
28 private $wcHelper;
29
30 public function __construct(
31 LoggerFactory $loggerFactory,
32 NewsletterPostsRepository $newsletterPostsRepository,
33 WPFunctions $wp,
34 WCHelper $wcHelper
35 ) {
36 $this->loggerFactory = $loggerFactory;
37 $this->newsletterPostsRepository = $newsletterPostsRepository;
38 $this->wp = $wp;
39 $this->wcHelper = $wcHelper;
40 }
41
42 public function filterOutSentPosts(string $where): string {
43 $newsletterPostsTableName = $this->newsletterPostsRepository->getTableName();
44 $sentPostsQuery = 'SELECT ' . $newsletterPostsTableName . '.post_id FROM '
45 . $newsletterPostsTableName . ' WHERE '
46 . $newsletterPostsTableName . ".newsletter_id='" . $this->newsletterId . "'";
47
48 $wherePostUnsent = 'ID NOT IN (' . $sentPostsQuery . ')';
49
50 if (!empty($where)) $wherePostUnsent = ' AND ' . $wherePostUnsent;
51
52 return $where . $wherePostUnsent;
53 }
54
55 public function ensureConsistentQueryType(\WP_Query $query) {
56 // Queries with taxonomies are autodetected as 'is_archive=true' and 'is_home=false'
57 // while queries without them end up being 'is_archive=false' and 'is_home=true'.
58 // This is to fix that by always enforcing constistent behavior.
59 $query->is_archive = true; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
60 $query->is_home = false; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
61 }
62
63 public function getPosts(BlockPostQuery $query) {
64 $this->newsletterId = $query->newsletterId;
65 // Get posts as logged out user, so private posts hidden by other plugins (e.g. UAM) are also excluded
66 $currentUserId = $this->wp->getCurrentUserId();
67 // phpcs:ignore Generic.PHP.ForbiddenFunctions.Discouraged
68 wp_set_current_user(0);
69
70 $this->loggerFactory->getLogger(LoggerFactory::TOPIC_POST_NOTIFICATIONS)->info(
71 'loading dynamic products',
72 [
73 'args' => $query->args,
74 'posts_to_exclude' => $query->postsToExclude,
75 'newsletter_id' => $query->newsletterId,
76 'newer_than_timestamp' => $query->newerThanTimestamp,
77 'include_product_ids' => $query->includeProductIds,
78 ]
79 );
80
81 // set low priority to execute 'ensureConstistentQueryType' before any other filter
82 $filterPriority = defined('PHP_INT_MIN') ? constant('PHP_INT_MIN') : ~PHP_INT_MAX;
83 $this->wp->addAction('pre_get_posts', [$this, 'ensureConsistentQueryType'], $filterPriority);
84 $this->_attachSentPostsFilter($query->newsletterId);
85 $parameters = $query->getQueryParams();
86 $this->loggerFactory->getLogger(LoggerFactory::TOPIC_POST_NOTIFICATIONS)->info(
87 'getting dynamic products',
88 ['parameters' => $parameters]
89 );
90
91 // Convert WP_Query parameters to WC_Product_Query parameters
92 $wcArgs = [
93 'limit' => $parameters['posts_per_page'] ?? -1,
94 'orderby' => $parameters['orderby'] ?? 'date',
95 'order' => $parameters['order'] ?? 'DESC',
96 'exclude' => $query->postsToExclude, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
97 ];
98
99 // Exclude out-of-stock and products that are not on backorder
100 $excludeOutOfStock = $query->args['excludeOutOfStock'] ?? false;
101 if ($excludeOutOfStock === true || $excludeOutOfStock === 'true') {
102 $wcArgs['stock_status'] = $this->wp->applyFilters('mailpoet_products_exclude_out_of_stock_stock_status', ['instock', 'lowstock', 'onbackorder']);
103 }
104
105 // If we have specific product IDs to include, use them
106 if (!empty($parameters['post__in'])) {
107 $wcArgs['include'] = $parameters['post__in'];
108 }
109
110 // WooCommerce Product Query does not support 'any' status,
111 // so we need to handle it manually
112 $postStatus = $parameters['post_status'] ?? 'publish';
113 // Default to published products only
114 $wcArgs['status'] = 'publish';
115
116 // Store original user capabilities for later permission filtering
117 $canEditPrivate = $this->wp->userCan($currentUserId, 'edit_private_posts');
118 $canEditOthers = $this->wp->userCan($currentUserId, 'edit_others_posts');
119 $isAdmin = $this->wp->userCan($currentUserId, 'manage_options');
120
121 if (!empty($parameters['tax_query'])) {
122 $wcArgs['tax_query'] = $parameters['tax_query'];
123 }
124
125 if (!empty($parameters['date_query'])) {
126 $wcArgs['date_query'] = $parameters['date_query'];
127 }
128
129 // Fetch published products (safe for everyone)
130 $products = $this->wcHelper->wcGetProducts($wcArgs);
131
132 // For privileged users, fetch additional product statuses separately and merge
133 if ($postStatus === 'any' && ($canEditPrivate || $isAdmin)) {
134 // Editors get private products
135 if ($canEditPrivate) {
136 $privateArgs = $wcArgs;
137 $privateArgs['status'] = 'private';
138 $privateProducts = $this->wcHelper->wcGetProducts($privateArgs);
139 $products = array_merge($products, $privateProducts);
140 }
141
142 // Admins get draft and pending products too
143 if ($isAdmin) {
144 $draftArgs = $wcArgs;
145 $draftArgs['status'] = ['draft', 'pending'];
146 $draftProducts = $this->wcHelper->wcGetProducts($draftArgs);
147 $products = array_merge($products, $draftProducts);
148 }
149 }
150
151 $this->logPosts($products);
152
153 $this->wp->removeAction('pre_get_posts', [$this, 'ensureConsistentQueryType'], $filterPriority);
154 $this->_detachSentPostsFilter($query->newsletterId);
155 // phpcs:ignore Generic.PHP.ForbiddenFunctions.Discouraged
156 wp_set_current_user($currentUserId);
157 return $products;
158 }
159
160 public function transformPosts($args, $posts) {
161 $transformer = new Transformer($args);
162 return $transformer->transform($posts);
163 }
164
165 private function _attachSentPostsFilter($newsletterId) {
166 if ($newsletterId > 0) {
167 $this->wp->addAction('posts_where', [$this, 'filterOutSentPosts']);
168 }
169 }
170
171 private function _detachSentPostsFilter($newsletterId) {
172 if ($newsletterId > 0) {
173 $this->wp->removeAction('posts_where', [$this, 'filterOutSentPosts']);
174 }
175 }
176
177 private function logPosts(array $posts) {
178 $postsToLog = [];
179 foreach ($posts as $post) {
180 $postsToLog[] = [
181 'id' => $post->get_id(),
182 'post_date' => $post->get_date_created()->format('Y-m-d H:i:s'), // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
183 ];
184 }
185 $this->loggerFactory->getLogger(LoggerFactory::TOPIC_POST_NOTIFICATIONS)->info(
186 'dynamic products loaded posts',
187 ['posts' => $postsToLog]
188 );
189 }
190 }
191