This commit is contained in:
emmymayo
2025-02-05 23:15:46 +01:00
commit 7269c99357
16995 changed files with 3389680 additions and 0 deletions
@@ -0,0 +1,75 @@
<?php declare(strict_types = 1);
namespace MailPoet\Router\Endpoints;
if (!defined('ABSPATH')) exit;
use MailPoet\Captcha\CaptchaRenderer;
use MailPoet\Captcha\PageRenderer;
use MailPoet\Config\AccessControl;
class Captcha {
const ENDPOINT = 'captcha';
const ACTION_RENDER = 'render';
const ACTION_IMAGE = 'image';
const ACTION_AUDIO = 'audio';
const ACTION_REFRESH = 'refresh';
private PageRenderer $pageRenderer;
private CaptchaRenderer $captchaRenderer;
public $allowedActions = [
self::ACTION_RENDER,
self::ACTION_IMAGE,
self::ACTION_AUDIO,
self::ACTION_REFRESH,
];
public $permissions = [
'global' => AccessControl::NO_ACCESS_RESTRICTION,
];
public function __construct(
PageRenderer $renderer,
CaptchaRenderer $captchaRenderer
) {
$this->pageRenderer = $renderer;
$this->captchaRenderer = $captchaRenderer;
}
public function render($data) {
$this->pageRenderer->render($data);
}
public function image($data) {
$width = !empty($data['width']) ? (int)$data['width'] : null;
$height = !empty($data['height']) ? (int)$data['height'] : null;
$sessionId = $data['captcha_session_id'] ?? null;
if (!$sessionId) {
return;
}
$this->captchaRenderer->renderImage($sessionId, $width, $height);
exit;
}
public function audio($data) {
$sessionId = $data['captcha_session_id'] ?? null;
if (!$sessionId) {
return;
}
$this->captchaRenderer->renderAudio($sessionId);
exit;
}
public function refresh($data) {
$sessionId = $data['captcha_session_id'] ?? null;
if (!$sessionId) {
return;
}
$this->captchaRenderer->refreshPhrase($sessionId);
}
}
@@ -0,0 +1,52 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Router\Endpoints;
if (!defined('ABSPATH')) exit;
use MailPoet\Config\AccessControl;
use MailPoet\Cron\CronHelper;
use MailPoet\Cron\DaemonHttpRunner;
class CronDaemon {
const ENDPOINT = 'cron_daemon';
const ACTION_RUN = 'run';
const ACTION_PING = 'ping';
const ACTION_PING_RESPONSE = 'pingResponse';
public $allowedActions = [
self::ACTION_RUN,
self::ACTION_PING,
self::ACTION_PING_RESPONSE,
];
public $data;
public $permissions = [
'global' => AccessControl::NO_ACCESS_RESTRICTION,
];
/** @var DaemonHttpRunner */
private $daemonRunner;
/** @var CronHelper */
private $cronHelper;
public function __construct(
DaemonHttpRunner $daemonRunner,
CronHelper $cronHelper
) {
$this->daemonRunner = $daemonRunner;
$this->cronHelper = $cronHelper;
}
public function run($data) {
$this->daemonRunner->run($data);
}
public function ping() {
die(esc_html($this->cronHelper->pingDaemon()));
}
public function pingResponse() {
$this->daemonRunner->ping();
}
}
@@ -0,0 +1,57 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Router\Endpoints;
if (!defined('ABSPATH')) exit;
use MailPoet\Config\AccessControl;
use MailPoet\Form\PreviewPage;
use MailPoet\WP\Functions as WPFunctions;
class FormPreview {
const ENDPOINT = 'form_preview';
const ACTION_VIEW = 'view';
/** @var WPFunctions */
private $wp;
/** @var array|null */
private $data;
/** @var PreviewPage */
private $formPreviewPage;
public $allowedActions = [self::ACTION_VIEW];
public $permissions = [
'global' => AccessControl::NO_ACCESS_RESTRICTION,
];
public function __construct(
WPFunctions $wp,
PreviewPage $formPreviewPage
) {
$this->wp = $wp;
$this->formPreviewPage = $formPreviewPage;
}
public function view(array $data) {
$this->data = $data;
$this->wp->addFilter('the_content', [$this, 'renderContent'], 10);
$this->wp->addFilter('the_title', [$this->formPreviewPage, 'renderTitle'], 10, 2);
$this->wp->addFilter('show_admin_bar', function () {
return false;
});
}
public function renderContent(): string {
if (!array_key_exists('id', $this->data ?? []) || !isset($this->data['form_type'])) {
return '';
}
return $this->formPreviewPage->renderPage(
(int)$this->data['id'],
(string)$this->data['form_type'],
(string)$this->data['editor_url']
);
}
}
@@ -0,0 +1,98 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Router\Endpoints;
if (!defined('ABSPATH')) exit;
use MailPoet\Config\AccessControl;
use MailPoet\Entities\StatisticsUnsubscribeEntity;
use MailPoet\Subscription as UserSubscription;
use MailPoet\Util\Request;
use MailPoet\WP\Functions as WPFunctions;
class Subscription {
const ENDPOINT = 'subscription';
const ACTION_CONFIRM = 'confirm';
const ACTION_MANAGE = 'manage';
const ACTION_UNSUBSCRIBE = 'unsubscribe';
const ACTION_CONFIRM_UNSUBSCRIBE = 'confirmUnsubscribe';
const ACTION_RE_ENGAGEMENT = 'reEngagement';
public $allowedActions = [
self::ACTION_CONFIRM,
self::ACTION_MANAGE,
self::ACTION_UNSUBSCRIBE,
self::ACTION_CONFIRM_UNSUBSCRIBE,
self::ACTION_RE_ENGAGEMENT,
];
public $permissions = [
'global' => AccessControl::NO_ACCESS_RESTRICTION,
];
/** @var UserSubscription\Pages */
private $subscriptionPages;
/** @var WPFunctions */
private $wp;
/*** @var Request */
private $request;
public function __construct(
UserSubscription\Pages $subscriptionPages,
WPFunctions $wp,
Request $request
) {
$this->subscriptionPages = $subscriptionPages;
$this->wp = $wp;
$this->request = $request;
}
public function confirm($data) {
$subscription = $this->initSubscriptionPage(UserSubscription\Pages::ACTION_CONFIRM, $data);
$subscription->confirm();
}
public function confirmUnsubscribe($data) {
$enableUnsubscribeConfirmation = $this->wp->applyFilters('mailpoet_unsubscribe_confirmation_enabled', true);
if ($this->request->isPost()) {
$this->applyOneClickUnsubscribeStrategy($data);
exit;
}
if ($enableUnsubscribeConfirmation) {
$this->initSubscriptionPage(UserSubscription\Pages::ACTION_CONFIRM_UNSUBSCRIBE, $data);
} else {
$this->unsubscribe($data);
}
}
public function manage($data) {
$this->initSubscriptionPage(UserSubscription\Pages::ACTION_MANAGE, $data);
}
public function unsubscribe($data) {
if ($this->request->isPost()) {
$this->applyOneClickUnsubscribeStrategy($data);
exit;
} else {
$subscription = $this->initSubscriptionPage(UserSubscription\Pages::ACTION_UNSUBSCRIBE, $data);
$subscription->unsubscribe(StatisticsUnsubscribeEntity::METHOD_LINK);
}
}
public function reEngagement($data) {
$this->initSubscriptionPage(UserSubscription\Pages::ACTION_RE_ENGAGEMENT, $data);
}
private function initSubscriptionPage($action, $data) {
return $this->subscriptionPages->init($action, $data, true, true);
}
private function applyOneClickUnsubscribeStrategy($data): void {
$subscription = $this->initSubscriptionPage(UserSubscription\Pages::ACTION_UNSUBSCRIBE, $data);
$subscription->unsubscribe(StatisticsUnsubscribeEntity::METHOD_ONE_CLICK);
}
}
@@ -0,0 +1,41 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Router\Endpoints;
if (!defined('ABSPATH')) exit;
use MailPoet\Config\AccessControl;
use MailPoet\NewsletterTemplates\TemplateImageLoader;
class TemplateImage {
const ENDPOINT = 'template_image';
const ACTION_GET_EXTERNAL_IMAGE = 'getExternalImage';
/** @var TemplateImageLoader */
private $templateImageLoader;
public $allowedActions = [self::ACTION_GET_EXTERNAL_IMAGE];
public $permissions = [
'global' => AccessControl::PERMISSION_MANAGE_EMAILS,
];
public function __construct(
TemplateImageLoader $templateImageLoader
) {
$this->templateImageLoader = $templateImageLoader;
}
public function getExternalImage($data = [], $return = false) {
if (empty($_GET['url'])) {
return false;
}
$result = $this->templateImageLoader->loadExternalImage(
sanitize_text_field(wp_unslash($_GET['url']))
);
if ($return) {
return $result;
}
exit;
}
}
@@ -0,0 +1,130 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Router\Endpoints;
if (!defined('ABSPATH')) exit;
use MailPoet\Config\AccessControl;
use MailPoet\Cron\Workers\StatsNotifications\NewsletterLinkRepository;
use MailPoet\Entities\SendingQueueEntity;
use MailPoet\Newsletter\Links\Links;
use MailPoet\Newsletter\NewslettersRepository;
use MailPoet\Newsletter\Sending\SendingQueuesRepository;
use MailPoet\Statistics\Track\Clicks;
use MailPoet\Statistics\Track\Opens;
use MailPoet\Subscribers\LinkTokens;
use MailPoet\Subscribers\SubscribersRepository;
use MailPoet\WP\Functions as WPFunctions;
class Track {
const ENDPOINT = 'track';
const ACTION_CLICK = 'click';
const ACTION_OPEN = 'open';
public $allowedActions = [
self::ACTION_CLICK,
self::ACTION_OPEN,
];
public $permissions = [
'global' => AccessControl::NO_ACCESS_RESTRICTION,
];
/** @var Clicks */
private $clicks;
/** @var Opens */
private $opens;
/** @var LinkTokens */
private $linkTokens;
/** @var SendingQueuesRepository */
private $sendingQueuesRepository;
/** @var SubscribersRepository */
private $subscribersRepository;
/** @var NewslettersRepository */
private $newslettersRepository;
/** @var NewsletterLinkRepository */
private $newsletterLinkRepository;
/** @var Links */
private $links;
public function __construct(
Clicks $clicks,
Opens $opens,
SendingQueuesRepository $sendingQueuesRepository,
SubscribersRepository $subscribersRepository,
NewslettersRepository $newslettersRepository,
NewsletterLinkRepository $newsletterLinkRepository,
LinkTokens $linkTokens,
Links $links
) {
$this->clicks = $clicks;
$this->opens = $opens;
$this->linkTokens = $linkTokens;
$this->sendingQueuesRepository = $sendingQueuesRepository;
$this->subscribersRepository = $subscribersRepository;
$this->newslettersRepository = $newslettersRepository;
$this->newsletterLinkRepository = $newsletterLinkRepository;
$this->links = $links;
}
public function click($data) {
return $this->clicks->track($this->_processTrackData($data));
}
public function open($data) {
return $this->opens->track($this->_processTrackData($data));
}
public function _processTrackData($data) {
$data = (object)$this->links->transformUrlDataObject($data);
if (
empty($data->queue_id) || // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
empty($data->subscriber_id) || // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
empty($data->subscriber_token) // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
) {
return false;
}
$data->queue = $this->sendingQueuesRepository->findOneById($data->queue_id);// phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
$data->subscriber = $this->subscribersRepository->findOneById($data->subscriber_id); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
$data->newsletter = (isset($data->newsletter_id)) ? $this->newslettersRepository->findOneById($data->newsletter_id) : null; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
if (!$data->newsletter && ($data->queue instanceof SendingQueueEntity)) {
$data->newsletter = $data->queue->getNewsletter();
}
if (!empty($data->link_hash)) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
$data->link = $this->newsletterLinkRepository->findOneBy([
'hash' => $data->link_hash, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
'queue' => $data->queue_id, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
]);
}
$data->userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : null;
return $this->_validateTrackData($data);
}
public function _validateTrackData($data) {
if (!$data->subscriber || !$data->queue || !$data->newsletter) return false;
$subscriberTokenMatch = $this->linkTokens->verifyToken($data->subscriber, $data->subscriber_token); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
if (!$subscriberTokenMatch) {
$this->terminate(403);
}
// return if this is a WP user previewing the newsletter
if ($data->subscriber->isWPUser() && $data->preview) {
return $data;
}
// check if the newsletter was sent to the subscriber
return ($this->sendingQueuesRepository->isSubscriberProcessed($data->queue, $data->subscriber)) ?
$data :
false;
}
public function terminate($code) {
WPFunctions::get()->statusHeader($code);
WPFunctions::get()->getTemplatePart((string)$code);
exit;
}
}
@@ -0,0 +1,54 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Router\Endpoints;
if (!defined('ABSPATH')) exit;
use MailPoet\Config\AccessControl;
use MailPoet\Newsletter\ViewInBrowser\ViewInBrowserController;
use MailPoet\WP\Functions as WPFunctions;
class ViewInBrowser {
const ENDPOINT = 'view_in_browser';
const ACTION_VIEW = 'view';
public $allowedActions = [self::ACTION_VIEW];
public $permissions = [
'global' => AccessControl::NO_ACCESS_RESTRICTION,
];
/** @var ViewInBrowserController */
private $viewInBrowserController;
public function __construct(
ViewInBrowserController $viewInBrowserController
) {
$this->viewInBrowserController = $viewInBrowserController;
}
public function view(array $data) {
try {
$viewData = $this->viewInBrowserController->view($data);
$this->displayNewsletter($viewData);
} catch (\InvalidArgumentException $e) {
$this->abort();
}
}
private function displayNewsletter($result) {
if ($result) {
header('Content-Type: text/html; charset=utf-8');
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $result;
}
exit;
}
private function abort() {
global $wp_query;// phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
WPFunctions::get()->statusHeader(404);
$wp_query->set_404();// phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
exit;
}
}
@@ -0,0 +1 @@
<?php
@@ -0,0 +1,117 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Router;
if (!defined('ABSPATH')) exit;
use MailPoet\Config\AccessControl;
use MailPoet\Util\Headers;
use MailPoet\Util\Helpers;
use MailPoet\WP\Functions as WPFunctions;
use MailPoetVendor\Psr\Container\ContainerInterface;
class Router {
public $apiRequest;
public $endpoint;
public $action;
public $data;
public $endpointAction;
public $accessControl;
/** @var ContainerInterface */
private $container;
const NAME = 'mailpoet_router';
const RESPONSE_ERROR = 404;
const RESPONE_FORBIDDEN = 403;
public function __construct(
AccessControl $accessControl,
ContainerInterface $container,
$apiData = false
) {
$apiData = ($apiData) ? $apiData : $_GET;
$this->apiRequest = is_array($apiData) && array_key_exists(self::NAME, $apiData);
$this->endpoint = (isset($apiData['endpoint']) && is_string($apiData['endpoint'])) ?
Helpers::underscoreToCamelCase($apiData['endpoint']) :
false;
$this->endpointAction = (isset($apiData['action']) && is_string($apiData['action'])) ?
Helpers::underscoreToCamelCase($apiData['action']) :
false;
$this->data = isset($apiData['data']) ?
self::decodeRequestData($apiData['data']) :
[];
$this->accessControl = $accessControl;
$this->container = $container;
}
public function init() {
if (!$this->apiRequest) return;
// The public MailPoet router is using GET requests,
// but we don't expect any caching of the responses.
Headers::setNoCacheHeaders();
$endpointClass = __NAMESPACE__ . "\\Endpoints\\" . ucfirst($this->endpoint);
if (!$this->endpoint || !class_exists($endpointClass)) {
return $this->terminateRequest(self::RESPONSE_ERROR, __('Invalid router endpoint', 'mailpoet'));
}
$endpoint = $this->container->get($endpointClass);
if (!method_exists($endpoint, $this->endpointAction) || !in_array($this->endpointAction, $endpoint->allowedActions)) {
return $this->terminateRequest(self::RESPONSE_ERROR, __('Invalid router endpoint action', 'mailpoet'));
}
if (!$this->validatePermissions($this->endpointAction, $endpoint->permissions)) {
return $this->terminateRequest(self::RESPONE_FORBIDDEN, __('You do not have the required permissions.', 'mailpoet'));
}
WPFunctions::get()->doAction('mailpoet_conflict_resolver_router_url_query_parameters');
$callback = [
$endpoint,
$this->endpointAction,
];
if (is_callable($callback)) {
return call_user_func($callback, $this->data);
}
}
public static function decodeRequestData($data) {
$data = !is_array($data) ? json_decode(base64_decode($data), true) : [];
if (!is_array($data)) {
$data = [];
}
return $data;
}
public static function encodeRequestData($data) {
$jsonEncoded = json_encode($data);
if ($jsonEncoded === false) {
return '';
}
return rtrim(base64_encode($jsonEncoded), '=');
}
public static function buildRequest($endpoint, $action, $data = false) {
$params = [
self::NAME => '',
'endpoint' => $endpoint,
'action' => $action,
];
if ($data) {
$params['data'] = self::encodeRequestData($data);
}
return WPFunctions::get()->addQueryArg($params, WPFunctions::get()->homeUrl());
}
public function terminateRequest($code, $message) {
WPFunctions::get()->statusHeader($code, $message);
exit;
}
public function validatePermissions($endpointAction, $permissions) {
// validate action permission if defined, otherwise validate global permission
return(!empty($permissions['actions'][$endpointAction])) ?
$this->accessControl->validatePermission($permissions['actions'][$endpointAction]) :
$this->accessControl->validatePermission($permissions['global']);
}
}
@@ -0,0 +1 @@
<?php