File: /home/cpt/public_html/wp-content/plugins/mailpoet/lib/Newsletter/DynamicProducts.php
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Newsletter;
if (!defined('ABSPATH')) exit;
use MailPoet\Logging\LoggerFactory;
use MailPoet\Newsletter\Editor\Transformer;
use MailPoet\WooCommerce\Helper as WCHelper;
use MailPoet\WP\Functions as WPFunctions;
class DynamicProducts {
/** @var LoggerFactory */
private $loggerFactory;
/** @var int|false */
private $newsletterId;
/** @var NewsletterPostsRepository */
private $newsletterPostsRepository;
/** @var WPFunctions */
private $wp;
/** @var WCHelper */
private $wcHelper;
public function __construct(
LoggerFactory $loggerFactory,
NewsletterPostsRepository $newsletterPostsRepository,
WPFunctions $wp,
WCHelper $wcHelper
) {
$this->loggerFactory = $loggerFactory;
$this->newsletterPostsRepository = $newsletterPostsRepository;
$this->wp = $wp;
$this->wcHelper = $wcHelper;
}
public function filterOutSentPosts(string $where): string {
$newsletterPostsTableName = $this->newsletterPostsRepository->getTableName();
$sentPostsQuery = 'SELECT ' . $newsletterPostsTableName . '.post_id FROM '
. $newsletterPostsTableName . ' WHERE '
. $newsletterPostsTableName . ".newsletter_id='" . $this->newsletterId . "'";
$wherePostUnsent = 'ID NOT IN (' . $sentPostsQuery . ')';
if (!empty($where)) $wherePostUnsent = ' AND ' . $wherePostUnsent;
return $where . $wherePostUnsent;
}
public function ensureConsistentQueryType(\WP_Query $query) {
// Queries with taxonomies are autodetected as 'is_archive=true' and 'is_home=false'
// while queries without them end up being 'is_archive=false' and 'is_home=true'.
// This is to fix that by always enforcing constistent behavior.
$query->is_archive = true; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
$query->is_home = false; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
}
public function getPosts(BlockPostQuery $query) {
$this->newsletterId = $query->newsletterId;
// Get posts as logged out user, so private posts hidden by other plugins (e.g. UAM) are also excluded
$currentUserId = $this->wp->getCurrentUserId();
// phpcs:ignore Generic.PHP.ForbiddenFunctions.Discouraged
wp_set_current_user(0);
$this->loggerFactory->getLogger(LoggerFactory::TOPIC_POST_NOTIFICATIONS)->info(
'loading dynamic products',
[
'args' => $query->args,
'posts_to_exclude' => $query->postsToExclude,
'newsletter_id' => $query->newsletterId,
'newer_than_timestamp' => $query->newerThanTimestamp,
'include_product_ids' => $query->includeProductIds,
]
);
// set low priority to execute 'ensureConstistentQueryType' before any other filter
$filterPriority = defined('PHP_INT_MIN') ? constant('PHP_INT_MIN') : ~PHP_INT_MAX;
$this->wp->addAction('pre_get_posts', [$this, 'ensureConsistentQueryType'], $filterPriority);
$this->_attachSentPostsFilter($query->newsletterId);
$parameters = $query->getQueryParams();
$this->loggerFactory->getLogger(LoggerFactory::TOPIC_POST_NOTIFICATIONS)->info(
'getting dynamic products',
['parameters' => $parameters]
);
// Convert WP_Query parameters to WC_Product_Query parameters
$wcArgs = [
'limit' => $parameters['posts_per_page'] ?? -1,
'orderby' => $parameters['orderby'] ?? 'date',
'order' => $parameters['order'] ?? 'DESC',
'exclude' => $query->postsToExclude, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
];
// If we have specific product IDs to include, use them
if (!empty($parameters['post__in'])) {
$wcArgs['include'] = $parameters['post__in'];
}
// WooCommerce Product Query does not support 'any' status,
// so we need to handle it manually
$postStatus = $parameters['post_status'] ?? 'publish';
// Default to published products only
$wcArgs['status'] = 'publish';
// Store original user capabilities for later permission filtering
$canEditPrivate = $this->wp->userCan($currentUserId, 'edit_private_posts');
$canEditOthers = $this->wp->userCan($currentUserId, 'edit_others_posts');
$isAdmin = $this->wp->userCan($currentUserId, 'manage_options');
if (!empty($parameters['tax_query'])) {
$wcArgs['tax_query'] = $parameters['tax_query'];
}
if (!empty($parameters['date_query'])) {
$wcArgs['date_query'] = $parameters['date_query'];
}
// Fetch published products (safe for everyone)
$products = $this->wcHelper->wcGetProducts($wcArgs);
// For privileged users, fetch additional product statuses separately and merge
if ($postStatus === 'any' && ($canEditPrivate || $isAdmin)) {
// Editors get private products
if ($canEditPrivate) {
$privateArgs = $wcArgs;
$privateArgs['status'] = 'private';
$privateProducts = $this->wcHelper->wcGetProducts($privateArgs);
$products = array_merge($products, $privateProducts);
}
// Admins get draft and pending products too
if ($isAdmin) {
$draftArgs = $wcArgs;
$draftArgs['status'] = ['draft', 'pending'];
$draftProducts = $this->wcHelper->wcGetProducts($draftArgs);
$products = array_merge($products, $draftProducts);
}
}
$this->logPosts($products);
$this->wp->removeAction('pre_get_posts', [$this, 'ensureConsistentQueryType'], $filterPriority);
$this->_detachSentPostsFilter($query->newsletterId);
// phpcs:ignore Generic.PHP.ForbiddenFunctions.Discouraged
wp_set_current_user($currentUserId);
return $products;
}
public function transformPosts($args, $posts) {
$transformer = new Transformer($args);
return $transformer->transform($posts);
}
private function _attachSentPostsFilter($newsletterId) {
if ($newsletterId > 0) {
$this->wp->addAction('posts_where', [$this, 'filterOutSentPosts']);
}
}
private function _detachSentPostsFilter($newsletterId) {
if ($newsletterId > 0) {
$this->wp->removeAction('posts_where', [$this, 'filterOutSentPosts']);
}
}
private function logPosts(array $posts) {
$postsToLog = [];
foreach ($posts as $post) {
$postsToLog[] = [
'id' => $post->get_id(),
'post_date' => $post->get_date_created()->format('Y-m-d H:i:s'), // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
];
}
$this->loggerFactory->getLogger(LoggerFactory::TOPIC_POST_NOTIFICATIONS)->info(
'dynamic products loaded posts',
['posts' => $postsToLog]
);
}
}