init
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\DI\ContainerWrapper;
|
||||
use MailPoetVendor\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
|
||||
|
||||
class API {
|
||||
/**
|
||||
* @param string $version
|
||||
* @return \MailPoet\API\MP\v1\API
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function MP($version) {
|
||||
/** @var class-string<\MailPoet\API\MP\v1\API> $apiClass */
|
||||
$apiClass = sprintf('%s\MP\%s\API', __NAMESPACE__, $version);
|
||||
try {
|
||||
return ContainerWrapper::getInstance()->get($apiClass);
|
||||
} catch (ServiceNotFoundException $e) {
|
||||
throw new \Exception(__('Invalid API version.', 'mailpoet'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Captcha\CaptchaConstants;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Exception;
|
||||
use MailPoet\Logging\LoggerFactory;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Tracy\ApiPanel\ApiPanel;
|
||||
use MailPoet\Tracy\DIPanel\DIPanel;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoetVendor\Psr\Container\ContainerInterface;
|
||||
use Throwable;
|
||||
use Tracy\Debugger;
|
||||
use Tracy\ILogger;
|
||||
|
||||
class API {
|
||||
private $requestApiVersion;
|
||||
private $requestEndpoint;
|
||||
private $requestMethod;
|
||||
private $requestToken;
|
||||
private $requestType;
|
||||
private $requestEndpointClass;
|
||||
private $requestData = [];
|
||||
private $endpointNamespaces = [];
|
||||
private $availableApiVersions = [
|
||||
'v1',
|
||||
];
|
||||
|
||||
/** @var ContainerInterface */
|
||||
private $container;
|
||||
|
||||
/** @var AccessControl */
|
||||
private $accessControl;
|
||||
|
||||
/** @var ErrorHandler */
|
||||
private $errorHandler;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
/** @var LoggerFactory */
|
||||
private $loggerFactory;
|
||||
|
||||
const CURRENT_VERSION = 'v1';
|
||||
|
||||
public function __construct(
|
||||
ContainerInterface $container,
|
||||
AccessControl $accessControl,
|
||||
ErrorHandler $errorHandler,
|
||||
SettingsController $settings,
|
||||
LoggerFactory $loggerFactory,
|
||||
WPFunctions $wp
|
||||
) {
|
||||
$this->container = $container;
|
||||
$this->accessControl = $accessControl;
|
||||
$this->errorHandler = $errorHandler;
|
||||
$this->settings = $settings;
|
||||
$this->wp = $wp;
|
||||
foreach ($this->availableApiVersions as $availableApiVersion) {
|
||||
$this->addEndpointNamespace(
|
||||
sprintf('%s\%s', __NAMESPACE__, $availableApiVersion),
|
||||
$availableApiVersion
|
||||
);
|
||||
}
|
||||
$this->loggerFactory = $loggerFactory;
|
||||
}
|
||||
|
||||
public function init() {
|
||||
// admin security token and API version
|
||||
WPFunctions::get()->addAction(
|
||||
'admin_head',
|
||||
[$this, 'setTokenAndAPIVersion']
|
||||
);
|
||||
|
||||
// ajax (logged in users)
|
||||
WPFunctions::get()->addAction(
|
||||
'wp_ajax_mailpoet',
|
||||
[$this, 'setupAjax']
|
||||
);
|
||||
|
||||
// ajax (logged out users)
|
||||
WPFunctions::get()->addAction(
|
||||
'wp_ajax_nopriv_mailpoet',
|
||||
[$this, 'setupAjax']
|
||||
);
|
||||
|
||||
// nonce refreshing via heartbeats
|
||||
WPFunctions::get()->addAction(
|
||||
'wp_refresh_nonces',
|
||||
[$this, 'addTokenToHeartbeatResponse']
|
||||
);
|
||||
}
|
||||
|
||||
public function setupAjax() {
|
||||
$this->wp->doAction('mailpoet_api_setup', [$this]);
|
||||
|
||||
if (isset($_POST['api_version'])) {
|
||||
$this->setRequestData($_POST, Endpoint::TYPE_POST);
|
||||
} else {
|
||||
$this->setRequestData($_GET, Endpoint::TYPE_GET);
|
||||
}
|
||||
|
||||
$ignoreToken = (
|
||||
$this->settings->get('captcha.type') != CaptchaConstants::TYPE_DISABLED &&
|
||||
$this->requestEndpoint === 'subscribers' &&
|
||||
$this->requestMethod === 'subscribe'
|
||||
) || (
|
||||
$this->requestEndpoint === 'captcha'
|
||||
);
|
||||
|
||||
if (!$ignoreToken && $this->wp->wpVerifyNonce($this->requestToken, 'mailpoet_token') === false) {
|
||||
$errorMessage = __("Sorry, but we couldn't connect to the MailPoet server. Please refresh the web page and try again.", 'mailpoet');
|
||||
$errorResponse = $this->createErrorResponse(Error::UNAUTHORIZED, $errorMessage, Response::STATUS_UNAUTHORIZED);
|
||||
return $errorResponse->send();
|
||||
}
|
||||
|
||||
$response = $this->processRoute();
|
||||
$response->send();
|
||||
}
|
||||
|
||||
public function setRequestData($data, $requestType) {
|
||||
$this->requestApiVersion = (!empty($data['api_version']) && is_string($data['api_version'])) ? $data['api_version'] : false;
|
||||
|
||||
$this->requestEndpoint = (isset($data['endpoint']) && is_string($data['endpoint']))
|
||||
? Helpers::underscoreToCamelCase(trim($data['endpoint']))
|
||||
: null;
|
||||
|
||||
// JS part of /wp-admin/customize.php does not like a 'method' field in a form widget
|
||||
$methodParamName = isset($data['mailpoet_method']) ? 'mailpoet_method' : 'method';
|
||||
$this->requestMethod = (isset($data[$methodParamName]) && is_string($data[$methodParamName]))
|
||||
? Helpers::underscoreToCamelCase(trim($data[$methodParamName]))
|
||||
: null;
|
||||
$this->requestType = $requestType;
|
||||
|
||||
$this->requestToken = (isset($data['token']) && is_string($data['token']))
|
||||
? trim($data['token'])
|
||||
: null;
|
||||
|
||||
if (!$this->requestEndpoint || !$this->requestMethod || !$this->requestApiVersion) {
|
||||
$errorMessage = __('Invalid API request.', 'mailpoet');
|
||||
$errorResponse = $this->createErrorResponse(Error::BAD_REQUEST, $errorMessage, Response::STATUS_BAD_REQUEST);
|
||||
return $errorResponse;
|
||||
} else if (!empty($this->endpointNamespaces[$this->requestApiVersion])) {
|
||||
foreach ($this->endpointNamespaces[$this->requestApiVersion] as $namespace) {
|
||||
$endpointClass = sprintf(
|
||||
'%s\%s',
|
||||
$namespace,
|
||||
ucfirst($this->requestEndpoint)
|
||||
);
|
||||
if ($this->container->has($endpointClass)) {
|
||||
$this->requestEndpointClass = $endpointClass;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->requestData = isset($data['data'])
|
||||
? WPFunctions::get()->stripslashesDeep($data['data'])
|
||||
: [];
|
||||
|
||||
// remove reserved keywords from data
|
||||
if (is_array($this->requestData) && !empty($this->requestData)) {
|
||||
// filter out reserved keywords from data
|
||||
$reservedKeywords = [
|
||||
'token',
|
||||
'endpoint',
|
||||
'method',
|
||||
'api_version',
|
||||
'mailpoet_method', // alias of 'method'
|
||||
'mailpoet_redirect',
|
||||
];
|
||||
$this->requestData = array_diff_key(
|
||||
$this->requestData,
|
||||
array_flip($reservedKeywords)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function processRoute() {
|
||||
try {
|
||||
if (
|
||||
empty($this->requestEndpointClass) ||
|
||||
!$this->container->has($this->requestEndpointClass)
|
||||
) {
|
||||
throw new \Exception(__('Invalid API endpoint.', 'mailpoet'));
|
||||
}
|
||||
|
||||
$endpoint = $this->container->get($this->requestEndpointClass);
|
||||
if (!method_exists($endpoint, $this->requestMethod)) {
|
||||
throw new \Exception(__('Invalid API endpoint method.', 'mailpoet'));
|
||||
}
|
||||
|
||||
if (!$endpoint->isMethodAllowed($this->requestMethod, $this->requestType)) {
|
||||
throw new \Exception(__('HTTP request method not allowed.', 'mailpoet'));
|
||||
}
|
||||
|
||||
if (
|
||||
class_exists(Debugger::class)
|
||||
&& class_exists(DIPanel::class)
|
||||
&& class_exists(ApiPanel::class)
|
||||
) {
|
||||
ApiPanel::init($endpoint, $this->requestMethod, $this->requestData);
|
||||
DIPanel::init();
|
||||
}
|
||||
|
||||
// check the accessibility of the requested endpoint's action
|
||||
// by default, an endpoint's action is considered "private"
|
||||
if (!$this->validatePermissions($this->requestMethod, $endpoint->permissions)) {
|
||||
$errorMessage = __('You do not have the required permissions.', 'mailpoet');
|
||||
$errorResponse = $this->createErrorResponse(Error::FORBIDDEN, $errorMessage, Response::STATUS_FORBIDDEN);
|
||||
return $errorResponse;
|
||||
}
|
||||
$response = $endpoint->{$this->requestMethod}($this->requestData);
|
||||
return $response;
|
||||
} catch (Exception $e) {
|
||||
$this->logError($e);
|
||||
return $this->errorHandler->convertToResponse($e);
|
||||
} catch (Throwable $e) {
|
||||
if (class_exists(Debugger::class) && Debugger::$logDirectory) {
|
||||
Debugger::log($e, ILogger::EXCEPTION);
|
||||
}
|
||||
$this->logError($e);
|
||||
$errorMessage = $e->getMessage();
|
||||
$errorResponse = $this->createErrorResponse(Error::BAD_REQUEST, $errorMessage, Response::STATUS_BAD_REQUEST);
|
||||
return $errorResponse;
|
||||
}
|
||||
}
|
||||
|
||||
public function validatePermissions($requestMethod, $permissions) {
|
||||
// validate method permission if defined, otherwise validate global permission
|
||||
return(!empty($permissions['methods'][$requestMethod])) ?
|
||||
$this->accessControl->validatePermission($permissions['methods'][$requestMethod]) :
|
||||
$this->accessControl->validatePermission($permissions['global']);
|
||||
}
|
||||
|
||||
public function setTokenAndAPIVersion() {
|
||||
echo sprintf(
|
||||
'<script type="text/javascript">' .
|
||||
'var mailpoet_token = "%s";' .
|
||||
'var mailpoet_api_version = "%s";' .
|
||||
'</script>',
|
||||
esc_js($this->wp->wpCreateNonce('mailpoet_token')),
|
||||
esc_js(self::CURRENT_VERSION)
|
||||
);
|
||||
}
|
||||
|
||||
public function addTokenToHeartbeatResponse($response) {
|
||||
$response['mailpoet_token'] = $this->wp->wpCreateNonce('mailpoet_token');
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function addEndpointNamespace($namespace, $version) {
|
||||
if (!empty($this->endpointNamespaces[$version][$namespace])) return;
|
||||
$this->endpointNamespaces[$version][] = $namespace;
|
||||
}
|
||||
|
||||
public function getEndpointNamespaces() {
|
||||
return $this->endpointNamespaces;
|
||||
}
|
||||
|
||||
public function getRequestedEndpointClass() {
|
||||
return $this->requestEndpointClass;
|
||||
}
|
||||
|
||||
public function getRequestedAPIVersion() {
|
||||
return $this->requestApiVersion;
|
||||
}
|
||||
|
||||
public function createErrorResponse($errorType, $errorMessage, $responseStatus) {
|
||||
$errorMessages = [
|
||||
$errorType => $errorMessage,
|
||||
];
|
||||
|
||||
if ($errorType === Error::BAD_REQUEST) {
|
||||
$mpReinstallErrorMessage = __('The plugin has encountered an unexpected error. Please reload the page. If that does not help, [link]re-install the MailPoet Plugin.[/link]', 'mailpoet');
|
||||
$mpReinstallErrorMessage = Helpers::replaceLinkTags(
|
||||
$mpReinstallErrorMessage,
|
||||
'https://kb.mailpoet.com/article/258-re-installing-updating-the-plugin-via-ftp',
|
||||
['target' => '_blank']
|
||||
);
|
||||
$errorMessages[Error::REINSTALL_PLUGIN] = $mpReinstallErrorMessage;
|
||||
}
|
||||
|
||||
$errorResponse = new ErrorResponse(
|
||||
$errorMessages,
|
||||
[],
|
||||
$responseStatus
|
||||
);
|
||||
return $errorResponse;
|
||||
}
|
||||
|
||||
private function logError(Throwable $e): void {
|
||||
// logging to the php log
|
||||
if (function_exists('error_log')) {
|
||||
error_log((string)$e); // phpcs:ignore Squiz.PHP.DiscouragedFunctions
|
||||
}
|
||||
// logging to the MailPoet table
|
||||
$this->loggerFactory->getLogger(LoggerFactory::TOPIC_API)->warning($e->getMessage(), [
|
||||
'requestMethod' => $this->requestMethod,
|
||||
'requestEndpoint' => $this->requestEndpoint,
|
||||
'exceptionMessage' => $e->getMessage(),
|
||||
'exceptionTrace' => $e->getTrace(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\v1\RedirectResponse;
|
||||
use MailPoet\Config\AccessControl;
|
||||
|
||||
abstract class Endpoint {
|
||||
const TYPE_POST = 'POST';
|
||||
const TYPE_GET = 'GET';
|
||||
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_SETTINGS,
|
||||
'methods' => [],
|
||||
];
|
||||
|
||||
protected static $getMethods = [];
|
||||
|
||||
public function successResponse(
|
||||
$data = [], $meta = [], $status = Response::STATUS_OK
|
||||
) {
|
||||
return new SuccessResponse($data, $meta, $status);
|
||||
}
|
||||
|
||||
public function errorResponse(
|
||||
$errors = [], $meta = [], $status = Response::STATUS_NOT_FOUND
|
||||
) {
|
||||
if (empty($errors)) {
|
||||
$errors = [
|
||||
Error::UNKNOWN => __('An unknown error occurred.', 'mailpoet'),
|
||||
];
|
||||
}
|
||||
return new ErrorResponse($errors, $meta, $status);
|
||||
}
|
||||
|
||||
public function badRequest($errors = [], $meta = []) {
|
||||
if (empty($errors)) {
|
||||
$errors = [
|
||||
Error::BAD_REQUEST => __('Invalid request parameters', 'mailpoet'),
|
||||
];
|
||||
}
|
||||
return new ErrorResponse($errors, $meta, Response::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
public function redirectResponse($url) {
|
||||
return new RedirectResponse($url);
|
||||
}
|
||||
|
||||
public function isMethodAllowed($name, $type) {
|
||||
// Block GET requests on POST endpoints, but allow POST requests on GET endpoints (some plugins
|
||||
// change REQUEST_METHOD to POST on GET requests, which caused them to be blocked)
|
||||
if ($type === self::TYPE_GET && !in_array($name, static::$getMethods)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
final class Error {
|
||||
const UNKNOWN = 'unknown';
|
||||
const BAD_REQUEST = 'bad_request';
|
||||
const UNAUTHORIZED = 'unauthorized';
|
||||
const FORBIDDEN = 'forbidden';
|
||||
const NOT_FOUND = 'not_found';
|
||||
const REINSTALL_PLUGIN = 'reinstall_plugin';
|
||||
|
||||
private function __construct() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\JSON;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Exception;
|
||||
use MailPoet\HttpAwareException;
|
||||
|
||||
class ErrorHandler {
|
||||
/** @var string[] */
|
||||
private $defaultErrors = [];
|
||||
|
||||
public function convertToResponse(\Throwable $e): ErrorResponse {
|
||||
$this->defaultErrors[Error::UNKNOWN] = __('An unknown error occurred.', 'mailpoet');
|
||||
|
||||
if ($e instanceof Exception) {
|
||||
$errors = $e->getErrors() ?: $this->defaultErrors;
|
||||
$statusCode = $e instanceof HttpAwareException ? $e->getHttpStatusCode() : Response::STATUS_UNKNOWN;
|
||||
return new ErrorResponse($errors, [], $statusCode);
|
||||
}
|
||||
return new ErrorResponse($this->defaultErrors, [], Response::STATUS_UNKNOWN);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
class ErrorResponse extends Response {
|
||||
public $errors;
|
||||
|
||||
public function __construct(
|
||||
$errors = [],
|
||||
$meta = [],
|
||||
$status = self::STATUS_NOT_FOUND
|
||||
) {
|
||||
parent::__construct($status, $meta);
|
||||
$this->errors = $this->formatErrors($errors);
|
||||
}
|
||||
|
||||
public function getData() {
|
||||
return (empty($this->errors)) ? null : ['errors' => $this->errors];
|
||||
}
|
||||
|
||||
public function formatErrors($errors = []) {
|
||||
return array_map(function($error, $message) {
|
||||
// sanitize SQL error
|
||||
if (preg_match('/^SQLSTATE/i', $message)) {
|
||||
$message = __('An unknown error occurred.', 'mailpoet');
|
||||
}
|
||||
return [
|
||||
'error' => $error,
|
||||
'message' => $message,
|
||||
];
|
||||
}, array_keys($errors), array_values($errors));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
abstract class Response {
|
||||
const STATUS_OK = 200;
|
||||
const REDIRECT = 302;
|
||||
const STATUS_BAD_REQUEST = 400;
|
||||
const STATUS_UNAUTHORIZED = 401;
|
||||
const STATUS_FORBIDDEN = 403;
|
||||
const STATUS_NOT_FOUND = 404;
|
||||
const STATUS_CONFLICT = 409;
|
||||
const STATUS_UNKNOWN = 500;
|
||||
|
||||
public $status;
|
||||
public $meta;
|
||||
public $location;
|
||||
|
||||
public function __construct($status, $meta = [], $location = null) { // phpcs:ignore
|
||||
$this->status = $status;
|
||||
$this->meta = $meta;
|
||||
$this->location = $location;
|
||||
}
|
||||
|
||||
public function send() {
|
||||
if ($this->status === self::REDIRECT && $this->location) {
|
||||
header("Location: " . $this->location, true, $this->status);
|
||||
exit;
|
||||
}
|
||||
|
||||
WPFunctions::get()->statusHeader($this->status);
|
||||
|
||||
$data = $this->getData();
|
||||
$response = [];
|
||||
|
||||
if (!empty($this->meta)) {
|
||||
$response['meta'] = $this->meta;
|
||||
}
|
||||
if ($data === null) {
|
||||
$data = [];
|
||||
}
|
||||
$response = array_merge($response, $data);
|
||||
|
||||
@header('Content-Type: application/json; charset=' . get_option('blog_charset'));
|
||||
echo wp_json_encode($response);
|
||||
die();
|
||||
}
|
||||
|
||||
public abstract function getData();
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\ResponseBuilders;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\CustomFieldEntity;
|
||||
|
||||
class CustomFieldsResponseBuilder {
|
||||
/**
|
||||
* @param CustomFieldEntity[] $customFields
|
||||
* @return array
|
||||
*/
|
||||
public function buildBatch(array $customFields) {
|
||||
return array_map([$this, 'build'], $customFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CustomFieldEntity $customField
|
||||
* @return array
|
||||
*/
|
||||
public function build(CustomFieldEntity $customField) {
|
||||
return [
|
||||
'id' => $customField->getId(),
|
||||
'name' => $customField->getName(),
|
||||
'type' => $customField->getType(),
|
||||
'params' => $customField->getParams(),
|
||||
'created_at' => ($createdAt = $customField->getCreatedAt()) ? $createdAt->format('Y-m-d H:i:s') : null,
|
||||
'updated_at' => $customField->getUpdatedAt()->format('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\ResponseBuilders;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\DynamicSegmentFilterData;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Segments\SegmentDependencyValidator;
|
||||
use MailPoet\Segments\SegmentSubscribersRepository;
|
||||
use MailPoet\Subscribers\SubscribersCountsController;
|
||||
use MailPoet\WP\Functions;
|
||||
|
||||
class DynamicSegmentsResponseBuilder {
|
||||
const DATE_FORMAT = 'Y-m-d H:i:s';
|
||||
|
||||
/** @var SegmentsResponseBuilder */
|
||||
private $segmentsResponseBuilder;
|
||||
|
||||
/** @var Functions */
|
||||
private $wp;
|
||||
|
||||
/** @var SegmentSubscribersRepository */
|
||||
private $segmentSubscriberRepository;
|
||||
|
||||
/** @var SegmentDependencyValidator */
|
||||
private $segmentDependencyValidator;
|
||||
|
||||
/** @var SubscribersCountsController */
|
||||
private $subscribersCountsController;
|
||||
|
||||
public function __construct(
|
||||
Functions $wp,
|
||||
SegmentSubscribersRepository $segmentSubscriberRepository,
|
||||
SegmentsResponseBuilder $segmentsResponseBuilder,
|
||||
SegmentDependencyValidator $segmentDependencyValidator,
|
||||
SubscribersCountsController $subscribersCountsController
|
||||
) {
|
||||
$this->segmentsResponseBuilder = $segmentsResponseBuilder;
|
||||
$this->segmentSubscriberRepository = $segmentSubscriberRepository;
|
||||
$this->wp = $wp;
|
||||
$this->segmentDependencyValidator = $segmentDependencyValidator;
|
||||
$this->subscribersCountsController = $subscribersCountsController;
|
||||
}
|
||||
|
||||
public function build(SegmentEntity $segmentEntity) {
|
||||
$data = $this->segmentsResponseBuilder->build($segmentEntity);
|
||||
$data = $this->addMissingPluginProperties($segmentEntity, $data);
|
||||
$dynamicFilters = $segmentEntity->getDynamicFilters();
|
||||
$filters = [];
|
||||
foreach ($dynamicFilters as $dynamicFilter) {
|
||||
$filter = $dynamicFilter->getFilterData()->getData();
|
||||
$filter['id'] = $dynamicFilter->getId();
|
||||
$filter['segmentType'] = $dynamicFilter->getFilterData()->getFilterType(); // We need to add filterType with key segmentType due to BC
|
||||
$filter['action'] = $dynamicFilter->getFilterData()->getAction();
|
||||
if (isset($filter['country_code']) && !is_array($filter['country_code'])) {
|
||||
// Convert to multiple values filter
|
||||
$filter['country_code'] = [$filter['country_code']];
|
||||
}
|
||||
if (isset($filter['wordpressRole']) && !is_array($filter['wordpressRole'])) {
|
||||
// new filters are always array, they support multiple values, the old didn't convert old filters to new format
|
||||
$filter['wordpressRole'] = [$filter['wordpressRole']];
|
||||
}
|
||||
if (($filter['segmentType'] === DynamicSegmentFilterData::TYPE_EMAIL) && isset($filter['newsletter_id'])) {
|
||||
$filter['newsletter_id'] = intval($filter['newsletter_id']);
|
||||
}
|
||||
$filters[] = $filter;
|
||||
}
|
||||
$data['filters'] = $filters;
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function buildForListing(array $segments): array {
|
||||
$data = [];
|
||||
foreach ($segments as $segment) {
|
||||
$data[] = $this->buildListingItem($segment);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function addMissingPluginProperties(SegmentEntity $segment, array $data): array {
|
||||
$missingPlugins = $this->segmentDependencyValidator->getMissingPluginsBySegment($segment);
|
||||
if ($missingPlugins) {
|
||||
$missingPlugin = reset($missingPlugins);
|
||||
$data['is_plugin_missing'] = true;
|
||||
|
||||
$missingPluginMessage = $this->segmentDependencyValidator->getCustomErrorMessage($missingPlugin);
|
||||
|
||||
if ($missingPluginMessage) {
|
||||
$data['missing_plugin_message'] = $missingPluginMessage;
|
||||
} else {
|
||||
$data['missing_plugin_message']['message'] =
|
||||
sprintf(
|
||||
// translators: %s is the name of the missing plugin.
|
||||
__('Activate the %s plugin to see the number of subscribers and enable the editing of this segment.', 'mailpoet'),
|
||||
$missingPlugin
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$data['is_plugin_missing'] = false;
|
||||
$data['missing_plugin_message'] = null;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function buildListingItem(SegmentEntity $segment): array {
|
||||
$data = $this->segmentsResponseBuilder->build($segment);
|
||||
$data = $this->addMissingPluginProperties($segment, $data);
|
||||
$data['subscribers_url'] = $this->wp->adminUrl(
|
||||
'admin.php?page=mailpoet-subscribers#/filter[segment=' . $segment->getId() . ']'
|
||||
);
|
||||
|
||||
$segmentStatisticsCount = $this->subscribersCountsController->getSegmentStatisticsCount($segment);
|
||||
$data['count_all'] = $segmentStatisticsCount['all'];
|
||||
$data['count_subscribed'] = $segmentStatisticsCount[SubscriberEntity::STATUS_SUBSCRIBED];
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\ResponseBuilders;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\FormEntity;
|
||||
use MailPoet\Statistics\StatisticsFormsRepository;
|
||||
|
||||
class FormsResponseBuilder {
|
||||
const DATE_FORMAT = 'Y-m-d H:i:s';
|
||||
|
||||
/** @var StatisticsFormsRepository */
|
||||
private $statisticsFormsRepository;
|
||||
|
||||
public function __construct(
|
||||
StatisticsFormsRepository $statisticsFormsRepository
|
||||
) {
|
||||
$this->statisticsFormsRepository = $statisticsFormsRepository;
|
||||
}
|
||||
|
||||
public function build(FormEntity $form) {
|
||||
return [
|
||||
'id' => (string)$form->getId(), // (string) for BC
|
||||
'name' => $form->getName(),
|
||||
'status' => $form->getStatus(),
|
||||
'body' => $form->getBody(),
|
||||
'settings' => $form->getSettings(),
|
||||
'styles' => $form->getStyles(),
|
||||
'created_at' => ($createdAt = $form->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
|
||||
'updated_at' => $form->getUpdatedAt()->format(self::DATE_FORMAT),
|
||||
'deleted_at' => ($deletedAt = $form->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
|
||||
];
|
||||
}
|
||||
|
||||
public function buildForListing(array $forms) {
|
||||
$data = [];
|
||||
|
||||
foreach ($forms as $form) {
|
||||
$form = $this->build($form);
|
||||
$form['signups'] = $this->statisticsFormsRepository->getTotalSignups($form['id']);
|
||||
$form['segments'] = (
|
||||
!empty($form['settings']['segments'])
|
||||
? $form['settings']['segments']
|
||||
: []
|
||||
);
|
||||
|
||||
$data[] = $form;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\ResponseBuilders;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\NewsletterTemplateEntity;
|
||||
|
||||
class NewsletterTemplatesResponseBuilder {
|
||||
const DATE_FORMAT = 'Y-m-d H:i:s';
|
||||
|
||||
public function build(NewsletterTemplateEntity $template): array {
|
||||
return [
|
||||
'id' => $template->getId(),
|
||||
'categories' => $template->getCategories(),
|
||||
'thumbnail' => $template->getThumbnail(),
|
||||
'name' => $template->getName(),
|
||||
'readonly' => $template->getReadonly(),
|
||||
'body' => $template->getBody(),
|
||||
'created_at' => ($createdAt = $template->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
|
||||
'updated_at' => $template->getUpdatedAt()->format(self::DATE_FORMAT),
|
||||
'newsletter_id' => ($newsletter = $template->getNewsletter()) ? $newsletter->getId() : null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param NewsletterTemplateEntity[] $newsletterTemplates
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function buildForListing(array $newsletterTemplates): array {
|
||||
$data = [];
|
||||
foreach ($newsletterTemplates as $template) {
|
||||
$data[] = [
|
||||
'id' => $template->getId(),
|
||||
'categories' => $template->getCategories(),
|
||||
'thumbnail' => $template->getThumbnail(),
|
||||
'name' => $template->getName(),
|
||||
'readonly' => $template->getReadonly(),
|
||||
];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
+328
@@ -0,0 +1,328 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\ResponseBuilders;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\DynamicSegmentFilterEntity;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Logging\LoggerFactory;
|
||||
use MailPoet\Logging\LogRepository;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Newsletter\Sending\SendingQueuesRepository;
|
||||
use MailPoet\Newsletter\Statistics\NewsletterStatistics;
|
||||
use MailPoet\Newsletter\Statistics\NewsletterStatisticsRepository;
|
||||
use MailPoet\Newsletter\Url as NewsletterUrl;
|
||||
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
||||
|
||||
class NewslettersResponseBuilder {
|
||||
const DATE_FORMAT = 'Y-m-d H:i:s';
|
||||
|
||||
const RELATION_QUEUE = 'queue';
|
||||
const RELATION_SEGMENTS = 'segments';
|
||||
const RELATION_OPTIONS = 'options';
|
||||
const RELATION_TOTAL_SENT = 'total_sent';
|
||||
const RELATION_CHILDREN_COUNT = 'children_count';
|
||||
const RELATION_SCHEDULED = 'scheduled';
|
||||
const RELATION_STATISTICS = 'statistics';
|
||||
|
||||
/** @var NewsletterStatisticsRepository */
|
||||
private $newslettersStatsRepository;
|
||||
|
||||
/** @var NewslettersRepository */
|
||||
private $newslettersRepository;
|
||||
|
||||
/** @var EntityManager */
|
||||
private $entityManager;
|
||||
|
||||
/** @var NewsletterUrl */
|
||||
private $newsletterUrl;
|
||||
|
||||
/** @var SendingQueuesRepository */
|
||||
private $sendingQueuesRepository;
|
||||
|
||||
/*** @var LogRepository */
|
||||
private $logRepository;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
NewslettersRepository $newslettersRepository,
|
||||
NewsletterStatisticsRepository $newslettersStatsRepository,
|
||||
NewsletterUrl $newsletterUrl,
|
||||
SendingQueuesRepository $sendingQueuesRepository,
|
||||
LogRepository $logRepository
|
||||
) {
|
||||
$this->newslettersStatsRepository = $newslettersStatsRepository;
|
||||
$this->newslettersRepository = $newslettersRepository;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->newsletterUrl = $newsletterUrl;
|
||||
$this->sendingQueuesRepository = $sendingQueuesRepository;
|
||||
$this->logRepository = $logRepository;
|
||||
}
|
||||
|
||||
public function build(NewsletterEntity $newsletter, $relations = []) {
|
||||
$data = [
|
||||
'id' => (string)$newsletter->getId(), // (string) for BC
|
||||
'hash' => $newsletter->getHash(),
|
||||
'subject' => $newsletter->getSubject(),
|
||||
'type' => $newsletter->getType(),
|
||||
'sender_address' => $newsletter->getSenderAddress(),
|
||||
'sender_name' => $newsletter->getSenderName(),
|
||||
'status' => $newsletter->getStatus(),
|
||||
'reply_to_address' => $newsletter->getReplyToAddress(),
|
||||
'reply_to_name' => $newsletter->getReplyToName(),
|
||||
'preheader' => $newsletter->getPreheader(),
|
||||
'body' => $newsletter->getBody(),
|
||||
'sent_at' => ($sentAt = $newsletter->getSentAt()) ? $sentAt->format(self::DATE_FORMAT) : null,
|
||||
'created_at' => ($createdAt = $newsletter->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
|
||||
'updated_at' => $newsletter->getUpdatedAt()->format(self::DATE_FORMAT),
|
||||
'deleted_at' => ($deletedAt = $newsletter->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
|
||||
'parent_id' => ($parent = $newsletter->getParent()) ? $parent->getId() : null,
|
||||
'unsubscribe_token' => $newsletter->getUnsubscribeToken(),
|
||||
'ga_campaign' => $newsletter->getGaCampaign(),
|
||||
'wp_post_id' => $newsletter->getWpPostId(),
|
||||
'campaign_name' => $newsletter->getCampaignName(),
|
||||
];
|
||||
|
||||
foreach ($relations as $relation) {
|
||||
if ($relation === self::RELATION_QUEUE) {
|
||||
$data['queue'] = ($queue = $newsletter->getLatestQueue()) ? $this->buildQueue($queue) : false; // false for BC
|
||||
}
|
||||
if ($relation === self::RELATION_SEGMENTS) {
|
||||
$data['segments'] = $this->buildSegments($newsletter);
|
||||
}
|
||||
if ($relation === self::RELATION_OPTIONS) {
|
||||
$data['options'] = $this->buildOptions($newsletter);
|
||||
}
|
||||
if ($relation === self::RELATION_TOTAL_SENT) {
|
||||
$data['total_sent'] = $this->newslettersStatsRepository->getTotalSentCount($newsletter);
|
||||
}
|
||||
if ($relation === self::RELATION_CHILDREN_COUNT) {
|
||||
$data['children_count'] = $this->newslettersStatsRepository->getChildrenCount($newsletter);
|
||||
}
|
||||
if ($relation === self::RELATION_SCHEDULED) {
|
||||
$data['total_scheduled'] = $this->sendingQueuesRepository->countAllToProcessByNewsletter(
|
||||
$newsletter
|
||||
);
|
||||
}
|
||||
|
||||
if ($relation === self::RELATION_STATISTICS) {
|
||||
$data['statistics'] = $this->newslettersStatsRepository->getStatistics($newsletter)->asArray();
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function processPersonalizationTags(?string $content): ?string {
|
||||
if (is_null($content) || strlen($content) === 0) {
|
||||
return $content;
|
||||
}
|
||||
if (strpos($content, '<!--') === false) {
|
||||
// we don't need to parse anything if there are no personalization tags
|
||||
return $content;
|
||||
}
|
||||
if (!class_exists('\MailPoet\EmailEditor\Engine\PersonalizationTags\HTML_Tag_Processor')) {
|
||||
// editor is not active, we cannot process personalization tags
|
||||
return $content;
|
||||
}
|
||||
|
||||
$content_processor = new \MailPoet\EmailEditor\Engine\PersonalizationTags\HTML_Tag_Processor($content);
|
||||
while ($content_processor->next_token()) {
|
||||
$type = $content_processor->get_token_type();
|
||||
if ($type === '#comment') {
|
||||
$token = $content_processor->get_modifiable_text();
|
||||
$content_processor->replace_token($token);
|
||||
}
|
||||
}
|
||||
$content_processor->flush_updates();
|
||||
return $content_processor->get_updated_html();
|
||||
}
|
||||
|
||||
public function buildForListing(array $newsletters): array {
|
||||
$statistics = $this->newslettersStatsRepository->getBatchStatistics($newsletters);
|
||||
$latestQueues = $this->getBatchLatestQueuesWithTasks($newsletters);
|
||||
$this->newslettersRepository->prefetchOptions($newsletters);
|
||||
$this->newslettersRepository->prefetchSegments($newsletters);
|
||||
|
||||
$data = [];
|
||||
foreach ($newsletters as $newsletter) {
|
||||
$id = $newsletter->getId();
|
||||
$data[] = $this->buildListingItem($newsletter, $statistics[$id] ?? null, $latestQueues[$id] ?? null);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param NewsletterEntity $newsletter
|
||||
* @param NewsletterStatistics|null $statistics
|
||||
* @param SendingQueueEntity|null $latestQueue
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function buildListingItem(NewsletterEntity $newsletter, NewsletterStatistics $statistics = null, SendingQueueEntity $latestQueue = null): array {
|
||||
$couponBlockLogs = array_map(function ($item) {
|
||||
return "Coupon block: $item";
|
||||
}, $this->logRepository->getRawMessagesForNewsletter($newsletter, LoggerFactory::TOPIC_COUPONS));
|
||||
$data = [
|
||||
'id' => (string)$newsletter->getId(), // (string) for BC
|
||||
'hash' => $newsletter->getHash(),
|
||||
'subject' => $this->processPersonalizationTags($newsletter->getSubject()),
|
||||
'type' => $newsletter->getType(),
|
||||
'status' => $newsletter->getStatus(),
|
||||
'sent_at' => ($sentAt = $newsletter->getSentAt()) ? $sentAt->format(self::DATE_FORMAT) : null,
|
||||
'updated_at' => $newsletter->getUpdatedAt()->format(self::DATE_FORMAT),
|
||||
'deleted_at' => ($deletedAt = $newsletter->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
|
||||
'segments' => [],
|
||||
'queue' => false,
|
||||
'wp_post_id' => $newsletter->getWpPostId(),
|
||||
'statistics' => ($statistics && $newsletter->getType() !== NewsletterEntity::TYPE_NOTIFICATION)
|
||||
? $statistics->asArray()
|
||||
: false,
|
||||
'preview_url' => $this->newsletterUrl->getViewInBrowserUrl(
|
||||
$newsletter,
|
||||
null,
|
||||
in_array($newsletter->getStatus(), [NewsletterEntity::STATUS_SENT, NewsletterEntity::STATUS_SENDING], true)
|
||||
? $latestQueue
|
||||
: null
|
||||
),
|
||||
'logs' => $couponBlockLogs,
|
||||
'campaign_name' => $newsletter->getCampaignName(),
|
||||
];
|
||||
|
||||
if ($newsletter->getType() === NewsletterEntity::TYPE_STANDARD) {
|
||||
$data['segments'] = $this->buildSegments($newsletter);
|
||||
$data['queue'] = $latestQueue ? $this->buildQueue($latestQueue) : false; // false for BC
|
||||
$data['options'] = $this->buildOptions($newsletter);
|
||||
} elseif (in_array($newsletter->getType(), [NewsletterEntity::TYPE_WELCOME, NewsletterEntity::TYPE_AUTOMATIC], true)) {
|
||||
$data['segments'] = [];
|
||||
$data['options'] = $this->buildOptions($newsletter);
|
||||
$data['total_sent'] = $statistics ? $statistics->getTotalSentCount() : 0;
|
||||
$data['total_scheduled'] = $this->sendingQueuesRepository->countAllToProcessByNewsletter(
|
||||
$newsletter
|
||||
);
|
||||
} elseif ($newsletter->getType() === NewsletterEntity::TYPE_NOTIFICATION) {
|
||||
$data['segments'] = $this->buildSegments($newsletter);
|
||||
$data['children_count'] = $this->newslettersStatsRepository->getChildrenCount($newsletter);
|
||||
$data['options'] = $this->buildOptions($newsletter);
|
||||
} elseif ($newsletter->getType() === NewsletterEntity::TYPE_NOTIFICATION_HISTORY) {
|
||||
$data['segments'] = $this->buildSegments($newsletter);
|
||||
$data['queue'] = $latestQueue ? $this->buildQueue($latestQueue) : false; // false for BC
|
||||
} elseif ($newsletter->getType() === NewsletterEntity::TYPE_RE_ENGAGEMENT) {
|
||||
$data['segments'] = $this->buildSegments($newsletter);
|
||||
$data['options'] = $this->buildOptions($newsletter);
|
||||
$data['total_sent'] = $statistics ? $statistics->getTotalSentCount() : 0;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function buildSegments(NewsletterEntity $newsletter) {
|
||||
$output = [];
|
||||
foreach ($newsletter->getNewsletterSegments() as $newsletterSegment) {
|
||||
$segment = $newsletterSegment->getSegment();
|
||||
if (!$segment || $segment->getDeletedAt()) {
|
||||
continue;
|
||||
}
|
||||
$output[] = $this->buildSegment($segment);
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
private function buildOptions(NewsletterEntity $newsletter) {
|
||||
$output = [];
|
||||
foreach ($newsletter->getOptions() as $option) {
|
||||
$optionField = $option->getOptionField();
|
||||
if (!$optionField) {
|
||||
continue;
|
||||
}
|
||||
$output[$optionField->getName()] = $option->getValue();
|
||||
}
|
||||
|
||||
// convert 'afterTimeNumber' string to integer
|
||||
if (isset($output['afterTimeNumber']) && is_numeric($output['afterTimeNumber'])) {
|
||||
$output['afterTimeNumber'] = (int)$output['afterTimeNumber'];
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
private function buildSegment(SegmentEntity $segment) {
|
||||
$filters = $segment->getType() === SegmentEntity::TYPE_DYNAMIC ? $segment->getDynamicFilters()->toArray() : [];
|
||||
return [
|
||||
'id' => (string)$segment->getId(), // (string) for BC
|
||||
'name' => $segment->getName(),
|
||||
'type' => $segment->getType(),
|
||||
'filters' => array_map(function(DynamicSegmentFilterEntity $filter) {
|
||||
return [
|
||||
'action' => $filter->getFilterData()->getAction(),
|
||||
'type' => $filter->getFilterData()->getFilterType(),
|
||||
];
|
||||
}, $filters),
|
||||
'description' => $segment->getDescription(),
|
||||
'created_at' => ($createdAt = $segment->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
|
||||
'updated_at' => $segment->getUpdatedAt()->format(self::DATE_FORMAT),
|
||||
'deleted_at' => ($deletedAt = $segment->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
|
||||
];
|
||||
}
|
||||
|
||||
private function buildQueue(SendingQueueEntity $queue) {
|
||||
$task = $queue->getTask();
|
||||
if ($task === null) {
|
||||
return null;
|
||||
}
|
||||
return [
|
||||
'id' => (string)$queue->getId(), // (string) for BC
|
||||
'type' => $task->getType(),
|
||||
'status' => $task->getStatus(),
|
||||
'priority' => (string)$task->getPriority(), // (string) for BC
|
||||
'scheduled_at' => ($scheduledAt = $task->getScheduledAt()) ? $scheduledAt->format(self::DATE_FORMAT) : null,
|
||||
'processed_at' => ($processedAt = $task->getProcessedAt()) ? $processedAt->format(self::DATE_FORMAT) : null,
|
||||
'created_at' => ($createdAt = $queue->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
|
||||
'updated_at' => $queue->getUpdatedAt()->format(self::DATE_FORMAT),
|
||||
'deleted_at' => ($deletedAt = $queue->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
|
||||
'meta' => $queue->getMeta(),
|
||||
'task_id' => (string)$task->getId(), // (string) for BC
|
||||
'newsletter_id' => ($newsletter = $queue->getNewsletter()) ? (string)$newsletter->getId() : null, // (string) for BC
|
||||
'newsletter_rendered_subject' => $this->processPersonalizationTags($queue->getNewsletterRenderedSubject()),
|
||||
'count_total' => (string)$queue->getCountTotal(), // (string) for BC
|
||||
'count_processed' => (string)$queue->getCountProcessed(), // (string) for BC
|
||||
'count_to_process' => (string)$queue->getCountToProcess(), // (string) for BC
|
||||
];
|
||||
}
|
||||
|
||||
private function getBatchLatestQueuesWithTasks(array $newsletters): array {
|
||||
// this implements the same logic as NewsletterEntity::getLatestQueue() but for a batch of $newsletters
|
||||
|
||||
$subqueryQueryBuilder = $this->entityManager->createQueryBuilder();
|
||||
$subquery = $subqueryQueryBuilder
|
||||
->select('MAX(subSq.id) AS maxId')
|
||||
->from(SendingQueueEntity::class, 'subSq')
|
||||
->where('subSq.newsletter IN (:newsletters)')
|
||||
->setParameter('newsletters', $newsletters)
|
||||
->groupBy('subSq.newsletter')
|
||||
->getQuery();
|
||||
$latestQueueIds = array_column($subquery->getResult(), 'maxId');
|
||||
if (empty($latestQueueIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$queryBuilder = $this->entityManager->createQueryBuilder();
|
||||
$results = $queryBuilder
|
||||
->select('PARTIAL sq.{id, createdAt, updatedAt, deletedAt, meta, newsletterRenderedSubject, countTotal, countProcessed, countToProcess}')
|
||||
->addSelect('PARTIAL t.{id, type, status, priority, scheduledAt, processedAt}')
|
||||
->addSelect('IDENTITY(sq.newsletter)')
|
||||
->from(SendingQueueEntity::class, 'sq')
|
||||
->join('sq.task', 't')
|
||||
->where('sq.id IN (:sub)')
|
||||
->setParameter('sub', $latestQueueIds)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
$latestQueues = [];
|
||||
foreach ($results as $result) {
|
||||
$latestQueues[(int)$result[1]] = $result[0];
|
||||
}
|
||||
return $latestQueues;
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\JSON\ResponseBuilders;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\ScheduledTaskSubscriberEntity;
|
||||
|
||||
class ScheduledTaskSubscriberResponseBuilder {
|
||||
public function build(ScheduledTaskSubscriberEntity $scheduledSubscriber) {
|
||||
$subscriber = $scheduledSubscriber->getSubscriber();
|
||||
$task = $scheduledSubscriber->getTask();
|
||||
return [
|
||||
'processed' => $scheduledSubscriber->getProcessed(),
|
||||
'failed' => $scheduledSubscriber->getFailed(),
|
||||
'error' => $scheduledSubscriber->getError(),
|
||||
'taskId' => $task ? $task->getId() : null,
|
||||
'email' => $subscriber ? $subscriber->getEmail() : null,
|
||||
'subscriberId' => $subscriber ? $subscriber->getId() : null,
|
||||
'firstName' => $subscriber ? $subscriber->getFirstName() : null,
|
||||
'lastName' => $subscriber ? $subscriber->getLastName() : null,
|
||||
];
|
||||
}
|
||||
|
||||
public function buildForListing(array $scheduledSubscribers) {
|
||||
$data = [];
|
||||
foreach ($scheduledSubscribers as $scheduledSubscriber) {
|
||||
$data[] = $this->build($scheduledSubscriber);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\JSON\ResponseBuilders;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Subscribers\SubscribersCountsController;
|
||||
use MailPoet\WP\Functions;
|
||||
|
||||
class SegmentsResponseBuilder {
|
||||
const DATE_FORMAT = 'Y-m-d H:i:s';
|
||||
|
||||
/** @var Functions */
|
||||
private $wp;
|
||||
|
||||
/** @var SubscribersCountsController */
|
||||
private $subscribersCountsController;
|
||||
|
||||
public function __construct(
|
||||
Functions $wp,
|
||||
SubscribersCountsController $subscribersCountsController
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->subscribersCountsController = $subscribersCountsController;
|
||||
}
|
||||
|
||||
public function build(SegmentEntity $segment): array {
|
||||
return [
|
||||
'id' => (string)$segment->getId(), // (string) for BC
|
||||
'name' => $segment->getName(),
|
||||
'type' => $segment->getType(),
|
||||
'description' => $segment->getDescription(),
|
||||
'created_at' => ($createdAt = $segment->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
|
||||
'updated_at' => $segment->getUpdatedAt()->format(self::DATE_FORMAT),
|
||||
'deleted_at' => ($deletedAt = $segment->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
|
||||
'average_engagement_score' => $segment->getAverageEngagementScore(),
|
||||
'filters_connect' => $segment->getFiltersConnectOperator(),
|
||||
'showInManageSubscriptionPage' => (int)$segment->getDisplayInManageSubscriptionPage(),
|
||||
];
|
||||
}
|
||||
|
||||
public function buildForListing(array $segments): array {
|
||||
$data = [];
|
||||
foreach ($segments as $segment) {
|
||||
$data[] = $this->buildListingItem($segment);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function buildListingItem(SegmentEntity $segment): array {
|
||||
$data = $this->build($segment);
|
||||
|
||||
$data['subscribers_count'] = $this->subscribersCountsController->getSegmentStatisticsCount($segment);
|
||||
$data['subscribers_url'] = $this->wp->adminUrl(
|
||||
'admin.php?page=mailpoet-subscribers#/filter[segment=' . $segment->getId() . ']'
|
||||
);
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\JSON\ResponseBuilders;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
|
||||
class SendingQueuesResponseBuilder {
|
||||
public function build(SendingQueueEntity $sendingQueue): array {
|
||||
if (!$sendingQueue->getTask() instanceof ScheduledTaskEntity) {
|
||||
throw new \RuntimeException('Invalid state. SendingQueue has no ScheduledTask associated.');
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $sendingQueue->getId(),
|
||||
'type' => $sendingQueue->getTask()->getType(),
|
||||
'status' => $sendingQueue->getTask()->getStatus(),
|
||||
'priority' => $sendingQueue->getTask()->getPriority(),
|
||||
'scheduled_at' => $this->getFormattedDateOrNull($sendingQueue->getTask()->getScheduledAt()),
|
||||
'processed_at' => $this->getFormattedDateOrNull($sendingQueue->getTask()->getProcessedAt()),
|
||||
'created_at' => $this->getFormattedDateOrNull($sendingQueue->getTask()->getCreatedAt()),
|
||||
'updated_at' => $this->getFormattedDateOrNull($sendingQueue->getTask()->getUpdatedAt()),
|
||||
'deleted_at' => $this->getFormattedDateOrNull($sendingQueue->getTask()->getDeletedAt()),
|
||||
'in_progress' => $sendingQueue->getTask()->getInProgress(),
|
||||
'reschedule_count' => $sendingQueue->getTask()->getRescheduleCount(),
|
||||
'meta' => $sendingQueue->getMeta(),
|
||||
'task_id' => $sendingQueue->getTask()->getId(),
|
||||
'newsletter_id' => ($sendingQueue->getNewsletter() instanceof NewsletterEntity) ? $sendingQueue->getNewsletter()->getId() : null,
|
||||
'newsletter_rendered_body' => $sendingQueue->getNewsletterRenderedBody(),
|
||||
'newsletter_rendered_subject' => $sendingQueue->getNewsletterRenderedSubject(),
|
||||
'count_total' => $sendingQueue->getCountTotal(),
|
||||
'count_processed' => $sendingQueue->getCountProcessed(),
|
||||
'count_to_process' => $sendingQueue->getCountToProcess(),
|
||||
];
|
||||
}
|
||||
|
||||
private function getFormattedDateOrNull(?\DateTimeInterface $date): ?string {
|
||||
return $date ? $date->format('Y-m-d H:i:s') : null;
|
||||
}
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\ResponseBuilders;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\CustomFields\CustomFieldsRepository;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Entities\SubscriberCustomFieldEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Statistics\StatisticsUnsubscribesRepository;
|
||||
use MailPoet\Subscribers\SubscriberCustomFieldRepository;
|
||||
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
||||
|
||||
class SubscribersResponseBuilder {
|
||||
const DATE_FORMAT = 'Y-m-d H:i:s';
|
||||
|
||||
/** @var StatisticsUnsubscribesRepository */
|
||||
private $statisticsUnsubscribesRepository;
|
||||
|
||||
/** @var CustomFieldsRepository */
|
||||
private $customFieldsRepository;
|
||||
|
||||
/** @var SubscriberCustomFieldRepository */
|
||||
private $subscriberCustomFieldRepository;
|
||||
|
||||
/** @var EntityManager */
|
||||
private $entityManager;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
CustomFieldsRepository $customFieldsRepository,
|
||||
SubscriberCustomFieldRepository $subscriberCustomFieldRepository,
|
||||
StatisticsUnsubscribesRepository $statisticsUnsubscribesRepository
|
||||
) {
|
||||
$this->statisticsUnsubscribesRepository = $statisticsUnsubscribesRepository;
|
||||
$this->customFieldsRepository = $customFieldsRepository;
|
||||
$this->subscriberCustomFieldRepository = $subscriberCustomFieldRepository;
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
public function buildForListing(array $subscribers): array {
|
||||
$this->prefetchRelations($subscribers);
|
||||
$data = [];
|
||||
foreach ($subscribers as $subscriber) {
|
||||
$data[] = $this->buildListingItem($subscriber);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function buildListingItem(SubscriberEntity $subscriber): array {
|
||||
return [
|
||||
'id' => (string)$subscriber->getId(), // (string) for BC
|
||||
'email' => $subscriber->getEmail(),
|
||||
'first_name' => $subscriber->getFirstName(),
|
||||
'last_name' => $subscriber->getLastName(),
|
||||
'subscriptions' => $this->buildSubscriptions($subscriber),
|
||||
'status' => $subscriber->getStatus(),
|
||||
'count_confirmations' => $subscriber->getConfirmationsCount(),
|
||||
'wp_user_id' => $subscriber->getWpUserId(),
|
||||
'is_woocommerce_user' => $subscriber->getIsWoocommerceUser(),
|
||||
'created_at' => ($createdAt = $subscriber->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
|
||||
'last_subscribed_at' => ($lastSubscribedAt = $subscriber->getLastSubscribedAt()) ? $lastSubscribedAt->format(self::DATE_FORMAT) : null,
|
||||
'engagement_score' => $subscriber->getEngagementScore(),
|
||||
'tags' => $this->buildTags($subscriber),
|
||||
];
|
||||
}
|
||||
|
||||
public function build(SubscriberEntity $subscriberEntity): array {
|
||||
$data = [
|
||||
'id' => (string)$subscriberEntity->getId(),
|
||||
'wp_user_id' => $subscriberEntity->getWpUserId(),
|
||||
'is_woocommerce_user' => $subscriberEntity->getIsWoocommerceUser(),
|
||||
'subscriptions' => $this->buildSubscriptions($subscriberEntity),
|
||||
'unsubscribes' => $this->buildUnsubscribes($subscriberEntity),
|
||||
'status' => $subscriberEntity->getStatus(),
|
||||
'last_name' => $subscriberEntity->getLastName(),
|
||||
'first_name' => $subscriberEntity->getFirstName(),
|
||||
'email' => $subscriberEntity->getEmail(),
|
||||
'created_at' => ($createdAt = $subscriberEntity->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
|
||||
'updated_at' => ($updatedAt = $subscriberEntity->getUpdatedAt()) ? $updatedAt->format(self::DATE_FORMAT) : null,
|
||||
'deleted_at' => ($deletedAt = $subscriberEntity->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
|
||||
'subscribed_ip' => $subscriberEntity->getSubscribedIp(),
|
||||
'confirmed_ip' => $subscriberEntity->getConfirmedIp(),
|
||||
'confirmed_at' => ($confirmedAt = $subscriberEntity->getConfirmedAt()) ? $confirmedAt->format(self::DATE_FORMAT) : null,
|
||||
'last_subscribed_at' => ($lastSubscribedAt = $subscriberEntity->getLastSubscribedAt()) ? $lastSubscribedAt->format(self::DATE_FORMAT) : null,
|
||||
'unconfirmed_data' => $subscriberEntity->getUnconfirmedData(),
|
||||
'source' => $subscriberEntity->getSource(),
|
||||
'count_confirmations' => $subscriberEntity->getConfirmationsCount(),
|
||||
'unsubscribe_token' => $subscriberEntity->getUnsubscribeToken(),
|
||||
'link_token' => $subscriberEntity->getLinkToken(),
|
||||
'tags' => $this->buildTags($subscriberEntity),
|
||||
];
|
||||
|
||||
return $this->buildCustomFields($subscriberEntity, $data);
|
||||
}
|
||||
|
||||
private function buildSubscriptions(SubscriberEntity $subscriberEntity): array {
|
||||
$result = [];
|
||||
foreach ($subscriberEntity->getSubscriberSegments() as $subscriberSegment) {
|
||||
$segment = $subscriberSegment->getSegment();
|
||||
if ($segment instanceof SegmentEntity) {
|
||||
$result[] = [
|
||||
'id' => $subscriberSegment->getId(),
|
||||
'subscriber_id' => (string)$subscriberEntity->getId(),
|
||||
'created_at' => ($createdAt = $subscriberSegment->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
|
||||
'segment_id' => (string)$segment->getId(),
|
||||
'status' => $subscriberSegment->getStatus(),
|
||||
'updated_at' => $subscriberSegment->getUpdatedAt()->format(self::DATE_FORMAT),
|
||||
];
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function buildUnsubscribes(SubscriberEntity $subscriberEntity): array {
|
||||
$unsubscribes = $this->statisticsUnsubscribesRepository->findBy([
|
||||
'subscriber' => $subscriberEntity,
|
||||
], [
|
||||
'createdAt' => 'desc',
|
||||
]);
|
||||
$result = [];
|
||||
foreach ($unsubscribes as $unsubscribe) {
|
||||
$mapped = [
|
||||
'source' => $unsubscribe->getSource(),
|
||||
'meta' => $unsubscribe->getMeta(),
|
||||
'createdAt' => $unsubscribe->getCreatedAt(),
|
||||
];
|
||||
$newsletter = $unsubscribe->getNewsletter();
|
||||
if ($newsletter instanceof NewsletterEntity) {
|
||||
$mapped['newsletterId'] = $newsletter->getId();
|
||||
$mapped['newsletterSubject'] = $newsletter->getSubject();
|
||||
}
|
||||
$result[] = $mapped;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function buildCustomFields(SubscriberEntity $subscriberEntity, array $data): array {
|
||||
$customFields = $this->customFieldsRepository->findAll();
|
||||
|
||||
foreach ($customFields as $customField) {
|
||||
$subscriberCustomField = $this->subscriberCustomFieldRepository->findOneBy(
|
||||
['subscriber' => $subscriberEntity, 'customField' => $customField]
|
||||
);
|
||||
if ($subscriberCustomField instanceof SubscriberCustomFieldEntity) {
|
||||
$data['cf_' . $customField->getId()] = $subscriberCustomField->getValue();
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function buildTags(SubscriberEntity $subscriber): array {
|
||||
$result = [];
|
||||
foreach ($subscriber->getSubscriberTags() as $subscriberTag) {
|
||||
$tag = $subscriberTag->getTag();
|
||||
if (!$tag) {
|
||||
continue;
|
||||
}
|
||||
$result[] = [
|
||||
'id' => $subscriberTag->getId(),
|
||||
'subscriber_id' => (string)$subscriber->getId(),
|
||||
'tag_id' => (string)$tag->getId(),
|
||||
'created_at' => ($createdAt = $subscriberTag->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
|
||||
'updated_at' => $subscriberTag->getUpdatedAt()->format(self::DATE_FORMAT),
|
||||
'name' => $tag->getName(),
|
||||
];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SubscriberEntity[] $subscribers
|
||||
*/
|
||||
private function prefetchRelations(array $subscribers): void {
|
||||
// Prefetch subscriptions
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->select('PARTIAL s.{id}, ssg, sg')
|
||||
->from(SubscriberEntity::class, 's')
|
||||
->leftJoin('s.subscriberSegments', 'ssg')
|
||||
->leftJoin('ssg.segment', 'sg')
|
||||
->where('s.id IN (:subscribers)')
|
||||
->setParameter('subscribers', $subscribers)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
// Prefetch tags
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->select('PARTIAL s.{id}, st, t')
|
||||
->from(SubscriberEntity::class, 's')
|
||||
->leftJoin('s.subscriberTags', 'st')
|
||||
->leftJoin('st.tag', 't')
|
||||
->where('s.id IN (:subscribers)')
|
||||
->setParameter('subscribers', $subscribers)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
class SuccessResponse extends Response {
|
||||
public $data;
|
||||
|
||||
public function __construct(
|
||||
$data = [],
|
||||
$meta = [],
|
||||
$status = self::STATUS_OK
|
||||
) {
|
||||
parent::__construct($status, $meta);
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function getData() {
|
||||
if ($this->data === null) return [];
|
||||
|
||||
return [
|
||||
'data' => $this->data,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Analytics\Reporter;
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\Config\AccessControl;
|
||||
|
||||
class Analytics extends APIEndpoint {
|
||||
|
||||
/** @var Reporter */
|
||||
private $reporter;
|
||||
|
||||
public $permissions = [
|
||||
'global' => AccessControl::NO_ACCESS_RESTRICTION,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
Reporter $reporter
|
||||
) {
|
||||
$this->reporter = $reporter;
|
||||
}
|
||||
|
||||
public function getTrackingData() {
|
||||
return $this->successResponse($this->reporter->getTrackingData());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\SuccessResponse;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Newsletter\AutomatedLatestContent as ALC;
|
||||
use MailPoet\Newsletter\BlockPostQuery;
|
||||
use MailPoet\Util\APIPermissionHelper;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoet\WP\Posts as WPPosts;
|
||||
|
||||
class AutomatedLatestContent extends APIEndpoint {
|
||||
/** @var ALC */
|
||||
public $ALC;
|
||||
|
||||
/*** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/*** @var APIPermissionHelper */
|
||||
private $permissionHelper;
|
||||
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_EMAILS,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
ALC $alc,
|
||||
APIPermissionHelper $permissionHelper,
|
||||
WPFunctions $wp
|
||||
) {
|
||||
$this->ALC = $alc;
|
||||
$this->wp = $wp;
|
||||
$this->permissionHelper = $permissionHelper;
|
||||
}
|
||||
|
||||
public function getPostTypes() {
|
||||
$postTypes = array_map(function($postType) {
|
||||
return [
|
||||
'name' => $postType->name,
|
||||
'label' => $postType->label,
|
||||
];
|
||||
}, WPPosts::getTypes([], 'objects'));
|
||||
return $this->successResponse(
|
||||
array_filter($postTypes)
|
||||
);
|
||||
}
|
||||
|
||||
public function getTaxonomies($data = []) {
|
||||
$postType = (isset($data['postType'])) ? $data['postType'] : 'post';
|
||||
$allTaxonomies = WPFunctions::get()->getObjectTaxonomies($postType, 'objects');
|
||||
$taxonomiesWithLabel = array_filter($allTaxonomies, function($taxonomy) {
|
||||
return $taxonomy->label;
|
||||
});
|
||||
return $this->successResponse($taxonomiesWithLabel);
|
||||
}
|
||||
|
||||
public function getTerms($data = []) {
|
||||
$taxonomies = (isset($data['taxonomies'])) ? $data['taxonomies'] : [];
|
||||
$search = (isset($data['search'])) ? $data['search'] : '';
|
||||
$limit = (isset($data['limit'])) ? (int)$data['limit'] : 100;
|
||||
$page = (isset($data['page'])) ? (int)$data['page'] : 1;
|
||||
$args = [
|
||||
'taxonomy' => $taxonomies,
|
||||
'hide_empty' => false,
|
||||
'search' => $search,
|
||||
'number' => $limit,
|
||||
'offset' => $limit * ($page - 1),
|
||||
'orderby' => 'name',
|
||||
'order' => 'ASC',
|
||||
];
|
||||
|
||||
$args = (array)$this->wp->applyFilters('mailpoet_search_terms_args', $args);
|
||||
$terms = WPFunctions::get()->getTerms($args);
|
||||
|
||||
return $this->successResponse(array_values($terms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches posts for Posts static block
|
||||
*/
|
||||
public function getPosts(array $data = []): SuccessResponse {
|
||||
return $this->successResponse(
|
||||
$this->getPermittedPosts($this->ALC->getPosts(new BlockPostQuery(['args' => $data, 'dynamic' => false])))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches products for Abandoned Cart Content dynamic block
|
||||
*/
|
||||
public function getTransformedPosts(array $data = []): SuccessResponse {
|
||||
$posts = $this->getPermittedPosts($this->ALC->getPosts(new BlockPostQuery([
|
||||
'args' => $data,
|
||||
// If the request is for Posts or Products block then we are fetching data for a static block
|
||||
'dynamic' => !(isset($data['type']) && in_array($data['type'], ["posts", "products"])),
|
||||
])));
|
||||
return $this->successResponse(
|
||||
$this->ALC->transformPosts($data, $posts)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches different post types for ALC dynamic block
|
||||
*/
|
||||
public function getBulkTransformedPosts(array $data = []): SuccessResponse {
|
||||
$usedPosts = [];
|
||||
$renderedPosts = [];
|
||||
|
||||
foreach ($data['blocks'] as $block) {
|
||||
$query = new BlockPostQuery(['args' => $block, 'postsToExclude' => $usedPosts]);
|
||||
$posts = $this->getPermittedPosts($this->ALC->getPosts($query));
|
||||
$renderedPosts[] = $this->ALC->transformPosts($block, $posts);
|
||||
|
||||
foreach ($posts as $post) {
|
||||
$usedPosts[] = $post->ID;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->successResponse($renderedPosts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_Post[] $posts
|
||||
* @return \WP_Post[]
|
||||
*/
|
||||
private function getPermittedPosts($posts) {
|
||||
return array_filter($posts, function ($post) {
|
||||
return $this->permissionHelper->checkReadPermission($post);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\AutomaticEmails\AutomaticEmails as AutomaticEmailsController;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class AutomaticEmails extends APIEndpoint {
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_SEGMENTS,
|
||||
];
|
||||
|
||||
/** @var AutomaticEmailsController */
|
||||
private $automaticEmails;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
public function __construct(
|
||||
AutomaticEmailsController $automaticEmails,
|
||||
WPFunctions $wp
|
||||
) {
|
||||
$this->automaticEmails = $automaticEmails;
|
||||
$this->wp = $wp;
|
||||
}
|
||||
|
||||
public function getEventOptions($data) {
|
||||
$query = (!empty($data['query'])) ? $data['query'] : null;
|
||||
$filter = (!empty($data['filter'])) ? $data['filter'] : null;
|
||||
$emailSlug = (!empty($data['email_slug'])) ? $data['email_slug'] : null;
|
||||
$eventSlug = (!empty($data['event_slug'])) ? $data['event_slug'] : null;
|
||||
|
||||
if (!$query || !$filter || !$emailSlug || !$eventSlug) {
|
||||
return $this->errorResponse(
|
||||
[
|
||||
APIError::BAD_REQUEST => __('Improperly formatted request.', 'mailpoet'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$event = $this->automaticEmails->getAutomaticEmailEventBySlug($emailSlug, $eventSlug);
|
||||
$eventFilter = (!empty($event['options']['remoteQueryFilter'])) ? $event['options']['remoteQueryFilter'] : null;
|
||||
|
||||
return ($eventFilter === $filter && WPFunctions::get()->hasFilter($eventFilter)) ?
|
||||
$this->successResponse($this->wp->applyFilters($eventFilter, $query)) :
|
||||
$this->errorResponse(
|
||||
[
|
||||
APIError::BAD_REQUEST => __('Automatic email event filter does not exist.', 'mailpoet'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function getEventShortcodes($data) {
|
||||
$emailSlug = (!empty($data['email_slug'])) ? $data['email_slug'] : null;
|
||||
$eventSlug = (!empty($data['event_slug'])) ? $data['event_slug'] : null;
|
||||
|
||||
if (!$emailSlug || !$eventSlug) {
|
||||
return $this->errorResponse(
|
||||
[
|
||||
APIError::BAD_REQUEST => __('Improperly formatted request.', 'mailpoet'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$automaticEmail = $this->automaticEmails->getAutomaticEmailBySlug($emailSlug);
|
||||
$event = $this->automaticEmails->getAutomaticEmailEventBySlug($emailSlug, $eventSlug);
|
||||
|
||||
if (!$event) {
|
||||
return $this->errorResponse(
|
||||
[
|
||||
APIError::BAD_REQUEST => __('Automatic email event does not exist.', 'mailpoet'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$eventShortcodes = (!empty($event['shortcodes']) && is_array($event['shortcodes'])) ?
|
||||
[
|
||||
$automaticEmail['title'] => $event['shortcodes'],
|
||||
] :
|
||||
null;
|
||||
|
||||
return $this->successResponse($eventShortcodes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\Captcha\CaptchaSession;
|
||||
use MailPoet\Captcha\CaptchaUrlFactory;
|
||||
use MailPoet\Config\AccessControl;
|
||||
|
||||
class Captcha extends APIEndpoint {
|
||||
private CaptchaSession $captchaSession;
|
||||
private CaptchaUrlFactory $urlFactory;
|
||||
|
||||
public $permissions = [
|
||||
'global' => AccessControl::NO_ACCESS_RESTRICTION,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
CaptchaSession $captchaSession,
|
||||
CaptchaUrlFactory $urlFactory
|
||||
) {
|
||||
$this->captchaSession = $captchaSession;
|
||||
$this->urlFactory = $urlFactory;
|
||||
}
|
||||
|
||||
public function render(array $data = []) {
|
||||
$sessionId = $this->captchaSession->generateSessionId();
|
||||
$data = array_merge($data, ['captcha_session_id' => $sessionId]);
|
||||
$captchaUrl = $this->urlFactory->getCaptchaUrl($data);
|
||||
|
||||
return $this->redirectResponse($captchaUrl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\SuccessResponse;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\WooCommerce\Helper;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class Coupons extends APIEndpoint {
|
||||
public const DEFAULT_PAGE_SIZE = 100;
|
||||
|
||||
/** @var Helper */
|
||||
public $helper;
|
||||
|
||||
/*** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_EMAILS,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
WPFunctions $wp,
|
||||
Helper $helper
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->helper = $helper;
|
||||
}
|
||||
|
||||
public function getCoupons(array $data = []): SuccessResponse {
|
||||
$pageSize = $data['page_size'] ?? self::DEFAULT_PAGE_SIZE;
|
||||
$pageNumber = $data['page_number'] ?? 1;
|
||||
$discountType = $data['discount_type'] ?? null;
|
||||
$search = $data['search'] ?? null;
|
||||
$includeCouponIds = $data['include_coupon_ids'] ?? [];
|
||||
return $this->successResponse(
|
||||
$this->formatCoupons($this->helper->getCouponList(
|
||||
(int)$pageSize,
|
||||
(int)$pageNumber,
|
||||
$discountType,
|
||||
$search,
|
||||
$includeCouponIds
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $couponPosts
|
||||
* @return array
|
||||
*/
|
||||
private function formatCoupons(array $couponPosts): array {
|
||||
return array_map(function (\WP_Post $post): array {
|
||||
$discountType = $this->wp->getPostMeta($post->ID, 'discount_type', true);
|
||||
return [
|
||||
'id' => $post->ID,
|
||||
'text' => $post->post_title, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
'excerpt' => $post->post_excerpt, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
'discountType' => $discountType,
|
||||
];
|
||||
}, $couponPosts);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\API\JSON\Response;
|
||||
use MailPoet\API\JSON\ResponseBuilders\CustomFieldsResponseBuilder;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\CustomFields\CustomFieldsRepository;
|
||||
use MailPoet\Entities\CustomFieldEntity;
|
||||
use MailPoet\Form\ApiDataSanitizer;
|
||||
|
||||
class CustomFields extends APIEndpoint {
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_FORMS,
|
||||
];
|
||||
|
||||
/** @var CustomFieldsRepository */
|
||||
private $customFieldsRepository;
|
||||
|
||||
/** @var CustomFieldsResponseBuilder */
|
||||
private $customFieldsResponseBuilder;
|
||||
|
||||
/** @var ApiDataSanitizer */
|
||||
private $dataSanitizer;
|
||||
|
||||
public function __construct(
|
||||
CustomFieldsRepository $customFieldsRepository,
|
||||
CustomFieldsResponseBuilder $customFieldsResponseBuilder,
|
||||
ApiDataSanitizer $dataSanitizer
|
||||
) {
|
||||
$this->customFieldsRepository = $customFieldsRepository;
|
||||
$this->customFieldsResponseBuilder = $customFieldsResponseBuilder;
|
||||
$this->dataSanitizer = $dataSanitizer;
|
||||
}
|
||||
|
||||
public function getAll() {
|
||||
$collection = $this->customFieldsRepository->findBy([], ['createdAt' => 'asc']);
|
||||
return $this->successResponse($this->customFieldsResponseBuilder->buildBatch($collection));
|
||||
}
|
||||
|
||||
public function delete($data = []) {
|
||||
$id = (isset($data['id']) ? (int)$data['id'] : null);
|
||||
$customField = $this->customFieldsRepository->findOneById($id);
|
||||
if ($customField instanceof CustomFieldEntity) {
|
||||
$this->customFieldsRepository->remove($customField);
|
||||
$this->customFieldsRepository->flush();
|
||||
|
||||
return $this->successResponse($this->customFieldsResponseBuilder->build($customField));
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This custom field does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function save($data = []) {
|
||||
try {
|
||||
$data = $this->dataSanitizer->sanitizeBlock($data);
|
||||
$customField = $this->customFieldsRepository->createOrUpdate($data);
|
||||
$customField = $this->customFieldsRepository->findOneById($customField->getId());
|
||||
if(!$customField instanceof CustomFieldEntity) return $this->errorResponse();
|
||||
return $this->successResponse($this->customFieldsResponseBuilder->build($customField));
|
||||
} catch (\Exception $e) {
|
||||
return $this->errorResponse($errors = [], $meta = [], $status = Response::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
public function get($data = []) {
|
||||
$id = (isset($data['id']) ? (int)$data['id'] : null);
|
||||
$customField = $this->customFieldsRepository->findOneById($id);
|
||||
if ($customField instanceof CustomFieldEntity) {
|
||||
return $this->successResponse($this->customFieldsResponseBuilder->build($customField));
|
||||
}
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This custom field does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error;
|
||||
use MailPoet\API\JSON\Response;
|
||||
use MailPoet\API\JSON\ResponseBuilders\DynamicSegmentsResponseBuilder;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\ConflictException;
|
||||
use MailPoet\Doctrine\Validator\ValidationException;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Listing\Handler;
|
||||
use MailPoet\Newsletter\Segment\NewsletterSegmentRepository;
|
||||
use MailPoet\Segments\DynamicSegments\DynamicSegmentsListingRepository;
|
||||
use MailPoet\Segments\DynamicSegments\Exceptions\InvalidFilterException;
|
||||
use MailPoet\Segments\DynamicSegments\FilterDataMapper;
|
||||
use MailPoet\Segments\DynamicSegments\SegmentSaveController;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Segments\SegmentSubscribersRepository;
|
||||
use MailPoet\UnexpectedValueException;
|
||||
use Throwable;
|
||||
|
||||
class DynamicSegments extends APIEndpoint {
|
||||
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_SEGMENTS,
|
||||
];
|
||||
|
||||
/** @var Handler */
|
||||
private $listingHandler;
|
||||
|
||||
/** @var DynamicSegmentsListingRepository */
|
||||
private $dynamicSegmentsListingRepository;
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentsRepository;
|
||||
|
||||
/** @var DynamicSegmentsResponseBuilder */
|
||||
private $segmentsResponseBuilder;
|
||||
|
||||
/** @var SegmentSaveController */
|
||||
private $saveController;
|
||||
|
||||
/** @var SegmentSubscribersRepository */
|
||||
private $segmentSubscribersRepository;
|
||||
|
||||
/** @var FilterDataMapper */
|
||||
private $filterDataMapper;
|
||||
|
||||
/** @var NewsletterSegmentRepository */
|
||||
private $newsletterSegmentRepository;
|
||||
|
||||
public function __construct(
|
||||
Handler $handler,
|
||||
DynamicSegmentsListingRepository $dynamicSegmentsListingRepository,
|
||||
DynamicSegmentsResponseBuilder $segmentsResponseBuilder,
|
||||
SegmentsRepository $segmentsRepository,
|
||||
SegmentSubscribersRepository $segmentSubscribersRepository,
|
||||
FilterDataMapper $filterDataMapper,
|
||||
SegmentSaveController $saveController,
|
||||
NewsletterSegmentRepository $newsletterSegmentRepository
|
||||
) {
|
||||
$this->listingHandler = $handler;
|
||||
$this->dynamicSegmentsListingRepository = $dynamicSegmentsListingRepository;
|
||||
$this->segmentsResponseBuilder = $segmentsResponseBuilder;
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
$this->saveController = $saveController;
|
||||
$this->segmentSubscribersRepository = $segmentSubscribersRepository;
|
||||
$this->filterDataMapper = $filterDataMapper;
|
||||
$this->newsletterSegmentRepository = $newsletterSegmentRepository;
|
||||
}
|
||||
|
||||
public function get($data = []) {
|
||||
if (isset($data['id'])) {
|
||||
$id = (int)$data['id'];
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
Error::BAD_REQUEST => __('Missing mandatory argument `id`.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$segment = $this->segmentsRepository->findOneById($id);
|
||||
if (!$segment instanceof SegmentEntity) {
|
||||
return $this->errorResponse([
|
||||
Error::NOT_FOUND => __('This segment does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->successResponse($this->segmentsResponseBuilder->build($segment));
|
||||
}
|
||||
|
||||
public function getCount($data = []) {
|
||||
try {
|
||||
$filterData = $this->filterDataMapper->map($data);
|
||||
$count = $this->segmentSubscribersRepository->getDynamicSubscribersCount($filterData);
|
||||
return $this->successResponse([
|
||||
'count' => $count,
|
||||
]);
|
||||
} catch (InvalidFilterException $e) {
|
||||
return $this->errorResponse([
|
||||
Error::BAD_REQUEST => $this->getErrorString($e),
|
||||
], [], Response::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
public function save($data) {
|
||||
try {
|
||||
$data['name'] = isset($data['name']) ? sanitize_text_field($data['name']) : '';
|
||||
$data['description'] = isset($data['description']) ? sanitize_textarea_field($data['description']) : '';
|
||||
$segment = $this->saveController->save($data);
|
||||
return $this->successResponse($this->segmentsResponseBuilder->build($segment));
|
||||
} catch (InvalidFilterException $e) {
|
||||
return $this->errorResponse([
|
||||
Error::BAD_REQUEST => $this->getErrorString($e),
|
||||
], [], Response::STATUS_BAD_REQUEST);
|
||||
} catch (ConflictException $e) {
|
||||
return $this->badRequest([
|
||||
Error::BAD_REQUEST => __('Another record already exists. Please specify a different "name".', 'mailpoet'),
|
||||
]);
|
||||
} catch (ValidationException $exception) {
|
||||
return $this->badRequest([
|
||||
Error::BAD_REQUEST => __('Please specify a name.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function duplicate($data = []) {
|
||||
$segment = $this->getSegment($data);
|
||||
|
||||
if ($segment instanceof SegmentEntity) {
|
||||
try {
|
||||
$duplicate = $this->saveController->duplicate($segment);
|
||||
} catch (Throwable $e) {
|
||||
return $this->errorResponse([
|
||||
// translators: %s is the error message
|
||||
Error::UNKNOWN => sprintf(__('Duplicating of segment failed: %s', 'mailpoet'), $e->getMessage()),
|
||||
], [], Response::STATUS_UNKNOWN);
|
||||
}
|
||||
return $this->successResponse(
|
||||
$this->segmentsResponseBuilder->build($duplicate),
|
||||
['count' => 1]
|
||||
);
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
Error::NOT_FOUND => __('This segment does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function getErrorString(InvalidFilterException $e) {
|
||||
switch ($e->getCode()) {
|
||||
case InvalidFilterException::MISSING_TYPE:
|
||||
return __('The segment type is missing.', 'mailpoet');
|
||||
case InvalidFilterException::INVALID_TYPE:
|
||||
return __('The segment type is unknown.', 'mailpoet');
|
||||
case InvalidFilterException::MISSING_ROLE:
|
||||
return __('Please select a user role.', 'mailpoet');
|
||||
case InvalidFilterException::MISSING_ACTION:
|
||||
case InvalidFilterException::INVALID_EMAIL_ACTION:
|
||||
return __('Please select an email action.', 'mailpoet');
|
||||
case InvalidFilterException::MISSING_NEWSLETTER_ID:
|
||||
return __('Please select an email.', 'mailpoet');
|
||||
case InvalidFilterException::MISSING_PRODUCT_ID:
|
||||
return __('Please select a product.', 'mailpoet');
|
||||
case InvalidFilterException::MISSING_COUNTRY:
|
||||
return __('Please select a country.', 'mailpoet');
|
||||
case InvalidFilterException::MISSING_CATEGORY_ID:
|
||||
return __('Please select a category.', 'mailpoet');
|
||||
case InvalidFilterException::MISSING_VALUE:
|
||||
return __('Please fill all required values.', 'mailpoet');
|
||||
case InvalidFilterException::MISSING_NUMBER_OF_ORDERS_FIELDS:
|
||||
return __('Please select a type for the comparison, a number of orders and a number of days.', 'mailpoet');
|
||||
case InvalidFilterException::MISSING_TOTAL_SPENT_FIELDS:
|
||||
case InvalidFilterException::MISSING_SINGLE_ORDER_VALUE_FIELDS:
|
||||
case InvalidFilterException::MISSING_AVERAGE_SPENT_FIELDS:
|
||||
return __('Please select a type for the comparison, an amount and a number of days.', 'mailpoet');
|
||||
case InvalidFilterException::MISSING_FILTER:
|
||||
return __('Please add at least one condition for filtering.', 'mailpoet');
|
||||
case InvalidFilterException::MISSING_OPERATOR:
|
||||
return __('Please select a type for the comparison.', 'mailpoet');
|
||||
default:
|
||||
return __('An error occurred while saving data.', 'mailpoet');
|
||||
}
|
||||
}
|
||||
|
||||
public function trash($data = []) {
|
||||
if (!isset($data['id'])) {
|
||||
return $this->errorResponse([
|
||||
Error::BAD_REQUEST => __('Missing mandatory argument `id`.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$segment = $this->getSegment($data);
|
||||
if ($segment === null) {
|
||||
return $this->errorResponse([
|
||||
Error::NOT_FOUND => __('This segment does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$activelyUsedErrors = $this->getErrorMessagesForSegmentsUsedInActiveNewsletters([$segment->getId()]);
|
||||
if (count($activelyUsedErrors) > 0) {
|
||||
return $this->badRequest($activelyUsedErrors);
|
||||
}
|
||||
|
||||
$this->segmentsRepository->bulkTrash([$segment->getId()], SegmentEntity::TYPE_DYNAMIC);
|
||||
return $this->successResponse(
|
||||
$this->segmentsResponseBuilder->build($segment),
|
||||
['count' => 1]
|
||||
);
|
||||
}
|
||||
|
||||
public function getErrorMessagesForSegmentsUsedInActiveNewsletters(array $segmentIds): array {
|
||||
$errors = [];
|
||||
$activelyUsedNewslettersSubjects = $this->newsletterSegmentRepository->getSubjectsOfActivelyUsedEmailsForSegments($segmentIds);
|
||||
foreach ($segmentIds as $segmentId) {
|
||||
if (isset($activelyUsedNewslettersSubjects[$segmentId])) {
|
||||
$segment = $this->getSegment(['id' => $segmentId]);
|
||||
if ($segment) {
|
||||
$errors[] = sprintf(
|
||||
// translators: %1$s is the name of the segment, %2$s is a comma-seperated list of emails for which the segment is used.
|
||||
_x('Segment \'%1$s\' cannot be deleted because it’s used for \'%2$s\' email', 'Alert shown when trying to delete segment, which is assigned to any automatic emails.', 'mailpoet'),
|
||||
$segment->getName(),
|
||||
join("', '", $activelyUsedNewslettersSubjects[$segmentId])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public function restore($data = []) {
|
||||
if (!isset($data['id'])) {
|
||||
return $this->errorResponse([
|
||||
Error::BAD_REQUEST => __('Missing mandatory argument `id`.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$segment = $this->getSegment($data);
|
||||
if ($segment === null) {
|
||||
return $this->errorResponse([
|
||||
Error::NOT_FOUND => __('This segment does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->segmentsRepository->bulkRestore([$segment->getId()], SegmentEntity::TYPE_DYNAMIC);
|
||||
return $this->successResponse(
|
||||
$this->segmentsResponseBuilder->build($segment),
|
||||
['count' => 1]
|
||||
);
|
||||
}
|
||||
|
||||
public function delete($data = []) {
|
||||
if (!isset($data['id'])) {
|
||||
return $this->errorResponse([
|
||||
Error::BAD_REQUEST => __('Missing mandatory argument `id`.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$segment = $this->getSegment($data);
|
||||
if ($segment === null) {
|
||||
return $this->errorResponse([
|
||||
Error::NOT_FOUND => __('This segment does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->segmentsRepository->bulkDelete([$segment->getId()], SegmentEntity::TYPE_DYNAMIC);
|
||||
return $this->successResponse(null, ['count' => 1]);
|
||||
}
|
||||
|
||||
public function listing($data = []) {
|
||||
$data['params'] = $data['params'] ?? ['segments']; // Dummy param to apply constraints properly
|
||||
$definition = $this->listingHandler->getListingDefinition($data);
|
||||
$items = $this->dynamicSegmentsListingRepository->getData($definition);
|
||||
$count = $this->dynamicSegmentsListingRepository->getCount($definition);
|
||||
$filters = $this->dynamicSegmentsListingRepository->getFilters($definition);
|
||||
$groups = $this->dynamicSegmentsListingRepository->getGroups($definition);
|
||||
$segments = $this->segmentsResponseBuilder->buildForListing($items);
|
||||
|
||||
return $this->successResponse($segments, [
|
||||
'count' => $count,
|
||||
'filters' => $filters,
|
||||
'groups' => $groups,
|
||||
]);
|
||||
}
|
||||
|
||||
public function bulkAction($data = []) {
|
||||
$definition = $this->listingHandler->getListingDefinition($data['listing']);
|
||||
$ids = $this->dynamicSegmentsListingRepository->getActionableIds($definition);
|
||||
$meta = [];
|
||||
if ($data['action'] === 'trash') {
|
||||
$errors = $this->getErrorMessagesForSegmentsUsedInActiveNewsletters($ids);
|
||||
if (count($errors) > 0) {
|
||||
$meta['errors'] = $errors;
|
||||
}
|
||||
$meta['count'] = $this->segmentsRepository->bulkTrash($ids, SegmentEntity::TYPE_DYNAMIC);
|
||||
} elseif ($data['action'] === 'restore') {
|
||||
$meta['count'] = $this->segmentsRepository->bulkRestore($ids, SegmentEntity::TYPE_DYNAMIC);
|
||||
} elseif ($data['action'] === 'delete') {
|
||||
$meta['count'] = $this->segmentsRepository->bulkDelete($ids, SegmentEntity::TYPE_DYNAMIC);
|
||||
} else {
|
||||
throw UnexpectedValueException::create()
|
||||
->withErrors([Error::BAD_REQUEST => "Invalid bulk action '{$data['action']}' provided."]);
|
||||
}
|
||||
return $this->successResponse(null, $meta);
|
||||
}
|
||||
|
||||
private function getSegment(array $data): ?SegmentEntity {
|
||||
return isset($data['id'])
|
||||
? $this->segmentsRepository->findOneById((int)$data['id'])
|
||||
: null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Features\FeatureFlagsController;
|
||||
use MailPoet\Features\FeaturesController;
|
||||
|
||||
class FeatureFlags extends APIEndpoint {
|
||||
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_FEATURES,
|
||||
];
|
||||
|
||||
/** @var FeaturesController */
|
||||
private $featuresController;
|
||||
|
||||
/** @var FeatureFlagsController */
|
||||
private $featureFlagsController;
|
||||
|
||||
public function __construct(
|
||||
FeaturesController $featuresController,
|
||||
FeatureFlagsController $featureFlags
|
||||
) {
|
||||
$this->featuresController = $featuresController;
|
||||
$this->featureFlagsController = $featureFlags;
|
||||
}
|
||||
|
||||
public function getAll() {
|
||||
$featureFlags = $this->featureFlagsController->getAll();
|
||||
return $this->successResponse($featureFlags);
|
||||
}
|
||||
|
||||
public function set(array $flags) {
|
||||
foreach ($flags as $name => $value) {
|
||||
if (!$this->featuresController->exists($name)) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => "Feature '$name' does not exist'",
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($flags as $name => $value) {
|
||||
$this->featureFlagsController->set($name, (bool)$value);
|
||||
}
|
||||
return $this->successResponse([]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use Exception;
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\API\JSON\Response;
|
||||
use MailPoet\API\JSON\ResponseBuilders\FormsResponseBuilder;
|
||||
use MailPoet\API\JSON\SuccessResponse;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Entities\FormEntity;
|
||||
use MailPoet\Form\ApiDataSanitizer;
|
||||
use MailPoet\Form\DisplayFormInWPContent;
|
||||
use MailPoet\Form\FormSaveController;
|
||||
use MailPoet\Form\FormsRepository;
|
||||
use MailPoet\Form\Listing\FormListingRepository;
|
||||
use MailPoet\Form\PreviewPage;
|
||||
use MailPoet\Form\Templates\TemplateRepository;
|
||||
use MailPoet\Listing;
|
||||
use MailPoet\Settings\UserFlagsController;
|
||||
use MailPoet\Tags\TagRepository;
|
||||
use MailPoet\UnexpectedValueException;
|
||||
use MailPoet\WP\Emoji;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class Forms extends APIEndpoint {
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_FORMS,
|
||||
];
|
||||
|
||||
/** @var Listing\Handler */
|
||||
private $listingHandler;
|
||||
|
||||
/** @var UserFlagsController */
|
||||
private $userFlags;
|
||||
|
||||
/** @var FormsResponseBuilder */
|
||||
private $formsResponseBuilder;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var FormsRepository */
|
||||
private $formsRepository;
|
||||
|
||||
/** @var TemplateRepository */
|
||||
private $templateRepository;
|
||||
|
||||
/** @var FormListingRepository */
|
||||
private $formListingRepository;
|
||||
|
||||
/** @var Emoji */
|
||||
private $emoji;
|
||||
|
||||
/** @var ApiDataSanitizer */
|
||||
private $dataSanitizer;
|
||||
|
||||
/** @var TagRepository */
|
||||
private $tagRepository;
|
||||
|
||||
/** @var FormSaveController */
|
||||
private $formSaveController;
|
||||
|
||||
public function __construct(
|
||||
Listing\Handler $listingHandler,
|
||||
UserFlagsController $userFlags,
|
||||
FormsRepository $formsRepository,
|
||||
TemplateRepository $templateRepository,
|
||||
FormListingRepository $formListingRepository,
|
||||
FormsResponseBuilder $formsResponseBuilder,
|
||||
WPFunctions $wp,
|
||||
Emoji $emoji,
|
||||
ApiDataSanitizer $dataSanitizer,
|
||||
TagRepository $tagRepository,
|
||||
FormSaveController $formSaveController
|
||||
) {
|
||||
$this->listingHandler = $listingHandler;
|
||||
$this->userFlags = $userFlags;
|
||||
$this->wp = $wp;
|
||||
$this->formsRepository = $formsRepository;
|
||||
$this->templateRepository = $templateRepository;
|
||||
$this->formListingRepository = $formListingRepository;
|
||||
$this->formsResponseBuilder = $formsResponseBuilder;
|
||||
$this->emoji = $emoji;
|
||||
$this->dataSanitizer = $dataSanitizer;
|
||||
$this->tagRepository = $tagRepository;
|
||||
$this->formSaveController = $formSaveController;
|
||||
}
|
||||
|
||||
public function get($data = []) {
|
||||
$id = (isset($data['id']) ? (int)$data['id'] : false);
|
||||
$form = $this->formsRepository->findOneById($id);
|
||||
if ($form instanceof FormEntity) {
|
||||
return $this->successResponse($this->formsResponseBuilder->build($form));
|
||||
}
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This form does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function setStatus($data = []) {
|
||||
$status = (isset($data['status']) ? $data['status'] : null);
|
||||
|
||||
if (!$status) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => __('You need to specify a status.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$id = (isset($data['id'])) ? (int)$data['id'] : false;
|
||||
$form = $this->formsRepository->findOneById($id);
|
||||
|
||||
if (!$form instanceof FormEntity) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This form does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
if (!in_array($status, [FormEntity::STATUS_ENABLED, FormEntity::STATUS_DISABLED])) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST =>
|
||||
sprintf(
|
||||
// translators: %1$s is a comma-seperated list of allowed values, %2$s the status the user specified.
|
||||
__('Invalid status. Allowed values are (%1$s), you specified %2$s', 'mailpoet'),
|
||||
join(', ', [FormEntity::STATUS_ENABLED, FormEntity::STATUS_DISABLED]),
|
||||
$status
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
$form->setStatus($status);
|
||||
$this->formsRepository->flush();
|
||||
|
||||
if ($status === FormEntity::STATUS_ENABLED) {
|
||||
$this->wp->deleteTransient(DisplayFormInWPContent::NO_FORM_TRANSIENT_KEY);
|
||||
}
|
||||
|
||||
$form = $this->formsRepository->findOneById($id);
|
||||
if (!$form instanceof FormEntity) return $this->errorResponse();
|
||||
return $this->successResponse(
|
||||
$form->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
public function listing($data = []) {
|
||||
$data['sort_order'] = $data['sort_order'] ?? 'desc';
|
||||
$data['sort_by'] = $data['sort_by'] ?? 'updatedAt';
|
||||
|
||||
$definition = $this->listingHandler->getListingDefinition($data);
|
||||
$items = $this->formListingRepository->getData($definition);
|
||||
$count = $this->formListingRepository->getCount($definition);
|
||||
$filters = $this->formListingRepository->getFilters($definition);
|
||||
$groups = $this->formListingRepository->getGroups($definition);
|
||||
|
||||
return $this->successResponse($this->formsResponseBuilder->buildForListing($items), [
|
||||
'count' => $count,
|
||||
'filters' => $filters,
|
||||
'groups' => $groups,
|
||||
]);
|
||||
}
|
||||
|
||||
public function previewEditor($data = []) {
|
||||
// We want to allow preview for unsaved forms
|
||||
$formId = $data['id'] ?? 0;
|
||||
$this->wp->setTransient(PreviewPage::PREVIEW_DATA_TRANSIENT_PREFIX . $formId, $data, PreviewPage::PREVIEW_DATA_EXPIRATION);
|
||||
return $this->successResponse();
|
||||
}
|
||||
|
||||
public function saveEditor($data = []) {
|
||||
$formId = (isset($data['id']) ? (int)$data['id'] : 0);
|
||||
$initialForm = $this->getFormTemplateData(TemplateRepository::INITIAL_FORM_TEMPLATE);
|
||||
$name = ($data['name'] ?? __('New form', 'mailpoet'));
|
||||
$body = ($data['body'] ?? $initialForm['body']);
|
||||
$body = $this->dataSanitizer->sanitizeBody($body);
|
||||
$settings = ($data['settings'] ?? $initialForm['settings']);
|
||||
$styles = ($data['styles'] ?? $initialForm['styles']);
|
||||
$status = ($data['status'] ?? FormEntity::STATUS_ENABLED);
|
||||
|
||||
// check if the form is used as a widget
|
||||
$isWidget = false;
|
||||
$widgets = $this->wp->getOption('widget_mailpoet_form');
|
||||
if (!empty($widgets)) {
|
||||
foreach ($widgets as $widget) {
|
||||
if (isset($widget['form']) && (int)$widget['form'] === $formId) {
|
||||
$isWidget = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset no form cache
|
||||
$this->wp->deleteTransient(DisplayFormInWPContent::NO_FORM_TRANSIENT_KEY);
|
||||
|
||||
// check if the user gets to pick his own lists
|
||||
// or if it's selected by the admin
|
||||
$formEntity = new FormEntity($name);
|
||||
$formEntity->setBody($body);
|
||||
$listSelection = $formEntity->getSegmentBlocksSegmentIds();
|
||||
|
||||
// check list selection
|
||||
if (count($listSelection)) {
|
||||
$settings['segments_selected_by'] = 'user';
|
||||
$settings['segments'] = $listSelection;
|
||||
} else {
|
||||
$settings['segments_selected_by'] = 'admin';
|
||||
}
|
||||
|
||||
// check tags and create them if they don't exist
|
||||
if (isset($settings['tags'])) {
|
||||
$this->createTagsIfDoNotExist($settings['tags']);
|
||||
}
|
||||
|
||||
// Check Custom HTML block permissions
|
||||
$customHtmlBlocks = $formEntity->getBlocksByTypes([FormEntity::HTML_BLOCK_TYPE]);
|
||||
if (count($customHtmlBlocks) && !$this->wp->currentUserCan('administrator')) {
|
||||
return $this->errorResponse([
|
||||
Error::FORBIDDEN => __('Only administrator can edit forms containing Custom HTML block.', 'mailpoet'),
|
||||
], [], Response::STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
if ($body !== null) {
|
||||
$body = $this->emoji->sanitizeEmojisInFormBody($body);
|
||||
}
|
||||
|
||||
$form = $this->getForm($data);
|
||||
|
||||
if (!$form instanceof FormEntity) {
|
||||
$form = new FormEntity($name);
|
||||
}
|
||||
$form->setName($name);
|
||||
$form->setBody($body);
|
||||
$form->setSettings($settings);
|
||||
$form->setStyles($styles);
|
||||
$form->setStatus($status);
|
||||
$this->formsRepository->persist($form);
|
||||
|
||||
try {
|
||||
$this->formsRepository->flush();
|
||||
} catch (\Exception $e) {
|
||||
return $this->badRequest();
|
||||
}
|
||||
|
||||
if (isset($data['editor_version']) && $data['editor_version'] === "2") {
|
||||
$this->userFlags->set('display_new_form_editor_nps_survey', true);
|
||||
}
|
||||
|
||||
$form = $this->getForm(['id' => $form->getId()]);
|
||||
if(!$form instanceof FormEntity) return $this->errorResponse();
|
||||
return $this->successResponse(
|
||||
$this->formsResponseBuilder->build($form),
|
||||
['is_widget' => $isWidget]
|
||||
);
|
||||
}
|
||||
|
||||
public function restore($data = []) {
|
||||
$form = $this->getForm($data);
|
||||
|
||||
if ($form instanceof FormEntity) {
|
||||
$this->formsRepository->restore($form);
|
||||
return $this->successResponse(
|
||||
$form->toArray(),
|
||||
['count' => 1]
|
||||
);
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This form does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function trash($data = []) {
|
||||
$form = $this->getForm($data);
|
||||
|
||||
if ($form instanceof FormEntity) {
|
||||
$this->formsRepository->trash($form);
|
||||
return $this->successResponse(
|
||||
$form->toArray(),
|
||||
['count' => 1]
|
||||
);
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This form does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete($data = []) {
|
||||
$form = $this->getForm($data);
|
||||
|
||||
if ($form instanceof FormEntity) {
|
||||
$this->formsRepository->delete($form);
|
||||
|
||||
return $this->successResponse(null, ['count' => 1]);
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This form does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function duplicate($data = []) {
|
||||
$form = $this->getForm($data);
|
||||
|
||||
if ($form instanceof FormEntity) {
|
||||
try {
|
||||
$duplicate = $this->formSaveController->duplicate($form);
|
||||
} catch (Exception $e) {
|
||||
return $this->errorResponse([
|
||||
APIError::UNKNOWN => __('Duplicating form failed.', 'mailpoet'),
|
||||
], [], Response::STATUS_UNKNOWN);
|
||||
}
|
||||
return $this->successResponse(
|
||||
$this->formsResponseBuilder->build($duplicate),
|
||||
['count' => 1]
|
||||
);
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This form does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function bulkAction($data = []): SuccessResponse {
|
||||
$definition = $this->listingHandler->getListingDefinition($data['listing']);
|
||||
$ids = $this->formListingRepository->getActionableIds($definition);
|
||||
if ($data['action'] === 'trash') {
|
||||
$this->formsRepository->bulkTrash($ids);
|
||||
} elseif ($data['action'] === 'restore') {
|
||||
$this->formsRepository->bulkRestore($ids);
|
||||
} elseif ($data['action'] === 'delete') {
|
||||
$this->formsRepository->bulkDelete($ids);
|
||||
} else {
|
||||
throw UnexpectedValueException::create()
|
||||
->withErrors([APIError::BAD_REQUEST => "Invalid bulk action '{$data['action']}' provided."]);
|
||||
}
|
||||
return $this->successResponse(null, ['count' => count($ids)]);
|
||||
}
|
||||
|
||||
private function getForm(array $data): ?FormEntity {
|
||||
return isset($data['id'])
|
||||
? $this->formsRepository->findOneById((int)$data['id'])
|
||||
: null;
|
||||
}
|
||||
|
||||
private function getFormTemplateData(string $templateId): array {
|
||||
$formTemplate = $this->templateRepository->getFormTemplate($templateId);
|
||||
$form = $formTemplate->toFormEntity();
|
||||
return $form->toArray();
|
||||
}
|
||||
|
||||
private function createTagsIfDoNotExist(array $tagNames): void {
|
||||
foreach ($tagNames as $tagName) {
|
||||
$this->tagRepository->createOrUpdate(['name' => $tagName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\API\JSON\Response;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
|
||||
use MailPoet\Util\DataInconsistency\DataInconsistencyController;
|
||||
|
||||
class Help extends APIEndpoint {
|
||||
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_HELP,
|
||||
];
|
||||
|
||||
private ScheduledTasksRepository $scheduledTasksRepository;
|
||||
private DataInconsistencyController $dataInconsistencyController;
|
||||
|
||||
public function __construct(
|
||||
ScheduledTasksRepository $scheduledTasksRepository,
|
||||
DataInconsistencyController $dataInconsistencyController
|
||||
) {
|
||||
$this->scheduledTasksRepository = $scheduledTasksRepository;
|
||||
$this->dataInconsistencyController = $dataInconsistencyController;
|
||||
}
|
||||
|
||||
public function cancelTask($data): Response {
|
||||
try {
|
||||
$this->validateTaskId($data);
|
||||
|
||||
$task = $this->scheduledTasksRepository->findOneById($data['id']);
|
||||
if (!$task instanceof ScheduledTaskEntity) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('Task not found.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->scheduledTasksRepository->cancelTask($task);
|
||||
return $this->successResponse();
|
||||
} catch (\Exception $e) {
|
||||
return $this->badRequest([ApiError::BAD_REQUEST => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
public function rescheduleTask($data): Response {
|
||||
try {
|
||||
$this->validateTaskId($data);
|
||||
|
||||
$task = $this->scheduledTasksRepository->findOneById($data['id']);
|
||||
if (!$task instanceof ScheduledTaskEntity) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('Task not found.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->scheduledTasksRepository->rescheduleTask($task);
|
||||
return $this->successResponse();
|
||||
} catch (\Exception $e) {
|
||||
return $this->badRequest([ApiError::BAD_REQUEST => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
public function getInconsistentDataStatus(): Response {
|
||||
return $this->successResponse($this->dataInconsistencyController->getInconsistentDataStatus());
|
||||
}
|
||||
|
||||
public function fixInconsistentData($data): Response {
|
||||
try {
|
||||
$this->dataInconsistencyController->fixInconsistentData($data['inconsistency'] ?? '');
|
||||
} catch (\Exception $e) {
|
||||
return $this->badRequest([ApiError::BAD_REQUEST => $e->getMessage()]);
|
||||
}
|
||||
return $this->successResponse($this->dataInconsistencyController->getInconsistentDataStatus());
|
||||
}
|
||||
|
||||
private function validateTaskId($data): void {
|
||||
$isValid = isset($data['id']) && is_numeric($data['id']);
|
||||
if (!$isValid) {
|
||||
throw new \Exception(__('Invalid or missing parameter `id`.', 'mailpoet'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\API\JSON\ResponseBuilders\SegmentsResponseBuilder;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\ConflictException;
|
||||
use MailPoet\Cron\CronWorkerScheduler;
|
||||
use MailPoet\Cron\Workers\WooCommerceSync;
|
||||
use MailPoet\CustomFields\CustomFieldsRepository;
|
||||
use MailPoet\Doctrine\Validator\ValidationException;
|
||||
use MailPoet\Newsletter\Options\NewsletterOptionsRepository;
|
||||
use MailPoet\Segments\SegmentSaveController;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Segments\WP;
|
||||
use MailPoet\Services\Validator;
|
||||
use MailPoet\Subscribers\ImportExport\Export\Export;
|
||||
use MailPoet\Subscribers\ImportExport\Import\Import;
|
||||
use MailPoet\Subscribers\ImportExport\Import\MailChimp;
|
||||
use MailPoet\Subscribers\ImportExport\ImportExportRepository;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\Tags\TagRepository;
|
||||
|
||||
class ImportExport extends APIEndpoint {
|
||||
|
||||
/** @var WP */
|
||||
private $wpSegment;
|
||||
|
||||
/** @var CustomFieldsRepository */
|
||||
private $customFieldsRepository;
|
||||
|
||||
/** @var ImportExportRepository */
|
||||
private $importExportRepository;
|
||||
|
||||
/** @var NewsletterOptionsRepository */
|
||||
private $newsletterOptionsRepository;
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentsRepository;
|
||||
|
||||
/** @var SubscribersRepository */
|
||||
private $subscriberRepository;
|
||||
|
||||
/** @var SegmentSaveController */
|
||||
private $segmentSavecontroller;
|
||||
|
||||
/** @var SegmentsResponseBuilder */
|
||||
private $segmentsResponseBuilder;
|
||||
|
||||
/** @var TagRepository */
|
||||
private $tagRepository;
|
||||
|
||||
/** @var Validator */
|
||||
private $validator;
|
||||
|
||||
/** @var CronWorkerScheduler */
|
||||
private $cronWorkerScheduler;
|
||||
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_SUBSCRIBERS,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
WP $wpSegment,
|
||||
CustomFieldsRepository $customFieldsRepository,
|
||||
ImportExportRepository $importExportRepository,
|
||||
NewsletterOptionsRepository $newsletterOptionsRepository,
|
||||
SegmentsRepository $segmentsRepository,
|
||||
SegmentSaveController $segmentSavecontroller,
|
||||
SegmentsResponseBuilder $segmentsResponseBuilder,
|
||||
CronWorkerScheduler $cronWorkerScheduler,
|
||||
SubscribersRepository $subscribersRepository,
|
||||
TagRepository $tagRepository,
|
||||
Validator $validator
|
||||
) {
|
||||
$this->wpSegment = $wpSegment;
|
||||
$this->customFieldsRepository = $customFieldsRepository;
|
||||
$this->importExportRepository = $importExportRepository;
|
||||
$this->newsletterOptionsRepository = $newsletterOptionsRepository;
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
$this->subscriberRepository = $subscribersRepository;
|
||||
$this->segmentSavecontroller = $segmentSavecontroller;
|
||||
$this->cronWorkerScheduler = $cronWorkerScheduler;
|
||||
$this->segmentsResponseBuilder = $segmentsResponseBuilder;
|
||||
$this->tagRepository = $tagRepository;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
public function getMailChimpLists($data) {
|
||||
try {
|
||||
$mailChimp = new MailChimp($data['api_key']);
|
||||
$lists = $mailChimp->getLists();
|
||||
return $this->successResponse($lists);
|
||||
} catch (\Exception $e) {
|
||||
return $this->errorResponse([
|
||||
$e->getCode() => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function getMailChimpSubscribers($data) {
|
||||
try {
|
||||
$mailChimp = new MailChimp($data['api_key']);
|
||||
$subscribers = $mailChimp->getSubscribers($data['lists']);
|
||||
return $this->successResponse($subscribers);
|
||||
} catch (\Exception $e) {
|
||||
return $this->errorResponse([
|
||||
$e->getCode() => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function addSegment($data) {
|
||||
try {
|
||||
$data['name'] = isset($data['name']) ? sanitize_text_field($data['name']) : '';
|
||||
$data['description'] = isset($data['description']) ? sanitize_textarea_field($data['description']) : '';
|
||||
$segment = $this->segmentSavecontroller->save($data);
|
||||
$response = $this->segmentsResponseBuilder->build($segment);
|
||||
return $this->successResponse($response);
|
||||
} catch (ValidationException $exception) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => __('Please specify a name.', 'mailpoet'),
|
||||
]);
|
||||
} catch (ConflictException $exception) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => __('Another record already exists. Please specify a different "name".', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function processImport($data) {
|
||||
try {
|
||||
$import = new Import(
|
||||
$this->wpSegment,
|
||||
$this->customFieldsRepository,
|
||||
$this->importExportRepository,
|
||||
$this->newsletterOptionsRepository,
|
||||
$this->subscriberRepository,
|
||||
$this->tagRepository,
|
||||
$this->validator,
|
||||
json_decode($data, true)
|
||||
);
|
||||
$process = $import->process();
|
||||
return $this->successResponse($process);
|
||||
} catch (\Exception $e) {
|
||||
return $this->errorResponse([
|
||||
$e->getCode() => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function processExport($data) {
|
||||
try {
|
||||
$export = new Export(
|
||||
$this->customFieldsRepository,
|
||||
$this->importExportRepository,
|
||||
$this->segmentsRepository,
|
||||
json_decode($data, true)
|
||||
);
|
||||
$process = $export->process();
|
||||
return $this->successResponse($process);
|
||||
} catch (\Exception $e) {
|
||||
return $this->errorResponse([
|
||||
$e->getCode() => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function setupWooCommerceInitialImport() {
|
||||
try {
|
||||
$this->cronWorkerScheduler->scheduleImmediatelyIfNotRunning(WooCommerceSync::TASK_TYPE);
|
||||
return $this->successResponse();
|
||||
} catch (\Exception $e) {
|
||||
return $this->errorResponse([
|
||||
$e->getCode() => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Mailer\MailerFactory;
|
||||
use MailPoet\Mailer\MailerLog;
|
||||
use MailPoet\Mailer\MetaInfo;
|
||||
use MailPoet\Services\AuthorizedEmailsController;
|
||||
use MailPoet\Services\AuthorizedSenderDomainController;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
|
||||
class Mailer extends APIEndpoint {
|
||||
|
||||
/** @var AuthorizedEmailsController */
|
||||
private $authorizedEmailsController;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
/** @var MetaInfo */
|
||||
private $mailerMetaInfo;
|
||||
|
||||
/** @var MailerFactory */
|
||||
private $mailerFactory;
|
||||
|
||||
/** @var AuthorizedSenderDomainController */
|
||||
private $senderDomainController;
|
||||
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_EMAILS,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
AuthorizedEmailsController $authorizedEmailsController,
|
||||
SettingsController $settings,
|
||||
MailerFactory $mailerFactory,
|
||||
MetaInfo $mailerMetaInfo,
|
||||
AuthorizedSenderDomainController $senderDomainController
|
||||
) {
|
||||
$this->authorizedEmailsController = $authorizedEmailsController;
|
||||
$this->settings = $settings;
|
||||
$this->mailerFactory = $mailerFactory;
|
||||
$this->mailerMetaInfo = $mailerMetaInfo;
|
||||
$this->senderDomainController = $senderDomainController;
|
||||
}
|
||||
|
||||
public function send($data = []) {
|
||||
try {
|
||||
$mailer = $this->mailerFactory->buildMailer(
|
||||
$data['mailer'] ?? null,
|
||||
$data['sender'] ?? null,
|
||||
$data['reply_to'] ?? null
|
||||
);
|
||||
// report this as 'sending_test' in metadata since this endpoint is only used to test sending methods for now
|
||||
$extraParams = [
|
||||
'meta' => $this->mailerMetaInfo->getSendingTestMetaInfo(),
|
||||
];
|
||||
$result = $mailer->send($data['newsletter'], $data['subscriber'], $extraParams);
|
||||
} catch (\Exception $e) {
|
||||
return $this->errorResponse([
|
||||
$e->getCode() => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($result['response'] === false) {
|
||||
$error = sprintf(
|
||||
// translators: %s is the error message.
|
||||
__('The email could not be sent: %s', 'mailpoet'),
|
||||
$result['error']->getMessage()
|
||||
);
|
||||
return $this->errorResponse([APIError::BAD_REQUEST => $error]);
|
||||
} else {
|
||||
return $this->successResponse(null);
|
||||
}
|
||||
}
|
||||
|
||||
public function resumeSending() {
|
||||
if ($this->settings->get(AuthorizedEmailsController::AUTHORIZED_EMAIL_ADDRESSES_ERROR_SETTING)) {
|
||||
$this->authorizedEmailsController->checkAuthorizedEmailAddresses();
|
||||
}
|
||||
MailerLog::resumeSending();
|
||||
return $this->successResponse(null);
|
||||
}
|
||||
|
||||
public function getAuthorizedEmailAddresses() {
|
||||
$authorizedEmails = $this->authorizedEmailsController->getAuthorizedEmailAddresses();
|
||||
return $this->successResponse($authorizedEmails);
|
||||
}
|
||||
|
||||
public function getVerifiedSenderDomains() {
|
||||
$verifiedDomains = $this->senderDomainController->getVerifiedSenderDomains();
|
||||
return $this->successResponse($verifiedDomains);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Cron\Workers\StatsNotifications\NewsletterLinkRepository;
|
||||
|
||||
class NewsletterLinks extends APIEndpoint {
|
||||
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_SEGMENTS,
|
||||
];
|
||||
|
||||
/** @var NewsletterLinkRepository */
|
||||
private $newsletterLinkRepository;
|
||||
|
||||
public function __construct(
|
||||
NewsletterLinkRepository $newsletterLinkRepository
|
||||
) {
|
||||
$this->newsletterLinkRepository = $newsletterLinkRepository;
|
||||
}
|
||||
|
||||
public function get($data = []) {
|
||||
$links = $this->newsletterLinkRepository->findBy(['newsletter' => $data['newsletterId']]);
|
||||
$response = [];
|
||||
foreach ($links as $link) {
|
||||
$response[] = [
|
||||
'id' => $link->getId(),
|
||||
'url' => $link->getUrl(),
|
||||
];
|
||||
}
|
||||
return $this->successResponse($response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\API\JSON\ResponseBuilders\NewsletterTemplatesResponseBuilder;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Newsletter\ApiDataSanitizer;
|
||||
use MailPoet\Newsletter\NewsletterCoupon;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\NewsletterTemplates\NewsletterTemplatesRepository;
|
||||
use MailPoet\NewsletterTemplates\ThumbnailSaver;
|
||||
|
||||
class NewsletterTemplates extends APIEndpoint {
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_EMAILS,
|
||||
];
|
||||
|
||||
protected static $getMethods = [
|
||||
'getAll',
|
||||
];
|
||||
|
||||
/** @var NewsletterTemplatesRepository */
|
||||
private $newsletterTemplatesRepository;
|
||||
|
||||
/** @var NewsletterTemplatesResponseBuilder */
|
||||
private $newsletterTemplatesResponseBuilder;
|
||||
|
||||
/** @var ThumbnailSaver */
|
||||
private $thumbnailImageSaver;
|
||||
|
||||
/** @var ApiDataSanitizer */
|
||||
private $apiDataSanitizer;
|
||||
|
||||
/** @var NewslettersRepository */
|
||||
private $newsletterRepository;
|
||||
|
||||
/*** @var NewsletterCoupon */
|
||||
private $newsletterCoupon;
|
||||
|
||||
public function __construct(
|
||||
NewsletterTemplatesRepository $newsletterTemplatesRepository,
|
||||
NewsletterTemplatesResponseBuilder $newsletterTemplatesResponseBuilder,
|
||||
ThumbnailSaver $thumbnailImageSaver,
|
||||
ApiDataSanitizer $apiDataSanitizer,
|
||||
NewslettersRepository $newsletterRepository,
|
||||
NewsletterCoupon $newsletterCoupon
|
||||
) {
|
||||
$this->newsletterTemplatesRepository = $newsletterTemplatesRepository;
|
||||
$this->newsletterTemplatesResponseBuilder = $newsletterTemplatesResponseBuilder;
|
||||
$this->thumbnailImageSaver = $thumbnailImageSaver;
|
||||
$this->apiDataSanitizer = $apiDataSanitizer;
|
||||
$this->newsletterRepository = $newsletterRepository;
|
||||
$this->newsletterCoupon = $newsletterCoupon;
|
||||
}
|
||||
|
||||
public function get($data = []) {
|
||||
$template = isset($data['id'])
|
||||
? $this->newsletterTemplatesRepository->findOneById((int)$data['id'])
|
||||
: null;
|
||||
|
||||
if (!$template) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This template does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$data = $this->newsletterTemplatesResponseBuilder->build($template);
|
||||
return $this->successResponse($data);
|
||||
}
|
||||
|
||||
public function getAll() {
|
||||
$templates = $this->newsletterTemplatesRepository->findAllForListing();
|
||||
$data = $this->newsletterTemplatesResponseBuilder->buildForListing($templates);
|
||||
return $this->successResponse($data);
|
||||
}
|
||||
|
||||
public function save($data = []) {
|
||||
ignore_user_abort(true);
|
||||
// Do not save templates for emails created via Gutenberg editor
|
||||
$newsletterId = isset($data['newsletter_id']) ? (int)$data['newsletter_id'] : null;
|
||||
if ($newsletterId) {
|
||||
$newsletter = $this->newsletterRepository->findOneById($newsletterId);
|
||||
if ($newsletter && $newsletter->getWpPostId() !== null) {
|
||||
return $this->successResponse($data);
|
||||
}
|
||||
}
|
||||
if (!empty($data['body'])) {
|
||||
$body = $this->apiDataSanitizer->sanitizeBody(json_decode($data['body'], true));
|
||||
$body = $this->newsletterCoupon->cleanupBodySensitiveData($body);
|
||||
$data['body'] = json_encode($body);
|
||||
}
|
||||
try {
|
||||
$template = $this->newsletterTemplatesRepository->createOrUpdate($data);
|
||||
$template = $this->thumbnailImageSaver->ensureTemplateThumbnailFile($template);
|
||||
if (!empty($data['categories']) && $data['categories'] === NewsletterTemplatesRepository::RECENTLY_SENT_CATEGORIES) {
|
||||
$this->newsletterTemplatesRepository->cleanRecentlySent();
|
||||
}
|
||||
$data = $this->newsletterTemplatesResponseBuilder->build($template);
|
||||
return $this->successResponse($data);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->errorResponse();
|
||||
}
|
||||
}
|
||||
|
||||
public function delete($data = []) {
|
||||
$template = isset($data['id'])
|
||||
? $this->newsletterTemplatesRepository->findOneById((int)$data['id'])
|
||||
: null;
|
||||
|
||||
if (!$template) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This template does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->newsletterTemplatesRepository->remove($template);
|
||||
$this->newsletterTemplatesRepository->flush();
|
||||
return $this->successResponse(null, ['count' => 1]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\API\JSON\Response;
|
||||
use MailPoet\API\JSON\ResponseBuilders\NewslettersResponseBuilder;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Doctrine\Validator\ValidationException;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\NewsletterOptionFieldEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\InvalidStateException;
|
||||
use MailPoet\Listing;
|
||||
use MailPoet\Newsletter\Listing\NewsletterListingRepository;
|
||||
use MailPoet\Newsletter\NewsletterDeleteController;
|
||||
use MailPoet\Newsletter\NewsletterSaveController;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Newsletter\NewsletterValidator;
|
||||
use MailPoet\Newsletter\Preview\SendPreviewController;
|
||||
use MailPoet\Newsletter\Preview\SendPreviewException;
|
||||
use MailPoet\Newsletter\Scheduler\PostNotificationScheduler;
|
||||
use MailPoet\Newsletter\Scheduler\Scheduler;
|
||||
use MailPoet\Newsletter\Url as NewsletterUrl;
|
||||
use MailPoet\Services\AuthorizedEmailsController;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\UnexpectedValueException;
|
||||
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
|
||||
use MailPoet\WP\Emoji;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
|
||||
class Newsletters extends APIEndpoint {
|
||||
|
||||
/** @var Listing\Handler */
|
||||
private $listingHandler;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
/** @var CronHelper */
|
||||
private $cronHelper;
|
||||
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_EMAILS,
|
||||
];
|
||||
|
||||
/** @var NewslettersRepository */
|
||||
private $newslettersRepository;
|
||||
|
||||
/** @var NewsletterListingRepository */
|
||||
private $newsletterListingRepository;
|
||||
|
||||
/** @var NewslettersResponseBuilder */
|
||||
private $newslettersResponseBuilder;
|
||||
|
||||
/** @var PostNotificationScheduler */
|
||||
private $postNotificationScheduler;
|
||||
|
||||
/** @var Emoji */
|
||||
private $emoji;
|
||||
|
||||
/** @var SubscribersFeature */
|
||||
private $subscribersFeature;
|
||||
|
||||
/** @var SendPreviewController */
|
||||
private $sendPreviewController;
|
||||
|
||||
/** @var NewsletterSaveController */
|
||||
private $newsletterSaveController;
|
||||
|
||||
private NewsletterDeleteController $newsletterDeleteController;
|
||||
|
||||
/** @var NewsletterUrl */
|
||||
private $newsletterUrl;
|
||||
|
||||
/** @var NewsletterValidator */
|
||||
private $newsletterValidator;
|
||||
|
||||
/** @var Scheduler */
|
||||
private $scheduler;
|
||||
|
||||
/** @var AuthorizedEmailsController */
|
||||
private $authorizedEmailsController;
|
||||
|
||||
public function __construct(
|
||||
Listing\Handler $listingHandler,
|
||||
WPFunctions $wp,
|
||||
SettingsController $settings,
|
||||
CronHelper $cronHelper,
|
||||
NewslettersRepository $newslettersRepository,
|
||||
NewsletterListingRepository $newsletterListingRepository,
|
||||
NewslettersResponseBuilder $newslettersResponseBuilder,
|
||||
PostNotificationScheduler $postNotificationScheduler,
|
||||
SubscribersFeature $subscribersFeature,
|
||||
Emoji $emoji,
|
||||
SendPreviewController $sendPreviewController,
|
||||
NewsletterSaveController $newsletterSaveController,
|
||||
NewsletterDeleteController $newsletterDeleteController,
|
||||
NewsletterUrl $newsletterUrl,
|
||||
Scheduler $scheduler,
|
||||
NewsletterValidator $newsletterValidator,
|
||||
AuthorizedEmailsController $authorizedEmailsController
|
||||
) {
|
||||
$this->listingHandler = $listingHandler;
|
||||
$this->wp = $wp;
|
||||
$this->settings = $settings;
|
||||
$this->cronHelper = $cronHelper;
|
||||
$this->newslettersRepository = $newslettersRepository;
|
||||
$this->newsletterListingRepository = $newsletterListingRepository;
|
||||
$this->newslettersResponseBuilder = $newslettersResponseBuilder;
|
||||
$this->postNotificationScheduler = $postNotificationScheduler;
|
||||
$this->subscribersFeature = $subscribersFeature;
|
||||
$this->emoji = $emoji;
|
||||
$this->sendPreviewController = $sendPreviewController;
|
||||
$this->newsletterSaveController = $newsletterSaveController;
|
||||
$this->newsletterDeleteController = $newsletterDeleteController;
|
||||
$this->newsletterUrl = $newsletterUrl;
|
||||
$this->scheduler = $scheduler;
|
||||
$this->newsletterValidator = $newsletterValidator;
|
||||
$this->authorizedEmailsController = $authorizedEmailsController;
|
||||
}
|
||||
|
||||
public function get($data = []) {
|
||||
$newsletter = $this->getNewsletter($data);
|
||||
if (!$newsletter) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This email does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$response = $this->newslettersResponseBuilder->build($newsletter, [
|
||||
NewslettersResponseBuilder::RELATION_SEGMENTS,
|
||||
NewslettersResponseBuilder::RELATION_OPTIONS,
|
||||
NewslettersResponseBuilder::RELATION_QUEUE,
|
||||
]);
|
||||
$response = $this->wp->applyFilters('mailpoet_api_newsletters_get_after', $response);
|
||||
return $this->successResponse($response, ['preview_url' => $this->getViewInBrowserUrl($newsletter)]);
|
||||
}
|
||||
|
||||
public function getWithStats($data = []) {
|
||||
$newsletter = $this->getNewsletter($data);
|
||||
if (!$newsletter) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This email does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$response = $this->newslettersResponseBuilder->build($newsletter, [
|
||||
NewslettersResponseBuilder::RELATION_SEGMENTS,
|
||||
NewslettersResponseBuilder::RELATION_OPTIONS,
|
||||
NewslettersResponseBuilder::RELATION_QUEUE,
|
||||
NewslettersResponseBuilder::RELATION_TOTAL_SENT,
|
||||
NewslettersResponseBuilder::RELATION_STATISTICS,
|
||||
]);
|
||||
$response = $this->wp->applyFilters('mailpoet_api_newsletters_get_after', $response);
|
||||
$response['preview_url'] = $this->getViewInBrowserUrl($newsletter);
|
||||
return $this->successResponse($response);
|
||||
}
|
||||
|
||||
public function save($data = []) {
|
||||
$data = $this->wp->applyFilters('mailpoet_api_newsletters_save_before', $data);
|
||||
$newsletter = $this->newsletterSaveController->save($data);
|
||||
$response = $this->newslettersResponseBuilder->build($newsletter, [
|
||||
NewslettersResponseBuilder::RELATION_SEGMENTS,
|
||||
]);
|
||||
$previewUrl = $this->getViewInBrowserUrl($newsletter);
|
||||
$response = $this->wp->applyFilters('mailpoet_api_newsletters_save_after', $response);
|
||||
return $this->successResponse($response, ['preview_url' => $previewUrl]);
|
||||
}
|
||||
|
||||
public function setStatus($data = []) {
|
||||
$status = (isset($data['status']) ? $data['status'] : null);
|
||||
|
||||
if (!$status) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => __('You need to specify a status.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($status === NewsletterEntity::STATUS_ACTIVE && $this->subscribersFeature->check()) {
|
||||
return $this->errorResponse([
|
||||
APIError::FORBIDDEN => __('Subscribers limit reached.', 'mailpoet'),
|
||||
], [], Response::STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
$newsletter = $this->getNewsletter($data);
|
||||
if ($newsletter === null) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This email does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($status === NewsletterEntity::STATUS_ACTIVE && !$this->authorizedEmailsController->isSenderAddressValid($newsletter)) {
|
||||
return $this->errorResponse([
|
||||
APIError::FORBIDDEN => __('The sender address is not an authorized sender domain.', 'mailpoet'),
|
||||
], [], Response::STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
if ($status === NewsletterEntity::STATUS_ACTIVE) {
|
||||
$validationError = $this->newsletterValidator->validate($newsletter);
|
||||
if ($validationError !== null) {
|
||||
return $this->errorResponse([APIError::FORBIDDEN => $validationError], [], Response::STATUS_FORBIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
$this->newslettersRepository->prefetchOptions([$newsletter]);
|
||||
$newsletter->setStatus($status);
|
||||
|
||||
// if there are paused tasks unpause them
|
||||
if ($newsletter->getStatus() === NewsletterEntity::STATUS_ACTIVE) {
|
||||
$queues = $newsletter->getUnfinishedQueues();
|
||||
foreach ($queues as $queue) {
|
||||
$task = $queue->getTask();
|
||||
if ($task && $task->getStatus() === ScheduledTaskEntity::STATUS_PAUSED) {
|
||||
$task->setStatus(ScheduledTaskEntity::STATUS_SCHEDULED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if there are past due notifications, reschedule them for the next send date
|
||||
if ($newsletter->getType() === NewsletterEntity::TYPE_NOTIFICATION && $status === NewsletterEntity::STATUS_ACTIVE) {
|
||||
$scheduleOption = $newsletter->getOption(NewsletterOptionFieldEntity::NAME_SCHEDULE);
|
||||
if ($scheduleOption === null) {
|
||||
return $this->errorResponse([
|
||||
APIError::BAD_REQUEST => __('This email has incorrect state.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
$nextRunDate = $this->scheduler->getNextRunDate($scheduleOption->getValue());
|
||||
$queues = $newsletter->getQueues();
|
||||
foreach ($queues as $queue) {
|
||||
$task = $queue->getTask();
|
||||
if (
|
||||
$task &&
|
||||
$task->getScheduledAt() <= Carbon::now()->millisecond(0) &&
|
||||
$task->getStatus() === SendingQueueEntity::STATUS_SCHEDULED
|
||||
) {
|
||||
$nextRunDate = $nextRunDate ? Carbon::createFromFormat('Y-m-d H:i:s', $nextRunDate) : null;
|
||||
if ($nextRunDate === false) {
|
||||
throw InvalidStateException::create()->withMessage('Invalid next run date generated');
|
||||
}
|
||||
$task->setScheduledAt($nextRunDate);
|
||||
}
|
||||
}
|
||||
$this->postNotificationScheduler->createPostNotificationSendingTask($newsletter);
|
||||
}
|
||||
|
||||
$this->newslettersRepository->flush();
|
||||
|
||||
return $this->successResponse(
|
||||
$this->newslettersResponseBuilder->build($newsletter)
|
||||
);
|
||||
}
|
||||
|
||||
public function restore($data = []) {
|
||||
$newsletter = $this->getNewsletter($data);
|
||||
if ($newsletter instanceof NewsletterEntity) {
|
||||
$this->newslettersRepository->bulkRestore([$newsletter->getId()]);
|
||||
$this->newslettersRepository->refresh($newsletter);
|
||||
return $this->successResponse(
|
||||
$this->newslettersResponseBuilder->build($newsletter),
|
||||
['count' => 1]
|
||||
);
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This email does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function trash($data = []) {
|
||||
$newsletter = $this->getNewsletter($data);
|
||||
if ($newsletter instanceof NewsletterEntity) {
|
||||
$this->newslettersRepository->bulkTrash([$newsletter->getId()]);
|
||||
$this->newslettersRepository->refresh($newsletter);
|
||||
return $this->successResponse(
|
||||
$this->newslettersResponseBuilder->build($newsletter),
|
||||
['count' => 1]
|
||||
);
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This email does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete($data = []) {
|
||||
$newsletter = $this->getNewsletter($data);
|
||||
if ($newsletter instanceof NewsletterEntity) {
|
||||
$this->wp->doAction('mailpoet_api_newsletters_delete_before', [$newsletter->getId()]);
|
||||
$this->newsletterDeleteController->bulkDelete([(int)$newsletter->getId()]);
|
||||
$this->wp->doAction('mailpoet_api_newsletters_delete_after', [$newsletter->getId()]);
|
||||
return $this->successResponse(null, ['count' => 1]);
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This email does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function duplicate($data = []) {
|
||||
$newsletter = $this->getNewsletter($data);
|
||||
|
||||
if ($newsletter instanceof NewsletterEntity) {
|
||||
$duplicate = $this->newsletterSaveController->duplicate($newsletter);
|
||||
$this->wp->doAction('mailpoet_api_newsletters_duplicate_after', $newsletter, $duplicate);
|
||||
return $this->successResponse(
|
||||
$this->newslettersResponseBuilder->build($duplicate),
|
||||
['count' => 1]
|
||||
);
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This email does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function showPreview($data = []) {
|
||||
if (empty($data['body'])) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => __('Newsletter data is missing.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$newsletter = $this->getNewsletter($data);
|
||||
if (!$newsletter) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This email does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$newslettersTableName = $this->newslettersRepository->getTableName();
|
||||
$newsletter->setBody(
|
||||
json_decode($this->emoji->encodeForUTF8Column($newslettersTableName, 'body', $data['body']), true)
|
||||
);
|
||||
$this->newslettersRepository->flush();
|
||||
|
||||
$response = $this->newslettersResponseBuilder->build($newsletter);
|
||||
return $this->successResponse($response, ['preview_url' => $this->getViewInBrowserUrl($newsletter)]);
|
||||
}
|
||||
|
||||
public function sendPreview($data = []) {
|
||||
if (empty($data['subscriber'])) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => __('Please specify receiver information.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$newsletter = $this->getNewsletter($data);
|
||||
if (!$newsletter) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This email does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->sendPreviewController->sendPreview($newsletter, $data['subscriber']);
|
||||
} catch (SendPreviewException $e) {
|
||||
return $this->errorResponse([APIError::BAD_REQUEST => $e->getMessage()]);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->errorResponse([$e->getCode() => $e->getMessage()]);
|
||||
}
|
||||
return $this->successResponse($this->newslettersResponseBuilder->build($newsletter));
|
||||
}
|
||||
|
||||
public function listing($data = []) {
|
||||
$definition = $this->listingHandler->getListingDefinition($data);
|
||||
$items = $this->newsletterListingRepository->getData($definition);
|
||||
$count = $this->newsletterListingRepository->getCount($definition);
|
||||
$filters = $this->newsletterListingRepository->getFilters($definition);
|
||||
$groups = $this->newsletterListingRepository->getGroups($definition);
|
||||
|
||||
$data = [];
|
||||
foreach ($this->newslettersResponseBuilder->buildForListing($items) as $newsletterData) {
|
||||
$data[] = $this->wp->applyFilters('mailpoet_api_newsletters_listing_item', $newsletterData);
|
||||
}
|
||||
|
||||
return $this->successResponse($data, [
|
||||
'count' => $count,
|
||||
'filters' => $filters,
|
||||
'groups' => $groups,
|
||||
'mta_log' => $this->settings->get('mta_log'),
|
||||
'mta_method' => $this->settings->get('mta.method'),
|
||||
'cron_accessible' => $this->cronHelper->isDaemonAccessible(),
|
||||
'current_time' => $this->wp->currentTime('mysql'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function bulkAction($data = []) {
|
||||
$definition = $this->listingHandler->getListingDefinition($data['listing']);
|
||||
$ids = $this->newsletterListingRepository->getActionableIds($definition);
|
||||
if ($data['action'] === 'trash') {
|
||||
$this->newslettersRepository->bulkTrash($ids);
|
||||
} elseif ($data['action'] === 'restore') {
|
||||
$this->newslettersRepository->bulkRestore($ids);
|
||||
} elseif ($data['action'] === 'delete') {
|
||||
$this->wp->doAction('mailpoet_api_newsletters_delete_before', $ids);
|
||||
$this->newsletterDeleteController->bulkDelete($ids);
|
||||
$this->wp->doAction('mailpoet_api_newsletters_delete_after', $ids);
|
||||
} else {
|
||||
throw UnexpectedValueException::create()
|
||||
->withErrors([APIError::BAD_REQUEST => "Invalid bulk action '{$data['action']}' provided."]);
|
||||
}
|
||||
return $this->successResponse(null, ['count' => count($ids)]);
|
||||
}
|
||||
|
||||
public function create($data = []) {
|
||||
try {
|
||||
$newsletter = $this->newsletterSaveController->save($data);
|
||||
} catch (ValidationException $exception) {
|
||||
return $this->badRequest(['Please specify a type.']);
|
||||
}
|
||||
$response = $this->newslettersResponseBuilder->build($newsletter);
|
||||
return $this->successResponse($response);
|
||||
}
|
||||
|
||||
/** @return NewsletterEntity|null */
|
||||
private function getNewsletter(array $data) {
|
||||
return isset($data['id'])
|
||||
? $this->newslettersRepository->findOneById((int)$data['id'])
|
||||
: null;
|
||||
}
|
||||
|
||||
private function getViewInBrowserUrl(NewsletterEntity $newsletter): string {
|
||||
$url = $this->newsletterUrl->getViewInBrowserUrl($newsletter);
|
||||
// strip protocol to avoid mix content error
|
||||
return preg_replace('/^https?:/i', '', $url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Config\ServicesChecker;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoet\WPCOM\DotcomHelperFunctions;
|
||||
use WP_Error;
|
||||
|
||||
class Premium extends APIEndpoint {
|
||||
const PREMIUM_PLUGIN_SLUG = 'mailpoet-premium';
|
||||
const PREMIUM_PLUGIN_PATH = 'mailpoet-premium/mailpoet-premium.php';
|
||||
// This is the path to the managed plugin on Dotcom platform. It is relative to WP_PLUGIN_DIR.
|
||||
const DOTCOM_SYMLINK_PATH = '../../../../wordpress/plugins/mailpoet-premium/latest';
|
||||
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_SETTINGS,
|
||||
];
|
||||
|
||||
/** @var ServicesChecker */
|
||||
private $servicesChecker;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var DotcomHelperFunctions */
|
||||
private $dotcomHelperFunctions;
|
||||
|
||||
public function __construct(
|
||||
ServicesChecker $servicesChecker,
|
||||
WPFunctions $wp,
|
||||
DotcomHelperFunctions $dotcomHelperFunctions
|
||||
) {
|
||||
$this->servicesChecker = $servicesChecker;
|
||||
$this->wp = $wp;
|
||||
$this->dotcomHelperFunctions = $dotcomHelperFunctions;
|
||||
}
|
||||
|
||||
public function installPlugin() {
|
||||
$premiumKeyValid = $this->servicesChecker->isPremiumKeyValid(false);
|
||||
if (!$premiumKeyValid) {
|
||||
return $this->error(__('Premium key is not valid.', 'mailpoet'));
|
||||
}
|
||||
|
||||
$pluginInfo = $this->wp->pluginsApi('plugin_information', [
|
||||
'slug' => self::PREMIUM_PLUGIN_SLUG,
|
||||
]);
|
||||
|
||||
if (!$pluginInfo || $pluginInfo instanceof WP_Error) {
|
||||
return $this->error(__('Error when installing MailPoet Premium plugin.', 'mailpoet'));
|
||||
}
|
||||
|
||||
$pluginInfo = (array)$pluginInfo;
|
||||
|
||||
// If we are in Dotcom platform, we try to symlink the plugin instead of downloading it
|
||||
try {
|
||||
if ($this->dotcomHelperFunctions->isDotcom()) {
|
||||
$result = symlink(self::DOTCOM_SYMLINK_PATH, WP_PLUGIN_DIR . '/' . self::PREMIUM_PLUGIN_SLUG);
|
||||
if ($result === true) {
|
||||
return $this->successResponse();
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Do nothing and continue with a regular installation
|
||||
}
|
||||
|
||||
$result = $this->wp->installPlugin($pluginInfo['download_link']);
|
||||
if ($result !== true) {
|
||||
return $this->error(__('Error when installing MailPoet Premium plugin.', 'mailpoet'));
|
||||
}
|
||||
return $this->successResponse();
|
||||
}
|
||||
|
||||
public function activatePlugin() {
|
||||
$premiumKeyValid = $this->servicesChecker->isPremiumKeyValid(false);
|
||||
if (!$premiumKeyValid) {
|
||||
return $this->error(__('Premium key is not valid.', 'mailpoet'));
|
||||
}
|
||||
|
||||
$result = $this->wp->activatePlugin(self::PREMIUM_PLUGIN_PATH);
|
||||
if ($result !== null) {
|
||||
return $this->error(__('Error when activating MailPoet Premium plugin.', 'mailpoet'));
|
||||
}
|
||||
return $this->successResponse();
|
||||
}
|
||||
|
||||
private function error($message) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => $message,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Response;
|
||||
|
||||
class RedirectResponse extends Response {
|
||||
|
||||
public function __construct($location) { // phpcs:ignore
|
||||
parent::__construct(self::REDIRECT, [], $location);
|
||||
}
|
||||
|
||||
public function getData() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use Exception;
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\API\JSON\Response;
|
||||
use MailPoet\API\JSON\ResponseBuilders\SegmentsResponseBuilder;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\ConflictException;
|
||||
use MailPoet\Cron\CronWorkerScheduler;
|
||||
use MailPoet\Cron\Workers\WooCommerceSync;
|
||||
use MailPoet\Doctrine\Validator\ValidationException;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Form\FormsRepository;
|
||||
use MailPoet\Listing;
|
||||
use MailPoet\Newsletter\Segment\NewsletterSegmentRepository;
|
||||
use MailPoet\Segments\SegmentListingRepository;
|
||||
use MailPoet\Segments\SegmentSaveController;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Segments\SegmentSubscribersRepository;
|
||||
use MailPoet\Segments\WooCommerce;
|
||||
use MailPoet\Segments\WP;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\UnexpectedValueException;
|
||||
|
||||
class Segments extends APIEndpoint {
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_SEGMENTS,
|
||||
];
|
||||
|
||||
/** @var Listing\Handler */
|
||||
private $listingHandler;
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentsRepository;
|
||||
|
||||
/** @var SegmentsResponseBuilder */
|
||||
private $segmentsResponseBuilder;
|
||||
|
||||
/** @var SegmentSaveController */
|
||||
private $segmentSavecontroller;
|
||||
|
||||
/** @var SubscribersRepository */
|
||||
private $subscribersRepository;
|
||||
|
||||
/** @var WooCommerce */
|
||||
private $wooCommerceSync;
|
||||
|
||||
/** @var WP */
|
||||
private $wpSegment;
|
||||
|
||||
/** @var SegmentListingRepository */
|
||||
private $segmentListingRepository;
|
||||
|
||||
/** @var NewsletterSegmentRepository */
|
||||
private $newsletterSegmentRepository;
|
||||
|
||||
/** @var CronWorkerScheduler */
|
||||
private $cronWorkerScheduler;
|
||||
|
||||
/** @var FormsRepository */
|
||||
private $formsRepository;
|
||||
|
||||
/** @var SegmentSubscribersRepository */
|
||||
private $segmentSubscribersRepository;
|
||||
|
||||
public function __construct(
|
||||
Listing\Handler $listingHandler,
|
||||
SegmentsRepository $segmentsRepository,
|
||||
SegmentListingRepository $segmentListingRepository,
|
||||
SegmentsResponseBuilder $segmentsResponseBuilder,
|
||||
SegmentSaveController $segmentSavecontroller,
|
||||
SegmentSubscribersRepository $segmentSubscribersRepository,
|
||||
SubscribersRepository $subscribersRepository,
|
||||
WooCommerce $wooCommerce,
|
||||
WP $wpSegment,
|
||||
NewsletterSegmentRepository $newsletterSegmentRepository,
|
||||
CronWorkerScheduler $cronWorkerScheduler,
|
||||
FormsRepository $formsRepository
|
||||
) {
|
||||
$this->listingHandler = $listingHandler;
|
||||
$this->wooCommerceSync = $wooCommerce;
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
$this->segmentsResponseBuilder = $segmentsResponseBuilder;
|
||||
$this->segmentSavecontroller = $segmentSavecontroller;
|
||||
$this->subscribersRepository = $subscribersRepository;
|
||||
$this->wpSegment = $wpSegment;
|
||||
$this->segmentListingRepository = $segmentListingRepository;
|
||||
$this->newsletterSegmentRepository = $newsletterSegmentRepository;
|
||||
$this->cronWorkerScheduler = $cronWorkerScheduler;
|
||||
$this->formsRepository = $formsRepository;
|
||||
$this->segmentSubscribersRepository = $segmentSubscribersRepository;
|
||||
}
|
||||
|
||||
public function get($data = []) {
|
||||
$id = (isset($data['id']) ? (int)$data['id'] : false);
|
||||
$segment = $this->segmentsRepository->findOneById($id);
|
||||
if ($segment instanceof SegmentEntity) {
|
||||
return $this->successResponse($this->segmentsResponseBuilder->build($segment));
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This list does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function listing($data = []) {
|
||||
$data['params'] = $data['params'] ?? ['lists']; // Dummy param to apply constraints properly
|
||||
$definition = $this->listingHandler->getListingDefinition($data);
|
||||
$items = $this->segmentListingRepository->getData($definition);
|
||||
$count = $this->segmentListingRepository->getCount($definition);
|
||||
$filters = $this->segmentListingRepository->getFilters($definition);
|
||||
$groups = $this->segmentListingRepository->getGroups($definition);
|
||||
$segments = $this->segmentsResponseBuilder->buildForListing($items);
|
||||
|
||||
return $this->successResponse($segments, [
|
||||
'count' => $count,
|
||||
'filters' => $filters,
|
||||
'groups' => $groups,
|
||||
]);
|
||||
}
|
||||
|
||||
public function save($data = []) {
|
||||
try {
|
||||
$data['name'] = isset($data['name']) ? sanitize_text_field($data['name']) : '';
|
||||
$data['description'] = isset($data['description']) ? sanitize_textarea_field($data['description']) : '';
|
||||
$segment = $this->segmentSavecontroller->save($data);
|
||||
} catch (ValidationException $exception) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => __('Please specify a name.', 'mailpoet'),
|
||||
]);
|
||||
} catch (ConflictException $exception) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => __('Another record already exists. Please specify a different "name".', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
$response = $this->segmentsResponseBuilder->build($segment);
|
||||
return $this->successResponse($response);
|
||||
}
|
||||
|
||||
public function restore($data = []) {
|
||||
$segment = $this->getSegment($data);
|
||||
if ($segment instanceof SegmentEntity) {
|
||||
if (!$this->isTrashOrRestoreAllowed($segment)) {
|
||||
return $this->errorResponse([
|
||||
APIError::FORBIDDEN => __('This list cannot be moved to trash.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
// When the segment is of type WP_USERS we want to restore all its subscribers
|
||||
if ($segment->getType() === SegmentEntity::TYPE_WP_USERS) {
|
||||
$subscribers = $this->subscribersRepository->findBySegment((int)$segment->getId());
|
||||
$subscriberIds = array_map(function (SubscriberEntity $subscriberEntity): int {
|
||||
return (int)$subscriberEntity->getId();
|
||||
}, $subscribers);
|
||||
$this->subscribersRepository->bulkRestore($subscriberIds);
|
||||
}
|
||||
|
||||
$this->segmentsRepository->bulkRestore([$segment->getId()], $segment->getType());
|
||||
$this->segmentsRepository->refresh($segment);
|
||||
return $this->successResponse(
|
||||
$this->segmentsResponseBuilder->build($segment),
|
||||
['count' => 1]
|
||||
);
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This list does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function trash($data = []) {
|
||||
$segment = $this->getSegment($data);
|
||||
if (!$segment instanceof SegmentEntity) {
|
||||
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This list does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$this->isTrashOrRestoreAllowed($segment)) {
|
||||
return $this->errorResponse([
|
||||
APIError::FORBIDDEN => __('This list cannot be moved to trash.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$activelyUsedNewslettersSubjects = $this->newsletterSegmentRepository->getSubjectsOfActivelyUsedEmailsForSegments([$segment->getId()]);
|
||||
if (isset($activelyUsedNewslettersSubjects[$segment->getId()])) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => str_replace(
|
||||
'%1$s',
|
||||
"'" . join("', '", $activelyUsedNewslettersSubjects[$segment->getId()]) . "'",
|
||||
// translators: %1$s is a comma-seperated list of emails for which the segment is used.
|
||||
_x('List cannot be deleted because it’s used for %1$s email', 'Alert shown when trying to delete segment, which is assigned to any automatic emails.', 'mailpoet')
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
$activelyUsedFormNames = $this->formsRepository->getNamesOfFormsForSegments();
|
||||
if (isset($activelyUsedFormNames[$segment->getId()])) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => str_replace(
|
||||
'%1$s',
|
||||
"'" . join("', '", $activelyUsedFormNames[$segment->getId()]) . "'",
|
||||
// translators: %1$s is a comma-seperated list of forms for which the segment is used.
|
||||
_nx(
|
||||
'List cannot be deleted because it’s used for %1$s form',
|
||||
'List cannot be deleted because it’s used for %1$s forms',
|
||||
count($activelyUsedFormNames[$segment->getId()]),
|
||||
'Alert shown when trying to delete segment, when it is assigned to a form.',
|
||||
'mailpoet'
|
||||
)
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
// When the segment is of type WP_USERS we want to trash all subscribers who aren't subscribed in another list
|
||||
if ($segment->getType() === SegmentEntity::TYPE_WP_USERS) {
|
||||
$subscribers = $this->subscribersRepository->findExclusiveSubscribersBySegment((int)$segment->getId());
|
||||
$subscriberIds = array_map(function (SubscriberEntity $subscriberEntity): int {
|
||||
return (int)$subscriberEntity->getId();
|
||||
}, $subscribers);
|
||||
$this->subscribersRepository->bulkTrash($subscriberIds);
|
||||
}
|
||||
|
||||
$this->segmentsRepository->doTrash([$segment->getId()], $segment->getType());
|
||||
$this->segmentsRepository->refresh($segment);
|
||||
return $this->successResponse(
|
||||
$this->segmentsResponseBuilder->build($segment),
|
||||
['count' => 1]
|
||||
);
|
||||
}
|
||||
|
||||
public function delete($data = []) {
|
||||
$segment = $this->getSegment($data);
|
||||
if ($segment instanceof SegmentEntity) {
|
||||
$this->segmentsRepository->bulkDelete([$segment->getId()]);
|
||||
return $this->successResponse(null, ['count' => 1]);
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This list does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function duplicate($data = []) {
|
||||
$segment = $this->getSegment($data);
|
||||
|
||||
if ($segment instanceof SegmentEntity) {
|
||||
try {
|
||||
$duplicate = $this->segmentSavecontroller->duplicate($segment);
|
||||
} catch (Exception $e) {
|
||||
return $this->errorResponse([
|
||||
APIError::UNKNOWN => __('Duplicating of segment failed.', 'mailpoet'),
|
||||
], [], Response::STATUS_UNKNOWN);
|
||||
}
|
||||
return $this->successResponse(
|
||||
$this->segmentsResponseBuilder->build($duplicate),
|
||||
['count' => 1]
|
||||
);
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This list does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function synchronize($data) {
|
||||
try {
|
||||
if ($data['type'] === SegmentEntity::TYPE_WC_USERS) {
|
||||
$this->cronWorkerScheduler->scheduleImmediatelyIfNotRunning(WooCommerceSync::TASK_TYPE);
|
||||
} else {
|
||||
$this->wpSegment->synchronizeUsers();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return $this->errorResponse([
|
||||
$e->getCode() => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->successResponse(null);
|
||||
}
|
||||
|
||||
public function bulkAction($data = []) {
|
||||
$definition = $this->listingHandler->getListingDefinition($data['listing']);
|
||||
$ids = $this->segmentListingRepository->getActionableIds($definition);
|
||||
$count = 0;
|
||||
if ($data['action'] === 'trash') {
|
||||
$count = $this->segmentsRepository->bulkTrash($ids);
|
||||
} elseif ($data['action'] === 'restore') {
|
||||
$count = $this->segmentsRepository->bulkRestore($ids);
|
||||
} elseif ($data['action'] === 'delete') {
|
||||
$count = $this->segmentsRepository->bulkDelete($ids);
|
||||
} else {
|
||||
throw UnexpectedValueException::create()
|
||||
->withErrors([APIError::BAD_REQUEST => "Invalid bulk action '{$data['action']}' provided."]);
|
||||
}
|
||||
return $this->successResponse(null, ['count' => $count]);
|
||||
}
|
||||
|
||||
public function subscriberCount($data = []) {
|
||||
$segmentIds = $data['segmentIds'] ?? [];
|
||||
if (empty($segmentIds)) {
|
||||
return $this->errorResponse([
|
||||
APIError::BAD_REQUEST => __('No segment IDs provided.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
$filterSegmentId = $data['filterSegmentId'] ?? null;
|
||||
$status = $data['status'] ?? SubscriberEntity::STATUS_SUBSCRIBED;
|
||||
$response['count'] = $this->segmentSubscribersRepository->getSubscribersCountBySegmentIds($segmentIds, $status, $filterSegmentId);
|
||||
|
||||
return $this->successResponse($response);
|
||||
}
|
||||
|
||||
private function isTrashOrRestoreAllowed(SegmentEntity $segment): bool {
|
||||
$allowedSegmentTypes = [
|
||||
SegmentEntity::TYPE_DEFAULT,
|
||||
SegmentEntity::TYPE_WP_USERS,
|
||||
];
|
||||
if (in_array($segment->getType(), $allowedSegmentTypes, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getSegment(array $data): ?SegmentEntity {
|
||||
return isset($data['id'])
|
||||
? $this->segmentsRepository->findOneById((int)$data['id'])
|
||||
: null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\API\JSON\Response;
|
||||
use MailPoet\API\JSON\ResponseBuilders\SendingQueuesResponseBuilder;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Cron\ActionScheduler\Actions\DaemonTrigger;
|
||||
use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Cron\CronTrigger;
|
||||
use MailPoet\Cron\Triggers\WordPress;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Mailer\MailerFactory;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Newsletter\NewsletterValidator;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
|
||||
use MailPoet\Newsletter\Sending\SendingQueuesRepository;
|
||||
use MailPoet\Segments\SubscribersFinder;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
|
||||
class SendingQueue extends APIEndpoint {
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_EMAILS,
|
||||
];
|
||||
|
||||
/** @var SubscribersFeature */
|
||||
private $subscribersFeature;
|
||||
|
||||
/** @var SubscribersFinder */
|
||||
private $subscribersFinder;
|
||||
|
||||
/** @var NewslettersRepository */
|
||||
private $newsletterRepository;
|
||||
|
||||
/** @var SendingQueuesRepository */
|
||||
private $sendingQueuesRepository;
|
||||
|
||||
/** @var ScheduledTasksRepository */
|
||||
private $scheduledTasksRepository;
|
||||
|
||||
/** @var MailerFactory */
|
||||
private $mailerFactory;
|
||||
|
||||
/** @var NewsletterValidator */
|
||||
private $newsletterValidator;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
/** @var DaemonTrigger */
|
||||
private $actionSchedulerDaemonTriggerAction;
|
||||
|
||||
/** @var SendingQueuesResponseBuilder */
|
||||
private $sendingQueuesResponseBuilder;
|
||||
|
||||
/** @var CronHelper */
|
||||
private $cronHelper;
|
||||
|
||||
public function __construct(
|
||||
SubscribersFeature $subscribersFeature,
|
||||
NewslettersRepository $newsletterRepository,
|
||||
SendingQueuesRepository $sendingQueuesRepository,
|
||||
SubscribersFinder $subscribersFinder,
|
||||
ScheduledTasksRepository $scheduledTasksRepository,
|
||||
MailerFactory $mailerFactory,
|
||||
SettingsController $settings,
|
||||
DaemonTrigger $actionSchedulerDaemonTriggerAction,
|
||||
NewsletterValidator $newsletterValidator,
|
||||
SendingQueuesResponseBuilder $sendingQueuesResponseBuilder,
|
||||
CronHelper $cronHelper
|
||||
) {
|
||||
$this->subscribersFeature = $subscribersFeature;
|
||||
$this->subscribersFinder = $subscribersFinder;
|
||||
$this->newsletterRepository = $newsletterRepository;
|
||||
$this->sendingQueuesRepository = $sendingQueuesRepository;
|
||||
$this->scheduledTasksRepository = $scheduledTasksRepository;
|
||||
$this->mailerFactory = $mailerFactory;
|
||||
$this->settings = $settings;
|
||||
$this->actionSchedulerDaemonTriggerAction = $actionSchedulerDaemonTriggerAction;
|
||||
$this->newsletterValidator = $newsletterValidator;
|
||||
$this->sendingQueuesResponseBuilder = $sendingQueuesResponseBuilder;
|
||||
$this->cronHelper = $cronHelper;
|
||||
}
|
||||
|
||||
public function add($data = []) {
|
||||
if ($this->subscribersFeature->check()) {
|
||||
return $this->errorResponse([
|
||||
APIError::FORBIDDEN => __('Subscribers limit reached.', 'mailpoet'),
|
||||
], [], Response::STATUS_FORBIDDEN);
|
||||
}
|
||||
$newsletterId = (isset($data['newsletter_id'])
|
||||
? (int)$data['newsletter_id']
|
||||
: false
|
||||
);
|
||||
|
||||
// check that the newsletter exists
|
||||
$newsletter = $this->newsletterRepository->findOneById($newsletterId);
|
||||
$this->newsletterRepository->prefetchOptions([$newsletter]);
|
||||
|
||||
if (!$newsletter instanceof NewsletterEntity) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This newsletter does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$validationError = $this->newsletterValidator->validate($newsletter);
|
||||
if ($validationError) {
|
||||
return $this->errorResponse([
|
||||
APIError::BAD_REQUEST => $validationError,
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
// check that the sending method has been configured properly by verifying that default mailer can be build
|
||||
$this->mailerFactory->getDefaultMailer();
|
||||
|
||||
$sendingQueue = $this->sendingQueuesRepository->findOneByNewsletterAndTaskStatus($newsletter, null);
|
||||
|
||||
if ($sendingQueue instanceof SendingQueueEntity) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This newsletter is already being sent.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$sendingQueue = $this->sendingQueuesRepository->findOneByNewsletterAndTaskStatus($newsletter, ScheduledTaskEntity::STATUS_SCHEDULED);
|
||||
|
||||
if (is_null($sendingQueue)) {
|
||||
$scheduledTask = new ScheduledTaskEntity();
|
||||
$scheduledTask->setType(SendingQueueWorker::TASK_TYPE);
|
||||
$sendingQueue = new SendingQueueEntity();
|
||||
$sendingQueue->setNewsletter($newsletter);
|
||||
$sendingQueue->setTask($scheduledTask);
|
||||
|
||||
$this->sendingQueuesRepository->persist($sendingQueue);
|
||||
$this->newsletterRepository->refresh($newsletter);
|
||||
} else {
|
||||
$scheduledTask = $sendingQueue->getTask();
|
||||
}
|
||||
|
||||
if (!$scheduledTask instanceof ScheduledTaskEntity) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('Unable to find scheduled task associated with this newsletter.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$scheduledTask->setPriority(ScheduledTaskEntity::PRIORITY_MEDIUM);
|
||||
$this->scheduledTasksRepository->persist($scheduledTask);
|
||||
$this->scheduledTasksRepository->flush();
|
||||
|
||||
WordPress::resetRunInterval();
|
||||
if ((bool)$newsletter->getOptionValue('isScheduled')) {
|
||||
// set newsletter status
|
||||
$newsletter->setStatus(NewsletterEntity::STATUS_SCHEDULED);
|
||||
|
||||
// set scheduled task status
|
||||
$scheduledTask->setStatus(ScheduledTaskEntity::STATUS_SCHEDULED);
|
||||
$scheduledTask->setScheduledAt(new Carbon($newsletter->getOptionValue('scheduledAt')));
|
||||
} else {
|
||||
$segments = $newsletter->getSegmentIds();
|
||||
|
||||
$this->scheduledTasksRepository->refresh($scheduledTask);
|
||||
$this->subscribersFinder->addSubscribersToTaskFromSegments($scheduledTask, $segments, $newsletter->getFilterSegmentId());
|
||||
$subscribersCount = $scheduledTask->getSubscribers()->count();
|
||||
|
||||
if (!$subscribersCount) {
|
||||
return $this->errorResponse([
|
||||
APIError::UNKNOWN => __('There are no subscribers in that list!', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->sendingQueuesRepository->updateCounts($sendingQueue);
|
||||
$scheduledTask->setStatus(null);
|
||||
$scheduledTask->setScheduledAt(null);
|
||||
|
||||
// set newsletter status
|
||||
$newsletter->setStatus(NewsletterEntity::STATUS_SENDING);
|
||||
}
|
||||
$this->scheduledTasksRepository->persist($scheduledTask);
|
||||
$this->newsletterRepository->flush();
|
||||
|
||||
$this->triggerSending($newsletter);
|
||||
return $this->successResponse(
|
||||
($newsletter->getLatestQueue() instanceof SendingQueueEntity) ? $this->sendingQueuesResponseBuilder->build($newsletter->getLatestQueue()) : null
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return $this->errorResponse([
|
||||
$e->getCode() => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function pause($data = []) {
|
||||
$newsletterId = (isset($data['newsletter_id'])
|
||||
? (int)$data['newsletter_id']
|
||||
: false
|
||||
);
|
||||
$newsletter = $this->newsletterRepository->findOneById($newsletterId);
|
||||
|
||||
if ($newsletter instanceof NewsletterEntity) {
|
||||
$queue = $newsletter->getLastUpdatedQueue();
|
||||
|
||||
if (!$queue instanceof SendingQueueEntity) {
|
||||
return $this->errorResponse([
|
||||
APIError::UNKNOWN => __('This newsletter has not been sent yet.', 'mailpoet'),
|
||||
]);
|
||||
} else {
|
||||
$this->sendingQueuesRepository->pause($queue);
|
||||
return $this->successResponse();
|
||||
}
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This newsletter does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function resume($data = []) {
|
||||
if ($this->subscribersFeature->check()) {
|
||||
return $this->errorResponse([
|
||||
APIError::FORBIDDEN => __('Subscribers limit reached.', 'mailpoet'),
|
||||
], [], Response::STATUS_FORBIDDEN);
|
||||
}
|
||||
$newsletterId = (isset($data['newsletter_id'])
|
||||
? (int)$data['newsletter_id']
|
||||
: false
|
||||
);
|
||||
$newsletter = $this->newsletterRepository->findOneById($newsletterId);
|
||||
|
||||
if ($newsletter instanceof NewsletterEntity) {
|
||||
$queue = $newsletter->getLastUpdatedQueue();
|
||||
|
||||
if (!$queue instanceof SendingQueueEntity) {
|
||||
return $this->errorResponse([
|
||||
APIError::UNKNOWN => __('This newsletter has not been sent yet.', 'mailpoet'),
|
||||
]);
|
||||
} else {
|
||||
$this->sendingQueuesRepository->resume($queue);
|
||||
$this->triggerSending($newsletter);
|
||||
return $this->successResponse();
|
||||
}
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This newsletter does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function pingCron() {
|
||||
try {
|
||||
$cronPingResponse = $this->cronHelper->pingDaemon();
|
||||
} catch (\Exception $e) {
|
||||
return $this->errorResponse([
|
||||
APIError::UNKNOWN => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
if (!$this->cronHelper->validatePingResponse($cronPingResponse)) {
|
||||
return $this->errorResponse([
|
||||
APIError::UNKNOWN => $cronPingResponse,
|
||||
]);
|
||||
}
|
||||
return $this->successResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* In case the newsletter was switched to sending trigger the background job immediately.
|
||||
* This is done so that user immediately sees that email is sending and doesn't have to wait on WP Cron to start it.
|
||||
*/
|
||||
private function triggerSending(NewsletterEntity $newsletter): void {
|
||||
if (
|
||||
$newsletter->getStatus() === NewsletterEntity::STATUS_SENDING
|
||||
&& $this->settings->get('cron_trigger.method') === CronTrigger::METHOD_ACTION_SCHEDULER
|
||||
) {
|
||||
$this->actionSchedulerDaemonTriggerAction->process();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\API\JSON\Response;
|
||||
use MailPoet\API\JSON\ResponseBuilders\ScheduledTaskSubscriberResponseBuilder;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Listing;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTaskSubscribersListingRepository;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTaskSubscribersRepository;
|
||||
use MailPoet\Newsletter\Sending\SendingQueuesRepository;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class SendingTaskSubscribers extends APIEndpoint {
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_EMAILS,
|
||||
];
|
||||
|
||||
/** @var Listing\Handler */
|
||||
private $listingHandler;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
/** @var CronHelper */
|
||||
private $cronHelper;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var SendingQueuesRepository */
|
||||
private $sendingQueuesRepository;
|
||||
|
||||
/** @var ScheduledTaskSubscribersRepository */
|
||||
private $scheduledTaskSubscribersRepository;
|
||||
|
||||
/** @var ScheduledTaskSubscribersListingRepository */
|
||||
private $taskSubscribersListingRepository;
|
||||
|
||||
/** @var ScheduledTaskSubscriberResponseBuilder */
|
||||
private $scheduledTaskSubscriberResponseBuilder;
|
||||
|
||||
public function __construct(
|
||||
Listing\Handler $listingHandler,
|
||||
SettingsController $settings,
|
||||
CronHelper $cronHelper,
|
||||
SendingQueuesRepository $sendingQueuesRepository,
|
||||
ScheduledTaskSubscribersListingRepository $taskSubscribersListingRepository,
|
||||
ScheduledTaskSubscriberResponseBuilder $scheduledTaskSubscriberResponseBuilder,
|
||||
ScheduledTaskSubscribersRepository $scheduledTaskSubscribersRepository,
|
||||
WPFunctions $wp
|
||||
) {
|
||||
$this->listingHandler = $listingHandler;
|
||||
$this->settings = $settings;
|
||||
$this->cronHelper = $cronHelper;
|
||||
$this->sendingQueuesRepository = $sendingQueuesRepository;
|
||||
$this->taskSubscribersListingRepository = $taskSubscribersListingRepository;
|
||||
$this->scheduledTaskSubscriberResponseBuilder = $scheduledTaskSubscriberResponseBuilder;
|
||||
$this->scheduledTaskSubscribersRepository = $scheduledTaskSubscribersRepository;
|
||||
$this->wp = $wp;
|
||||
}
|
||||
|
||||
public function listing($data = []) {
|
||||
$newsletterId = !empty($data['params']['id']) ? (int)$data['params']['id'] : false;
|
||||
if (empty($newsletterId)) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('Newsletter not found!', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
$tasksIds = $this->sendingQueuesRepository->getTaskIdsByNewsletterId($newsletterId);
|
||||
|
||||
if (empty($tasksIds)) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This email has not been sent yet.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
$data['params']['task_ids'] = $tasksIds;
|
||||
$definition = $this->listingHandler->getListingDefinition($data);
|
||||
$items = $this->taskSubscribersListingRepository->getData($definition);
|
||||
$groups = $this->taskSubscribersListingRepository->getGroups($definition);
|
||||
$filters = $this->taskSubscribersListingRepository->getFilters($definition);
|
||||
$count = $this->taskSubscribersListingRepository->getCount($definition);
|
||||
|
||||
return $this->successResponse($this->scheduledTaskSubscriberResponseBuilder->buildForListing($items), [
|
||||
'count' => $count,
|
||||
'filters' => $filters,
|
||||
'groups' => $groups,
|
||||
'mta_log' => $this->settings->get('mta_log'),
|
||||
'mta_method' => $this->settings->get('mta.method'),
|
||||
'cron_accessible' => $this->cronHelper->isDaemonAccessible(),
|
||||
'current_time' => $this->wp->currentTime('mysql'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function resend($data = []) {
|
||||
$taskId = !empty($data['taskId']) ? (int)$data['taskId'] : 0;
|
||||
$subscriberId = !empty($data['subscriberId']) ? (int)$data['subscriberId'] : 0;
|
||||
|
||||
$taskSubscriber = $this->scheduledTaskSubscribersRepository->findOneBy([
|
||||
'task' => $taskId,
|
||||
'subscriber' => $subscriberId,
|
||||
'failed' => 1,
|
||||
]);
|
||||
|
||||
$sendingQueue = $this->sendingQueuesRepository->findOneBy(['task' => $taskId]);
|
||||
|
||||
if (
|
||||
!$taskSubscriber
|
||||
|| !$taskSubscriber->getTask()
|
||||
|| !$sendingQueue
|
||||
) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('Failed sending task not found!', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$newsletter = $sendingQueue->getNewsletter();
|
||||
if (!$newsletter) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('Newsletter not found!', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($newsletter->canBeSetActive() && $newsletter->getStatus() !== NewsletterEntity::STATUS_ACTIVE) {
|
||||
return $this->errorResponse([
|
||||
// translators: This error occurs when resending a failed email message to a recipient and the associated email definition (e.g., a welcome email, an automation email) is inactive.
|
||||
APIError::BAD_REQUEST => __('Failed to resend! The email is not active. Please activate it first.', 'mailpoet'),
|
||||
], [], Response::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$taskSubscriber->resetToUnprocessed();
|
||||
$taskSubscriber->getTask()->setStatus(null);
|
||||
if (!$newsletter->canBeSetActive()) {
|
||||
$newsletter->setStatus(NewsletterEntity::STATUS_SENDING);
|
||||
}
|
||||
// Each repository flushes all changes
|
||||
$this->scheduledTaskSubscribersRepository->flush();
|
||||
return $this->successResponse([]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Analytics\Analytics as AnalyticsHelper;
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\API\JSON\Response;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Config\Installer;
|
||||
use MailPoet\Config\ServicesChecker;
|
||||
use MailPoet\Cron\Workers\KeyCheck\PremiumKeyCheck;
|
||||
use MailPoet\Cron\Workers\KeyCheck\SendingServiceKeyCheck;
|
||||
use MailPoet\Mailer\MailerLog;
|
||||
use MailPoet\Services\AuthorizedEmailsController;
|
||||
use MailPoet\Services\AuthorizedSenderDomainController;
|
||||
use MailPoet\Services\Bridge;
|
||||
use MailPoet\Services\CongratulatoryMssEmailController;
|
||||
use MailPoet\Services\SubscribersCountReporter;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\WP\DateTime;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class Services extends APIEndpoint {
|
||||
/** @var Bridge */
|
||||
private $bridge;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
/** @var AnalyticsHelper */
|
||||
private $analytics;
|
||||
|
||||
/** @var DateTime */
|
||||
public $dateTime;
|
||||
|
||||
/** @var SendingServiceKeyCheck */
|
||||
private $mssWorker;
|
||||
|
||||
/** @var PremiumKeyCheck */
|
||||
private $premiumWorker;
|
||||
|
||||
/** @var ServicesChecker */
|
||||
private $servicesChecker;
|
||||
|
||||
/** @var CongratulatoryMssEmailController */
|
||||
private $congratulatoryMssEmailController;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var AuthorizedSenderDomainController */
|
||||
private $senderDomainController;
|
||||
|
||||
/** @var AuthorizedEmailsController */
|
||||
private $authorizedEmailsController;
|
||||
|
||||
/** @var SubscribersCountReporter */
|
||||
private $subscribersCountReporter;
|
||||
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_SETTINGS,
|
||||
'methods' => ['pingBridge' => AccessControl::PERMISSION_ACCESS_PLUGIN_ADMIN],
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
Bridge $bridge,
|
||||
SettingsController $settings,
|
||||
AnalyticsHelper $analytics,
|
||||
SendingServiceKeyCheck $mssWorker,
|
||||
PremiumKeyCheck $premiumWorker,
|
||||
ServicesChecker $servicesChecker,
|
||||
SubscribersCountReporter $subscribersCountReporter,
|
||||
CongratulatoryMssEmailController $congratulatoryMssEmailController,
|
||||
WPFunctions $wp,
|
||||
AuthorizedSenderDomainController $senderDomainController,
|
||||
AuthorizedEmailsController $authorizedEmailsController
|
||||
) {
|
||||
$this->bridge = $bridge;
|
||||
$this->settings = $settings;
|
||||
$this->analytics = $analytics;
|
||||
$this->mssWorker = $mssWorker;
|
||||
$this->premiumWorker = $premiumWorker;
|
||||
$this->dateTime = new DateTime();
|
||||
$this->servicesChecker = $servicesChecker;
|
||||
$this->subscribersCountReporter = $subscribersCountReporter;
|
||||
$this->congratulatoryMssEmailController = $congratulatoryMssEmailController;
|
||||
$this->wp = $wp;
|
||||
$this->senderDomainController = $senderDomainController;
|
||||
$this->authorizedEmailsController = $authorizedEmailsController;
|
||||
}
|
||||
|
||||
public function checkMSSKey($data = []) {
|
||||
$key = isset($data['key']) ? trim($data['key']) : null;
|
||||
|
||||
if (!$key) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => __('Please specify a key.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$wasPendingApproval = $this->servicesChecker->isMailPoetAPIKeyPendingApproval();
|
||||
|
||||
try {
|
||||
$result = $this->bridge->checkMSSKey($key);
|
||||
$this->bridge->storeMSSKeyAndState($key, $result);
|
||||
} catch (\Exception $e) {
|
||||
return $this->errorResponse([
|
||||
$e->getCode() => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
// pause sending when key is pending approval, resume when not pending anymore
|
||||
$isPendingApproval = $this->servicesChecker->isMailPoetAPIKeyPendingApproval();
|
||||
if (!$wasPendingApproval && $isPendingApproval) {
|
||||
MailerLog::pauseSending(MailerLog::getMailerLog());
|
||||
} elseif ($wasPendingApproval && !$isPendingApproval) {
|
||||
MailerLog::resumeSending();
|
||||
}
|
||||
|
||||
$state = !empty($result['state']) ? $result['state'] : null;
|
||||
|
||||
$successMessage = null;
|
||||
if ($state == Bridge::KEY_VALID) {
|
||||
$successMessage = __('Your MailPoet Sending Service key has been successfully validated', 'mailpoet');
|
||||
} else if ($state == Bridge::KEY_VALID_UNDERPRIVILEGED) {
|
||||
$successMessage = __('Your Premium key has been successfully validated, but is not valid for MailPoet Sending Service', 'mailpoet');
|
||||
} elseif ($state == Bridge::KEY_EXPIRING) {
|
||||
$successMessage = sprintf(
|
||||
// translators: %s is the expiration date.
|
||||
__('Your MailPoet Sending Service key expires on %s!', 'mailpoet'),
|
||||
$this->dateTime->formatDate(strtotime($result['data']['expire_at']))
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($result['data']['public_id'])) {
|
||||
$this->analytics->setPublicId($result['data']['public_id']);
|
||||
}
|
||||
|
||||
if ($successMessage) {
|
||||
return $this->successResponse(['message' => $successMessage, 'state' => $state, 'result' => $result]);
|
||||
}
|
||||
|
||||
switch ($state) {
|
||||
case Bridge::KEY_INVALID:
|
||||
$error = __('Your key is not valid for the MailPoet Sending Service', 'mailpoet');
|
||||
break;
|
||||
case Bridge::KEY_ALREADY_USED:
|
||||
$error = __('Your MailPoet Sending Service key is already <a>used on another site</a>', 'mailpoet'); // we will use createInterpolateElement to replace <a> element
|
||||
break;
|
||||
default:
|
||||
$code = !empty($result['code']) ? $result['code'] : Bridge::CHECK_ERROR_UNKNOWN;
|
||||
// translators: %s is the error message.
|
||||
$errorMessage = __('Error validating MailPoet Sending Service key, please try again later (%s).', 'mailpoet');
|
||||
// If site runs on localhost
|
||||
if (1 === preg_match("/^(http|https)\:\/\/(localhost|127\.0\.0\.1)/", $this->wp->siteUrl())) {
|
||||
$errorMessage .= ' ' . __("Note that it doesn't work on localhost.", 'mailpoet');
|
||||
}
|
||||
$error = sprintf(
|
||||
$errorMessage,
|
||||
$this->getErrorDescriptionByCode($code)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->errorResponse([APIError::BAD_REQUEST => $error]);
|
||||
}
|
||||
|
||||
public function checkPremiumKey($data = []) {
|
||||
$key = isset($data['key']) ? trim($data['key']) : null;
|
||||
|
||||
if (!$key) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => __('Please specify a key.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->bridge->checkPremiumKey($key);
|
||||
$this->bridge->storePremiumKeyAndState($key, $result);
|
||||
} catch (\Exception $e) {
|
||||
return $this->errorResponse([
|
||||
$e->getCode() => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
$state = !empty($result['state']) ? $result['state'] : null;
|
||||
|
||||
$successMessage = null;
|
||||
if ($state == Bridge::KEY_VALID) {
|
||||
$successMessage = __('Your Premium key has been successfully validated', 'mailpoet');
|
||||
} else if ($state == Bridge::KEY_VALID_UNDERPRIVILEGED) {
|
||||
$successMessage = __('Your Premium key has been successfully validated, but is not valid for MailPoet Sending Service', 'mailpoet');
|
||||
} elseif ($state == Bridge::KEY_EXPIRING) {
|
||||
$successMessage = sprintf(
|
||||
// translators: %s is the expiration date.
|
||||
__('Your Premium key expires on %s', 'mailpoet'),
|
||||
$this->dateTime->formatDate(strtotime($result['data']['expire_at']))
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($result['data']['public_id'])) {
|
||||
$this->analytics->setPublicId($result['data']['public_id']);
|
||||
}
|
||||
|
||||
if ($successMessage) {
|
||||
return $this->successResponse(
|
||||
['message' => $successMessage, 'state' => $state, 'result' => $result],
|
||||
Installer::getPremiumStatus()
|
||||
);
|
||||
}
|
||||
|
||||
switch ($state) {
|
||||
case Bridge::KEY_INVALID:
|
||||
$error = __('Your key is not valid for MailPoet Premium', 'mailpoet');
|
||||
break;
|
||||
case Bridge::KEY_ALREADY_USED:
|
||||
$error = __('Your Premium key is already <a>used on another site</a>', 'mailpoet'); // we will use createInterpolateElement to replace <a> element
|
||||
break;
|
||||
default:
|
||||
$code = !empty($result['code']) ? $result['code'] : Bridge::CHECK_ERROR_UNKNOWN;
|
||||
$error = sprintf(
|
||||
// translators: %s is the error message.
|
||||
__('Error validating Premium key, please try again later (%s)', 'mailpoet'),
|
||||
$this->getErrorDescriptionByCode($code)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->errorResponse(
|
||||
[APIError::BAD_REQUEST => $error],
|
||||
['code' => $result['code'] ?? null]
|
||||
);
|
||||
}
|
||||
|
||||
public function recheckKeys() {
|
||||
// Report subscribers count before rechecking keys so that shop can lift access restrictions in case
|
||||
// user deleted subscribers and no longer exceeds the limit.
|
||||
$key = $this->servicesChecker->getValidAccountKey();
|
||||
if ($key) {
|
||||
$this->subscribersCountReporter->report($key);
|
||||
}
|
||||
$this->mssWorker->init();
|
||||
$mssCheck = $this->mssWorker->checkKey();
|
||||
$this->premiumWorker->init();
|
||||
$premiumCheck = $this->premiumWorker->checkKey();
|
||||
// continue sending when it is paused and states are valid
|
||||
$mailerLog = MailerLog::getMailerLog();
|
||||
if (
|
||||
(isset($mailerLog['status']) && $mailerLog['status'] === MailerLog::STATUS_PAUSED)
|
||||
&& (isset($mssCheck['state']) && $mssCheck['state'] === Bridge::KEY_VALID)
|
||||
&& (isset($premiumCheck['state']) && $premiumCheck['state'] === Bridge::PREMIUM_KEY_VALID)
|
||||
) {
|
||||
MailerLog::resumeSending();
|
||||
}
|
||||
return $this->successResponse();
|
||||
}
|
||||
|
||||
public function sendCongratulatoryMssEmail() {
|
||||
if (!Bridge::isMPSendingServiceEnabled()) {
|
||||
return $this->createBadRequest(__('MailPoet Sending Service is not active.', 'mailpoet'));
|
||||
}
|
||||
|
||||
$fromEmail = $this->settings->get('sender.address');
|
||||
if (!$fromEmail) {
|
||||
return $this->createBadRequest(__('Sender email address is not set.', 'mailpoet'));
|
||||
}
|
||||
|
||||
$verifiedDomains = $this->senderDomainController->getVerifiedSenderDomainsIgnoringCache();
|
||||
|
||||
$emailDomain = Helpers::extractEmailDomain($fromEmail);
|
||||
|
||||
if (!$this->isItemInArray($emailDomain, $verifiedDomains)) {
|
||||
$authorizedEmails = $this->authorizedEmailsController->getAuthorizedEmailAddresses();
|
||||
|
||||
if (!$authorizedEmails) {
|
||||
return $this->createBadRequest(__('No FROM email addresses are authorized.', 'mailpoet'));
|
||||
}
|
||||
|
||||
if (!$this->isItemInArray($fromEmail, $authorizedEmails)) {
|
||||
// translators: %s is the email address, which is not authorized.
|
||||
return $this->createBadRequest(sprintf(__("Sender email address '%s' is not authorized.", 'mailpoet'), $fromEmail));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// congratulatory email is sent to the current FROM address (authorized at this point)
|
||||
$this->congratulatoryMssEmailController->sendCongratulatoryEmail($fromEmail);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->errorResponse([
|
||||
APIError::UNKNOWN => __('Sending of congratulatory email failed.', 'mailpoet'),
|
||||
], [], Response::STATUS_UNKNOWN);
|
||||
}
|
||||
return $this->successResponse([
|
||||
'email_address' => $fromEmail,
|
||||
]);
|
||||
}
|
||||
|
||||
public function pingBridge() {
|
||||
$response = $this->bridge->pingBridge();
|
||||
if ($this->wp->isWpError($response)) {
|
||||
/** @var \WP_Error $response */
|
||||
$errorDesc = $this->getErrorDescriptionByCode(Bridge::CHECK_ERROR_UNKNOWN);
|
||||
return $this->errorResponse([
|
||||
APIError::UNKNOWN => "{$errorDesc}: {$response->get_error_message()}",
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$this->bridge->validateBridgePingResponse($response)) {
|
||||
$code = $this->wp->wpRemoteRetrieveResponseCode($response) ?: Bridge::CHECK_ERROR_UNKNOWN;
|
||||
return $this->errorResponse([
|
||||
APIError::UNKNOWN => $this->getErrorDescriptionByCode($code),
|
||||
]);
|
||||
}
|
||||
return $this->successResponse();
|
||||
}
|
||||
|
||||
public function refreshMSSKeyStatus() {
|
||||
$key = $this->settings->get('mta.mailpoet_api_key');
|
||||
return $this->checkMSSKey(['key' => $key]);
|
||||
}
|
||||
|
||||
public function refreshPremiumKeyStatus() {
|
||||
$key = $this->settings->get('premium.premium_key');
|
||||
return $this->checkPremiumKey(['key' => $key]);
|
||||
}
|
||||
|
||||
private function isItemInArray($item, $array): bool {
|
||||
return in_array($item, $array, true);
|
||||
}
|
||||
|
||||
private function getErrorDescriptionByCode($code) {
|
||||
switch ($code) {
|
||||
case Bridge::CHECK_ERROR_UNAVAILABLE:
|
||||
$text = __('Service unavailable', 'mailpoet');
|
||||
break;
|
||||
case Bridge::CHECK_ERROR_UNKNOWN:
|
||||
$text = __('Contact your hosting support to check the connection between your host and https://bridge.mailpoet.com', 'mailpoet');
|
||||
break;
|
||||
default:
|
||||
// translators: %s is the code.
|
||||
$text = sprintf(_x('code: %s', 'Error code (inside parentheses)', 'mailpoet'), $code);
|
||||
break;
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private function createBadRequest(string $message) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => $message,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,499 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\API\JSON\ErrorResponse;
|
||||
use MailPoet\API\JSON\Response;
|
||||
use MailPoet\API\JSON\SuccessResponse;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Config\ServicesChecker;
|
||||
use MailPoet\Cron\Workers\SubscribersEngagementScore;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Form\FormMessageController;
|
||||
use MailPoet\Mailer\Mailer;
|
||||
use MailPoet\Mailer\MailerLog;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Services\AuthorizedEmailsController;
|
||||
use MailPoet\Services\AuthorizedSenderDomainController;
|
||||
use MailPoet\Services\Bridge;
|
||||
use MailPoet\Settings\SettingsChangeHandler;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Settings\TrackingConfig;
|
||||
use MailPoet\Statistics\StatisticsOpensRepository;
|
||||
use MailPoet\Subscribers\ConfirmationEmailCustomizer;
|
||||
use MailPoet\Subscribers\SubscribersCountsController;
|
||||
use MailPoet\Util\Notices\DisabledMailFunctionNotice;
|
||||
use MailPoet\WooCommerce\TransactionalEmails;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
||||
|
||||
class Settings extends APIEndpoint {
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
/** @var Bridge */
|
||||
private $bridge;
|
||||
|
||||
/** @var AuthorizedEmailsController */
|
||||
private $authorizedEmailsController;
|
||||
|
||||
/** @var AuthorizedSenderDomainController */
|
||||
private $senderDomainController;
|
||||
|
||||
/** @var TransactionalEmails */
|
||||
private $wcTransactionalEmails;
|
||||
|
||||
/** @var ServicesChecker */
|
||||
private $servicesChecker;
|
||||
|
||||
/** @var EntityManager */
|
||||
private $entityManager;
|
||||
|
||||
/** @var StatisticsOpensRepository */
|
||||
private $statisticsOpensRepository;
|
||||
|
||||
/** @var ScheduledTasksRepository */
|
||||
private $scheduledTasksRepository;
|
||||
|
||||
/** @var FormMessageController */
|
||||
private $messageController;
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentsRepository;
|
||||
|
||||
/** @var SubscribersCountsController */
|
||||
private $subscribersCountsController;
|
||||
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_SETTINGS,
|
||||
];
|
||||
/** @var NewslettersRepository */
|
||||
private $newsletterRepository;
|
||||
|
||||
/** @var TrackingConfig */
|
||||
private $trackingConfig;
|
||||
|
||||
/** @var SettingsChangeHandler */
|
||||
private $settingsChangeHandler;
|
||||
|
||||
/** @var ConfirmationEmailCustomizer */
|
||||
private $confirmationEmailCustomizer;
|
||||
|
||||
public function __construct(
|
||||
SettingsController $settings,
|
||||
Bridge $bridge,
|
||||
AuthorizedEmailsController $authorizedEmailsController,
|
||||
AuthorizedSenderDomainController $senderDomainController,
|
||||
TransactionalEmails $wcTransactionalEmails,
|
||||
EntityManager $entityManager,
|
||||
NewslettersRepository $newslettersRepository,
|
||||
StatisticsOpensRepository $statisticsOpensRepository,
|
||||
ScheduledTasksRepository $scheduledTasksRepository,
|
||||
FormMessageController $messageController,
|
||||
ServicesChecker $servicesChecker,
|
||||
SegmentsRepository $segmentsRepository,
|
||||
SettingsChangeHandler $settingsChangeHandler,
|
||||
SubscribersCountsController $subscribersCountsController,
|
||||
TrackingConfig $trackingConfig,
|
||||
ConfirmationEmailCustomizer $confirmationEmailCustomizer
|
||||
) {
|
||||
$this->settings = $settings;
|
||||
$this->bridge = $bridge;
|
||||
$this->authorizedEmailsController = $authorizedEmailsController;
|
||||
$this->senderDomainController = $senderDomainController;
|
||||
$this->wcTransactionalEmails = $wcTransactionalEmails;
|
||||
$this->servicesChecker = $servicesChecker;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->newsletterRepository = $newslettersRepository;
|
||||
$this->statisticsOpensRepository = $statisticsOpensRepository;
|
||||
$this->scheduledTasksRepository = $scheduledTasksRepository;
|
||||
$this->messageController = $messageController;
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
$this->settingsChangeHandler = $settingsChangeHandler;
|
||||
$this->subscribersCountsController = $subscribersCountsController;
|
||||
$this->trackingConfig = $trackingConfig;
|
||||
$this->confirmationEmailCustomizer = $confirmationEmailCustomizer;
|
||||
}
|
||||
|
||||
public function get() {
|
||||
return $this->successResponse($this->settings->getAll());
|
||||
}
|
||||
|
||||
public function set($settings = []) {
|
||||
if (empty($settings)) {
|
||||
return $this->badRequest(
|
||||
[
|
||||
APIError::BAD_REQUEST =>
|
||||
__('You have not specified any settings to be saved.', 'mailpoet'),
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$oldSettings = $this->settings->getAll();
|
||||
$meta = [];
|
||||
$signupConfirmation = $this->settings->get('signup_confirmation.enabled');
|
||||
foreach ($settings as $name => $value) {
|
||||
$this->settings->set($name, $value);
|
||||
}
|
||||
|
||||
$this->onSettingsChange($oldSettings, $this->settings->getAll());
|
||||
|
||||
// when pending approval, leave this to cron / Key Activation tab logic
|
||||
if (!$this->servicesChecker->isMailPoetAPIKeyPendingApproval()) {
|
||||
$this->settingsChangeHandler->updateApiKeyState($settings);
|
||||
}
|
||||
|
||||
$meta = $this->authorizedEmailsController->onSettingsSave($settings);
|
||||
if ($signupConfirmation !== $this->settings->get('signup_confirmation.enabled')) {
|
||||
$this->messageController->updateSuccessMessages();
|
||||
}
|
||||
|
||||
// Tracking and re-engagement Emails
|
||||
$meta['showNotice'] = false;
|
||||
if ($oldSettings['tracking'] !== $this->settings->get('tracking')) {
|
||||
try {
|
||||
$meta = $this->updateReEngagementEmailStatus($this->settings->get('tracking'));
|
||||
} catch (\Exception $e) {
|
||||
return $this->badRequest([
|
||||
APIError::UNKNOWN => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->successResponse($this->settings->getAll(), $meta);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(string $settingName): Response {
|
||||
if (empty($settingName)) {
|
||||
return $this->badRequest(
|
||||
[
|
||||
APIError::BAD_REQUEST =>
|
||||
__('You have not specified any setting to be deleted.', 'mailpoet'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$setting = $this->settings->get($settingName);
|
||||
|
||||
if (is_null($setting)) {
|
||||
return $this->badRequest(
|
||||
[
|
||||
APIError::BAD_REQUEST =>
|
||||
__('Setting doesn\'t exist.', 'mailpoet'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$this->settings->delete($settingName);
|
||||
|
||||
return $this->successResponse();
|
||||
}
|
||||
|
||||
public function recalculateSubscribersScore() {
|
||||
$this->statisticsOpensRepository->resetSubscribersScoreCalculation();
|
||||
$this->statisticsOpensRepository->resetSegmentsScoreCalculation();
|
||||
$task = $this->scheduledTasksRepository->findOneBy([
|
||||
'type' => SubscribersEngagementScore::TASK_TYPE,
|
||||
'status' => ScheduledTaskEntity::STATUS_SCHEDULED,
|
||||
]);
|
||||
if (!$task) {
|
||||
$task = new ScheduledTaskEntity();
|
||||
$task->setType(SubscribersEngagementScore::TASK_TYPE);
|
||||
$task->setStatus(ScheduledTaskEntity::STATUS_SCHEDULED);
|
||||
}
|
||||
$task->setScheduledAt(Carbon::now()->millisecond(0));
|
||||
$this->entityManager->persist($task);
|
||||
$this->entityManager->flush();
|
||||
return $this->successResponse();
|
||||
}
|
||||
|
||||
public function setAuthorizedFromAddress($data = []) {
|
||||
$address = $data['address'] ?? null;
|
||||
if (!$address) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => __('No email address specified.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
$address = trim($address);
|
||||
|
||||
try {
|
||||
$this->authorizedEmailsController->setFromEmailAddress($address);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return $this->badRequest([
|
||||
APIError::UNAUTHORIZED => __('Can’t use this email yet! Please authorize it first.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$this->servicesChecker->isMailPoetAPIKeyPendingApproval()) {
|
||||
MailerLog::resumeSending();
|
||||
}
|
||||
return $this->successResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create POST request to Bridge endpoint to add email to user email authorization list
|
||||
*/
|
||||
public function authorizeSenderEmailAddress($data = []) {
|
||||
$emailAddress = $data['email'] ?? null;
|
||||
|
||||
if (!$emailAddress) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => __('No email address specified.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$emailAddress = trim($emailAddress);
|
||||
|
||||
try {
|
||||
$response = $this->authorizedEmailsController->createAuthorizedEmailAddress($emailAddress);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
if (
|
||||
$e->getMessage() === AuthorizedEmailsController::AUTHORIZED_EMAIL_ERROR_ALREADY_AUTHORIZED ||
|
||||
$e->getMessage() === AuthorizedEmailsController::AUTHORIZED_EMAIL_ERROR_PENDING_CONFIRMATION
|
||||
) {
|
||||
// return true if the email is already authorized or pending confirmation
|
||||
$response = ['status' => true];
|
||||
} else {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->successResponse($response);
|
||||
}
|
||||
|
||||
public function confirmSenderEmailAddressIsAuthorized($data = []) {
|
||||
$emailAddress = $data['email'] ?? null;
|
||||
|
||||
if (!$emailAddress) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => __('No email address specified.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$emailAddress = trim($emailAddress);
|
||||
|
||||
$response = ['isAuthorized' => $this->authorizedEmailsController->isEmailAddressAuthorized($emailAddress)];
|
||||
|
||||
return $this->successResponse($response);
|
||||
}
|
||||
|
||||
public function getAuthorizedSenderDomains($data = []) {
|
||||
$domain = $data['domain'] ?? null;
|
||||
|
||||
if (!$domain) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => __('No sender domain specified.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$domain = strtolower(trim($domain));
|
||||
|
||||
$records = $this->bridge->getAuthorizedSenderDomains($domain);
|
||||
return $this->successResponse($records);
|
||||
}
|
||||
|
||||
public function createAuthorizedSenderDomain($data = []) {
|
||||
$domain = $data['domain'] ?? null;
|
||||
|
||||
if (!$domain) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => __('No sender domain specified.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$domain = strtolower(trim($domain));
|
||||
|
||||
try {
|
||||
$response = $this->senderDomainController->createAuthorizedSenderDomain($domain);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
if (
|
||||
$e->getMessage() === AuthorizedSenderDomainController::AUTHORIZED_SENDER_DOMAIN_ERROR_ALREADY_CREATED
|
||||
) {
|
||||
// domain already created
|
||||
$response = $this->senderDomainController->getDomainRecords($domain);
|
||||
} else {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->successResponse($response);
|
||||
}
|
||||
|
||||
public function verifyAuthorizedSenderDomain($data = []) {
|
||||
$domain = $data['domain'] ?? null;
|
||||
|
||||
if (!$domain) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => __('No sender domain specified.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$domain = strtolower(trim($domain));
|
||||
|
||||
try {
|
||||
$response = $this->senderDomainController->verifyAuthorizedSenderDomain($domain);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
if (
|
||||
$e->getMessage() === AuthorizedSenderDomainController::AUTHORIZED_SENDER_DOMAIN_ERROR_ALREADY_VERIFIED
|
||||
) {
|
||||
// domain already verified, we have to wrap this in the format returned by the api
|
||||
$response = ['ok' => true, 'dns' => $this->senderDomainController->getDomainRecords($domain)];
|
||||
} else {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$response['ok']) {
|
||||
// sender domain verification error. probably an improper setup
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => $response['message'] ?? __('Sender domain verification failed.', 'mailpoet'),
|
||||
], $response);
|
||||
}
|
||||
|
||||
return $this->successResponse($response);
|
||||
}
|
||||
|
||||
private function onSettingsChange($oldSettings, $newSettings) {
|
||||
// Recalculate inactive subscribers
|
||||
$oldInactivationInterval = $oldSettings['deactivate_subscriber_after_inactive_days'];
|
||||
$newInactivationInterval = $newSettings['deactivate_subscriber_after_inactive_days'];
|
||||
if ($oldInactivationInterval !== $newInactivationInterval) {
|
||||
$this->settingsChangeHandler->onInactiveSubscribersIntervalChange();
|
||||
}
|
||||
|
||||
$oldSendingMethod = $oldSettings['mta_group'];
|
||||
$newSendingMethod = $newSettings['mta_group'];
|
||||
if (($oldSendingMethod !== $newSendingMethod) && ($newSendingMethod === 'mailpoet')) {
|
||||
$this->settingsChangeHandler->onMSSActivate($newSettings);
|
||||
}
|
||||
|
||||
if (($oldSendingMethod !== $newSendingMethod)) {
|
||||
$sendingMethodSet = $newSettings['mta']['method'] ?? null;
|
||||
if ($sendingMethodSet === 'PHPMail') {
|
||||
// check for valid mail function
|
||||
$this->settings->set(DisabledMailFunctionNotice::QUEUE_DISABLED_MAIL_FUNCTION_CHECK, true);
|
||||
} else {
|
||||
// when the user switch to a new sending method
|
||||
// do not display the DisabledMailFunctionNotice
|
||||
$this->settings->set(DisabledMailFunctionNotice::QUEUE_DISABLED_MAIL_FUNCTION_CHECK, false);
|
||||
$this->settings->set(DisabledMailFunctionNotice::DISABLED_MAIL_FUNCTION_CHECK, false); // do not display notice
|
||||
}
|
||||
}
|
||||
|
||||
// Sync WooCommerce Customers list
|
||||
$oldSubscribeOldWoocommerceCustomers = isset($oldSettings['mailpoet_subscribe_old_woocommerce_customers']['enabled'])
|
||||
? $oldSettings['mailpoet_subscribe_old_woocommerce_customers']['enabled']
|
||||
: '0';
|
||||
$newSubscribeOldWoocommerceCustomers = isset($newSettings['mailpoet_subscribe_old_woocommerce_customers']['enabled'])
|
||||
? $newSettings['mailpoet_subscribe_old_woocommerce_customers']['enabled']
|
||||
: '0';
|
||||
if ($oldSubscribeOldWoocommerceCustomers !== $newSubscribeOldWoocommerceCustomers) {
|
||||
$this->settingsChangeHandler->onSubscribeOldWoocommerceCustomersChange();
|
||||
}
|
||||
|
||||
if (!empty($newSettings['woocommerce']['use_mailpoet_editor'])) {
|
||||
$this->wcTransactionalEmails->init();
|
||||
}
|
||||
|
||||
if (!empty($newSettings['signup_confirmation']['use_mailpoet_editor'])) {
|
||||
$this->confirmationEmailCustomizer->init();
|
||||
}
|
||||
}
|
||||
|
||||
public function recalculateSubscribersCountsCache() {
|
||||
$segments = $this->segmentsRepository->findAll();
|
||||
foreach ($segments as $segment) {
|
||||
$this->subscribersCountsController->recalculateSegmentStatisticsCache($segment);
|
||||
}
|
||||
$this->subscribersCountsController->recalculateSubscribersWithoutSegmentStatisticsCache();
|
||||
// remove redundancies from cache
|
||||
$this->subscribersCountsController->removeRedundancyFromStatisticsCache();
|
||||
return $this->successResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function updateReEngagementEmailStatus($newTracking): array {
|
||||
if (!empty($newTracking['level']) && $this->trackingConfig->isEmailTrackingEnabled($newTracking['level'])) {
|
||||
return $this->reactivateReEngagementEmails();
|
||||
}
|
||||
try {
|
||||
return $this->deactivateReEngagementEmails();
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception(
|
||||
sprintf(
|
||||
// translators: %s is the error message.
|
||||
__('Unable to deactivate re-engagement emails: %s', 'mailpoet'),
|
||||
$e->getMessage()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function deactivateReEngagementEmails(): array {
|
||||
$reEngagementEmails = $this->newsletterRepository->findActiveByTypes(([NewsletterEntity::TYPE_RE_ENGAGEMENT]));
|
||||
if (!$reEngagementEmails) {
|
||||
return [
|
||||
'showNotice' => false,
|
||||
'action' => 'deactivate',
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($reEngagementEmails as $reEngagementEmail) {
|
||||
$reEngagementEmail->setStatus(NewsletterEntity::STATUS_DRAFT);
|
||||
$this->entityManager->persist($reEngagementEmail);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
return [
|
||||
'showNotice' => true,
|
||||
'action' => 'deactivate',
|
||||
];
|
||||
}
|
||||
|
||||
public function reactivateReEngagementEmails(): array {
|
||||
$draftReEngagementEmails = $this->newsletterRepository->findDraftByTypes(([NewsletterEntity::TYPE_RE_ENGAGEMENT]));
|
||||
return [
|
||||
'showNotice' => !!$draftReEngagementEmails,
|
||||
'action' => 'reactivate',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the settings to set up MSS with the given key and calls the set method.
|
||||
*
|
||||
* @param string $apiKey
|
||||
* @return ErrorResponse|SuccessResponse
|
||||
*/
|
||||
public function setKeyAndSetupMss(string $apiKey) {
|
||||
$new_settings = [
|
||||
'mta_group' => 'mailpoet',
|
||||
'mta' => [
|
||||
'method' => Mailer::METHOD_MAILPOET,
|
||||
'mailpoet_api_key' => $apiKey,
|
||||
],
|
||||
'signup_confirmation' => [
|
||||
'enabled' => '1',
|
||||
],
|
||||
'premium.premium_key' => $apiKey,
|
||||
];
|
||||
return $this->set($new_settings);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Config\Activator;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class Setup extends APIEndpoint {
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_SETTINGS,
|
||||
];
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var Activator */
|
||||
private $activator;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
public function __construct(
|
||||
WPFunctions $wp,
|
||||
Activator $activator,
|
||||
SettingsController $settings
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->activator = $activator;
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function reset() {
|
||||
try {
|
||||
$this->activator->deactivate();
|
||||
$this->settings->resetCache();
|
||||
$this->activator->activate();
|
||||
$this->wp->doAction('mailpoet_setup_reset');
|
||||
return $this->successResponse();
|
||||
} catch (\Exception $e) {
|
||||
return $this->errorResponse([
|
||||
$e->getCode() => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Subscribers\Statistics\SubscriberStatistics;
|
||||
use MailPoet\Subscribers\Statistics\SubscriberStatisticsRepository;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\WooCommerce\Helper;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
|
||||
class SubscriberStats extends APIEndpoint {
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_SUBSCRIBERS,
|
||||
];
|
||||
|
||||
/** @var SubscribersRepository */
|
||||
private $subscribersRepository;
|
||||
|
||||
/** @var SubscriberStatisticsRepository */
|
||||
private $subscribersStatisticsRepository;
|
||||
|
||||
/** @var Helper */
|
||||
private $wooCommerceHelper;
|
||||
|
||||
public function __construct(
|
||||
SubscribersRepository $subscribersRepository,
|
||||
SubscriberStatisticsRepository $subscribersStatisticsRepository,
|
||||
Helper $wooCommerceHelper
|
||||
) {
|
||||
$this->subscribersRepository = $subscribersRepository;
|
||||
$this->subscribersStatisticsRepository = $subscribersStatisticsRepository;
|
||||
$this->wooCommerceHelper = $wooCommerceHelper;
|
||||
}
|
||||
|
||||
public function get($data) {
|
||||
$subscriber = isset($data['subscriber_id'])
|
||||
? $this->subscribersRepository->findOneById((int)$data['subscriber_id'])
|
||||
: null;
|
||||
if (!$subscriber instanceof SubscriberEntity) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This subscriber does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
$response = [
|
||||
'email' => $subscriber->getEmail(),
|
||||
'engagement_score' => $subscriber->getEngagementScore(),
|
||||
'is_woo_active' => $this->wooCommerceHelper->isWooCommerceActive(),
|
||||
];
|
||||
|
||||
$statsMapper = function(SubscriberStatistics $statistics, string $timeframe) {
|
||||
return [
|
||||
'timeframe' => $timeframe,
|
||||
'total_sent' => $statistics->getTotalSentCount(),
|
||||
'open' => $statistics->getOpenCount(),
|
||||
'machine_open' => $statistics->getMachineOpenCount(),
|
||||
'click' => $statistics->getClickCount(),
|
||||
'woocommerce' => $statistics->getWooCommerceRevenue() ? $statistics->getWooCommerceRevenue()->asArray() : null,
|
||||
];
|
||||
};
|
||||
|
||||
$lifetimeStats = $this->subscribersStatisticsRepository->getStatistics($subscriber);
|
||||
$oneYearStats = $this->subscribersStatisticsRepository->getStatistics($subscriber, Carbon::now()->subYear());
|
||||
$thirtyDaysStats = $this->subscribersStatisticsRepository->getStatistics($subscriber, Carbon::now()->subDays(30));
|
||||
|
||||
$response['periodic_stats'] = [
|
||||
// translators: table header meaning 30 days
|
||||
$statsMapper($thirtyDaysStats, __('30(d)', 'mailpoet')),
|
||||
// translators: table header meaning 12 months
|
||||
$statsMapper($oneYearStats, __('12(m)', 'mailpoet')),
|
||||
$statsMapper($lifetimeStats, __('Lifetime', 'mailpoet')),
|
||||
];
|
||||
|
||||
$dateFormat = 'Y-m-d H:i:s';
|
||||
$lastEngagement = $subscriber->getLastEngagementAt();
|
||||
if ($lastEngagement instanceof \DateTimeInterface) {
|
||||
$response['last_engagement'] = $lastEngagement->format($dateFormat);
|
||||
}
|
||||
$lastClick = $subscriber->getLastClickAt();
|
||||
if ($lastClick instanceof \DateTimeInterface) {
|
||||
$response['last_click'] = $lastClick->format($dateFormat);
|
||||
}
|
||||
$lastOpen = $subscriber->getLastOpenAt();
|
||||
if ($lastOpen instanceof \DateTimeInterface) {
|
||||
$response['last_open'] = $lastOpen->format($dateFormat);
|
||||
}
|
||||
$lastPageView = $subscriber->getLastPageViewAt();
|
||||
if ($lastPageView instanceof \DateTimeInterface) {
|
||||
$response['last_page_view'] = $lastPageView->format($dateFormat);
|
||||
}
|
||||
$lastPurchase = $subscriber->getLastPurchaseAt();
|
||||
if ($lastPurchase instanceof \DateTimeInterface) {
|
||||
$response['last_purchase'] = $lastPurchase->format($dateFormat);
|
||||
}
|
||||
$lastSending = $subscriber->getLastSendingAt();
|
||||
if ($lastSending instanceof \DateTimeInterface) {
|
||||
$response['last_sending'] = $lastSending->format($dateFormat);
|
||||
}
|
||||
return $this->successResponse($response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\API\JSON\ErrorResponse;
|
||||
use MailPoet\API\JSON\Response;
|
||||
use MailPoet\API\JSON\ResponseBuilders\SubscribersResponseBuilder;
|
||||
use MailPoet\API\JSON\SuccessResponse;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\ConflictException;
|
||||
use MailPoet\Doctrine\Validator\ValidationException;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Entities\TagEntity;
|
||||
use MailPoet\Exception;
|
||||
use MailPoet\Listing;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Subscribers\ConfirmationEmailMailer;
|
||||
use MailPoet\Subscribers\SubscriberListingRepository;
|
||||
use MailPoet\Subscribers\SubscriberSaveController;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\Subscribers\SubscriberSubscribeController;
|
||||
use MailPoet\Tags\TagRepository;
|
||||
use MailPoet\UnexpectedValueException;
|
||||
use MailPoet\Util\Helpers;
|
||||
|
||||
class Subscribers extends APIEndpoint {
|
||||
const SUBSCRIPTION_LIMIT_COOLDOWN = 60;
|
||||
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_SUBSCRIBERS,
|
||||
'methods' => ['subscribe' => AccessControl::NO_ACCESS_RESTRICTION],
|
||||
];
|
||||
|
||||
/** @var Listing\Handler */
|
||||
private $listingHandler;
|
||||
|
||||
/** @var ConfirmationEmailMailer; */
|
||||
private $confirmationEmailMailer;
|
||||
|
||||
/** @var SubscribersRepository */
|
||||
private $subscribersRepository;
|
||||
|
||||
/** @var SubscribersResponseBuilder */
|
||||
private $subscribersResponseBuilder;
|
||||
|
||||
/** @var SubscriberListingRepository */
|
||||
private $subscriberListingRepository;
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentsRepository;
|
||||
|
||||
/** @var TagRepository */
|
||||
private $tagRepository;
|
||||
|
||||
/** @var SubscriberSaveController */
|
||||
private $saveController;
|
||||
|
||||
/** @var SubscriberSubscribeController */
|
||||
private $subscribeController;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
public function __construct(
|
||||
Listing\Handler $listingHandler,
|
||||
ConfirmationEmailMailer $confirmationEmailMailer,
|
||||
SubscribersRepository $subscribersRepository,
|
||||
SubscribersResponseBuilder $subscribersResponseBuilder,
|
||||
SubscriberListingRepository $subscriberListingRepository,
|
||||
SegmentsRepository $segmentsRepository,
|
||||
TagRepository $tagRepository,
|
||||
SubscriberSaveController $saveController,
|
||||
SubscriberSubscribeController $subscribeController,
|
||||
SettingsController $settings
|
||||
) {
|
||||
$this->listingHandler = $listingHandler;
|
||||
$this->confirmationEmailMailer = $confirmationEmailMailer;
|
||||
$this->subscribersRepository = $subscribersRepository;
|
||||
$this->subscribersResponseBuilder = $subscribersResponseBuilder;
|
||||
$this->subscriberListingRepository = $subscriberListingRepository;
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
$this->tagRepository = $tagRepository;
|
||||
$this->saveController = $saveController;
|
||||
$this->subscribeController = $subscribeController;
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function get($data = []) {
|
||||
$subscriber = $this->getSubscriber($data);
|
||||
if (!$subscriber instanceof SubscriberEntity) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This subscriber does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
$result = $this->subscribersResponseBuilder->build($subscriber);
|
||||
return $this->successResponse($result);
|
||||
}
|
||||
|
||||
public function listing($data = []) {
|
||||
$definition = $this->listingHandler->getListingDefinition($data);
|
||||
$items = $this->subscriberListingRepository->getData($definition);
|
||||
$count = $this->subscriberListingRepository->getCount($definition);
|
||||
$filters = $this->subscriberListingRepository->getFilters($definition);
|
||||
$groups = $this->subscriberListingRepository->getGroups($definition);
|
||||
$subscribers = $this->subscribersResponseBuilder->buildForListing($items);
|
||||
if ($data['filter']['segment'] ?? false) {
|
||||
foreach ($subscribers as $key => $subscriber) {
|
||||
$subscribers[$key] = $this->preferUnsubscribedStatusFromSegment($subscriber, $data['filter']['segment']);
|
||||
}
|
||||
}
|
||||
return $this->successResponse($subscribers, [
|
||||
'count' => $count,
|
||||
'filters' => $filters,
|
||||
'groups' => $groups,
|
||||
]);
|
||||
}
|
||||
|
||||
private function preferUnsubscribedStatusFromSegment(array $subscriber, $segmentId) {
|
||||
$segmentStatus = $this->findSegmentStatus($subscriber, $segmentId);
|
||||
|
||||
if ($segmentStatus === SubscriberEntity::STATUS_UNSUBSCRIBED) {
|
||||
$subscriber['status'] = $segmentStatus;
|
||||
}
|
||||
return $subscriber;
|
||||
}
|
||||
|
||||
private function findSegmentStatus(array $subscriber, $segmentId) {
|
||||
foreach ($subscriber['subscriptions'] as $segment) {
|
||||
if ($segment['segment_id'] === $segmentId) {
|
||||
return $segment['status'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function subscribe($data = []) {
|
||||
try {
|
||||
$meta = $this->subscribeController->subscribe($data);
|
||||
} catch (Exception $exception) {
|
||||
return $this->badRequest([$exception->getMessage()]);
|
||||
}
|
||||
|
||||
if (!empty($meta['error'])) {
|
||||
$errorMessage = $meta['error'];
|
||||
unset($meta['error']);
|
||||
return $this->badRequest([APIError::BAD_REQUEST => $errorMessage], $meta);
|
||||
}
|
||||
|
||||
return $this->successResponse(
|
||||
[],
|
||||
$meta
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return ErrorResponse|SuccessResponse
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function save(array $data = []) {
|
||||
try {
|
||||
$subscriber = $this->saveController->save($data);
|
||||
} catch (ValidationException $validationException) {
|
||||
return $this->badRequest([$this->getErrorMessage($validationException)]);
|
||||
} catch (ConflictException $conflictException) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => $conflictException->getMessage(),
|
||||
]);
|
||||
};
|
||||
|
||||
return $this->successResponse(
|
||||
$this->subscribersResponseBuilder->build($subscriber)
|
||||
);
|
||||
}
|
||||
|
||||
public function restore($data = []) {
|
||||
$subscriber = $this->getSubscriber($data);
|
||||
if ($subscriber instanceof SubscriberEntity) {
|
||||
$this->subscribersRepository->bulkRestore([$subscriber->getId()]);
|
||||
$this->subscribersRepository->refresh($subscriber);
|
||||
return $this->successResponse(
|
||||
$this->subscribersResponseBuilder->build($subscriber),
|
||||
['count' => 1]
|
||||
);
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This subscriber does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function trash($data = []) {
|
||||
$subscriber = $this->getSubscriber($data);
|
||||
if ($subscriber instanceof SubscriberEntity) {
|
||||
$this->subscribersRepository->bulkTrash([$subscriber->getId()]);
|
||||
$this->subscribersRepository->refresh($subscriber);
|
||||
return $this->successResponse(
|
||||
$this->subscribersResponseBuilder->build($subscriber),
|
||||
['count' => 1]
|
||||
);
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This subscriber does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete($data = []) {
|
||||
$subscriber = $this->getSubscriber($data);
|
||||
if ($subscriber instanceof SubscriberEntity) {
|
||||
$count = $this->subscribersRepository->bulkDelete([$subscriber->getId()]);
|
||||
return $this->successResponse(null, ['count' => $count]);
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This subscriber does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function sendConfirmationEmail($data = []) {
|
||||
if (!(bool)$this->settings->get('signup_confirmation.enabled', true)) {
|
||||
$errorMessage = __('Sign-up confirmation is disabled in your [link]MailPoet settings[/link]. Please enable it to resend confirmation emails or update your subscriber’s status manually.', 'mailpoet');
|
||||
$errorMessage = Helpers::replaceLinkTags($errorMessage, 'admin.php?page=mailpoet-settings#/signup');
|
||||
return $this->errorResponse([APIError::BAD_REQUEST => $errorMessage], [], Response::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$id = (isset($data['id']) ? (int)$data['id'] : false);
|
||||
$subscriber = $this->subscribersRepository->findOneById($id);
|
||||
if ($subscriber instanceof SubscriberEntity) {
|
||||
try {
|
||||
if ($this->confirmationEmailMailer->sendConfirmationEmail($subscriber)) {
|
||||
return $this->successResponse();
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::UNKNOWN => __('There was a problem with your sending method. Please check if your sending method is properly configured.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return $this->errorResponse([
|
||||
APIError::UNKNOWN => __('There was a problem with your sending method. Please check if your sending method is properly configured.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This subscriber does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function bulkAction($data = []) {
|
||||
$definition = $this->listingHandler->getListingDefinition($data['listing']);
|
||||
$ids = $this->subscriberListingRepository->getActionableIds($definition);
|
||||
|
||||
$count = 0;
|
||||
$segment = null;
|
||||
if (isset($data['segment_id'])) {
|
||||
$segment = $this->getSegment($data);
|
||||
if (!$segment) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This segment does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$tag = null;
|
||||
if (isset($data['tag_id'])) {
|
||||
$tag = $this->getTag($data);
|
||||
if (!$tag) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This tag does not exist.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($data['action'] === 'trash') {
|
||||
$count = $this->subscribersRepository->bulkTrash($ids);
|
||||
} elseif ($data['action'] === 'restore') {
|
||||
$count = $this->subscribersRepository->bulkRestore($ids);
|
||||
} elseif ($data['action'] === 'delete') {
|
||||
$count = $this->subscribersRepository->bulkDelete($ids);
|
||||
} elseif ($data['action'] === 'removeFromAllLists') {
|
||||
$count = $this->subscribersRepository->bulkRemoveFromAllSegments($ids);
|
||||
} elseif ($data['action'] === 'removeFromList' && $segment instanceof SegmentEntity) {
|
||||
$count = $this->subscribersRepository->bulkRemoveFromSegment($segment, $ids);
|
||||
} elseif ($data['action'] === 'addToList' && $segment instanceof SegmentEntity) {
|
||||
$count = $this->subscribersRepository->bulkAddToSegment($segment, $ids);
|
||||
} elseif ($data['action'] === 'moveToList' && $segment instanceof SegmentEntity) {
|
||||
$count = $this->subscribersRepository->bulkMoveToSegment($segment, $ids);
|
||||
} elseif ($data['action'] === 'unsubscribe') {
|
||||
$count = $this->subscribersRepository->bulkUnsubscribe($ids);
|
||||
} elseif ($data['action'] === 'addTag' && $tag instanceof TagEntity) {
|
||||
$count = $this->subscribersRepository->bulkAddTag($tag, $ids);
|
||||
} elseif ($data['action'] === 'removeTag' && $tag instanceof TagEntity) {
|
||||
$count = $this->subscribersRepository->bulkRemoveTag($tag, $ids);
|
||||
} else {
|
||||
throw UnexpectedValueException::create()
|
||||
->withErrors([APIError::BAD_REQUEST => "Invalid bulk action '{$data['action']}' provided."]);
|
||||
}
|
||||
$meta = [
|
||||
'count' => $count,
|
||||
];
|
||||
|
||||
if ($segment) {
|
||||
$meta['segment'] = $segment->getName();
|
||||
}
|
||||
if ($tag) {
|
||||
$meta['tag'] = $tag->getName();
|
||||
}
|
||||
return $this->successResponse(null, $meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return SubscriberEntity|null
|
||||
*/
|
||||
private function getSubscriber($data) {
|
||||
return isset($data['id'])
|
||||
? $this->subscribersRepository->findOneById((int)$data['id'])
|
||||
: null;
|
||||
}
|
||||
|
||||
private function getSegment(array $data): ?SegmentEntity {
|
||||
return isset($data['segment_id'])
|
||||
? $this->segmentsRepository->findOneById((int)$data['segment_id'])
|
||||
: null;
|
||||
}
|
||||
|
||||
private function getTag(array $data): ?TagEntity {
|
||||
return isset($data['tag_id'])
|
||||
? $this->tagRepository->findOneById((int)$data['tag_id'])
|
||||
: null;
|
||||
}
|
||||
|
||||
private function getErrorMessage(ValidationException $exception): string {
|
||||
$exceptionMessage = $exception->getMessage();
|
||||
if (strpos($exceptionMessage, 'This value should not be blank.') !== false) {
|
||||
return __('Please enter your email address', 'mailpoet');
|
||||
} elseif (strpos($exceptionMessage, 'This value is not a valid email address.') !== false) {
|
||||
return __('Your email address is invalid!', 'mailpoet');
|
||||
}
|
||||
|
||||
return __('Unexpected error.', 'mailpoet');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\Entities\TagEntity;
|
||||
use MailPoet\Tags\TagRepository;
|
||||
|
||||
class Tags extends APIEndpoint {
|
||||
|
||||
private $repository;
|
||||
|
||||
public function __construct(
|
||||
TagRepository $repository
|
||||
) {
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
public function create($data = []) {
|
||||
if (!isset($data['name'])) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => __('A tag needs to have a name.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$data['name'] = sanitize_text_field(wp_unslash($data['name']));
|
||||
$data['description'] = isset($data['description']) ? sanitize_text_field(wp_unslash($data['description'])) : '';
|
||||
|
||||
return $this->successResponse(
|
||||
$this->mapTagEntity($this->repository->createOrUpdate($data))
|
||||
);
|
||||
}
|
||||
|
||||
public function listing() {
|
||||
return $this->successResponse(
|
||||
array_map(
|
||||
[$this, 'mapTagEntity'],
|
||||
$this->repository->findAll()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function mapTagEntity(TagEntity $tag): array {
|
||||
return [
|
||||
'id' => $tag->getId(),
|
||||
'name' => $tag->getName(),
|
||||
'description' => $tag->getDescription(),
|
||||
'created_at' => $tag->getCreatedAt(),
|
||||
'updated_at' => $tag->getUpdatedAt(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Settings\UserFlagsController;
|
||||
|
||||
class UserFlags extends APIEndpoint {
|
||||
|
||||
/** @var UserFlagsController */
|
||||
private $userFlags;
|
||||
|
||||
public $permissions = [
|
||||
'global' => AccessControl::ALL_ROLES_ACCESS,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
UserFlagsController $userFlags
|
||||
) {
|
||||
$this->userFlags = $userFlags;
|
||||
}
|
||||
|
||||
public function set(array $flags = []) {
|
||||
if (empty($flags)) {
|
||||
return $this->badRequest(
|
||||
[
|
||||
APIError::BAD_REQUEST =>
|
||||
__('You have not specified any user flags to be saved.', 'mailpoet'),
|
||||
]
|
||||
);
|
||||
} else {
|
||||
foreach ($flags as $name => $value) {
|
||||
$this->userFlags->set($name, $value);
|
||||
}
|
||||
return $this->successResponse([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class WoocommerceSettings extends APIEndpoint {
|
||||
public $permissions = [
|
||||
'global' => AccessControl::PERMISSION_MANAGE_EMAILS,
|
||||
];
|
||||
|
||||
private $allowedSettings = [
|
||||
'woocommerce_email_base_color',
|
||||
];
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
public function __construct(
|
||||
WPFunctions $wp
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
}
|
||||
|
||||
public function set($data = []) {
|
||||
foreach ($data as $option => $value) {
|
||||
if (in_array($option, $this->allowedSettings)) {
|
||||
$this->wp->updateOption($option, $value);
|
||||
}
|
||||
}
|
||||
return $this->successResponse([]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\MP\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Config\Changelog;
|
||||
|
||||
/**
|
||||
* API used by other plugins
|
||||
* Do not add bodies of methods into this class. Use other classes. See CustomFields or Subscribers.
|
||||
* This class is under refactor, and we are going to move most of the remaining implementations from here.
|
||||
*/
|
||||
class API {
|
||||
/** @var CustomFields */
|
||||
private $customFields;
|
||||
|
||||
/** @var Segments */
|
||||
private $segments;
|
||||
|
||||
/** @var Subscribers */
|
||||
private $subscribers;
|
||||
|
||||
/** @var Changelog */
|
||||
private $changelog;
|
||||
|
||||
public function __construct(
|
||||
CustomFields $customFields,
|
||||
Segments $segments,
|
||||
Subscribers $subscribers,
|
||||
Changelog $changelog
|
||||
) {
|
||||
$this->customFields = $customFields;
|
||||
$this->segments = $segments;
|
||||
$this->subscribers = $subscribers;
|
||||
$this->changelog = $changelog;
|
||||
}
|
||||
|
||||
public function getSubscriberFields() {
|
||||
return $this->customFields->getSubscriberFields();
|
||||
}
|
||||
|
||||
public function addSubscriberField(array $data = []) {
|
||||
try {
|
||||
return $this->customFields->addSubscriberField($data);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new APIException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws APIException
|
||||
*/
|
||||
public function subscribeToList($subscriberId, $listId, $options = []): array {
|
||||
return $this->subscribeToLists($subscriberId, [$listId], $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws APIException
|
||||
*/
|
||||
public function subscribeToLists($subscriberId, array $listIds, $options = []) {
|
||||
return $this->subscribers->subscribeToLists($subscriberId, $listIds, $options);
|
||||
}
|
||||
|
||||
public function unsubscribeFromList($subscriberId, $listId) {
|
||||
return $this->unsubscribeFromLists($subscriberId, [$listId]);
|
||||
}
|
||||
|
||||
public function unsubscribeFromLists($subscriberId, array $listIds) {
|
||||
return $this->subscribers->unsubscribeFromLists($subscriberId, $listIds);
|
||||
}
|
||||
|
||||
public function unsubscribe($subscriberIdOrEmail) {
|
||||
return $this->subscribers->unsubscribe($subscriberIdOrEmail);
|
||||
}
|
||||
|
||||
public function getLists(): array {
|
||||
return $this->segments->getAll();
|
||||
}
|
||||
|
||||
public function addSubscriber(array $subscriber, $listIds = [], $options = []): array {
|
||||
return $this->subscribers->addSubscriber($subscriber, $listIds, $options);
|
||||
}
|
||||
|
||||
public function updateSubscriber($subscriberIdOrEmail, array $subscriber): array {
|
||||
return $this->subscribers->updateSubscriber($subscriberIdOrEmail, $subscriber);
|
||||
}
|
||||
|
||||
public function addList(array $list) {
|
||||
return $this->segments->addList($list);
|
||||
}
|
||||
|
||||
public function deleteList(string $listId): bool {
|
||||
return $this->segments->deleteList($listId);
|
||||
}
|
||||
|
||||
public function updateList(array $list): array {
|
||||
return $this->segments->updateList($list);
|
||||
}
|
||||
|
||||
public function getSubscriber($subscriberEmail) {
|
||||
return $this->subscribers->getSubscriber($subscriberEmail);
|
||||
}
|
||||
|
||||
public function getSubscribers(array $filter = [], int $limit = 50, int $offset = 0): array {
|
||||
return $this->subscribers->getSubscribers($filter, $limit, $offset);
|
||||
}
|
||||
|
||||
public function getSubscribersCount(array $filter = []): int {
|
||||
return $this->subscribers->getSubscribersCount($filter);
|
||||
}
|
||||
|
||||
public function isSetupComplete() {
|
||||
return !(
|
||||
$this->changelog->shouldShowWelcomeWizard()
|
||||
|| $this->changelog->shouldShowWooCommerceListImportPage()
|
||||
|| $this->changelog->shouldShowRevenueTrackingPermissionPage()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\MP\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
class APIException extends \Exception {
|
||||
const FAILED_TO_SAVE_SUBSCRIBER_FIELD = 1;
|
||||
const SEGMENT_REQUIRED = 3;
|
||||
const SUBSCRIBER_NOT_EXISTS = 4;
|
||||
const LIST_NOT_EXISTS = 5;
|
||||
const SUBSCRIBING_TO_WP_LIST_NOT_ALLOWED = 6;
|
||||
const SUBSCRIBING_TO_WC_LIST_NOT_ALLOWED = 7;
|
||||
const SUBSCRIBING_TO_LIST_NOT_ALLOWED = 8;
|
||||
const CONFIRMATION_FAILED_TO_SEND = 10;
|
||||
const EMAIL_ADDRESS_REQUIRED = 11;
|
||||
const SUBSCRIBER_EXISTS = 12;
|
||||
const FAILED_TO_SAVE_SUBSCRIBER = 13;
|
||||
const LIST_NAME_REQUIRED = 14;
|
||||
const LIST_EXISTS = 15;
|
||||
const FAILED_TO_SAVE_LIST = 16;
|
||||
const WELCOME_FAILED_TO_SEND = 17;
|
||||
const LIST_ID_REQUIRED = 18;
|
||||
const FAILED_TO_UPDATE_LIST = 19;
|
||||
const LIST_USED_IN_EMAIL = 20;
|
||||
const LIST_USED_IN_FORM = 21;
|
||||
const FAILED_TO_DELETE_LIST = 22;
|
||||
const LIST_TYPE_IS_NOT_SUPPORTED = 23;
|
||||
const SUBSCRIBER_ALREADY_UNSUBSCRIBED = 24;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\MP\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\CustomFields\ApiDataSanitizer;
|
||||
use MailPoet\CustomFields\CustomFieldsRepository;
|
||||
|
||||
class CustomFields {
|
||||
/** @var ApiDataSanitizer */
|
||||
private $customFieldsDataSanitizer;
|
||||
|
||||
/** @var CustomFieldsRepository */
|
||||
private $customFieldsRepository;
|
||||
|
||||
public function __construct(
|
||||
ApiDataSanitizer $customFieldsDataSanitizer,
|
||||
CustomFieldsRepository $customFieldsRepository
|
||||
) {
|
||||
$this->customFieldsDataSanitizer = $customFieldsDataSanitizer;
|
||||
$this->customFieldsRepository = $customFieldsRepository;
|
||||
}
|
||||
|
||||
public function getSubscriberFields(): array {
|
||||
$data = [
|
||||
[
|
||||
'id' => 'email',
|
||||
'name' => __('Email', 'mailpoet'),
|
||||
'type' => 'text',
|
||||
'params' => [
|
||||
'required' => '1',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'first_name',
|
||||
'name' => __('First name', 'mailpoet'),
|
||||
'type' => 'text',
|
||||
'params' => [
|
||||
'required' => '',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'last_name',
|
||||
'name' => __('Last name', 'mailpoet'),
|
||||
'type' => 'text',
|
||||
'params' => [
|
||||
'required' => '',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$customFields = $this->customFieldsRepository->findAll();
|
||||
foreach ($customFields as $customField) {
|
||||
$result = [
|
||||
'id' => 'cf_' . $customField->getId(),
|
||||
'name' => $customField->getName(),
|
||||
'type' => $customField->getType(),
|
||||
'params' => $customField->getParams(),
|
||||
];
|
||||
$data[] = $result;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function addSubscriberField(array $data = []): array {
|
||||
try {
|
||||
$customField = $this->customFieldsRepository->createOrUpdate($this->customFieldsDataSanitizer->sanitize($data));
|
||||
} catch (\Exception $e) {
|
||||
throw new APIException('Failed to save a new subscriber field ' . $e->getMessage(), APIException::FAILED_TO_SAVE_SUBSCRIBER_FIELD);
|
||||
}
|
||||
return [
|
||||
'id' => 'cf_' . $customField->getId(),
|
||||
'name' => $customField->getName(),
|
||||
'type' => $customField->getType(),
|
||||
'params' => $customField->getParams(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\MP\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Form\FormsRepository;
|
||||
use MailPoet\Newsletter\Segment\NewsletterSegmentRepository;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
|
||||
class Segments {
|
||||
private const DATE_FORMAT = 'Y-m-d H:i:s';
|
||||
|
||||
/** @var NewsletterSegmentRepository */
|
||||
private $newsletterSegmentRepository;
|
||||
|
||||
/** @var FormsRepository */
|
||||
private $formsRepository;
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentsRepository;
|
||||
|
||||
public function __construct (
|
||||
NewsletterSegmentRepository $newsletterSegmentRepository,
|
||||
FormsRepository $formsRepository,
|
||||
SegmentsRepository $segmentsRepository
|
||||
) {
|
||||
$this->newsletterSegmentRepository = $newsletterSegmentRepository;
|
||||
$this->formsRepository = $formsRepository;
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
}
|
||||
|
||||
public function getAll(): array {
|
||||
$segments = $this->segmentsRepository->findBy(['type' => SegmentEntity::TYPE_DEFAULT], ['id' => 'asc']);
|
||||
$result = [];
|
||||
foreach ($segments as $segment) {
|
||||
$result[] = $this->buildItem($segment);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function addList(array $data): array {
|
||||
$this->validateSegmentName($data);
|
||||
|
||||
try {
|
||||
$name = isset($data['name']) ? sanitize_text_field($data['name']) : '';
|
||||
$description = isset($data['description']) ? sanitize_textarea_field($data['description']) : '';
|
||||
$segment = $this->segmentsRepository->createOrUpdate($name, $description);
|
||||
} catch (\Exception $e) {
|
||||
throw new APIException(
|
||||
__('The list couldn’t be created in the database', 'mailpoet'),
|
||||
APIException::FAILED_TO_SAVE_LIST
|
||||
);
|
||||
}
|
||||
|
||||
return $this->buildItem($segment);
|
||||
}
|
||||
|
||||
public function updateList(array $data): array {
|
||||
// firstly validation on list id
|
||||
$this->validateSegmentId((string)($data['id'] ?? ''));
|
||||
|
||||
// secondly validation on list name
|
||||
$this->validateSegmentName($data);
|
||||
|
||||
// update is supported only for default segment type
|
||||
$this->validateSegmentType((string)$data['id']);
|
||||
|
||||
$name = isset($data['name']) ? sanitize_text_field($data['name']) : '';
|
||||
$description = isset($data['description']) ? sanitize_textarea_field($data['description']) : '';
|
||||
|
||||
try {
|
||||
$segment = $this->segmentsRepository->createOrUpdate(
|
||||
$name,
|
||||
$description,
|
||||
SegmentEntity::TYPE_DEFAULT,
|
||||
[],
|
||||
(int)$data['id']
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
throw new APIException(
|
||||
__('The list couldn’t be updated in the database', 'mailpoet'),
|
||||
APIException::FAILED_TO_UPDATE_LIST
|
||||
);
|
||||
}
|
||||
|
||||
return $this->buildItem($segment);
|
||||
}
|
||||
|
||||
public function deleteList(string $listId): bool {
|
||||
$this->validateSegmentId($listId);
|
||||
|
||||
// delete is supported only for default segment type
|
||||
$this->validateSegmentType($listId);
|
||||
|
||||
$activelyUsedNewslettersSubjects = $this->newsletterSegmentRepository->getSubjectsOfActivelyUsedEmailsForSegments([$listId]);
|
||||
if (isset($activelyUsedNewslettersSubjects[$listId])) {
|
||||
throw new APIException(
|
||||
str_replace(
|
||||
'%1$s',
|
||||
"'" . join("', '", $activelyUsedNewslettersSubjects[$listId]) . "'",
|
||||
// translators: %1$s is a comma-seperated list of emails for which the segment is used.
|
||||
_x('List cannot be deleted because it’s used for %1$s email', 'Alert shown when trying to delete segment, which is assigned to any automatic emails.', 'mailpoet')
|
||||
),
|
||||
APIException::LIST_USED_IN_EMAIL
|
||||
);
|
||||
}
|
||||
|
||||
$activelyUsedFormNames = $this->formsRepository->getNamesOfFormsForSegments();
|
||||
if (isset($activelyUsedFormNames[$listId])) {
|
||||
throw new APIException(
|
||||
str_replace(
|
||||
'%1$s',
|
||||
"'" . join("', '", $activelyUsedFormNames[$listId]) . "'",
|
||||
// translators: %1$s is a comma-seperated list of forms for which the segment is used.
|
||||
_nx(
|
||||
'List cannot be deleted because it’s used for %1$s form',
|
||||
'List cannot be deleted because it’s used for %1$s forms',
|
||||
count($activelyUsedFormNames[$listId]),
|
||||
'Alert shown when trying to delete segment, when it is assigned to a form.',
|
||||
'mailpoet'
|
||||
)
|
||||
),
|
||||
APIException::LIST_USED_IN_FORM
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->segmentsRepository->bulkDelete([$listId]);
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
throw new APIException(
|
||||
__('The list couldn’t be deleted from the database', 'mailpoet'),
|
||||
APIException::FAILED_TO_DELETE_LIST
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function validateSegmentId(string $segmentId): void {
|
||||
if (empty($segmentId)) {
|
||||
throw new APIException(
|
||||
__('List id is required.', 'mailpoet'),
|
||||
APIException::LIST_ID_REQUIRED
|
||||
);
|
||||
}
|
||||
|
||||
if (!$this->segmentsRepository->findOneById($segmentId)) {
|
||||
throw new APIException(
|
||||
__('The list does not exist.', 'mailpoet'),
|
||||
APIException::LIST_NOT_EXISTS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception when the segment's name is invalid
|
||||
* @return void
|
||||
*/
|
||||
private function validateSegmentName(array $data): void {
|
||||
if (empty($data['name'])) {
|
||||
throw new APIException(
|
||||
__('List name is required.', 'mailpoet'),
|
||||
APIException::LIST_NAME_REQUIRED
|
||||
);
|
||||
}
|
||||
|
||||
$segmentId = isset($data['id']) ? (int)$data['id'] : null;
|
||||
if (!$this->segmentsRepository->isNameUnique($data['name'], $segmentId)) {
|
||||
throw new APIException(
|
||||
__('This list already exists.', 'mailpoet'),
|
||||
APIException::LIST_EXISTS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function validateSegmentType(string $segmentId): void {
|
||||
$segment = $this->segmentsRepository->findOneById($segmentId);
|
||||
if ($segment && $segment->getType() !== SegmentEntity::TYPE_DEFAULT) {
|
||||
throw new APIException(
|
||||
str_replace(
|
||||
'%1$s',
|
||||
"'" . $segment->getType() . "'",
|
||||
// translators: %1$s is an invalid segment type.
|
||||
__('List of the type %1$s is not supported for this action.', 'mailpoet')
|
||||
),
|
||||
APIException::LIST_TYPE_IS_NOT_SUPPORTED
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SegmentEntity $segment
|
||||
* @return array
|
||||
*/
|
||||
private function buildItem(SegmentEntity $segment): array {
|
||||
return [
|
||||
'id' => (string)$segment->getId(), // (string) for BC
|
||||
'name' => $segment->getName(),
|
||||
'type' => $segment->getType(),
|
||||
'description' => $segment->getDescription(),
|
||||
'created_at' => ($createdAt = $segment->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
|
||||
'updated_at' => $segment->getUpdatedAt()->format(self::DATE_FORMAT),
|
||||
'deleted_at' => ($deletedAt = $segment->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\API\MP\v1;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\ResponseBuilders\SubscribersResponseBuilder;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Entities\StatisticsUnsubscribeEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Listing\ListingDefinition;
|
||||
use MailPoet\Newsletter\Scheduler\WelcomeScheduler;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Statistics\Track\Unsubscribes;
|
||||
use MailPoet\Subscribers\ConfirmationEmailMailer;
|
||||
use MailPoet\Subscribers\NewSubscriberNotificationMailer;
|
||||
use MailPoet\Subscribers\RequiredCustomFieldValidator;
|
||||
use MailPoet\Subscribers\Source;
|
||||
use MailPoet\Subscribers\SubscriberListingRepository;
|
||||
use MailPoet\Subscribers\SubscriberSaveController;
|
||||
use MailPoet\Subscribers\SubscriberSegmentRepository;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
|
||||
class Subscribers {
|
||||
const CONTEXT_SUBSCRIBE = 'subscribe';
|
||||
const CONTEXT_UNSUBSCRIBE = 'unsubscribe';
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
/** @var SubscribersRepository */
|
||||
private $subscribersRepository;
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentsRepository;
|
||||
|
||||
/** @var SubscriberSegmentRepository */
|
||||
private $subscribersSegmentRepository;
|
||||
|
||||
/** @var ConfirmationEmailMailer */
|
||||
private $confirmationEmailMailer;
|
||||
|
||||
/** @var WelcomeScheduler */
|
||||
private $welcomeScheduler;
|
||||
|
||||
/** @var SubscribersResponseBuilder */
|
||||
private $subscribersResponseBuilder;
|
||||
|
||||
/** @var NewSubscriberNotificationMailer */
|
||||
private $newSubscriberNotificationMailer;
|
||||
|
||||
/** @var SubscriberSaveController */
|
||||
private $subscriberSaveController;
|
||||
|
||||
/** @var RequiredCustomFieldValidator */
|
||||
private $requiredCustomFieldsValidator;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var SubscriberListingRepository */
|
||||
private $subscriberListingRepository;
|
||||
|
||||
/** @var Unsubscribes */
|
||||
private $unsubscribesTracker;
|
||||
|
||||
public function __construct (
|
||||
ConfirmationEmailMailer $confirmationEmailMailer,
|
||||
NewSubscriberNotificationMailer $newSubscriberNotificationMailer,
|
||||
SegmentsRepository $segmentsRepository,
|
||||
SettingsController $settings,
|
||||
SubscriberSegmentRepository $subscriberSegmentRepository,
|
||||
SubscribersRepository $subscribersRepository,
|
||||
SubscriberSaveController $subscriberSaveController,
|
||||
SubscribersResponseBuilder $subscribersResponseBuilder,
|
||||
WelcomeScheduler $welcomeScheduler,
|
||||
RequiredCustomFieldValidator $requiredCustomFieldsValidator,
|
||||
SubscriberListingRepository $subscriberListingRepository,
|
||||
WPFunctions $wp,
|
||||
Unsubscribes $unsubscribesTracker
|
||||
) {
|
||||
$this->confirmationEmailMailer = $confirmationEmailMailer;
|
||||
$this->newSubscriberNotificationMailer = $newSubscriberNotificationMailer;
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
$this->settings = $settings;
|
||||
$this->subscribersSegmentRepository = $subscriberSegmentRepository;
|
||||
$this->subscribersRepository = $subscribersRepository;
|
||||
$this->subscriberSaveController = $subscriberSaveController;
|
||||
$this->subscribersResponseBuilder = $subscribersResponseBuilder;
|
||||
$this->welcomeScheduler = $welcomeScheduler;
|
||||
$this->requiredCustomFieldsValidator = $requiredCustomFieldsValidator;
|
||||
$this->wp = $wp;
|
||||
$this->subscriberListingRepository = $subscriberListingRepository;
|
||||
$this->unsubscribesTracker = $unsubscribesTracker;
|
||||
}
|
||||
|
||||
public function getSubscriber($subscriberIdOrEmail): array {
|
||||
$subscriber = $this->findSubscriber($subscriberIdOrEmail);
|
||||
return $this->subscribersResponseBuilder->build($subscriber);
|
||||
}
|
||||
|
||||
public function addSubscriber(array $data, array $listIds = [], array $options = []): array {
|
||||
$sendConfirmationEmail = !(isset($options['send_confirmation_email']) && $options['send_confirmation_email'] === false);
|
||||
$scheduleWelcomeEmail = !(isset($options['schedule_welcome_email']) && $options['schedule_welcome_email'] === false);
|
||||
$skipSubscriberNotification = (isset($options['skip_subscriber_notification']) && $options['skip_subscriber_notification'] === true);
|
||||
|
||||
// throw exception when subscriber email is missing
|
||||
if (empty($data['email'])) {
|
||||
throw new APIException(
|
||||
__('Subscriber email address is required.', 'mailpoet'),
|
||||
APIException::EMAIL_ADDRESS_REQUIRED
|
||||
);
|
||||
}
|
||||
|
||||
// throw exception when subscriber already exists
|
||||
if ($this->subscribersRepository->findOneBy(['email' => $data['email']])) {
|
||||
throw new APIException(
|
||||
__('This subscriber already exists.', 'mailpoet'),
|
||||
APIException::SUBSCRIBER_EXISTS
|
||||
);
|
||||
}
|
||||
|
||||
[$defaultFields, $customFields] = $this->extractCustomFieldsFromFromSubscriberData($data);
|
||||
|
||||
$this->requiredCustomFieldsValidator->validate($customFields);
|
||||
|
||||
// filter out all incoming data that we don't want to change, like status ...
|
||||
$defaultFields = array_intersect_key($defaultFields, array_flip(['email', 'first_name', 'last_name', 'subscribed_ip']));
|
||||
|
||||
if (empty($defaultFields['subscribed_ip'])) {
|
||||
$defaultFields['subscribed_ip'] = Helpers::getIP();
|
||||
}
|
||||
$defaultFields['source'] = Source::API;
|
||||
|
||||
try {
|
||||
$subscriberEntity = $this->subscriberSaveController->createOrUpdate($defaultFields, null);
|
||||
} catch (\Exception $e) {
|
||||
throw new APIException(
|
||||
// translators: %s is an error message.
|
||||
sprintf(__('Failed to add subscriber: %s', 'mailpoet'), $e->getMessage()),
|
||||
APIException::FAILED_TO_SAVE_SUBSCRIBER
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->subscriberSaveController->updateCustomFields($customFields, $subscriberEntity);
|
||||
} catch (\Exception $e) {
|
||||
throw new APIException(
|
||||
// translators: %s is an error message
|
||||
sprintf(__('Failed to save subscriber custom fields: %s', 'mailpoet'), $e->getMessage()),
|
||||
APIException::FAILED_TO_SAVE_SUBSCRIBER
|
||||
);
|
||||
}
|
||||
|
||||
// subscribe to segments and optionally: 1) send confirmation email, 2) schedule welcome email(s)
|
||||
if (!empty($listIds)) {
|
||||
$this->subscribeToLists($subscriberEntity->getId(), $listIds, [
|
||||
'send_confirmation_email' => $sendConfirmationEmail,
|
||||
'schedule_welcome_email' => $scheduleWelcomeEmail,
|
||||
'skip_subscriber_notification' => $skipSubscriberNotification,
|
||||
]);
|
||||
}
|
||||
return $this->subscribersResponseBuilder->build($subscriberEntity);
|
||||
}
|
||||
|
||||
public function updateSubscriber($subscriberIdOrEmail, array $data): array {
|
||||
$this->checkSubscriberParam($subscriberIdOrEmail);
|
||||
|
||||
$subscriber = $this->findSubscriber($subscriberIdOrEmail);
|
||||
|
||||
[$defaultFields, $customFields] = $this->extractCustomFieldsFromFromSubscriberData($data);
|
||||
|
||||
$this->requiredCustomFieldsValidator->validate($customFields);
|
||||
|
||||
// filter out all incoming data that we don't want to change, like status ...
|
||||
$defaultFields = array_intersect_key($defaultFields, array_flip(['email', 'first_name', 'last_name', 'subscribed_ip']));
|
||||
|
||||
if ($subscriber->getWpUserId() !== null) {
|
||||
unset($defaultFields['email']);
|
||||
unset($defaultFields['first_name']);
|
||||
unset($defaultFields['last_name']);
|
||||
};
|
||||
|
||||
if (empty($defaultFields['subscribed_ip'])) {
|
||||
$defaultFields['subscribed_ip'] = Helpers::getIP();
|
||||
}
|
||||
$defaultFields['source'] = Source::API;
|
||||
|
||||
try {
|
||||
$subscriberEntity = $this->subscriberSaveController->createOrUpdate($defaultFields, $subscriber);
|
||||
} catch (\Exception $e) {
|
||||
throw new APIException(
|
||||
// translators: %s is an error message.
|
||||
sprintf(__('Failed to update subscriber: %s', 'mailpoet'), $e->getMessage()),
|
||||
APIException::FAILED_TO_SAVE_SUBSCRIBER
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->subscriberSaveController->updateCustomFields($customFields, $subscriberEntity);
|
||||
} catch (\Exception $e) {
|
||||
throw new APIException(
|
||||
// translators: %s is an error message
|
||||
sprintf(__('Failed to save subscriber custom fields: %s', 'mailpoet'), $e->getMessage()),
|
||||
APIException::FAILED_TO_SAVE_SUBSCRIBER
|
||||
);
|
||||
}
|
||||
|
||||
return $this->subscribersResponseBuilder->build($subscriberEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws APIException
|
||||
*/
|
||||
public function subscribeToLists(
|
||||
$subscriberId,
|
||||
array $listIds,
|
||||
array $options = []
|
||||
): array {
|
||||
$scheduleWelcomeEmail = !((isset($options['schedule_welcome_email']) && $options['schedule_welcome_email'] === false));
|
||||
$sendConfirmationEmail = !((isset($options['send_confirmation_email']) && $options['send_confirmation_email'] === false));
|
||||
$skipSubscriberNotification = isset($options['skip_subscriber_notification']) && $options['skip_subscriber_notification'] === true;
|
||||
$signupConfirmationEnabled = (bool)$this->settings->get('signup_confirmation.enabled');
|
||||
|
||||
$this->checkSubscriberAndListParams($subscriberId, $listIds);
|
||||
$subscriber = $this->findSubscriber($subscriberId);
|
||||
$foundSegments = $this->getAndValidateSegments($listIds, self::CONTEXT_SUBSCRIBE);
|
||||
|
||||
// restore trashed subscriber
|
||||
if ($subscriber->getDeletedAt()) {
|
||||
$subscriber->setDeletedAt(null);
|
||||
}
|
||||
|
||||
$this->subscribersSegmentRepository->subscribeToSegments($subscriber, $foundSegments);
|
||||
|
||||
// set status depending on signup confirmation setting
|
||||
if ($subscriber->getStatus() !== SubscriberEntity::STATUS_SUBSCRIBED) {
|
||||
if ($signupConfirmationEnabled === true) {
|
||||
$subscriber->setStatus(SubscriberEntity::STATUS_UNCONFIRMED);
|
||||
} else {
|
||||
$subscriber->setStatus(SubscriberEntity::STATUS_SUBSCRIBED);
|
||||
}
|
||||
try {
|
||||
$this->subscribersRepository->flush();
|
||||
} catch (\Exception $e) {
|
||||
throw new APIException(
|
||||
// translators: %s is the error message
|
||||
sprintf(__('Failed to save a status of a subscriber : %s', 'mailpoet'), $e->getMessage()),
|
||||
APIException::FAILED_TO_SAVE_SUBSCRIBER
|
||||
);
|
||||
}
|
||||
|
||||
// when global status changes to subscribed, fire subscribed hook for all subscribed segments
|
||||
/** @var SubscriberEntity $subscriber - From some reason PHPStan evaluates $subscriber->getStatus() as mixed */
|
||||
if ($subscriber->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED) {
|
||||
$subscriberSegments = $subscriber->getSubscriberSegments();
|
||||
foreach ($subscriberSegments as $subscriberSegment) {
|
||||
if ($subscriberSegment->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED) {
|
||||
$this->wp->doAction('mailpoet_segment_subscribed', $subscriberSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// schedule welcome email
|
||||
$foundSegmentsIds = array_map(
|
||||
function(SegmentEntity $segment) {
|
||||
return $segment->getId();
|
||||
},
|
||||
$foundSegments
|
||||
);
|
||||
if ($scheduleWelcomeEmail && $subscriber->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED) {
|
||||
$this->_scheduleWelcomeNotification($subscriber, $foundSegmentsIds);
|
||||
}
|
||||
|
||||
// send confirmation email
|
||||
if ($sendConfirmationEmail) {
|
||||
$this->_sendConfirmationEmail($subscriber);
|
||||
}
|
||||
|
||||
if (!$skipSubscriberNotification && ($subscriber->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED)) {
|
||||
$this->newSubscriberNotificationMailer->send($subscriber, $this->segmentsRepository->findBy(['id' => $foundSegmentsIds]));
|
||||
}
|
||||
|
||||
$this->subscribersRepository->refresh($subscriber);
|
||||
return $this->subscribersResponseBuilder->build($subscriber);
|
||||
}
|
||||
|
||||
public function unsubscribe($subscriberIdOrEmail): array {
|
||||
$this->checkSubscriberParam($subscriberIdOrEmail);
|
||||
$subscriber = $this->findSubscriber($subscriberIdOrEmail);
|
||||
|
||||
if ($subscriber->getStatus() === SubscriberEntity::STATUS_UNSUBSCRIBED) {
|
||||
throw new APIException(__('This subscriber is already unsubscribed.', 'mailpoet'), APIException::SUBSCRIBER_ALREADY_UNSUBSCRIBED);
|
||||
}
|
||||
|
||||
$this->unsubscribesTracker->track(
|
||||
(int)$subscriber->getId(),
|
||||
StatisticsUnsubscribeEntity::SOURCE_MP_API
|
||||
);
|
||||
|
||||
$subscriber->setStatus(SubscriberEntity::STATUS_UNSUBSCRIBED);
|
||||
$this->subscribersRepository->persist($subscriber);
|
||||
$this->subscribersRepository->flush();
|
||||
|
||||
$this->subscribersSegmentRepository->unsubscribeFromSegments($subscriber);
|
||||
|
||||
return $this->subscribersResponseBuilder->build($subscriber);
|
||||
}
|
||||
|
||||
public function unsubscribeFromLists($subscriberIdOrEmail, array $listIds): array {
|
||||
$this->checkSubscriberAndListParams($subscriberIdOrEmail, $listIds);
|
||||
$subscriber = $this->findSubscriber($subscriberIdOrEmail);
|
||||
$foundSegments = $this->getAndValidateSegments($listIds, self::CONTEXT_UNSUBSCRIBE);
|
||||
$this->subscribersSegmentRepository->unsubscribeFromSegments($subscriber, $foundSegments);
|
||||
|
||||
return $this->subscribersResponseBuilder->build($subscriber);
|
||||
}
|
||||
|
||||
public function getSubscribers(array $filter, int $limit, int $offset): array {
|
||||
$listingDefinition = $this->buildListingDefinition($filter, $limit, $offset);
|
||||
$subscribers = $this->subscriberListingRepository->getData($listingDefinition);
|
||||
$result = [];
|
||||
foreach ($subscribers as $subscriber) {
|
||||
$result[] = $this->subscribersResponseBuilder->build($subscriber);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getSubscribersCount(array $filter): int {
|
||||
$listingDefinition = $this->buildListingDefinition($filter);
|
||||
return $this->subscriberListingRepository->getCount($listingDefinition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $filter {
|
||||
* Filters to retrieve subscribers.
|
||||
*
|
||||
* @type string $status One of values: subscribed, unconfirmed, unsubscribed, inactive, bounced
|
||||
* @type int $listId id of a list or dynamic segment
|
||||
* @type \DateTime|int $minUpdatedAt DateTime object or timestamp of last update of subscriber.
|
||||
* }
|
||||
*/
|
||||
private function buildListingDefinition(array $filter, int $limit = 50, int $offset = 0): ListingDefinition {
|
||||
$group = isset($filter['status']) && is_string($filter['status']) ? $filter['status'] : null;
|
||||
$listingFilters = [];
|
||||
// Set filtering by listId
|
||||
if (isset($filter['listId']) && is_int($filter['listId'])) {
|
||||
$listingFilters['segment'] = $filter['listId'];
|
||||
}
|
||||
// Set filtering by minimal updatedAt
|
||||
if (isset($filter['minUpdatedAt'])) {
|
||||
if ($filter['minUpdatedAt'] instanceof \DateTime) {
|
||||
$listingFilters['minUpdatedAt'] = $filter['minUpdatedAt'];
|
||||
} elseif (is_int($filter['minUpdatedAt'])) {
|
||||
$listingFilters['minUpdatedAt'] = Carbon::createFromTimestamp($filter['minUpdatedAt']);
|
||||
}
|
||||
}
|
||||
|
||||
return new ListingDefinition($group, $listingFilters, null, [], 'id', 'asc', $offset, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws APIException
|
||||
*/
|
||||
protected function _scheduleWelcomeNotification(SubscriberEntity $subscriber, array $segments) {
|
||||
try {
|
||||
$this->welcomeScheduler->scheduleSubscriberWelcomeNotification($subscriber->getId(), $segments);
|
||||
} catch (\Throwable $e) {
|
||||
throw new APIException(
|
||||
// translators: %s is an error message
|
||||
sprintf(__('Subscriber added, but welcome email failed to send: %s', 'mailpoet'), $e->getMessage()),
|
||||
APIException::WELCOME_FAILED_TO_SEND
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws APIException
|
||||
*/
|
||||
protected function _sendConfirmationEmail(SubscriberEntity $subscriberEntity) {
|
||||
try {
|
||||
$this->confirmationEmailMailer->sendConfirmationEmailOnce($subscriberEntity);
|
||||
} catch (\Exception $e) {
|
||||
throw new APIException(
|
||||
// translators: %s is the error message
|
||||
sprintf(__('Subscriber added to lists, but confirmation email failed to send: %s', 'mailpoet'), strtolower($e->getMessage())),
|
||||
APIException::CONFIRMATION_FAILED_TO_SEND
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws APIException
|
||||
*/
|
||||
private function checkSubscriberAndListParams($subscriberIdOrEmail, array $listIds): void {
|
||||
if (empty($listIds)) {
|
||||
throw new APIException(__('At least one segment ID is required.', 'mailpoet'), APIException::SEGMENT_REQUIRED);
|
||||
}
|
||||
$this->checkSubscriberParam($subscriberIdOrEmail);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws APIException
|
||||
*/
|
||||
private function checkSubscriberParam($subscriberIdOrEmail): void {
|
||||
if (empty($subscriberIdOrEmail)) {
|
||||
throw new APIException(__('A subscriber is required.', 'mailpoet'), APIException::SUBSCRIBER_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws APIException
|
||||
*/
|
||||
private function findSubscriber($subscriberIdOrEmail): SubscriberEntity {
|
||||
// throw exception when subscriber does not exist
|
||||
$subscriber = null;
|
||||
if (is_int($subscriberIdOrEmail) || (string)(int)$subscriberIdOrEmail === $subscriberIdOrEmail) {
|
||||
$subscriber = $this->subscribersRepository->findOneById($subscriberIdOrEmail);
|
||||
} else if (strlen(trim($subscriberIdOrEmail)) > 0) {
|
||||
$subscriber = $this->subscribersRepository->findOneBy(['email' => $subscriberIdOrEmail]);
|
||||
}
|
||||
|
||||
if (!$subscriber) {
|
||||
throw new APIException(__('This subscriber does not exist.', 'mailpoet'), APIException::SUBSCRIBER_NOT_EXISTS);
|
||||
}
|
||||
|
||||
return $subscriber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SegmentEntity[]
|
||||
* @throws APIException
|
||||
*/
|
||||
private function getAndValidateSegments(array $listIds, string $context): array {
|
||||
// throw exception when none of the segments exist
|
||||
$foundSegments = $this->segmentsRepository->findBy(['id' => $listIds]);
|
||||
if (!$foundSegments) {
|
||||
$exception = _n('This list does not exist.', 'These lists do not exist.', count($listIds), 'mailpoet');
|
||||
throw new APIException($exception, APIException::LIST_NOT_EXISTS);
|
||||
}
|
||||
|
||||
// throw exception when trying to subscribe to WP Users or WooCommerce Customers segments
|
||||
$foundSegmentsIds = [];
|
||||
foreach ($foundSegments as $foundSegment) {
|
||||
if ($foundSegment->getType() === SegmentEntity::TYPE_WP_USERS) {
|
||||
if ($context === self::CONTEXT_SUBSCRIBE) {
|
||||
// translators: %d is the ID of the segment
|
||||
$message = __("Can't subscribe to a WordPress Users list with ID '%d'.", 'mailpoet');
|
||||
} else {
|
||||
// translators: %d is the ID of the segment
|
||||
$message = __("Can't unsubscribe from a WordPress Users list with ID '%d'.", 'mailpoet');
|
||||
}
|
||||
throw new APIException(sprintf($message, $foundSegment->getId()), APIException::SUBSCRIBING_TO_WP_LIST_NOT_ALLOWED);
|
||||
}
|
||||
if ($foundSegment->getType() === SegmentEntity::TYPE_WC_USERS) {
|
||||
if ($context === self::CONTEXT_SUBSCRIBE) {
|
||||
// translators: %d is the ID of the segment
|
||||
$message = __("Can't subscribe to a WooCommerce Customers list with ID '%d'.", 'mailpoet');
|
||||
} else {
|
||||
// translators: %d is the ID of the segment
|
||||
$message = __("Can't unsubscribe from a WooCommerce Customers list with ID '%d'.", 'mailpoet');
|
||||
}
|
||||
throw new APIException(sprintf($message, $foundSegment->getId()), APIException::SUBSCRIBING_TO_WC_LIST_NOT_ALLOWED);
|
||||
}
|
||||
if ($foundSegment->getType() !== SegmentEntity::TYPE_DEFAULT) {
|
||||
if ($context === self::CONTEXT_SUBSCRIBE) {
|
||||
// translators: %d is the ID of the segment
|
||||
$message = __("Can't subscribe to a list with ID '%d'.", 'mailpoet');
|
||||
} else {
|
||||
// translators: %d is the ID of the segment
|
||||
$message = __("Can't unsubscribe from a list with ID '%d'.", 'mailpoet');
|
||||
}
|
||||
throw new APIException(sprintf($message, $foundSegment->getId()), APIException::SUBSCRIBING_TO_LIST_NOT_ALLOWED);
|
||||
}
|
||||
$foundSegmentsIds[] = $foundSegment->getId();
|
||||
}
|
||||
|
||||
// throw an exception when one or more segments do not exist
|
||||
if (count($foundSegmentsIds) !== count($listIds)) {
|
||||
$missingIds = array_values(array_diff($listIds, $foundSegmentsIds));
|
||||
$exception = sprintf(
|
||||
// translators: %s is the count of lists
|
||||
_n("List with ID '%s' does not exist.", "Lists with IDs '%s' do not exist.", count($missingIds), 'mailpoet'),
|
||||
implode(', ', $missingIds)
|
||||
);
|
||||
throw new APIException(sprintf($exception, implode(', ', $missingIds)), APIException::LIST_NOT_EXISTS);
|
||||
}
|
||||
|
||||
return $foundSegments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits subscriber data into two arrays with basic data (index 0) and custom fields data (index 1)
|
||||
* @return array<int, array>
|
||||
*/
|
||||
private function extractCustomFieldsFromFromSubscriberData($data): array {
|
||||
$customFields = [];
|
||||
foreach ($data as $key => $value) {
|
||||
if (strpos($key, 'cf_') === 0) {
|
||||
$customFields[$key] = $value;
|
||||
unset($data[$key]);
|
||||
}
|
||||
}
|
||||
return [$data, $customFields];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\REST;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Validator\Schema;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use Throwable;
|
||||
use WP_REST_Request;
|
||||
|
||||
class API {
|
||||
public const REST_API_INIT_ACTION = 'mailpoet/rest-api/init';
|
||||
|
||||
private const PREFIX = 'mailpoet/v1';
|
||||
private const WP_REST_API_INIT_ACTION = 'rest_api_init';
|
||||
|
||||
/** @var EndpointContainer */
|
||||
private $endpointContainer;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
public function __construct(
|
||||
EndpointContainer $endpointContainer,
|
||||
WPFunctions $wp
|
||||
) {
|
||||
$this->endpointContainer = $endpointContainer;
|
||||
$this->wp = $wp;
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
$this->wp->addAction(self::WP_REST_API_INIT_ACTION, function () {
|
||||
$this->wp->doAction(self::REST_API_INIT_ACTION, [$this]);
|
||||
});
|
||||
}
|
||||
|
||||
public function registerGetRoute(string $route, string $endpoint): void {
|
||||
$this->registerRoute($route, $endpoint, 'GET');
|
||||
}
|
||||
|
||||
public function registerPostRoute(string $route, string $endpoint): void {
|
||||
$this->registerRoute($route, $endpoint, 'POST');
|
||||
}
|
||||
|
||||
public function registerPutRoute(string $route, string $endpoint): void {
|
||||
$this->registerRoute($route, $endpoint, 'PUT');
|
||||
}
|
||||
|
||||
public function registerPatchRoute(string $route, string $endpoint): void {
|
||||
$this->registerRoute($route, $endpoint, 'PATCH');
|
||||
}
|
||||
|
||||
public function registerDeleteRoute(string $route, string $endpoint): void {
|
||||
$this->registerRoute($route, $endpoint, 'DELETE');
|
||||
}
|
||||
|
||||
protected function registerRoute(string $route, string $endpointClass, string $method): void {
|
||||
$schema = array_map(function (Schema $field) {
|
||||
return $field->toArray();
|
||||
}, $endpointClass::getRequestSchema());
|
||||
|
||||
$this->wp->registerRestRoute(self::PREFIX, $route, [
|
||||
'methods' => $method,
|
||||
'callback' => function (WP_REST_Request $wpRequest) use ($endpointClass, $schema) {
|
||||
try {
|
||||
$endpoint = $this->endpointContainer->get($endpointClass);
|
||||
$wpRequest = $this->sanitizeUnknownParams($wpRequest, $schema);
|
||||
$request = new Request($wpRequest);
|
||||
return $endpoint->handle($request);
|
||||
} catch (Throwable $e) {
|
||||
return $this->convertToErrorResponse($e);
|
||||
}
|
||||
},
|
||||
'permission_callback' => function () use ($endpointClass) {
|
||||
$endpoint = $this->endpointContainer->get($endpointClass);
|
||||
return $endpoint->checkPermissions(); // nosemgrep: scanner.php.wp.security.rest-route.permission-callback.incorrect-return
|
||||
},
|
||||
'args' => $schema,
|
||||
]);
|
||||
}
|
||||
|
||||
private function convertToErrorResponse(Throwable $e): ErrorResponse {
|
||||
$response = $e instanceof Exception
|
||||
? new ErrorResponse($e->getStatusCode(), $e->getMessage(), $e->getErrorCode(), $e->getErrors())
|
||||
: new ErrorResponse(500, __('An unknown error occurred.', 'mailpoet'), 'mailpoet_automation_unknown_error');
|
||||
|
||||
if ($response->get_status() >= 500 && function_exists('error_log')) {
|
||||
error_log((string)$e); // phpcs:ignore Squiz.PHP.DiscouragedFunctions
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function sanitizeUnknownParams(WP_REST_Request $wpRequest, array $args): WP_REST_Request {
|
||||
// Remove all params that are not declared in the schema, so we use just the validated ones.
|
||||
// Note that this doesn't work recursively for object properties as it is harder to solve
|
||||
// with features like oneOf, anyOf, additional properties, or pattern properties.
|
||||
$extraParams = array_diff(array_keys($wpRequest->get_params()), array_keys($args));
|
||||
foreach ($extraParams as $extraParam) {
|
||||
unset($wpRequest[(string)$extraParam]);
|
||||
}
|
||||
return $wpRequest;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\REST;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Validator\Schema;
|
||||
|
||||
abstract class Endpoint {
|
||||
abstract public function handle(Request $request): Response;
|
||||
|
||||
public function checkPermissions(): bool {
|
||||
return current_user_can('admin');
|
||||
}
|
||||
|
||||
/** @return array<string, Schema> */
|
||||
public static function getRequestSchema(): array {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\REST;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\InvalidStateException;
|
||||
use MailPoetVendor\Psr\Container\ContainerInterface;
|
||||
|
||||
class EndpointContainer {
|
||||
/** @var ContainerInterface */
|
||||
private $container;
|
||||
|
||||
public function __construct(
|
||||
ContainerInterface $container
|
||||
) {
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public function get(string $class): Endpoint {
|
||||
$endpoint = $this->container->get($class);
|
||||
if (!$endpoint instanceof Endpoint) {
|
||||
throw new InvalidStateException(sprintf("Class '%s' doesn't implement '%s'", $class, Endpoint::class));
|
||||
}
|
||||
return $endpoint;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\REST;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
class ErrorResponse extends Response {
|
||||
public function __construct(
|
||||
int $status,
|
||||
string $message,
|
||||
string $code,
|
||||
array $errors = []
|
||||
) {
|
||||
parent::__construct(null, $status);
|
||||
$this->set_data([
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
'data' => [
|
||||
'status' => $status,
|
||||
'errors' => $errors,
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\REST;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
interface Exception {
|
||||
public function getStatusCode(): int;
|
||||
|
||||
public function getErrorCode(): string;
|
||||
|
||||
public function getErrors(): array;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\REST;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use WP_REST_Request;
|
||||
|
||||
class Request {
|
||||
/** @var WP_REST_Request */
|
||||
private $wpRequest;
|
||||
|
||||
public function __construct(
|
||||
WP_REST_Request $wpRequest
|
||||
) {
|
||||
$this->wpRequest = $wpRequest;
|
||||
}
|
||||
|
||||
public function getHeader(string $key): ?string {
|
||||
return $this->wpRequest->get_header($key);
|
||||
}
|
||||
|
||||
public function getParams(): array {
|
||||
return $this->wpRequest->get_params();
|
||||
}
|
||||
|
||||
/** @return mixed */
|
||||
public function getParam(string $name) {
|
||||
return $this->wpRequest->get_param($name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\API\REST;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use WP_REST_Response;
|
||||
|
||||
class Response extends WP_REST_Response {
|
||||
public function __construct(
|
||||
array $data = null,
|
||||
int $status = 200
|
||||
) {
|
||||
parent::__construct(['data' => $data], $status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\AdminPages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Config\Env;
|
||||
use MailPoet\Config\Renderer;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class AssetsController {
|
||||
/** @var Renderer */
|
||||
private $renderer;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
public function __construct(
|
||||
Renderer $renderer,
|
||||
WPFunctions $wp
|
||||
) {
|
||||
$this->renderer = $renderer;
|
||||
$this->wp = $wp;
|
||||
}
|
||||
|
||||
public function setupAdminPagesDependencies(): void {
|
||||
$this->registerAdminDeps();
|
||||
$this->wp->wpEnqueueScript('mailpoet_admin');
|
||||
}
|
||||
|
||||
public function setupHomepageDependencies(): void {
|
||||
$this->wp->wpEnqueueStyle('mailpoet_homepage', $this->getCssUrl('mailpoet-homepage.css'));
|
||||
}
|
||||
|
||||
public function setupNewsletterEditorDependencies(): void {
|
||||
$this->enqueueJsEntrypoint('newsletter_editor', ['underscore']);
|
||||
$this->wp->wpEnqueueStyle('mailpoet_newsletter_editor', $this->getCssUrl('mailpoet-editor.css'));
|
||||
}
|
||||
|
||||
public function setupFormEditorDependencies(): void {
|
||||
$this->enqueueJsEntrypoint('form_editor', ['underscore']);
|
||||
$this->wp->wpEnqueueStyle('mailpoet_form_editor', $this->getCssUrl('mailpoet-form-editor.css'));
|
||||
}
|
||||
|
||||
public function setupSettingsDependencies(): void {
|
||||
$this->enqueueJsEntrypoint('settings');
|
||||
}
|
||||
|
||||
public function setupDynamicSegmentsDependencies(): void {
|
||||
$this->wp->wpEnqueueStyle('mailpoet_templates', $this->getCssUrl('mailpoet-templates.css'));
|
||||
$this->wp->wpEnqueueStyle('mailpoet_dynamic_segments', $this->getCssUrl('mailpoet-dynamic-segments.css'));
|
||||
}
|
||||
|
||||
public function setupAutomationListingDependencies(): void {
|
||||
$this->enqueueJsEntrypoint('automation');
|
||||
$this->wp->wpEnqueueStyle('mailpoet_automation', $this->getCssUrl('mailpoet-automation.css'));
|
||||
}
|
||||
|
||||
public function setupAutomationTemplatesDependencies(): void {
|
||||
$this->enqueueJsEntrypoint('automation_templates');
|
||||
$this->wp->wpEnqueueStyle('mailpoet_automation_templates', $this->getCssUrl('mailpoet-automation-templates.css'));
|
||||
}
|
||||
|
||||
public function setupAutomationEditorDependencies(): void {
|
||||
$this->enqueueJsEntrypoint('automation_editor', ['wp-date']);
|
||||
$this->wp->wpEnqueueStyle('mailpoet_automation_editor', $this->getCssUrl('mailpoet-automation-editor.css'));
|
||||
}
|
||||
|
||||
public function setupAutomationAnalyticsDependencies(): void {
|
||||
$this->enqueueJsEntrypoint('automation_analytics');
|
||||
$this->wp->wpEnqueueStyle('mailpoet_automation_analytics', $this->getCssUrl('mailpoet-automation-analytics.css'));
|
||||
}
|
||||
|
||||
private function enqueueJsEntrypoint(string $asset, array $dependencies = []): void {
|
||||
$this->registerAdminDeps();
|
||||
|
||||
$name = "mailpoet_$asset";
|
||||
$this->wp->wpEnqueueScript(
|
||||
$name,
|
||||
Env::$assetsUrl . '/dist/js/' . $this->renderer->getJsAsset("$asset.js"),
|
||||
array_merge($dependencies, ['mailpoet_admin']),
|
||||
Env::$version,
|
||||
true
|
||||
);
|
||||
$this->wp->wpSetScriptTranslations($name, 'mailpoet');
|
||||
|
||||
// Ensure Lodash doesn't override Underscore from WordPress on "window._" global.
|
||||
// Checking for "_.at" detects Lodash (the function doesn't exist in Underscore).
|
||||
$noConflict = 'if (window._ && window._.at && window._.noConflict) window._.noConflict();';
|
||||
$this->wp->wpAddInlineScript('mailpoet_admin_commons', $noConflict);
|
||||
$this->wp->wpAddInlineScript('mailpoet_mailpoet', $noConflict);
|
||||
$this->wp->wpAddInlineScript('mailpoet_admin_vendor', $noConflict);
|
||||
$this->wp->wpAddInlineScript('mailpoet_admin', $noConflict);
|
||||
$this->wp->wpAddInlineScript($name, $noConflict);
|
||||
}
|
||||
|
||||
private function registerAdminDeps(): void {
|
||||
// runtime
|
||||
$this->registerFooterScript('mailpoet_runtime', $this->getScriptUrl('runtime.js'));
|
||||
|
||||
// vendor
|
||||
$this->registerFooterScript('mailpoet_vendor', $this->getScriptUrl('vendor.js'));
|
||||
|
||||
// commons
|
||||
$this->registerFooterScript('mailpoet_admin_commons', $this->getScriptUrl('commons.js'));
|
||||
$this->wp->wpSetScriptTranslations('mailpoet_admin_commons', 'mailpoet');
|
||||
|
||||
// mailpoet
|
||||
$this->registerFooterScript('mailpoet_mailpoet', $this->getScriptUrl('mailpoet.js'));
|
||||
$this->wp->wpSetScriptTranslations('mailpoet_mailpoet', 'mailpoet');
|
||||
|
||||
// admin_vendor
|
||||
$this->registerFooterScript(
|
||||
'mailpoet_admin_vendor',
|
||||
$this->getScriptUrl('admin_vendor.js'),
|
||||
[
|
||||
'wp-i18n',
|
||||
'mailpoet_runtime',
|
||||
'mailpoet_vendor',
|
||||
'mailpoet_admin_commons',
|
||||
'mailpoet_mailpoet',
|
||||
]
|
||||
);
|
||||
|
||||
// append Parsley validation string translations
|
||||
$this->wp->wpAddInlineScript('mailpoet_admin_vendor', $this->renderer->render('parsley-translations.html'));
|
||||
|
||||
// enqueue "mailpoet_admin_vendor" so the hook fires after it, but before "mailpoet_admin"
|
||||
$this->wp->wpEnqueueScript('mailpoet_admin_vendor');
|
||||
if ($this->wp->didAction('mailpoet_scripts_admin_before') === 0) {
|
||||
$this->wp->doAction('mailpoet_scripts_admin_before');
|
||||
}
|
||||
|
||||
// admin
|
||||
$this->registerFooterScript(
|
||||
'mailpoet_admin',
|
||||
$this->getScriptUrl('admin.js'),
|
||||
['mailpoet_admin_vendor']
|
||||
);
|
||||
$this->wp->wpSetScriptTranslations('mailpoet_admin', 'mailpoet');
|
||||
}
|
||||
|
||||
private function getScriptUrl(string $name): string {
|
||||
return Env::$assetsUrl . '/dist/js/' . $this->renderer->getJsAsset($name);
|
||||
}
|
||||
|
||||
private function getCssUrl(string $name): string {
|
||||
return Env::$assetsUrl . '/dist/css/' . $this->renderer->getCssAsset($name);
|
||||
}
|
||||
|
||||
private function registerFooterScript(string $handle, string $src, array $deps = []): void {
|
||||
$this->wp->wpRegisterScript($handle, $src, $deps, Env::$version, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AdminPages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\Pages\WelcomeWizard;
|
||||
use MailPoet\Cache\TransientCache;
|
||||
use MailPoet\Config\Installer;
|
||||
use MailPoet\Config\Menu;
|
||||
use MailPoet\Config\Renderer;
|
||||
use MailPoet\Config\ServicesChecker;
|
||||
use MailPoet\Cron\Workers\SubscribersCountCacheRecalculation;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Entities\TagEntity;
|
||||
use MailPoet\Features\FeaturesController;
|
||||
use MailPoet\Referrals\ReferralDetector;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Services\Bridge;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Settings\TrackingConfig;
|
||||
use MailPoet\Settings\UserFlagsController;
|
||||
use MailPoet\Tags\TagRepository;
|
||||
use MailPoet\Tracy\DIPanel\DIPanel;
|
||||
use MailPoet\Util\Installation;
|
||||
use MailPoet\Util\License\Features\CapabilitiesManager;
|
||||
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
|
||||
use MailPoet\Util\License\License;
|
||||
use MailPoet\WooCommerce;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoet\WP\Notice as WPNotice;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
use Tracy\Debugger;
|
||||
|
||||
class PageRenderer {
|
||||
/** @var Bridge */
|
||||
private $bridge;
|
||||
|
||||
/** @var Renderer */
|
||||
private $renderer;
|
||||
|
||||
/** @var ServicesChecker */
|
||||
private $servicesChecker;
|
||||
|
||||
/** @var FeaturesController */
|
||||
private $featuresController;
|
||||
|
||||
/** @var Installation */
|
||||
private $installation;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
/** @var UserFlagsController */
|
||||
private $userFlags;
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentRepository;
|
||||
|
||||
private $tagRepository;
|
||||
|
||||
/** @var SubscribersCountCacheRecalculation */
|
||||
private $subscribersCountCacheRecalculation;
|
||||
|
||||
/** @var SubscribersFeature */
|
||||
private $subscribersFeature;
|
||||
|
||||
/** @var TrackingConfig */
|
||||
private $trackingConfig;
|
||||
|
||||
/** @var TransientCache */
|
||||
private $transientCache;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/*** @var AssetsController */
|
||||
private $assetsController;
|
||||
|
||||
/** @var WooCommerce\Helper */
|
||||
private $wooCommerceHelper;
|
||||
|
||||
/** @var WooCommerce\WooCommerceSubscriptions\Helper */
|
||||
private $wooCommerceSubscriptionsHelper;
|
||||
|
||||
private CapabilitiesManager $capabilitiesManager;
|
||||
|
||||
public function __construct(
|
||||
Bridge $bridge,
|
||||
Renderer $renderer,
|
||||
ServicesChecker $servicesChecker,
|
||||
FeaturesController $featuresController,
|
||||
Installation $installation,
|
||||
SettingsController $settings,
|
||||
UserFlagsController $userFlags,
|
||||
SegmentsRepository $segmentRepository,
|
||||
TagRepository $tagRepository,
|
||||
SubscribersCountCacheRecalculation $subscribersCountCacheRecalculation,
|
||||
SubscribersFeature $subscribersFeature,
|
||||
TrackingConfig $trackingConfig,
|
||||
TransientCache $transientCache,
|
||||
WPFunctions $wp,
|
||||
AssetsController $assetsController,
|
||||
WooCommerce\Helper $wooCommerceHelper,
|
||||
WooCommerce\WooCommerceSubscriptions\Helper $wooCommerceSubscriptionsHelper,
|
||||
CapabilitiesManager $capabilitiesManager
|
||||
) {
|
||||
$this->bridge = $bridge;
|
||||
$this->renderer = $renderer;
|
||||
$this->servicesChecker = $servicesChecker;
|
||||
$this->featuresController = $featuresController;
|
||||
$this->installation = $installation;
|
||||
$this->settings = $settings;
|
||||
$this->userFlags = $userFlags;
|
||||
$this->segmentRepository = $segmentRepository;
|
||||
$this->tagRepository = $tagRepository;
|
||||
$this->subscribersCountCacheRecalculation = $subscribersCountCacheRecalculation;
|
||||
$this->subscribersFeature = $subscribersFeature;
|
||||
$this->trackingConfig = $trackingConfig;
|
||||
$this->transientCache = $transientCache;
|
||||
$this->wp = $wp;
|
||||
$this->assetsController = $assetsController;
|
||||
$this->wooCommerceHelper = $wooCommerceHelper;
|
||||
$this->wooCommerceSubscriptionsHelper = $wooCommerceSubscriptionsHelper;
|
||||
$this->capabilitiesManager = $capabilitiesManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set common data for template and display template
|
||||
* @param string $template
|
||||
* @param array $data
|
||||
*/
|
||||
public function displayPage($template, array $data = []) {
|
||||
$installer = new Installer(Installer::PREMIUM_PLUGIN_SLUG);
|
||||
$premiumDownloadUrl = $this->subscribersFeature->hasValidPremiumKey()
|
||||
? $installer->generatePluginDownloadUrl()
|
||||
: null;
|
||||
|
||||
$wpSegment = $this->segmentRepository->getWPUsersSegment();
|
||||
$wpSegmentState = ($wpSegment instanceof SegmentEntity) && $wpSegment->getDeletedAt() === null ?
|
||||
SegmentEntity::SEGMENT_ENABLED : SegmentEntity::SEGMENT_DISABLED;
|
||||
$installedAtDiff = (new \DateTime($this->settings->get('installed_at')))->diff(new \DateTime());
|
||||
$subscriberCount = $this->subscribersFeature->getSubscribersCount();
|
||||
$subscribersCacheCreatedAt = Carbon::now();
|
||||
if ($this->subscribersFeature->isSubscribersCountEnoughForCache($subscriberCount)) {
|
||||
$subscribersCacheCreatedAt = $this->transientCache->getOldestCreatedAt(TransientCache::SUBSCRIBERS_STATISTICS_COUNT_KEY) ?: Carbon::now();
|
||||
}
|
||||
|
||||
$defaults = [
|
||||
'current_page' => sanitize_text_field(wp_unslash($_GET['page'] ?? '')),
|
||||
'site_name' => $this->wp->wpSpecialcharsDecode($this->wp->getOption('blogname'), ENT_QUOTES),
|
||||
'main_page' => Menu::MAIN_PAGE_SLUG,
|
||||
'site_url' => $this->wp->siteUrl(),
|
||||
'site_address' => $this->wp->wpParseUrl($this->wp->homeUrl(), PHP_URL_HOST),
|
||||
'feature_flags' => $this->featuresController->getAllFlags(),
|
||||
'referral_id' => $this->settings->get(ReferralDetector::REFERRAL_SETTING_NAME),
|
||||
'mailpoet_api_key_state' => $this->settings->get('mta.mailpoet_api_key_state'),
|
||||
'mta_method' => $this->settings->get('mta.method'),
|
||||
'premium_key_state' => $this->settings->get('premium.premium_key_state'),
|
||||
'wp_segment_state' => $wpSegmentState,
|
||||
'tracking_config' => $this->trackingConfig->getConfig(),
|
||||
'is_new_user' => $this->installation->isNewInstallation(),
|
||||
'installed_days_ago' => (int)$installedAtDiff->format('%a'),
|
||||
'deactivate_subscriber_after_inactive_days' => $this->settings->get('deactivate_subscriber_after_inactive_days'),
|
||||
'send_transactional_emails' => (bool)$this->settings->get('send_transactional_emails'),
|
||||
'transactional_emails_opt_in_notice_dismissed' => (bool)$this->userFlags->get('transactional_emails_opt_in_notice_dismissed'),
|
||||
'track_wizard_loaded_via_woocommerce' => (bool)$this->settings->get(WelcomeWizard::TRACK_LOADDED_VIA_WOOCOMMERCE_SETTING_NAME),
|
||||
'track_wizard_loaded_via_woocommerce_marketing_dashboard' => (bool)$this->settings->get(WelcomeWizard::TRACK_LOADDED_VIA_WOOCOMMERCE_MARKETING_DASHBOARD_SETTING_NAME),
|
||||
'mail_function_enabled' => function_exists('mail') && is_callable('mail'),
|
||||
'admin_plugins_url' => WPFunctions::get()->adminUrl('plugins.php'),
|
||||
|
||||
// Premium & plan upgrade info
|
||||
'current_wp_user_email' => $this->wp->wpGetCurrentUser()->user_email,
|
||||
'link_premium' => $this->wp->getSiteUrl(null, '/wp-admin/admin.php?page=mailpoet-upgrade'),
|
||||
'premium_plugin_installed' => Installer::isPluginInstalled(Installer::PREMIUM_PLUGIN_SLUG),
|
||||
'premium_plugin_active' => $this->servicesChecker->isPremiumPluginActive(),
|
||||
'premium_plugin_download_url' => $premiumDownloadUrl,
|
||||
'premium_plugin_activation_url' => $installer->generatePluginActivationUrl(Installer::PREMIUM_PLUGIN_PATH),
|
||||
'has_valid_api_key' => $this->subscribersFeature->hasValidApiKey(),
|
||||
'has_valid_premium_key' => $this->subscribersFeature->hasValidPremiumKey(),
|
||||
'has_premium_support' => $this->subscribersFeature->hasPremiumSupport(),
|
||||
'has_mss_key_specified' => Bridge::isMSSKeySpecified(),
|
||||
'mss_key_invalid' => $this->servicesChecker->isMailPoetAPIKeyValid() === false,
|
||||
'mss_key_valid' => $this->subscribersFeature->hasValidMssKey(),
|
||||
'mss_key_pending_approval' => $this->servicesChecker->isMailPoetAPIKeyPendingApproval(),
|
||||
'mss_active' => $this->bridge->isMailpoetSendingServiceEnabled(),
|
||||
'plugin_partial_key' => $this->servicesChecker->generatePartialApiKey(),
|
||||
'subscriber_count' => $subscriberCount,
|
||||
'subscribers_counts_cache_created_at' => $subscribersCacheCreatedAt->format('Y-m-d\TH:i:sO'),
|
||||
'subscribers_limit' => $this->subscribersFeature->getSubscribersLimit(),
|
||||
'subscribers_limit_reached' => $this->subscribersFeature->check(),
|
||||
'email_volume_limit' => $this->subscribersFeature->getEmailVolumeLimit(),
|
||||
'email_volume_limit_reached' => $this->subscribersFeature->checkEmailVolumeLimitIsReached(),
|
||||
'capabilities' => $this->capabilitiesManager->getCapabilities(),
|
||||
'tier' => $this->capabilitiesManager->getTier(),
|
||||
'urls' => [
|
||||
'automationListing' => admin_url('admin.php?page=mailpoet-automation'),
|
||||
'automationEditor' => admin_url('admin.php?page=mailpoet-automation-editor'),
|
||||
'automationTemplates' => admin_url('admin.php?page=mailpoet-automation-templates'),
|
||||
'automationAnalytics' => admin_url('admin.php?page=mailpoet-automation-analytics'),
|
||||
],
|
||||
'woocommerce_store_config' => $this->wooCommerceHelper->isWooCommerceActive() ? $this->getWoocommerceStoreConfig() : null,
|
||||
'tags' => array_map(function (TagEntity $tag): array {
|
||||
return [
|
||||
'id' => $tag->getId(),
|
||||
'name' => $tag->getName(),
|
||||
];
|
||||
}, $this->tagRepository->findAll()),
|
||||
'display_docsbot_widget' => $this->displayDocsBotWidget(),
|
||||
'is_woocommerce_subscriptions_active' => $this->wooCommerceSubscriptionsHelper->isWooCommerceSubscriptionsActive(),
|
||||
'cron_trigger_method' => $this->settings->get('cron_trigger.method'),
|
||||
];
|
||||
|
||||
if (!$defaults['premium_plugin_active']) {
|
||||
$defaults['free_premium_subscribers_limit'] = License::FREE_PREMIUM_SUBSCRIBERS_LIMIT;
|
||||
}
|
||||
|
||||
try {
|
||||
if (
|
||||
class_exists(Debugger::class)
|
||||
&& class_exists(DIPanel::class)
|
||||
) {
|
||||
DIPanel::init();
|
||||
}
|
||||
if (is_admin() && $this->subscribersCountCacheRecalculation->shouldBeScheduled()) {
|
||||
$this->subscribersCountCacheRecalculation->schedule();
|
||||
}
|
||||
|
||||
// If the page didn't enqueue any assets, this will act as a fallback.
|
||||
// If some assets were enqueued, this won't change the queue ordering.
|
||||
$this->assetsController->setupAdminPagesDependencies();
|
||||
$this->wp->doAction('mailpoet_styles_admin_after');
|
||||
|
||||
// We are in control of the template and the data can be considered safe at this point
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $this->renderer->render($template, $data + $defaults);
|
||||
} catch (\Exception $e) {
|
||||
$notice = new WPNotice(WPNotice::TYPE_ERROR, $e->getMessage());
|
||||
$notice->displayWPNotice();
|
||||
}
|
||||
}
|
||||
|
||||
private function getWoocommerceStoreConfig() {
|
||||
|
||||
return [
|
||||
'precision' => $this->wooCommerceHelper->wcGetPriceDecimals(),
|
||||
'decimalSeparator' => $this->wooCommerceHelper->wcGetPriceDecimalSeperator(),
|
||||
'thousandSeparator' => $this->wooCommerceHelper->wcGetPriceThousandSeparator(),
|
||||
'code' => $this->wooCommerceHelper->getWoocommerceCurrency(),
|
||||
'symbol' => html_entity_decode($this->wooCommerceHelper->getWoocommerceCurrencySymbol()),
|
||||
'symbolPosition' => $this->wp->getOption('woocommerce_currency_pos'),
|
||||
'priceFormat' => $this->wooCommerceHelper->getWoocommercePriceFormat(),
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
public function displayDocsBotWidget(): bool {
|
||||
$display = $this->wp->applyFilters('mailpoet_display_docsbot_widget', $this->settings->get('3rd_party_libs.enabled') === '1');
|
||||
return (bool)$display;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\AssetsController;
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\AutomaticEmails\AutomaticEmails;
|
||||
use MailPoet\Automation\Engine\Data\AutomationTemplate;
|
||||
use MailPoet\Automation\Engine\Data\AutomationTemplateCategory;
|
||||
use MailPoet\Automation\Engine\Registry;
|
||||
use MailPoet\Automation\Engine\Storage\AutomationStorage;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Segments\SegmentsSimpleListRepository;
|
||||
use MailPoet\Settings\UserFlagsController;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class Automation {
|
||||
/** @var AssetsController */
|
||||
private $assetsController;
|
||||
|
||||
private AutomaticEmails $automaticEmails;
|
||||
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var AutomationStorage */
|
||||
private $automationStorage;
|
||||
|
||||
/** @var Registry */
|
||||
private $registry;
|
||||
|
||||
private NewslettersRepository $newslettersRepository;
|
||||
|
||||
private SegmentsSimpleListRepository $segmentsListRepository;
|
||||
|
||||
private UserFlagsController $userFlagsController;
|
||||
|
||||
public function __construct(
|
||||
AssetsController $assetsController,
|
||||
AutomaticEmails $automaticEmails,
|
||||
PageRenderer $pageRenderer,
|
||||
WPFunctions $wp,
|
||||
AutomationStorage $automationStorage,
|
||||
Registry $registry,
|
||||
NewslettersRepository $newslettersRepository,
|
||||
SegmentsSimpleListRepository $segmentsListRepository,
|
||||
UserFlagsController $userFlagsController
|
||||
) {
|
||||
$this->assetsController = $assetsController;
|
||||
$this->automaticEmails = $automaticEmails;
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->wp = $wp;
|
||||
$this->automationStorage = $automationStorage;
|
||||
$this->registry = $registry;
|
||||
$this->newslettersRepository = $newslettersRepository;
|
||||
$this->segmentsListRepository = $segmentsListRepository;
|
||||
$this->userFlagsController = $userFlagsController;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
global $wp_roles; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
|
||||
$this->assetsController->setupAutomationListingDependencies();
|
||||
$this->pageRenderer->displayPage('automation.html', [
|
||||
'locale_full' => $this->wp->getLocale(),
|
||||
'api' => [
|
||||
'root' => rtrim($this->wp->escUrlRaw($this->wp->restUrl()), '/'),
|
||||
'nonce' => $this->wp->wpCreateNonce('wp_rest'),
|
||||
],
|
||||
'automationCount' => $this->automationStorage->getAutomationCount(),
|
||||
'legacyAutomationCount' => $this->newslettersRepository->countBy([
|
||||
'type' => [NewsletterEntity::TYPE_WELCOME, NewsletterEntity::TYPE_AUTOMATIC],
|
||||
]),
|
||||
'templates' => array_map(
|
||||
function(AutomationTemplate $template): array {
|
||||
return $template->toArray();
|
||||
},
|
||||
array_values($this->registry->getTemplates())
|
||||
),
|
||||
'template_categories' => array_map(
|
||||
function (AutomationTemplateCategory $category): array {
|
||||
return [
|
||||
'slug' => $category->getSlug(),
|
||||
'name' => $category->getName(),
|
||||
];
|
||||
},
|
||||
array_values($this->registry->getTemplateCategories())
|
||||
),
|
||||
'registry' => $this->buildRegistry(),
|
||||
'context' => $this->buildContext(),
|
||||
'segments' => $this->segmentsListRepository->getListWithSubscribedSubscribersCounts(),
|
||||
'roles' => $wp_roles->get_names() + ['mailpoet_all' => __('In any WordPress role', 'mailpoet')],
|
||||
'automatic_emails' => $this->automaticEmails->getAutomaticEmails(),
|
||||
'legacy_automations_notice_dismissed' => (bool)$this->userFlagsController->get('legacy_automations_notice_dismissed'),
|
||||
]);
|
||||
}
|
||||
|
||||
private function buildRegistry(): array {
|
||||
$steps = [];
|
||||
foreach ($this->registry->getSteps() as $key => $step) {
|
||||
$steps[$key] = [
|
||||
'key' => $step->getKey(),
|
||||
'name' => $step->getName(),
|
||||
'args_schema' => $step->getArgsSchema()->toArray(),
|
||||
];
|
||||
}
|
||||
|
||||
$subjects = [];
|
||||
foreach ($this->registry->getSubjects() as $key => $subject) {
|
||||
$subjects[$key] = [
|
||||
'key' => $subject->getKey(),
|
||||
'name' => $subject->getName(),
|
||||
'args_schema' => $subject->getArgsSchema()->toArray(),
|
||||
'field_keys' => array_map(function ($field) {
|
||||
return $field->getKey();
|
||||
}, $subject->getFields()),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'steps' => $steps,
|
||||
'subjects' => $subjects,
|
||||
];
|
||||
}
|
||||
|
||||
private function buildContext(): array {
|
||||
$data = [];
|
||||
foreach ($this->registry->getContextFactories() as $key => $factory) {
|
||||
$data[$key] = $factory();
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\AssetsController;
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\Automation\Engine\Mappers\AutomationMapper;
|
||||
use MailPoet\Automation\Engine\Registry;
|
||||
use MailPoet\Automation\Engine\Storage\AutomationStorage;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoet\WP\Notice as WPNotice;
|
||||
|
||||
class AutomationAnalytics {
|
||||
|
||||
/** @var AssetsController */
|
||||
private $assetsController;
|
||||
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
/** @var AutomationStorage */
|
||||
private $automationStorage;
|
||||
|
||||
/** @var AutomationMapper */
|
||||
private $automationMapper;
|
||||
|
||||
/** @var Registry */
|
||||
private $registry;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
public function __construct(
|
||||
AssetsController $assetsController,
|
||||
PageRenderer $pageRenderer,
|
||||
AutomationStorage $automationStorage,
|
||||
AutomationMapper $automationMapper,
|
||||
Registry $registry,
|
||||
WPFunctions $wp
|
||||
) {
|
||||
$this->assetsController = $assetsController;
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->automationStorage = $automationStorage;
|
||||
$this->automationMapper = $automationMapper;
|
||||
$this->registry = $registry;
|
||||
$this->wp = $wp;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$this->assetsController->setupAutomationAnalyticsDependencies();
|
||||
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
$automation = $id ? $this->automationStorage->getAutomation($id) : null;
|
||||
if (!$automation) {
|
||||
$notice = new WPNotice(
|
||||
WPNotice::TYPE_ERROR,
|
||||
__('Automation not found.', 'mailpoet')
|
||||
);
|
||||
$notice->displayWPNotice();
|
||||
$this->pageRenderer->displayPage('blank.html');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->pageRenderer->displayPage('automation/analytics.html', [
|
||||
'registry' => $this->buildRegistry(),
|
||||
'context' => $this->buildContext(),
|
||||
'automation' => $this->automationMapper->buildAutomation($automation),
|
||||
'locale_full' => $this->wp->getLocale(),
|
||||
'api' => [
|
||||
'root' => rtrim($this->wp->escUrlRaw($this->wp->restUrl()), '/'),
|
||||
'nonce' => $this->wp->wpCreateNonce('wp_rest'),
|
||||
],
|
||||
'jsonapi' => [
|
||||
'root' => rtrim($this->wp->escUrlRaw(admin_url('admin-ajax.php')), '/'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function buildRegistry(): array {
|
||||
$steps = [];
|
||||
foreach ($this->registry->getSteps() as $key => $step) {
|
||||
$steps[$key] = [
|
||||
'key' => $step->getKey(),
|
||||
'name' => $step->getName(),
|
||||
'args_schema' => $step->getArgsSchema()->toArray(),
|
||||
];
|
||||
}
|
||||
|
||||
$subjects = [];
|
||||
foreach ($this->registry->getSubjects() as $key => $subject) {
|
||||
$subjects[$key] = [
|
||||
'key' => $subject->getKey(),
|
||||
'name' => $subject->getName(),
|
||||
'args_schema' => $subject->getArgsSchema()->toArray(),
|
||||
'field_keys' => array_map(function ($field) {
|
||||
return $field->getKey();
|
||||
}, $subject->getFields()),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'steps' => $steps,
|
||||
'subjects' => $subjects,
|
||||
];
|
||||
}
|
||||
|
||||
private function buildContext(): array {
|
||||
$data = [];
|
||||
foreach ($this->registry->getContextFactories() as $key => $factory) {
|
||||
$data[$key] = $factory();
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\AssetsController;
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\Automation\Engine\Control\SubjectTransformerHandler;
|
||||
use MailPoet\Automation\Engine\Data\Automation;
|
||||
use MailPoet\Automation\Engine\Data\Field;
|
||||
use MailPoet\Automation\Engine\Hooks;
|
||||
use MailPoet\Automation\Engine\Integration\Trigger;
|
||||
use MailPoet\Automation\Engine\Mappers\AutomationMapper;
|
||||
use MailPoet\Automation\Engine\Registry;
|
||||
use MailPoet\Automation\Engine\Storage\AutomationStorage;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoet\WP\Notice as WPNotice;
|
||||
|
||||
class AutomationEditor {
|
||||
/** @var AssetsController */
|
||||
private $assetsController;
|
||||
|
||||
/** @var AutomationMapper */
|
||||
private $automationMapper;
|
||||
|
||||
/** @var AutomationStorage */
|
||||
private $automationStorage;
|
||||
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
/** @var Registry */
|
||||
private $registry;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var SubjectTransformerHandler */
|
||||
private $subjectTransformerHandler;
|
||||
|
||||
public function __construct(
|
||||
AssetsController $assetsController,
|
||||
AutomationMapper $automationMapper,
|
||||
AutomationStorage $automationStorage,
|
||||
PageRenderer $pageRenderer,
|
||||
Registry $registry,
|
||||
WPFunctions $wp,
|
||||
SubjectTransformerHandler $subjectTransformerHandler
|
||||
) {
|
||||
$this->assetsController = $assetsController;
|
||||
$this->automationMapper = $automationMapper;
|
||||
$this->automationStorage = $automationStorage;
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->registry = $registry;
|
||||
$this->wp = $wp;
|
||||
$this->subjectTransformerHandler = $subjectTransformerHandler;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$this->assetsController->setupAutomationEditorDependencies();
|
||||
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
|
||||
$this->wp->doAction(Hooks::EDITOR_BEFORE_LOAD, (int)$id);
|
||||
|
||||
$automation = $id ? $this->automationStorage->getAutomation($id) : null;
|
||||
if (!$automation) {
|
||||
$notice = new WPNotice(
|
||||
WPNotice::TYPE_ERROR,
|
||||
__('Automation not found.', 'mailpoet')
|
||||
);
|
||||
$notice->displayWPNotice();
|
||||
$this->pageRenderer->displayPage('blank.html');
|
||||
return;
|
||||
}
|
||||
|
||||
if ($automation->getStatus() === Automation::STATUS_TRASH) {
|
||||
$this->wp->wpSafeRedirect($this->wp->adminUrl('admin.php?page=mailpoet-automation&status=trash¬ice=had-been-deleted'));
|
||||
exit();
|
||||
}
|
||||
|
||||
$this->pageRenderer->displayPage('automation/editor.html', [
|
||||
'registry' => $this->buildRegistry(),
|
||||
'context' => $this->buildContext(),
|
||||
'automation' => $this->automationMapper->buildAutomation($automation),
|
||||
'locale_full' => $this->wp->getLocale(),
|
||||
'api' => [
|
||||
'root' => rtrim($this->wp->escUrlRaw($this->wp->restUrl()), '/'),
|
||||
'nonce' => $this->wp->wpCreateNonce('wp_rest'),
|
||||
],
|
||||
'jsonapi' => [
|
||||
'root' => rtrim($this->wp->escUrlRaw(admin_url('admin-ajax.php')), '/'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function buildRegistry(): array {
|
||||
$steps = [];
|
||||
foreach ($this->registry->getSteps() as $key => $step) {
|
||||
$steps[$key] = [
|
||||
'key' => $step->getKey(),
|
||||
'name' => $step->getName(),
|
||||
'subject_keys' => $step instanceof Trigger ? $this->subjectTransformerHandler->getSubjectKeysForTrigger($step) : $step->getSubjectKeys(),
|
||||
'args_schema' => $step->getArgsSchema()->toArray(),
|
||||
];
|
||||
}
|
||||
|
||||
$subjects = [];
|
||||
foreach ($this->registry->getSubjects() as $key => $subject) {
|
||||
$subjectFields = $subject->getFields();
|
||||
usort($subjectFields, function (Field $a, Field $b) {
|
||||
return $a->getName() <=> $b->getName();
|
||||
});
|
||||
|
||||
$subjects[$key] = [
|
||||
'key' => $subject->getKey(),
|
||||
'name' => $subject->getName(),
|
||||
'args_schema' => $subject->getArgsSchema()->toArray(),
|
||||
'field_keys' => array_map(function ($field) {
|
||||
return $field->getKey();
|
||||
}, $subjectFields),
|
||||
];
|
||||
}
|
||||
|
||||
$fields = [];
|
||||
foreach ($this->registry->getFields() as $key => $field) {
|
||||
$fields[$key] = [
|
||||
'key' => $field->getKey(),
|
||||
'type' => $field->getType(),
|
||||
'name' => $field->getName(),
|
||||
'args' => $field->getArgs(),
|
||||
];
|
||||
}
|
||||
|
||||
$filters = [];
|
||||
foreach ($this->registry->getFilters() as $fieldType => $filter) {
|
||||
$conditions = [];
|
||||
foreach ($filter->getConditions() as $key => $label) {
|
||||
$conditions[] = [
|
||||
'key' => $key,
|
||||
'label' => $label,
|
||||
];
|
||||
}
|
||||
$filters[$fieldType] = [
|
||||
'field_type' => $filter->getFieldType(),
|
||||
'conditions' => $conditions,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'steps' => $steps,
|
||||
'subjects' => $subjects,
|
||||
'fields' => $fields,
|
||||
'filters' => $filters,
|
||||
];
|
||||
}
|
||||
|
||||
private function buildContext(): array {
|
||||
$data = [];
|
||||
foreach ($this->registry->getContextFactories() as $key => $factory) {
|
||||
$data[$key] = $factory();
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\AssetsController;
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\Automation\Engine\Data\AutomationTemplate;
|
||||
use MailPoet\Automation\Engine\Data\AutomationTemplateCategory;
|
||||
use MailPoet\Automation\Engine\Registry;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class AutomationTemplates {
|
||||
/** @var AssetsController */
|
||||
private $assetsController;
|
||||
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
/** @var Registry */
|
||||
private $registry;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
public function __construct(
|
||||
AssetsController $assetsController,
|
||||
PageRenderer $pageRenderer,
|
||||
Registry $registry,
|
||||
WPFunctions $wp
|
||||
) {
|
||||
$this->assetsController = $assetsController;
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->registry = $registry;
|
||||
$this->wp = $wp;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$this->assetsController->setupAutomationTemplatesDependencies();
|
||||
|
||||
$this->pageRenderer->displayPage(
|
||||
'automation/templates.html',
|
||||
[
|
||||
'locale_full' => $this->wp->getLocale(),
|
||||
'api' => [
|
||||
'root' => rtrim($this->wp->escUrlRaw($this->wp->restUrl()), '/'),
|
||||
'nonce' => $this->wp->wpCreateNonce('wp_rest'),
|
||||
],
|
||||
'templates' => array_map(
|
||||
function(AutomationTemplate $template): array {
|
||||
return $template->toArray();
|
||||
},
|
||||
array_values($this->registry->getTemplates())
|
||||
),
|
||||
'template_categories' => array_map(
|
||||
function (AutomationTemplateCategory $category): array {
|
||||
return [
|
||||
'slug' => $category->getSlug(),
|
||||
'name' => $category->getName(),
|
||||
];
|
||||
},
|
||||
array_values($this->registry->getTemplateCategories())
|
||||
),
|
||||
'registry' => $this->buildRegistry(),
|
||||
'context' => $this->buildContext(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function buildRegistry(): array {
|
||||
$steps = [];
|
||||
foreach ($this->registry->getSteps() as $key => $step) {
|
||||
$steps[$key] = [
|
||||
'key' => $step->getKey(),
|
||||
'name' => $step->getName(),
|
||||
'args_schema' => $step->getArgsSchema()->toArray(),
|
||||
];
|
||||
}
|
||||
|
||||
$subjects = [];
|
||||
foreach ($this->registry->getSubjects() as $key => $subject) {
|
||||
$subjects[$key] = [
|
||||
'key' => $subject->getKey(),
|
||||
'name' => $subject->getName(),
|
||||
'args_schema' => $subject->getArgsSchema()->toArray(),
|
||||
'field_keys' => array_map(function ($field) {
|
||||
return $field->getKey();
|
||||
}, $subject->getFields()),
|
||||
];
|
||||
}
|
||||
|
||||
$fields = [];
|
||||
foreach ($this->registry->getFields() as $key => $field) {
|
||||
$fields[$key] = [
|
||||
'key' => $field->getKey(),
|
||||
'type' => $field->getType(),
|
||||
'name' => $field->getName(),
|
||||
'args' => $field->getArgs(),
|
||||
];
|
||||
}
|
||||
|
||||
$filters = [];
|
||||
foreach ($this->registry->getFilters() as $fieldType => $filter) {
|
||||
$conditions = [];
|
||||
foreach ($filter->getConditions() as $key => $label) {
|
||||
$conditions[] = [
|
||||
'key' => $key,
|
||||
'label' => $label,
|
||||
];
|
||||
}
|
||||
$filters[$fieldType] = [
|
||||
'field_type' => $filter->getFieldType(),
|
||||
'conditions' => $conditions,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'steps' => $steps,
|
||||
'subjects' => $subjects,
|
||||
'fields' => $fields,
|
||||
'filters' => $filters,
|
||||
];
|
||||
}
|
||||
|
||||
private function buildContext(): array {
|
||||
$data = [];
|
||||
foreach ($this->registry->getContextFactories() as $key => $factory) {
|
||||
$data[$key] = $factory();
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\AssetsController;
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\API\JSON\ResponseBuilders\CustomFieldsResponseBuilder;
|
||||
use MailPoet\Automation\Engine\Data\Automation;
|
||||
use MailPoet\Automation\Engine\Storage\AutomationStorage;
|
||||
use MailPoet\CustomFields\CustomFieldsRepository;
|
||||
use MailPoet\Entities\DynamicSegmentFilterData;
|
||||
use MailPoet\Entities\FormEntity;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Form\FormsRepository;
|
||||
use MailPoet\Listing\PageLimit;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Segments\SegmentDependencyValidator;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\WooCommerce\Helper as WooCommerceHelper;
|
||||
use MailPoet\WP\AutocompletePostListLoader as WPPostListLoader;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoetVendor\Doctrine\Common\Collections\Criteria;
|
||||
|
||||
class DynamicSegments {
|
||||
/** @var AssetsController */
|
||||
private $assetsController;
|
||||
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
/** @var PageLimit */
|
||||
private $listingPageLimit;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var WooCommerceHelper */
|
||||
private $woocommerceHelper;
|
||||
|
||||
/** @var WPPostListLoader */
|
||||
private $wpPostListLoader;
|
||||
|
||||
/** @var SegmentDependencyValidator */
|
||||
private $segmentDependencyValidator;
|
||||
|
||||
/** @var CustomFieldsRepository */
|
||||
private $customFieldsRepository;
|
||||
|
||||
/** @var CustomFieldsResponseBuilder */
|
||||
private $customFieldsResponseBuilder;
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentsRepository;
|
||||
|
||||
/** @var NewslettersRepository */
|
||||
private $newslettersRepository;
|
||||
|
||||
/** @var FormsRepository */
|
||||
private $formsRepository;
|
||||
|
||||
/** @var AutomationStorage */
|
||||
private $automationStorage;
|
||||
|
||||
public function __construct(
|
||||
AssetsController $assetsController,
|
||||
PageRenderer $pageRenderer,
|
||||
PageLimit $listingPageLimit,
|
||||
WPFunctions $wp,
|
||||
WooCommerceHelper $woocommerceHelper,
|
||||
WPPostListLoader $wpPostListLoader,
|
||||
CustomFieldsRepository $customFieldsRepository,
|
||||
CustomFieldsResponseBuilder $customFieldsResponseBuilder,
|
||||
SegmentDependencyValidator $segmentDependencyValidator,
|
||||
SegmentsRepository $segmentsRepository,
|
||||
NewslettersRepository $newslettersRepository,
|
||||
FormsRepository $formsRepository,
|
||||
AutomationStorage $automationStorage
|
||||
) {
|
||||
$this->assetsController = $assetsController;
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->listingPageLimit = $listingPageLimit;
|
||||
$this->wp = $wp;
|
||||
$this->woocommerceHelper = $woocommerceHelper;
|
||||
$this->wpPostListLoader = $wpPostListLoader;
|
||||
$this->segmentDependencyValidator = $segmentDependencyValidator;
|
||||
$this->customFieldsRepository = $customFieldsRepository;
|
||||
$this->customFieldsResponseBuilder = $customFieldsResponseBuilder;
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
$this->newslettersRepository = $newslettersRepository;
|
||||
$this->formsRepository = $formsRepository;
|
||||
$this->automationStorage = $automationStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function render() {
|
||||
$data = [];
|
||||
$data['dynamic_segment_count'] = $this->segmentsRepository->countBy([
|
||||
'deletedAt' => null,
|
||||
'type' => SegmentEntity::TYPE_DYNAMIC,
|
||||
]);
|
||||
$data['items_per_page'] = $this->listingPageLimit->getLimitPerPage('segments');
|
||||
|
||||
$customFields = $this->customFieldsRepository->findBy([], ['name' => 'asc']);
|
||||
$data['custom_fields'] = $this->customFieldsResponseBuilder->buildBatch($customFields);
|
||||
|
||||
$wpRoles = $this->wp->getEditableRoles();
|
||||
$data['wordpress_editable_roles_list'] = array_map(function($roleId, $role) {
|
||||
return [
|
||||
'role_id' => $roleId,
|
||||
'role_name' => $role['name'],
|
||||
];
|
||||
}, array_keys($wpRoles), $wpRoles);
|
||||
|
||||
$data['newsletters_list'] = $this->getNewslettersList();
|
||||
|
||||
$data['static_segments_list'] = [];
|
||||
$criteria = new Criteria();
|
||||
$criteria->where(Criteria::expr()->isNull('deletedAt'));
|
||||
$criteria->andWhere(Criteria::expr()->neq('type', SegmentEntity::TYPE_DYNAMIC));
|
||||
$criteria->orderBy(['name' => 'ASC']);
|
||||
$segments = $this->segmentsRepository->matching($criteria);
|
||||
foreach ($segments as $segment) {
|
||||
$data['static_segments_list'][] = [
|
||||
'id' => $segment->getId(),
|
||||
'name' => $segment->getName(),
|
||||
'type' => $segment->getType(),
|
||||
'description' => $segment->getDescription(),
|
||||
];
|
||||
}
|
||||
|
||||
$data['product_attributes'] = [];
|
||||
if ($this->woocommerceHelper->isWooCommerceActive()) {
|
||||
$productAttributes = $this->woocommerceHelper->wcGetAttributeTaxonomies();
|
||||
|
||||
foreach ($productAttributes as $attribute) {
|
||||
$taxonomy = 'pa_' . $attribute->attribute_name;// phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$attributeTerms = $this->wp->getTerms(
|
||||
[
|
||||
'taxonomy' => $taxonomy,
|
||||
'hide_empty' => false,
|
||||
]
|
||||
);
|
||||
|
||||
if (!isset($attributeTerms['errors'])) {
|
||||
$data['product_attributes'][$taxonomy] = [ // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
'id' => $attribute->attribute_id, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
'label' => $attribute->attribute_label, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
'terms' => $attributeTerms,
|
||||
'taxonomy' => $taxonomy,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch local attributes used for product variations
|
||||
$data['local_product_attributes'] = [];
|
||||
$localAttributes = $this->getLocalAttributesUsedInProductVariations();
|
||||
foreach ($localAttributes as $localAttribute => $values) {
|
||||
$data['local_product_attributes'][$localAttribute] = [
|
||||
'name' => $localAttribute,
|
||||
'values' => $values,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$data['product_categories'] = $this->wpPostListLoader->getWooCommerceCategories();
|
||||
$data['product_tags'] = $this->wpPostListLoader->getWooCommerceTags();
|
||||
|
||||
$data['products'] = $this->wpPostListLoader->getProducts();
|
||||
$data['membership_plans'] = $this->wpPostListLoader->getMembershipPlans();
|
||||
$data['subscription_products'] = $this->wpPostListLoader->getSubscriptionProducts();
|
||||
$wcCountries = $this->woocommerceHelper->isWooCommerceActive() ? $this->woocommerceHelper->getAllowedCountries() : [];
|
||||
$data['woocommerce_countries'] = array_map(function ($code, $name) {
|
||||
return [
|
||||
'name' => $name,
|
||||
'code' => $code,
|
||||
];
|
||||
}, array_keys($wcCountries), $wcCountries);
|
||||
$data['can_use_woocommerce_memberships'] = $this->segmentDependencyValidator->canUseDynamicFilterType(
|
||||
DynamicSegmentFilterData::TYPE_WOOCOMMERCE_MEMBERSHIP
|
||||
);
|
||||
$data['can_use_woocommerce_subscriptions'] = $this->segmentDependencyValidator->canUseDynamicFilterType(
|
||||
DynamicSegmentFilterData::TYPE_WOOCOMMERCE_SUBSCRIPTION
|
||||
);
|
||||
$wooCurrencySymbol = $this->woocommerceHelper->isWooCommerceActive() ? $this->woocommerceHelper->getWoocommerceCurrencySymbol() : '';
|
||||
$data['woocommerce_currency_symbol'] = html_entity_decode($wooCurrencySymbol);
|
||||
$data['signup_forms'] = array_map(function(FormEntity $form) {
|
||||
return [
|
||||
'id' => $form->getId(),
|
||||
'name' => $form->getName(),
|
||||
];
|
||||
}, $this->formsRepository->findAll());
|
||||
|
||||
$data['woocommerce_payment_methods'] = [];
|
||||
$data['woocommerce_shipping_methods'] = [];
|
||||
|
||||
if ($this->woocommerceHelper->isWooCommerceActive()) {
|
||||
$allGateways = $this->woocommerceHelper->getPaymentGateways()->payment_gateways();
|
||||
$paymentMethods = [];
|
||||
foreach ($allGateways as $gatewayId => $gateway) {
|
||||
$paymentMethods[] = [
|
||||
'id' => $gatewayId,
|
||||
'name' => $gateway->get_method_title(),
|
||||
];
|
||||
}
|
||||
$data['woocommerce_payment_methods'] = $paymentMethods;
|
||||
|
||||
$data['woocommerce_shipping_methods'] = array_values($this->woocommerceHelper->getShippingMethodInstancesData());
|
||||
}
|
||||
$data['automations'] = array_map(function(Automation $automation) {
|
||||
return [
|
||||
'id' => (string)$automation->getId(),
|
||||
'name' => $automation->getName(),
|
||||
];
|
||||
}, $this->automationStorage->getAutomations());
|
||||
|
||||
$this->assetsController->setupDynamicSegmentsDependencies();
|
||||
$this->pageRenderer->displayPage('segments/dynamic.html', $data);
|
||||
}
|
||||
|
||||
private function getLocalAttributesUsedInProductVariations(): array {
|
||||
$attributes = [];
|
||||
|
||||
if (!$this->woocommerceHelper->isWooCommerceActive()) {
|
||||
return $attributes;
|
||||
}
|
||||
global $wpdb;
|
||||
|
||||
$results = $wpdb->get_results($wpdb->prepare("
|
||||
SELECT DISTINCT pm.meta_key, pm.meta_value
|
||||
FROM %i pm
|
||||
INNER JOIN %i p ON pm.post_id = p.ID
|
||||
WHERE pm.meta_key LIKE %s
|
||||
AND p.post_type = 'product_variation'
|
||||
GROUP BY pm.meta_key, pm.meta_value
|
||||
", $wpdb->postmeta, $wpdb->posts, 'attribute_%'), ARRAY_A);
|
||||
|
||||
foreach ($results as $result) {
|
||||
$attribute = substr($result['meta_key'], 10);
|
||||
if (!isset($attributes[$attribute])) {
|
||||
$attributes[$attribute] = [];
|
||||
}
|
||||
$attributes[$attribute][] = $result['meta_value'];
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
private function getNewslettersList(): array {
|
||||
$result = [];
|
||||
foreach ($this->newslettersRepository->getStandardNewsletterList() as $newsletter) {
|
||||
$result[] = [
|
||||
'id' => (string)$newsletter->getId(),
|
||||
'subject' => $newsletter->getSubject(),
|
||||
'name' => $newsletter->getCampaignNameOrSubject(),
|
||||
'sent_at' => ($sentAt = $newsletter->getSentAt()) ? $sentAt->format('Y-m-d H:i:s') : null,
|
||||
];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
|
||||
class ExperimentalFeatures {
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
public function __construct(
|
||||
PageRenderer $pageRenderer
|
||||
) {
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$this->pageRenderer->displayPage('experimental-features.html', []);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\AssetsController;
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\API\JSON\ResponseBuilders\CustomFieldsResponseBuilder;
|
||||
use MailPoet\Config\Localizer;
|
||||
use MailPoet\CustomFields\CustomFieldsRepository;
|
||||
use MailPoet\Entities\FormEntity;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Form\Block;
|
||||
use MailPoet\Form\FormsRepository;
|
||||
use MailPoet\Form\Renderer as FormRenderer;
|
||||
use MailPoet\Form\Templates\TemplateRepository;
|
||||
use MailPoet\Form\Templates\Templates\Template10BelowPages;
|
||||
use MailPoet\Form\Templates\Templates\Template10FixedBar;
|
||||
use MailPoet\Form\Templates\Templates\Template10Popup;
|
||||
use MailPoet\Form\Templates\Templates\Template10SlideIn;
|
||||
use MailPoet\Form\Templates\Templates\Template10Widget;
|
||||
use MailPoet\Form\Templates\Templates\Template11BelowPages;
|
||||
use MailPoet\Form\Templates\Templates\Template11FixedBar;
|
||||
use MailPoet\Form\Templates\Templates\Template11Popup;
|
||||
use MailPoet\Form\Templates\Templates\Template11SlideIn;
|
||||
use MailPoet\Form\Templates\Templates\Template11Widget;
|
||||
use MailPoet\Form\Templates\Templates\Template12BelowPages;
|
||||
use MailPoet\Form\Templates\Templates\Template12FixedBar;
|
||||
use MailPoet\Form\Templates\Templates\Template12Popup;
|
||||
use MailPoet\Form\Templates\Templates\Template12SlideIn;
|
||||
use MailPoet\Form\Templates\Templates\Template12Widget;
|
||||
use MailPoet\Form\Templates\Templates\Template13BelowPages;
|
||||
use MailPoet\Form\Templates\Templates\Template13FixedBar;
|
||||
use MailPoet\Form\Templates\Templates\Template13Popup;
|
||||
use MailPoet\Form\Templates\Templates\Template13SlideIn;
|
||||
use MailPoet\Form\Templates\Templates\Template13Widget;
|
||||
use MailPoet\Form\Templates\Templates\Template14BelowPages;
|
||||
use MailPoet\Form\Templates\Templates\Template14FixedBar;
|
||||
use MailPoet\Form\Templates\Templates\Template14Popup;
|
||||
use MailPoet\Form\Templates\Templates\Template14SlideIn;
|
||||
use MailPoet\Form\Templates\Templates\Template14Widget;
|
||||
use MailPoet\Form\Templates\Templates\Template17BelowPages;
|
||||
use MailPoet\Form\Templates\Templates\Template17FixedBar;
|
||||
use MailPoet\Form\Templates\Templates\Template17Popup;
|
||||
use MailPoet\Form\Templates\Templates\Template17SlideIn;
|
||||
use MailPoet\Form\Templates\Templates\Template17Widget;
|
||||
use MailPoet\Form\Templates\Templates\Template18BelowPages;
|
||||
use MailPoet\Form\Templates\Templates\Template18FixedBar;
|
||||
use MailPoet\Form\Templates\Templates\Template18Popup;
|
||||
use MailPoet\Form\Templates\Templates\Template18SlideIn;
|
||||
use MailPoet\Form\Templates\Templates\Template18Widget;
|
||||
use MailPoet\Form\Templates\Templates\Template1BelowPages;
|
||||
use MailPoet\Form\Templates\Templates\Template1FixedBar;
|
||||
use MailPoet\Form\Templates\Templates\Template1Popup;
|
||||
use MailPoet\Form\Templates\Templates\Template1SlideIn;
|
||||
use MailPoet\Form\Templates\Templates\Template1Widget;
|
||||
use MailPoet\Form\Templates\Templates\Template3BelowPages;
|
||||
use MailPoet\Form\Templates\Templates\Template3FixedBar;
|
||||
use MailPoet\Form\Templates\Templates\Template3Popup;
|
||||
use MailPoet\Form\Templates\Templates\Template3SlideIn;
|
||||
use MailPoet\Form\Templates\Templates\Template3Widget;
|
||||
use MailPoet\Form\Templates\Templates\Template4BelowPages;
|
||||
use MailPoet\Form\Templates\Templates\Template4FixedBar;
|
||||
use MailPoet\Form\Templates\Templates\Template4Popup;
|
||||
use MailPoet\Form\Templates\Templates\Template4SlideIn;
|
||||
use MailPoet\Form\Templates\Templates\Template4Widget;
|
||||
use MailPoet\Form\Templates\Templates\Template6BelowPages;
|
||||
use MailPoet\Form\Templates\Templates\Template6FixedBar;
|
||||
use MailPoet\Form\Templates\Templates\Template6Popup;
|
||||
use MailPoet\Form\Templates\Templates\Template6SlideIn;
|
||||
use MailPoet\Form\Templates\Templates\Template6Widget;
|
||||
use MailPoet\Form\Templates\Templates\Template7BelowPages;
|
||||
use MailPoet\Form\Templates\Templates\Template7FixedBar;
|
||||
use MailPoet\Form\Templates\Templates\Template7Popup;
|
||||
use MailPoet\Form\Templates\Templates\Template7SlideIn;
|
||||
use MailPoet\Form\Templates\Templates\Template7Widget;
|
||||
use MailPoet\Form\Util\CustomFonts;
|
||||
use MailPoet\Form\Util\Export;
|
||||
use MailPoet\NotFoundException;
|
||||
use MailPoet\Router\Endpoints\FormPreview;
|
||||
use MailPoet\Router\Router;
|
||||
use MailPoet\Segments\SegmentsSimpleListRepository;
|
||||
use MailPoet\Settings\Pages;
|
||||
use MailPoet\Settings\UserFlagsController;
|
||||
use MailPoet\WP\AutocompletePostListLoader as WPPostListLoader;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class FormEditor {
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
/** @var CustomFieldsRepository */
|
||||
private $customFieldsRepository;
|
||||
|
||||
/** @var CustomFieldsResponseBuilder */
|
||||
private $customFieldsResponseBuilder;
|
||||
|
||||
/** @var FormRenderer */
|
||||
private $formRenderer;
|
||||
|
||||
/** @var Block\Date */
|
||||
private $dateBlock;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var Localizer */
|
||||
private $localizer;
|
||||
|
||||
/** @var TemplateRepository */
|
||||
private $templatesRepository;
|
||||
|
||||
/** @var UserFlagsController */
|
||||
private $userFlags;
|
||||
|
||||
/** @var WPPostListLoader */
|
||||
private $wpPostListLoader;
|
||||
|
||||
/** @var SegmentsSimpleListRepository */
|
||||
private $segmentsListRepository;
|
||||
|
||||
/** @var FormsRepository */
|
||||
private $formsRepository;
|
||||
|
||||
private $activeTemplates = [
|
||||
FormEntity::DISPLAY_TYPE_POPUP => [
|
||||
Template1Popup::ID,
|
||||
Template3Popup::ID,
|
||||
Template4Popup::ID,
|
||||
Template6Popup::ID,
|
||||
Template7Popup::ID,
|
||||
Template10Popup::ID,
|
||||
Template11Popup::ID,
|
||||
Template12Popup::ID,
|
||||
Template13Popup::ID,
|
||||
Template14Popup::ID,
|
||||
Template17Popup::ID,
|
||||
Template18Popup::ID,
|
||||
],
|
||||
FormEntity::DISPLAY_TYPE_SLIDE_IN => [
|
||||
Template1SlideIn::ID,
|
||||
Template3SlideIn::ID,
|
||||
Template4SlideIn::ID,
|
||||
Template6SlideIn::ID,
|
||||
Template7SlideIn::ID,
|
||||
Template10SlideIn::ID,
|
||||
Template11SlideIn::ID,
|
||||
Template12SlideIn::ID,
|
||||
Template13SlideIn::ID,
|
||||
Template14SlideIn::ID,
|
||||
Template17SlideIn::ID,
|
||||
Template18SlideIn::ID,
|
||||
],
|
||||
FormEntity::DISPLAY_TYPE_FIXED_BAR => [
|
||||
Template1FixedBar::ID,
|
||||
Template3FixedBar::ID,
|
||||
Template4FixedBar::ID,
|
||||
Template6FixedBar::ID,
|
||||
Template7FixedBar::ID,
|
||||
Template10FixedBar::ID,
|
||||
Template11FixedBar::ID,
|
||||
Template12FixedBar::ID,
|
||||
Template13FixedBar::ID,
|
||||
Template14FixedBar::ID,
|
||||
Template17FixedBar::ID,
|
||||
Template18FixedBar::ID,
|
||||
],
|
||||
FormEntity::DISPLAY_TYPE_BELOW_POST => [
|
||||
Template1BelowPages::ID,
|
||||
Template3BelowPages::ID,
|
||||
Template4BelowPages::ID,
|
||||
Template6BelowPages::ID,
|
||||
Template7BelowPages::ID,
|
||||
Template10BelowPages::ID,
|
||||
Template11BelowPages::ID,
|
||||
Template12BelowPages::ID,
|
||||
Template13BelowPages::ID,
|
||||
Template14BelowPages::ID,
|
||||
Template17BelowPages::ID,
|
||||
Template18BelowPages::ID,
|
||||
],
|
||||
FormEntity::DISPLAY_TYPE_OTHERS => [
|
||||
Template1Widget::ID,
|
||||
Template3Widget::ID,
|
||||
Template4Widget::ID,
|
||||
Template6Widget::ID,
|
||||
Template7Widget::ID,
|
||||
Template10Widget::ID,
|
||||
Template11Widget::ID,
|
||||
Template12Widget::ID,
|
||||
Template13Widget::ID,
|
||||
Template14Widget::ID,
|
||||
Template17Widget::ID,
|
||||
Template18Widget::ID,
|
||||
],
|
||||
];
|
||||
|
||||
/** @var AssetsController */
|
||||
private $assetsController;
|
||||
|
||||
public function __construct(
|
||||
AssetsController $assetsController,
|
||||
PageRenderer $pageRenderer,
|
||||
CustomFieldsRepository $customFieldsRepository,
|
||||
CustomFieldsResponseBuilder $customFieldsResponseBuilder,
|
||||
FormRenderer $formRenderer,
|
||||
Block\Date $dateBlock,
|
||||
WPFunctions $wp,
|
||||
Localizer $localizer,
|
||||
UserFlagsController $userFlags,
|
||||
WPPostListLoader $wpPostListLoader,
|
||||
TemplateRepository $templateRepository,
|
||||
FormsRepository $formsRepository,
|
||||
SegmentsSimpleListRepository $segmentsListRepository
|
||||
) {
|
||||
$this->assetsController = $assetsController;
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->customFieldsRepository = $customFieldsRepository;
|
||||
$this->customFieldsResponseBuilder = $customFieldsResponseBuilder;
|
||||
$this->formRenderer = $formRenderer;
|
||||
$this->dateBlock = $dateBlock;
|
||||
$this->wp = $wp;
|
||||
$this->localizer = $localizer;
|
||||
$this->templatesRepository = $templateRepository;
|
||||
$this->userFlags = $userFlags;
|
||||
$this->wpPostListLoader = $wpPostListLoader;
|
||||
$this->segmentsListRepository = $segmentsListRepository;
|
||||
$this->formsRepository = $formsRepository;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
if (!isset($_GET['id']) && !isset($_GET['action']) && !isset($_GET['template_id'])) {
|
||||
$this->renderTemplateSelection();
|
||||
return;
|
||||
}
|
||||
if (isset($_GET['template_id'])) {
|
||||
$template = $this->templatesRepository->getFormTemplate(sanitize_text_field(wp_unslash($_GET['template_id'])));
|
||||
$form = $template->toFormEntity();
|
||||
} else {
|
||||
$form = $this->getFormData((int)$_GET['id']);
|
||||
}
|
||||
$customFields = $this->customFieldsRepository->findAll();
|
||||
if (!$form instanceof FormEntity) {
|
||||
throw new NotFoundException('Form does not exist');
|
||||
}
|
||||
$dateTypes = $this->dateBlock->getDateTypes();
|
||||
$data = [
|
||||
'form' => $form->toArray(),
|
||||
'form_exports' => [
|
||||
'php' => Export::get('php'),
|
||||
'iframe' => Export::get('iframe'),
|
||||
'shortcode' => Export::get('shortcode'),
|
||||
],
|
||||
'segments' => $this->segmentsListRepository->getListWithSubscribedSubscribersCounts([SegmentEntity::TYPE_DEFAULT]),
|
||||
'styles' => $this->formRenderer->getCustomStyles($form),
|
||||
'date_types' => array_map(function ($label, $value) {
|
||||
return [
|
||||
'label' => $label,
|
||||
'value' => $value,
|
||||
];
|
||||
}, $dateTypes, array_keys($dateTypes)),
|
||||
'date_formats' => $this->dateBlock->getDateFormats(),
|
||||
'month_names' => $this->dateBlock->getMonthNames(),
|
||||
'custom_fields' => $this->customFieldsResponseBuilder->buildBatch($customFields),
|
||||
'editor_tutorial_seen' => $this->userFlags->get('form_editor_tutorial_seen'),
|
||||
'preview_page_url' => $this->getPreviewPageUrl(),
|
||||
'custom_fonts' => CustomFonts::FONTS,
|
||||
'translations' => $this->getGutenbergScriptsTranslations(),
|
||||
'posts' => $this->wpPostListLoader->getPosts(),
|
||||
'pages' => $this->wpPostListLoader->getPages(),
|
||||
'categories' => $this->wpPostListLoader->getCategories(),
|
||||
'tags' => $this->wpPostListLoader->getTags(),
|
||||
'products' => $this->wpPostListLoader->getProducts(),
|
||||
'product_categories' => $this->wpPostListLoader->getWooCommerceCategories(),
|
||||
'product_tags' => $this->wpPostListLoader->getWooCommerceTags(),
|
||||
'is_administrator' => $this->wp->currentUserCan('administrator'),
|
||||
'theme_support_widgets' => $this->wp->wpGetThemeSupport('widgets'),
|
||||
'theme_support_fse' => $this->wp->wpGetTheme()->is_block_theme(),
|
||||
];
|
||||
$this->wp->wpEnqueueMedia();
|
||||
$this->assetsController->setupFormEditorDependencies();
|
||||
$this->pageRenderer->displayPage('form/editor.html', $data);
|
||||
}
|
||||
|
||||
public function renderTemplateSelection() {
|
||||
$templatesData = [];
|
||||
foreach ($this->activeTemplates as $formType => $templateIds) {
|
||||
$templateForms = $this->templatesRepository->getFormTemplates($this->activeTemplates[$formType]);
|
||||
$templatesData[$formType] = [];
|
||||
foreach ($templateForms as $templateId => $form) {
|
||||
$templatesData[$formType][] = [
|
||||
'id' => $templateId,
|
||||
'name' => $form->getName(),
|
||||
'thumbnail' => $form->getThumbnailUrl(),
|
||||
];
|
||||
}
|
||||
}
|
||||
$data = [
|
||||
'templates' => $templatesData,
|
||||
];
|
||||
$this->assetsController->setupFormEditorDependencies();
|
||||
$this->pageRenderer->displayPage('form/template_selection.html', $data);
|
||||
}
|
||||
|
||||
private function getPreviewPageUrl() {
|
||||
$mailpoetPage = Pages::getMailPoetPage(Pages::PAGE_SUBSCRIPTIONS);
|
||||
if (!$mailpoetPage) {
|
||||
return null;
|
||||
}
|
||||
$url = $this->wp->getPermalink($mailpoetPage);
|
||||
$params = [
|
||||
Router::NAME,
|
||||
'endpoint=' . FormPreview::ENDPOINT,
|
||||
'action=' . FormPreview::ACTION_VIEW,
|
||||
];
|
||||
$url .= (parse_url($url, PHP_URL_QUERY) ? '&' : '?') . join('&', $params);
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* JS Translations are distributed and loaded per script. We can't use wp_set_script_translations
|
||||
* because translation filename is determined based on script filename and path.
|
||||
* This function loads JSON files with Gutenberg script's translations distributed within WordPress.
|
||||
* Implemented based on load_script_textdomain function
|
||||
* @see https://developer.wordpress.org/reference/functions/load_script_textdomain/
|
||||
* @return string[]
|
||||
*/
|
||||
private function getGutenbergScriptsTranslations() {
|
||||
$locale = $this->localizer->locale();
|
||||
if (!$locale) {
|
||||
return [];
|
||||
}
|
||||
// List of scripts - relative path to translations directory (default: wp-content/languages)
|
||||
$translationsToLoad = [
|
||||
'wp-includes/js/dist/blocks.js',
|
||||
'wp-includes/js/dist/components.js',
|
||||
'wp-includes/js/dist/block-editor.js',
|
||||
'wp-includes/js/dist/block-library.js',
|
||||
'wp-includes/js/dist/editor.js',
|
||||
'wp-includes/js/dist/media-utils.js',
|
||||
'wp-includes/js/dist/format-library.js',
|
||||
'wp-includes/js/dist/edit-post.js',
|
||||
];
|
||||
|
||||
$translations = [];
|
||||
foreach ($translationsToLoad as $translation) {
|
||||
$file = WP_LANG_DIR . '/' . $locale . '-' . md5($translation) . '.json';
|
||||
if (!file_exists($file)) {
|
||||
continue;
|
||||
}
|
||||
$translationsData = file_get_contents($file);
|
||||
if ($translationsData) {
|
||||
$translations[] = $translationsData;
|
||||
}
|
||||
}
|
||||
return $translations;
|
||||
}
|
||||
|
||||
private function getFormData(int $id): ?FormEntity {
|
||||
$form = $this->formsRepository->findOneById($id);
|
||||
if (!$form instanceof FormEntity) {
|
||||
return null;
|
||||
}
|
||||
$form->setStyles($this->formRenderer->getCustomStyles($form));
|
||||
// Use empty settings in case they are corrupted or missing
|
||||
if (!is_array($form->getSettings())) {
|
||||
$initialFormTemplate = $this->templatesRepository->getFormTemplate(TemplateRepository::INITIAL_FORM_TEMPLATE);
|
||||
$form->setSettings($initialFormTemplate->getSettings());
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\API\JSON\ResponseBuilders\SegmentsResponseBuilder;
|
||||
use MailPoet\Listing\PageLimit;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Settings\UserFlagsController;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class Forms {
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
/** @var PageLimit */
|
||||
private $listingPageLimit;
|
||||
|
||||
/** @var UserFlagsController */
|
||||
private $userFlags;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentsRepository;
|
||||
|
||||
/** @var SegmentsResponseBuilder */
|
||||
private $segmentsResponseBuilder;
|
||||
|
||||
public function __construct(
|
||||
PageRenderer $pageRenderer,
|
||||
PageLimit $listingPageLimit,
|
||||
UserFlagsController $userFlags,
|
||||
SegmentsRepository $segmentsRepository,
|
||||
SegmentsResponseBuilder $segmentsResponseBuilder,
|
||||
WPFunctions $wp
|
||||
) {
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->listingPageLimit = $listingPageLimit;
|
||||
$this->userFlags = $userFlags;
|
||||
$this->wp = $wp;
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
$this->segmentsResponseBuilder = $segmentsResponseBuilder;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$data = [];
|
||||
$data['items_per_page'] = $this->listingPageLimit->getLimitPerPage('forms');
|
||||
$data['segments'] = $this->segmentsResponseBuilder->buildForListing($this->segmentsRepository->findAll());
|
||||
|
||||
$data = $this->getNPSSurveyData($data);
|
||||
|
||||
$this->pageRenderer->displayPage('forms.html', $data);
|
||||
}
|
||||
|
||||
public function getNPSSurveyData($data) {
|
||||
$data['display_nps_survey'] = false;
|
||||
if ($this->userFlags->get('display_new_form_editor_nps_survey')) {
|
||||
$data['current_wp_user'] = $this->wp->wpGetCurrentUser()->to_array();
|
||||
$data['current_wp_user_firstname'] = $this->wp->wpGetCurrentUser()->user_firstname;
|
||||
$data['display_nps_survey'] = true;
|
||||
$this->userFlags->set('display_new_form_editor_nps_survey', false);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\Cron\ActionScheduler\Actions\DaemonRun;
|
||||
use MailPoet\Cron\ActionScheduler\Actions\DaemonTrigger;
|
||||
use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Mailer\MailerLog;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
|
||||
use MailPoet\Newsletter\Sending\SendingQueuesRepository;
|
||||
use MailPoet\Newsletter\Url as NewsletterURL;
|
||||
use MailPoet\Router\Endpoints\CronDaemon;
|
||||
use MailPoet\Services\Bridge;
|
||||
use MailPoet\SystemReport\SystemReportCollector;
|
||||
use MailPoet\WP\DateTime;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class Help {
|
||||
private PageRenderer $pageRenderer;
|
||||
private CronHelper $cronHelper;
|
||||
private SystemReportCollector $systemReportCollector;
|
||||
private Bridge $bridge;
|
||||
private ScheduledTasksRepository $scheduledTasksRepository;
|
||||
private SendingQueuesRepository $sendingQueuesRepository;
|
||||
private NewsletterURL $newsletterUrl;
|
||||
|
||||
public function __construct(
|
||||
PageRenderer $pageRenderer,
|
||||
CronHelper $cronHelper,
|
||||
SystemReportCollector $systemReportCollector,
|
||||
Bridge $bridge,
|
||||
ScheduledTasksRepository $scheduledTasksRepository,
|
||||
SendingQueuesRepository $sendingQueuesRepository,
|
||||
NewsletterURL $newsletterUrl
|
||||
) {
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->cronHelper = $cronHelper;
|
||||
$this->systemReportCollector = $systemReportCollector;
|
||||
$this->bridge = $bridge;
|
||||
$this->scheduledTasksRepository = $scheduledTasksRepository;
|
||||
$this->sendingQueuesRepository = $sendingQueuesRepository;
|
||||
$this->newsletterUrl = $newsletterUrl;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
/**
|
||||
* Filter the system info.
|
||||
*
|
||||
* @param array<string, string> $systemInfoData The system info data array.
|
||||
*/
|
||||
$systemInfoData = WPFunctions::get()->applyFilters('mailpoet_system_info_data', $this->systemReportCollector->getData(true));
|
||||
|
||||
try {
|
||||
$cronPingUrl = $this->cronHelper->getCronUrl(CronDaemon::ACTION_PING);
|
||||
$cronPingResponse = $this->systemReportCollector->getCronPingResponse();
|
||||
} catch (\Exception $e) {
|
||||
$cronPingResponse = __('Can‘t generate cron URL.', 'mailpoet') . ' (' . $e->getMessage() . ')';
|
||||
$cronPingUrl = $cronPingResponse;
|
||||
}
|
||||
|
||||
$mailerLog = MailerLog::getMailerLog();
|
||||
$mailerLog['sent'] = MailerLog::sentSince();
|
||||
$bridgePingResponse = $this->systemReportCollector->getBridgePingResponse();
|
||||
$systemStatusData = [
|
||||
'cron' => [
|
||||
'url' => $cronPingUrl,
|
||||
'isReachable' => $this->cronHelper->validatePingResponse($cronPingResponse),
|
||||
'pingResponse' => $cronPingResponse,
|
||||
],
|
||||
'mss' => [
|
||||
'enabled' => $this->bridge->isMailpoetSendingServiceEnabled(),
|
||||
'isReachable' => $this->bridge->validateBridgePingResponse($bridgePingResponse),
|
||||
],
|
||||
'cronStatus' => $this->cronHelper->getDaemon(),
|
||||
'queueStatus' => $mailerLog,
|
||||
];
|
||||
|
||||
$systemStatusData['cronStatus']['accessible'] = $this->cronHelper->isDaemonAccessible();
|
||||
$systemStatusData['queueStatus']['tasksStatusCounts'] = $this->scheduledTasksRepository->getCountsPerStatus();
|
||||
|
||||
$scheduledTasks = $this->scheduledTasksRepository->getLatestTasks(SendingQueue::TASK_TYPE);
|
||||
$systemStatusData['queueStatus']['latestTasks'] = array_map(fn($task) => $this->buildTaskData($task), $scheduledTasks);
|
||||
|
||||
$this->pageRenderer->displayPage(
|
||||
'help.html',
|
||||
[
|
||||
'systemInfoData' => $systemInfoData,
|
||||
'systemStatusData' => $systemStatusData,
|
||||
'actionSchedulerData' => $this->getActionSchedulerData(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function getActionSchedulerData(): ?array {
|
||||
if (!class_exists(\ActionScheduler_Store::class)) {
|
||||
return null;
|
||||
}
|
||||
$actionSchedulerData = [];
|
||||
$actionSchedulerData['version'] = \ActionScheduler_Versions::instance()->latest_version();
|
||||
$actionSchedulerData['storage'] = str_replace('ActionScheduler_', '', get_class(\ActionScheduler_Store::instance()));
|
||||
$actionSchedulerData['latestTrigger'] = $this->getLatestActionSchedulerActionDate(DaemonTrigger::NAME);
|
||||
$actionSchedulerData['latestCompletedTrigger'] = $this->getLatestActionSchedulerActionDate(DaemonTrigger::NAME, 'complete');
|
||||
$actionSchedulerData['latestCompletedRun'] = $this->getLatestActionSchedulerActionDate(DaemonRun::NAME, 'complete');
|
||||
return $actionSchedulerData;
|
||||
}
|
||||
|
||||
private function getLatestActionSchedulerActionDate(string $hook, string $status = null): ?string {
|
||||
$query = [
|
||||
'per_page' => 1,
|
||||
'order' => 'DESC',
|
||||
'hook' => $hook,
|
||||
];
|
||||
if ($status) {
|
||||
$query['status'] = $status;
|
||||
}
|
||||
$store = \ActionScheduler_Store::instance();
|
||||
$action = $store->query_actions($query);
|
||||
if (!empty($action)) {
|
||||
$dateObject = $store->get_date($action[0]);
|
||||
return $dateObject->format('Y-m-d H:i:s');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function buildTaskData(ScheduledTaskEntity $task): array {
|
||||
$queue = $newsletter = $subscriber = null;
|
||||
if ($task->getType() === SendingQueue::TASK_TYPE) {
|
||||
$queue = $this->sendingQueuesRepository->findOneBy(['task' => $task]);
|
||||
$newsletter = $queue ? $queue->getNewsletter() : null;
|
||||
$subscribers = $task->getSubscribers();
|
||||
// We only show subscriber's email for 1:1 emails (e.g. automations) and not bulk campaigns
|
||||
if ($subscribers->count() === 1) {
|
||||
$subscriber = $subscribers->first() ? $subscribers->first()->getSubscriber() : null;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $task->getId(),
|
||||
'type' => $task->getType(),
|
||||
'priority' => $task->getPriority(),
|
||||
'updatedAt' => $task->getUpdatedAt()->format(DateTime::DEFAULT_DATE_TIME_FORMAT),
|
||||
'scheduledAt' => $task->getScheduledAt() ?
|
||||
$task->getScheduledAt()->format(DateTime::DEFAULT_DATE_TIME_FORMAT)
|
||||
: null,
|
||||
'cancelledAt' => $task->getCancelledAt() ?
|
||||
$task->getCancelledAt()->format(DateTime::DEFAULT_DATE_TIME_FORMAT)
|
||||
: null,
|
||||
'status' => $task->getStatus(),
|
||||
'newsletter' => $queue && $newsletter ? [
|
||||
'newsletterId' => $newsletter->getId(),
|
||||
'queueId' => $queue->getId(),
|
||||
'subject' => $queue->getNewsletterRenderedSubject() ?: $newsletter->getSubject(),
|
||||
'previewUrl' => $this->newsletterUrl->getViewInBrowserUrl($newsletter, null, $queue),
|
||||
] : [
|
||||
'newsletterId' => null,
|
||||
'queueId' => null,
|
||||
'subject' => null,
|
||||
'previewUrl' => null,
|
||||
],
|
||||
'subscriberEmail' => $subscriber ? $subscriber->getEmail() : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\AssetsController;
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\Homepage\HomepageDataController;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
|
||||
class Homepage {
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settingsController;
|
||||
|
||||
/** @var HomepageDataController */
|
||||
private $homepageDataController;
|
||||
|
||||
/** @var AssetsController */
|
||||
private $assetsController;
|
||||
|
||||
public function __construct(
|
||||
AssetsController $assetsController,
|
||||
PageRenderer $pageRenderer,
|
||||
SettingsController $settingsController,
|
||||
HomepageDataController $homepageDataController
|
||||
) {
|
||||
$this->assetsController = $assetsController;
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->settingsController = $settingsController;
|
||||
$this->homepageDataController = $homepageDataController;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$data = [
|
||||
'mta_log' => $this->settingsController->get('mta_log'),
|
||||
'homepage' => $this->homepageDataController->getPageData(),
|
||||
];
|
||||
$this->assetsController->setupHomepageDependencies();
|
||||
$this->pageRenderer->displayPage('homepage.html', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\Config\Menu;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class Landingpage {
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
private SettingsController $settingsController;
|
||||
|
||||
public function __construct(
|
||||
PageRenderer $pageRenderer,
|
||||
WPFunctions $wp,
|
||||
SettingsController $settingsController
|
||||
) {
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->wp = $wp;
|
||||
$this->settingsController = $settingsController;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$data = [
|
||||
'welcome_wizard_url' => $this->wp->adminUrl('admin.php?page=' . Menu::WELCOME_WIZARD_PAGE_SLUG),
|
||||
'welcome_wizard_current_step' => $this->settingsController->get('welcome_wizard_current_step', ''),
|
||||
];
|
||||
$this->pageRenderer->displayPage('landingpage.html', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\Logging\LogRepository;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
|
||||
class Logs {
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
/** @var LogRepository */
|
||||
private $logRepository;
|
||||
|
||||
public function __construct(
|
||||
LogRepository $logRepository,
|
||||
PageRenderer $pageRenderer
|
||||
) {
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->logRepository = $logRepository;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$search = isset($_GET['search']) ? sanitize_text_field(wp_unslash($_GET['search'])) : null;
|
||||
$from = isset($_GET['from']) ? sanitize_text_field(wp_unslash($_GET['from'])) : null;
|
||||
$to = isset($_GET['to']) ? sanitize_text_field(wp_unslash($_GET['to'])) : null;
|
||||
$offset = isset($_GET['offset']) ? sanitize_text_field(wp_unslash($_GET['offset'])) : null;
|
||||
$limit = isset($_GET['limit']) ? sanitize_text_field(wp_unslash($_GET['limit'])) : null;
|
||||
$dateFrom = (new Carbon())->subDays(7);
|
||||
$defaultFrom = $dateFrom->format('Y-m-d');
|
||||
if (isset($from)) {
|
||||
$dateFrom = new Carbon($from);
|
||||
}
|
||||
$dateTo = null;
|
||||
if (isset($to)) {
|
||||
$dateTo = new Carbon($to);
|
||||
}
|
||||
$logs = $this->logRepository->getLogs($dateFrom, $dateTo, $search, $offset, $limit);
|
||||
$data = [
|
||||
'logs' => [],
|
||||
'logs_default_from' => $defaultFrom,
|
||||
];
|
||||
foreach ($logs as $log) {
|
||||
$data['logs'][] = [
|
||||
'id' => $log->getId(),
|
||||
'name' => $log->getName(),
|
||||
'message' => $log->getMessage(),
|
||||
'created_at' => ($createdAt = $log->getCreatedAt()) ? $createdAt->format('Y-m-d H:i:s') : null,
|
||||
];
|
||||
}
|
||||
$this->pageRenderer->displayPage('logs.html', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\AssetsController;
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Form\Util\CustomFonts;
|
||||
use MailPoet\Newsletter\Renderer\Blocks\Coupon;
|
||||
use MailPoet\Newsletter\Shortcodes\ShortcodesHelper;
|
||||
use MailPoet\NewsletterTemplates\BrandStyles;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Settings\UserFlagsController;
|
||||
use MailPoet\Subscribers\ConfirmationEmailCustomizer;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\WooCommerce\Helper as WooCommerceHelper;
|
||||
use MailPoet\WooCommerce\TransactionalEmailHooks;
|
||||
use MailPoet\WooCommerce\TransactionalEmails;
|
||||
use MailPoet\WooCommerce\TransactionalEmails\Template as WooTransactionalEmailTemplate;
|
||||
use MailPoet\WP\AutocompletePostListLoader as WPPostListLoader;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class NewsletterEditor {
|
||||
private const DATE_FORMAT = 'Y-m-d H:i:s';
|
||||
|
||||
private PageRenderer $pageRenderer;
|
||||
private SettingsController $settings;
|
||||
private UserFlagsController $userFlags;
|
||||
private WooCommerceHelper $woocommerceHelper;
|
||||
private WPFunctions $wp;
|
||||
private TransactionalEmails $wcTransactionalEmails;
|
||||
private ShortcodesHelper $shortcodesHelper;
|
||||
private SubscribersRepository $subscribersRepository;
|
||||
private TransactionalEmailHooks $wooEmailHooks;
|
||||
private WPPostListLoader $wpPostListLoader;
|
||||
private CustomFonts $customFonts;
|
||||
private AssetsController $assetsController;
|
||||
private BrandStyles $brandStyles;
|
||||
private WooTransactionalEmailTemplate $template;
|
||||
|
||||
public function __construct(
|
||||
PageRenderer $pageRenderer,
|
||||
SettingsController $settings,
|
||||
UserFlagsController $userFlags,
|
||||
WooCommerceHelper $woocommerceHelper,
|
||||
WPFunctions $wp,
|
||||
TransactionalEmails $wcTransactionalEmails,
|
||||
ShortcodesHelper $shortcodesHelper,
|
||||
SubscribersRepository $subscribersRepository,
|
||||
TransactionalEmailHooks $wooEmailHooks,
|
||||
WPPostListLoader $wpPostListLoader,
|
||||
CustomFonts $customFonts,
|
||||
AssetsController $assetsController,
|
||||
WooTransactionalEmailTemplate $template,
|
||||
BrandStyles $brandStyles
|
||||
) {
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->settings = $settings;
|
||||
$this->userFlags = $userFlags;
|
||||
$this->woocommerceHelper = $woocommerceHelper;
|
||||
$this->wp = $wp;
|
||||
$this->wcTransactionalEmails = $wcTransactionalEmails;
|
||||
$this->shortcodesHelper = $shortcodesHelper;
|
||||
$this->subscribersRepository = $subscribersRepository;
|
||||
$this->wooEmailHooks = $wooEmailHooks;
|
||||
$this->wpPostListLoader = $wpPostListLoader;
|
||||
$this->customFonts = $customFonts;
|
||||
$this->assetsController = $assetsController;
|
||||
$this->template = $template;
|
||||
$this->brandStyles = $brandStyles;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$this->setupImageSize();
|
||||
$this->assetsController->setupNewsletterEditorDependencies();
|
||||
$newsletterId = (isset($_GET['id']) ? (int)$_GET['id'] : 0);
|
||||
$woocommerceTemplateId = (int)$this->settings->get(TransactionalEmails::SETTING_EMAIL_ID, null);
|
||||
if (
|
||||
$woocommerceTemplateId
|
||||
&& $newsletterId === $woocommerceTemplateId
|
||||
&& !$this->woocommerceHelper->isWooCommerceActive()
|
||||
) {
|
||||
$location = 'admin.php?page=mailpoet-settings&enable-customizer-notice#woocommerce';
|
||||
if (headers_sent()) {
|
||||
echo '<script>window.location = "' . esc_js($location) . '";</script>';
|
||||
} else {
|
||||
header('Location: ' . $location, true, 302);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
$subscriber = $this->subscribersRepository->getCurrentWPUser();
|
||||
$subscriberData = $subscriber ? $this->formatSubscriber($subscriber) : [];
|
||||
$woocommerceData = [];
|
||||
$originalTemplateBody = null;
|
||||
if ($this->woocommerceHelper->isWooCommerceActive()) {
|
||||
$wcEmailSettings = $this->wcTransactionalEmails->getWCEmailSettings();
|
||||
|
||||
// Activate hooks for Woo emails styles so that we always load styles set in Woo email customizer
|
||||
if ($newsletterId === (int)$this->settings->get(TransactionalEmails::SETTING_EMAIL_ID)) {
|
||||
$this->wooEmailHooks->overrideStylesForWooEmails();
|
||||
$originalTemplateBody = $this->template->create($wcEmailSettings);
|
||||
}
|
||||
$discountTypes = $this->woocommerceHelper->wcGetCouponTypes();
|
||||
$discountType = (string)current(array_keys($discountTypes));
|
||||
$amountMax = strpos($discountType, 'percent') !== false ? 100 : null;
|
||||
$woocommerceData = [
|
||||
'email_headings' => $this->wcTransactionalEmails->getEmailHeadings(),
|
||||
'customizer_enabled' => (bool)$this->settings->get('woocommerce.use_mailpoet_editor'),
|
||||
'coupon' => [
|
||||
'config' => [
|
||||
'discount_types' => array_map(function($label, $value): array {
|
||||
return ['label' => $label, 'value' => $value];
|
||||
}, $discountTypes, array_keys($discountTypes)),
|
||||
'code_placeholder' => Coupon::CODE_PLACEHOLDER,
|
||||
'price_decimal_separator' => $this->woocommerceHelper->wcGetPriceDecimalSeparator(),
|
||||
],
|
||||
'defaults' => [
|
||||
'code' => Coupon::CODE_PLACEHOLDER,
|
||||
'discountType' => $discountType,
|
||||
'amountMax' => $amountMax,
|
||||
],
|
||||
],
|
||||
];
|
||||
$woocommerceData = array_merge($wcEmailSettings, $woocommerceData);
|
||||
}
|
||||
|
||||
$confirmationEmailTemplateId = (int)$this->settings->get(ConfirmationEmailCustomizer::SETTING_EMAIL_ID, null);
|
||||
|
||||
$data = [
|
||||
'customFontsEnabled' => $this->customFonts->displayCustomFonts(),
|
||||
'shortcodes' => $this->shortcodesHelper->getShortcodes(),
|
||||
'settings' => $this->settings->getAll(),
|
||||
'editor_tutorial_seen' => $this->userFlags->get('editor_tutorial_seen'),
|
||||
'current_wp_user' => array_merge($subscriberData, $this->wp->wpGetCurrentUser()->to_array()),
|
||||
'woocommerce' => $woocommerceData,
|
||||
'is_wc_transactional_email' => $newsletterId === $woocommerceTemplateId,
|
||||
'is_confirmation_email_template' => $newsletterId === $confirmationEmailTemplateId,
|
||||
'is_confirmation_email_customizer_enabled' => (bool)$this->settings->get('signup_confirmation.use_mailpoet_editor', false),
|
||||
'original_template_body' => $originalTemplateBody,
|
||||
'product_categories' => $this->wpPostListLoader->getWooCommerceCategories(),
|
||||
'products' => $this->wpPostListLoader->getProducts(),
|
||||
'brand_styles' => [
|
||||
'available' => $this->brandStyles->isAvailable(),
|
||||
],
|
||||
];
|
||||
$this->wp->wpEnqueueMedia();
|
||||
$this->wp->wpEnqueueStyle('editor', $this->wp->includesUrl('css/editor.css'));
|
||||
$this->pageRenderer->displayPage('newsletter/editor.html', $data);
|
||||
}
|
||||
|
||||
private function formatSubscriber(SubscriberEntity $subscriber): array {
|
||||
return [
|
||||
'id' => $subscriber->getId(),
|
||||
'wp_user_id' => $subscriber->getWpUserId(),
|
||||
'is_woocommerce_user' => (string)$subscriber->getIsWoocommerceUser(), // BC compatibility
|
||||
'first_name' => $subscriber->getFirstName(),
|
||||
'last_name' => $subscriber->getLastName(),
|
||||
'email' => $subscriber->getEmail(),
|
||||
'status' => $subscriber->getStatus(),
|
||||
'subscribed_ip' => $subscriber->getSubscribedIp(),
|
||||
'confirmed_ip' => $subscriber->getConfirmedIp(),
|
||||
'confirmed_at' => ($confirmedAt = $subscriber->getConfirmedAt()) ? $confirmedAt->format(self::DATE_FORMAT) : null,
|
||||
'last_subscribed_at' => ($lastSubscribedAt = $subscriber->getLastSubscribedAt()) ? $lastSubscribedAt->format(self::DATE_FORMAT) : null,
|
||||
'created_at' => ($createdAt = $subscriber->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
|
||||
'updated_at' => $subscriber->getUpdatedAt()->format(self::DATE_FORMAT),
|
||||
'deleted_at' => ($deletedAt = $subscriber->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
|
||||
'unconfirmed_data' => $subscriber->getUnconfirmedData(),
|
||||
'source' => $subscriber->getSource(),
|
||||
'count_confirmation' => $subscriber->getConfirmationsCount(),
|
||||
'unsubscribe_token' => $subscriber->getUnsubscribeToken(),
|
||||
'link_token' => $subscriber->getLinkToken(),
|
||||
];
|
||||
}
|
||||
|
||||
private function setupImageSize(): void {
|
||||
$this->wp->addFilter(
|
||||
'image_size_names_choose',
|
||||
function ($sizes): array {
|
||||
return array_merge($sizes, [
|
||||
'mailpoet_newsletter_max' => __('MailPoet Newsletter', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\AutomaticEmails\AutomaticEmails;
|
||||
use MailPoet\Config\Env;
|
||||
use MailPoet\Config\Menu;
|
||||
use MailPoet\EmailEditor\Engine\Dependency_Check;
|
||||
use MailPoet\EmailEditor\Integrations\MailPoet\DependencyNotice;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Listing\PageLimit;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\NewsletterTemplates\NewsletterTemplatesRepository;
|
||||
use MailPoet\Segments\SegmentsSimpleListRepository;
|
||||
use MailPoet\Segments\WooCommerce;
|
||||
use MailPoet\Services\AuthorizedEmailsController;
|
||||
use MailPoet\Services\AuthorizedSenderDomainController;
|
||||
use MailPoet\Services\Bridge;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Settings\UserFlagsController;
|
||||
use MailPoet\Util\License\Features\CapabilitiesManager;
|
||||
use MailPoet\WooCommerce\TransactionalEmails;
|
||||
use MailPoet\WP\AutocompletePostListLoader as WPPostListLoader;
|
||||
use MailPoet\WP\DateTime;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class Newsletters {
|
||||
private PageRenderer $pageRenderer;
|
||||
|
||||
private PageLimit $listingPageLimit;
|
||||
|
||||
private WPFunctions $wp;
|
||||
|
||||
private SettingsController $settings;
|
||||
|
||||
private NewsletterTemplatesRepository $newsletterTemplatesRepository;
|
||||
|
||||
private AutomaticEmails $automaticEmails;
|
||||
|
||||
private WPPostListLoader $wpPostListLoader;
|
||||
|
||||
private SegmentsSimpleListRepository $segmentsListRepository;
|
||||
|
||||
private NewslettersRepository $newslettersRepository;
|
||||
|
||||
private Bridge $bridge;
|
||||
|
||||
private AuthorizedSenderDomainController $senderDomainController;
|
||||
|
||||
private AuthorizedEmailsController $authorizedEmailsController;
|
||||
|
||||
private UserFlagsController $userFlagsController;
|
||||
|
||||
private WooCommerce $wooCommerceSegment;
|
||||
|
||||
private Dependency_Check $dependencyCheck;
|
||||
|
||||
private DependencyNotice $dependencyNotice;
|
||||
|
||||
private CapabilitiesManager $capabilitiesManager;
|
||||
|
||||
public function __construct(
|
||||
PageRenderer $pageRenderer,
|
||||
PageLimit $listingPageLimit,
|
||||
WPFunctions $wp,
|
||||
SettingsController $settings,
|
||||
NewsletterTemplatesRepository $newsletterTemplatesRepository,
|
||||
WPPostListLoader $wpPostListLoader,
|
||||
AutomaticEmails $automaticEmails,
|
||||
SegmentsSimpleListRepository $segmentsListRepository,
|
||||
NewslettersRepository $newslettersRepository,
|
||||
Bridge $bridge,
|
||||
AuthorizedSenderDomainController $senderDomainController,
|
||||
AuthorizedEmailsController $authorizedEmailsController,
|
||||
UserFlagsController $userFlagsController,
|
||||
WooCommerce $wooCommerceSegment,
|
||||
Dependency_Check $dependencyCheck,
|
||||
DependencyNotice $dependencyNotice,
|
||||
CapabilitiesManager $capabilitiesManager
|
||||
) {
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->listingPageLimit = $listingPageLimit;
|
||||
$this->wp = $wp;
|
||||
$this->settings = $settings;
|
||||
$this->newsletterTemplatesRepository = $newsletterTemplatesRepository;
|
||||
$this->automaticEmails = $automaticEmails;
|
||||
$this->wpPostListLoader = $wpPostListLoader;
|
||||
$this->segmentsListRepository = $segmentsListRepository;
|
||||
$this->newslettersRepository = $newslettersRepository;
|
||||
$this->bridge = $bridge;
|
||||
$this->senderDomainController = $senderDomainController;
|
||||
$this->authorizedEmailsController = $authorizedEmailsController;
|
||||
$this->userFlagsController = $userFlagsController;
|
||||
$this->wooCommerceSegment = $wooCommerceSegment;
|
||||
$this->dependencyCheck = $dependencyCheck;
|
||||
$this->dependencyNotice = $dependencyNotice;
|
||||
$this->capabilitiesManager = $capabilitiesManager;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
global $wp_roles; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
|
||||
$data = [];
|
||||
|
||||
$data['items_per_page'] = $this->listingPageLimit->getLimitPerPage('newsletters');
|
||||
$includedSegmentTypes = $this->wooCommerceSegment->shouldShowWooCommerceSegment() ? [] : SegmentEntity::NON_WOO_RELATED_TYPES;
|
||||
$segments = $this->segmentsListRepository->getListWithSubscribedSubscribersCounts($includedSegmentTypes);
|
||||
$data['segments'] = $segments;
|
||||
$data['settings'] = $this->settings->getAll();
|
||||
$data['current_wp_user'] = $this->wp->wpGetCurrentUser()->to_array();
|
||||
$data['current_wp_user_firstname'] = $this->wp->wpGetCurrentUser()->user_firstname;
|
||||
$data['roles'] = $wp_roles->get_names(); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$data['roles']['mailpoet_all'] = __('In any WordPress role', 'mailpoet');
|
||||
|
||||
$dateTime = new DateTime();
|
||||
$data['current_date'] = $dateTime->getCurrentDate(DateTime::DEFAULT_DATE_FORMAT);
|
||||
$data['tomorrow_date'] = $dateTime->getCurrentDateTime()->modify("+1 day")
|
||||
->format(DateTime::DEFAULT_DATE_FORMAT);
|
||||
$data['current_time'] = $dateTime->getCurrentTime();
|
||||
$data['current_date_time'] = $dateTime->getCurrentDateTime()->format(DateTime::DEFAULT_DATE_TIME_FORMAT);
|
||||
$data['schedule_time_of_day'] = $dateTime->getTimeInterval(
|
||||
'00:00:00',
|
||||
'+15 minutes',
|
||||
96
|
||||
);
|
||||
$data['mailpoet_emails_page'] = $this->wp->adminUrl('admin.php?page=' . Menu::EMAILS_PAGE_SLUG);
|
||||
$data['show_congratulate_after_first_newsletter'] = isset($data['settings']['show_congratulate_after_first_newsletter']) ? $data['settings']['show_congratulate_after_first_newsletter'] : 'false';
|
||||
|
||||
$data['is_mailpoet_update_available'] = array_key_exists(Env::$pluginPath, $this->wp->getPluginUpdates());
|
||||
$data['newsletters_count'] = $this->newslettersRepository->countBy([]);
|
||||
|
||||
$data['automatic_emails'] = $this->automaticEmails->getAutomaticEmails();
|
||||
$data['woocommerce_optin_on_checkout'] = $this->settings->get('woocommerce.optin_on_checkout.enabled', false);
|
||||
|
||||
$data['sent_newsletters_count'] = $this->newslettersRepository->countBy(['status' => NewsletterEntity::STATUS_SENT]);
|
||||
$data['woocommerce_transactional_email_id'] = $this->settings->get(TransactionalEmails::SETTING_EMAIL_ID);
|
||||
$detailedAnalyticsCapability = $this->capabilitiesManager->getCapability('detailedAnalytics');
|
||||
$data['display_detailed_stats'] = isset($detailedAnalyticsCapability) && !$detailedAnalyticsCapability->isRestricted;
|
||||
$data['newsletters_templates_recently_sent_count'] = $this->newsletterTemplatesRepository->getRecentlySentCount();
|
||||
|
||||
$data['product_categories'] = $this->wpPostListLoader->getWooCommerceCategories();
|
||||
|
||||
$data['products'] = $this->wpPostListLoader->getProducts();
|
||||
|
||||
$data['authorized_emails'] = [];
|
||||
$data['verified_sender_domains'] = [];
|
||||
$data['partially_verified_sender_domains'] = [];
|
||||
$data['all_sender_domains'] = [];
|
||||
$data['sender_restrictions'] = [];
|
||||
|
||||
if ($this->bridge->isMailpoetSendingServiceEnabled()) {
|
||||
$data['authorized_emails'] = $this->authorizedEmailsController->getAuthorizedEmailAddresses();
|
||||
$data['verified_sender_domains'] = $this->senderDomainController->getFullyVerifiedSenderDomains(true);
|
||||
$data['partially_verified_sender_domains'] = $this->senderDomainController->getPartiallyVerifiedSenderDomains(true);
|
||||
$data['all_sender_domains'] = $this->senderDomainController->getAllSenderDomains();
|
||||
$data['sender_restrictions'] = [
|
||||
'lowerLimit' => AuthorizedSenderDomainController::LOWER_LIMIT,
|
||||
'isAuthorizedDomainRequiredForNewCampaigns' => $this->senderDomainController->isAuthorizedDomainRequiredForNewCampaigns(),
|
||||
'campaignTypes' => NewsletterEntity::CAMPAIGN_TYPES,
|
||||
];
|
||||
}
|
||||
|
||||
$data['corrupt_newsletters'] = $this->getCorruptNewsletterSubjects();
|
||||
|
||||
$data['legacy_automatic_emails_count'] = $this->newslettersRepository->countBy([
|
||||
'type' => [NewsletterEntity::TYPE_WELCOME, NewsletterEntity::TYPE_AUTOMATIC],
|
||||
]);
|
||||
|
||||
$data['legacy_automatic_emails_notice_dismissed'] = (bool)$this->userFlagsController->get('legacy_automatic_emails_notice_dismissed');
|
||||
|
||||
$data['block_email_editor_enabled'] = $this->dependencyCheck->are_dependencies_met(); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$this->dependencyNotice->displayMessageIfNeeded();
|
||||
$this->pageRenderer->displayPage('newsletters.html', $data);
|
||||
}
|
||||
|
||||
private function getCorruptNewsletterSubjects(): array {
|
||||
return array_map(function ($newsletter) {
|
||||
return [
|
||||
'id' => $newsletter->getId(),
|
||||
'subject' => $newsletter->getSubject(),
|
||||
];
|
||||
}, $this->newslettersRepository->getCorruptNewsletters());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\AssetsController;
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\Captcha\CaptchaRenderer;
|
||||
use MailPoet\Config\Installer;
|
||||
use MailPoet\Config\ServicesChecker;
|
||||
use MailPoet\Segments\SegmentsSimpleListRepository;
|
||||
use MailPoet\Services\AuthorizedEmailsController;
|
||||
use MailPoet\Services\AuthorizedSenderDomainController;
|
||||
use MailPoet\Services\Bridge;
|
||||
use MailPoet\Settings\Hosts;
|
||||
use MailPoet\Settings\Pages;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoet\WP\Notice as WPNotice;
|
||||
|
||||
class Settings {
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var ServicesChecker */
|
||||
private $servicesChecker;
|
||||
|
||||
/** @var CaptchaRenderer */
|
||||
private $captchaRenderer;
|
||||
|
||||
/** @var SegmentsSimpleListRepository */
|
||||
private $segmentsListRepository;
|
||||
|
||||
/** @var Bridge */
|
||||
private $bridge;
|
||||
|
||||
/** @var AuthorizedSenderDomainController */
|
||||
private $senderDomainController;
|
||||
|
||||
/** @var AuthorizedEmailsController */
|
||||
private $authorizedEmailsController;
|
||||
|
||||
/** @var AssetsController */
|
||||
private $assetsController;
|
||||
|
||||
public function __construct(
|
||||
AssetsController $assetsController,
|
||||
PageRenderer $pageRenderer,
|
||||
SettingsController $settings,
|
||||
WPFunctions $wp,
|
||||
ServicesChecker $servicesChecker,
|
||||
CaptchaRenderer $captchaRenderer,
|
||||
SegmentsSimpleListRepository $segmentsListRepository,
|
||||
Bridge $bridge,
|
||||
AuthorizedSenderDomainController $senderDomainController,
|
||||
AuthorizedEmailsController $authorizedEmailsController
|
||||
) {
|
||||
$this->assetsController = $assetsController;
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->settings = $settings;
|
||||
$this->wp = $wp;
|
||||
$this->servicesChecker = $servicesChecker;
|
||||
$this->captchaRenderer = $captchaRenderer;
|
||||
$this->segmentsListRepository = $segmentsListRepository;
|
||||
$this->bridge = $bridge;
|
||||
$this->senderDomainController = $senderDomainController;
|
||||
$this->authorizedEmailsController = $authorizedEmailsController;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$settings = $this->settings->getAll();
|
||||
|
||||
$premiumKeyValid = $this->servicesChecker->isPremiumKeyValid(false);
|
||||
// force MSS key check even if the method isn't active
|
||||
$mpApiKeyValid = $this->servicesChecker->isMailPoetAPIKeyValid(false, true);
|
||||
|
||||
$data = [
|
||||
'settings' => $settings,
|
||||
'segments' => $this->segmentsListRepository->getListWithSubscribedSubscribersCounts(),
|
||||
'premium_key_valid' => !empty($premiumKeyValid),
|
||||
'mss_key_valid' => !empty($mpApiKeyValid),
|
||||
'pages' => Pages::getAll(),
|
||||
'current_user' => $this->wp->wpGetCurrentUser(),
|
||||
'is_members_plugin_active' => $this->wp->isPluginActive('members/members.php'),
|
||||
'hosts' => [
|
||||
'web' => Hosts::getWebHosts(),
|
||||
'smtp' => Hosts::getSMTPHosts(),
|
||||
],
|
||||
'paths' => [
|
||||
'root' => ABSPATH,
|
||||
'plugin' => dirname(dirname(dirname(__DIR__))),
|
||||
],
|
||||
'current_site_title' => $this->wp->getBloginfo('name'),
|
||||
'built_in_captcha_supported' => $this->captchaRenderer->isSupported(),
|
||||
];
|
||||
|
||||
$data['authorized_emails'] = [];
|
||||
$data['verified_sender_domains'] = [];
|
||||
$data['partially_verified_sender_domains'] = [];
|
||||
$data['all_sender_domains'] = [];
|
||||
$data['sender_restrictions'] = [];
|
||||
|
||||
if ($this->bridge->isMailpoetSendingServiceEnabled()) {
|
||||
$data['authorized_emails'] = $this->authorizedEmailsController->getAuthorizedEmailAddresses();
|
||||
$data['verified_sender_domains'] = $this->senderDomainController->getFullyVerifiedSenderDomains(true);
|
||||
$data['partially_verified_sender_domains'] = $this->senderDomainController->getPartiallyVerifiedSenderDomains(true);
|
||||
$data['all_sender_domains'] = $this->senderDomainController->getAllSenderDomains();
|
||||
$data['sender_restrictions'] = [
|
||||
'lowerLimit' => AuthorizedSenderDomainController::LOWER_LIMIT,
|
||||
];
|
||||
}
|
||||
|
||||
$data = array_merge($data, Installer::getPremiumStatus());
|
||||
|
||||
if (isset($_GET['enable-customizer-notice'])) {
|
||||
$notice = new WPNotice(WPNotice::TYPE_ERROR, _x(
|
||||
'You need to have WooCommerce active to access the MailPoet email customizer for WooCommerce.',
|
||||
'Notice in Settings when WooCommerce is not enabled',
|
||||
'mailpoet'
|
||||
));
|
||||
$notice->displayWPNotice();
|
||||
}
|
||||
|
||||
$this->assetsController->setupSettingsDependencies();
|
||||
$this->pageRenderer->displayPage('settings.html', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\Listing\PageLimit;
|
||||
|
||||
class StaticSegments {
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
/** @var PageLimit */
|
||||
private $listingPageLimit;
|
||||
|
||||
public function __construct(
|
||||
PageRenderer $pageRenderer,
|
||||
PageLimit $listingPageLimit
|
||||
) {
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->listingPageLimit = $listingPageLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function render() {
|
||||
$data = [];
|
||||
$data['items_per_page'] = $this->listingPageLimit->getLimitPerPage('segments');
|
||||
|
||||
$this->pageRenderer->displayPage('segments/static.html', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\API\JSON\ResponseBuilders\CustomFieldsResponseBuilder;
|
||||
use MailPoet\CustomFields\CustomFieldsRepository;
|
||||
use MailPoet\Entities\CustomFieldEntity;
|
||||
use MailPoet\Form\Block;
|
||||
use MailPoet\Listing\PageLimit;
|
||||
use MailPoet\Segments\SegmentsSimpleListRepository;
|
||||
|
||||
class Subscribers {
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
/** @var PageLimit */
|
||||
private $listingPageLimit;
|
||||
|
||||
/** @var Block\Date */
|
||||
private $dateBlock;
|
||||
|
||||
/** @var SegmentsSimpleListRepository */
|
||||
private $segmentsListRepository;
|
||||
|
||||
/** @var CustomFieldsRepository */
|
||||
private $customFieldsRepository;
|
||||
|
||||
/** @var CustomFieldsResponseBuilder */
|
||||
private $customFieldsResponseBuilder;
|
||||
|
||||
public function __construct(
|
||||
PageRenderer $pageRenderer,
|
||||
PageLimit $listingPageLimit,
|
||||
Block\Date $dateBlock,
|
||||
SegmentsSimpleListRepository $segmentsListRepository,
|
||||
CustomFieldsRepository $customFieldsRepository,
|
||||
CustomFieldsResponseBuilder $customFieldsResponseBuilder
|
||||
) {
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->listingPageLimit = $listingPageLimit;
|
||||
$this->dateBlock = $dateBlock;
|
||||
$this->segmentsListRepository = $segmentsListRepository;
|
||||
$this->customFieldsRepository = $customFieldsRepository;
|
||||
$this->customFieldsResponseBuilder = $customFieldsResponseBuilder;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$data = [];
|
||||
|
||||
$data['items_per_page'] = $this->listingPageLimit->getLimitPerPage('subscribers');
|
||||
$data['segments'] = $this->segmentsListRepository->getListWithSubscribedSubscribersCounts();
|
||||
|
||||
$data['custom_fields'] = array_map(function(CustomFieldEntity $customField): array {
|
||||
$field = $this->customFieldsResponseBuilder->build($customField);
|
||||
|
||||
if (!empty($field['params']['values'])) {
|
||||
$values = [];
|
||||
|
||||
foreach ($field['params']['values'] as $value) {
|
||||
$values[$value['value']] = $value['value'];
|
||||
}
|
||||
$field['params']['values'] = $values;
|
||||
}
|
||||
return $field;
|
||||
}, $this->customFieldsRepository->findAll());
|
||||
|
||||
$data['date_formats'] = $this->dateBlock->getDateFormats();
|
||||
$data['month_names'] = $this->dateBlock->getMonthNames();
|
||||
$this->pageRenderer->displayPage('subscribers/subscribers.html', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\Subscribers\ImportExport\ImportExportFactory;
|
||||
|
||||
class SubscribersExport {
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
public function __construct(
|
||||
PageRenderer $pageRenderer
|
||||
) {
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$export = new ImportExportFactory(ImportExportFactory::EXPORT_ACTION);
|
||||
$data = $export->bootstrap();
|
||||
$this->pageRenderer->displayPage('subscribers/importExport/export.html', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\Form\Block;
|
||||
use MailPoet\Services\Validator;
|
||||
use MailPoet\Subscribers\ImportExport\ImportExportFactory;
|
||||
|
||||
class SubscribersImport {
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
/** @var Block\Date */
|
||||
private $dateBlock;
|
||||
|
||||
public function __construct(
|
||||
PageRenderer $pageRenderer,
|
||||
Block\Date $dateBlock
|
||||
) {
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->dateBlock = $dateBlock;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$import = new ImportExportFactory(ImportExportFactory::IMPORT_ACTION);
|
||||
$data = $import->bootstrap();
|
||||
$data = array_merge($data, [
|
||||
'date_types' => $this->dateBlock->getDateTypes(),
|
||||
'date_formats' => $this->dateBlock->getDateFormats(),
|
||||
'month_names' => $this->dateBlock->getMonthNames(),
|
||||
'role_based_emails' => json_encode(Validator::ROLE_EMAILS),
|
||||
]);
|
||||
$this->pageRenderer->displayPage('subscribers/importExport/import.html', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\Util\License\Features\CapabilitiesManager;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class Upgrade {
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
private CapabilitiesManager $capabilitiesManager;
|
||||
|
||||
public function __construct(
|
||||
PageRenderer $pageRenderer,
|
||||
WPFunctions $wp,
|
||||
CapabilitiesManager $capabilitiesManager
|
||||
) {
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->wp = $wp;
|
||||
$this->capabilitiesManager = $capabilitiesManager;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$data = [
|
||||
'current_wp_user' => $this->wp->wpGetCurrentUser()->to_array(),
|
||||
];
|
||||
|
||||
// @todo change this after a/b test to keep only one of the pages
|
||||
if ($this->capabilitiesManager->showNewUpgradePage()) {
|
||||
$data = [
|
||||
'current_mailpoet_plan_tier' => $this->capabilitiesManager->getTier(),
|
||||
];
|
||||
|
||||
$this->pageRenderer->displayPage('upgrade_tiers.html', $data);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->pageRenderer->displayPage('upgrade.html', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\Config\Menu;
|
||||
use MailPoet\Config\ServicesChecker;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\WooCommerce\Helper as WooCommerceHelper;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class WelcomeWizard {
|
||||
const TRACK_LOADDED_VIA_WOOCOMMERCE_SETTING_NAME = 'send_event_that_wizard_was_loaded_via_woocommerce';
|
||||
const TRACK_LOADDED_VIA_WOOCOMMERCE_MARKETING_DASHBOARD_SETTING_NAME = 'wizard_loaded_via_woocommerce_marketing_dashboard';
|
||||
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var WooCommerceHelper */
|
||||
private $wooCommerceHelper;
|
||||
|
||||
/** @var ServicesChecker */
|
||||
private $servicesChecker;
|
||||
|
||||
public function __construct(
|
||||
PageRenderer $pageRenderer,
|
||||
SettingsController $settings,
|
||||
WooCommerceHelper $wooCommerceHelper,
|
||||
WPFunctions $wp,
|
||||
ServicesChecker $servicesChecker
|
||||
) {
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->settings = $settings;
|
||||
$this->wooCommerceHelper = $wooCommerceHelper;
|
||||
$this->wp = $wp;
|
||||
$this->servicesChecker = $servicesChecker;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
if ((bool)(defined('DOING_AJAX') && DOING_AJAX)) return;
|
||||
|
||||
$loadedViaWooCommerce = $this->settings->get(WelcomeWizard::TRACK_LOADDED_VIA_WOOCOMMERCE_SETTING_NAME, false);
|
||||
|
||||
if (!$loadedViaWooCommerce && isset($_GET['mailpoet_wizard_loaded_via_woocommerce'])) {
|
||||
// This setting is used to send an event to Mixpanel in another request as, before completing the wizard, Mixpanel is not enabled.
|
||||
$this->settings->set(WelcomeWizard::TRACK_LOADDED_VIA_WOOCOMMERCE_SETTING_NAME, 1);
|
||||
}
|
||||
|
||||
$loadedViaWooCommerceMarketingDashboard = $this->settings->get(WelcomeWizard::TRACK_LOADDED_VIA_WOOCOMMERCE_MARKETING_DASHBOARD_SETTING_NAME, false);
|
||||
|
||||
if (!$loadedViaWooCommerceMarketingDashboard && isset($_GET['mailpoet_wizard_loaded_via_woocommerce_marketing_dashboard'])) {
|
||||
// This setting is used to send an event to Mixpanel in another request as, before completing the wizard, Mixpanel is not enabled.
|
||||
$this->settings->set(WelcomeWizard::TRACK_LOADDED_VIA_WOOCOMMERCE_MARKETING_DASHBOARD_SETTING_NAME, 1);
|
||||
}
|
||||
|
||||
|
||||
$premiumKeyValid = $this->servicesChecker->isPremiumKeyValid(false);
|
||||
// force MSS key check even if the method isn't active
|
||||
$mpApiKeyValid = $this->servicesChecker->isMailPoetAPIKeyValid(false, true);
|
||||
|
||||
$data = [
|
||||
'finish_wizard_url' => $this->wp->adminUrl('admin.php?page=' . Menu::MAIN_PAGE_SLUG),
|
||||
'admin_email' => $this->wp->getOption('admin_email'),
|
||||
'current_wp_user' => $this->wp->wpGetCurrentUser()->to_array(),
|
||||
'show_customers_import' => $this->wooCommerceHelper->getCustomersCount() > 0,
|
||||
'settings' => $this->getSettings(),
|
||||
'premium_key_valid' => !empty($premiumKeyValid),
|
||||
'mss_key_valid' => !empty($mpApiKeyValid),
|
||||
'has_tracking_settings' => $this->settings->hasSavedValue('analytics') && $this->settings->hasSavedValue('3rd_party_libs'),
|
||||
'welcome_wizard_current_step' => $this->settings->get('welcome_wizard_current_step', ''),
|
||||
];
|
||||
$this->pageRenderer->displayPage('welcome_wizard.html', $data);
|
||||
}
|
||||
|
||||
private function getSettings(): array {
|
||||
$settings = $this->settings->getAll();
|
||||
|
||||
$user = $this->wp->wpGetCurrentUser();
|
||||
$settings['sender'] = [
|
||||
'name' => $user->display_name, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
'address' => $user->user_email, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
];
|
||||
return $settings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\Config\Menu;
|
||||
use MailPoet\WooCommerce\Helper;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class WooCommerceSetup {
|
||||
/** @var PageRenderer */
|
||||
private $pageRenderer;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var Helper */
|
||||
private $wooCommerceHelper;
|
||||
|
||||
public function __construct(
|
||||
PageRenderer $pageRenderer,
|
||||
Helper $wooCommerceHelper,
|
||||
WPFunctions $wp
|
||||
) {
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
$this->wooCommerceHelper = $wooCommerceHelper;
|
||||
$this->wp = $wp;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
if ((bool)(defined('DOING_AJAX') && DOING_AJAX)) return;
|
||||
$data = [
|
||||
'finish_wizard_url' => $this->wp->adminUrl('admin.php?page=' . Menu::MAIN_PAGE_SLUG),
|
||||
'show_customers_import' => $this->wooCommerceHelper->getCustomersCount() > 0,
|
||||
];
|
||||
$this->pageRenderer->displayPage('woocommerce_setup.html', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Analytics;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Util\Security;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
|
||||
class Analytics {
|
||||
|
||||
const SETTINGS_LAST_SENT_KEY = 'analytics_last_sent';
|
||||
const SEND_AFTER_DAYS = 7;
|
||||
const ANALYTICS_FILTER = 'mailpoet_analytics';
|
||||
|
||||
/** @var Reporter */
|
||||
private $reporter;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
public function __construct(
|
||||
Reporter $reporter,
|
||||
SettingsController $settingsController
|
||||
) {
|
||||
$this->reporter = $reporter;
|
||||
$this->settings = $settingsController;
|
||||
$this->wp = new WPFunctions;
|
||||
}
|
||||
|
||||
/** @return array|null */
|
||||
public function generateAnalytics() {
|
||||
if ($this->shouldSend()) {
|
||||
$data = $this->getAnalyticsData();
|
||||
$this->recordDataSent();
|
||||
return $data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getAnalyticsData() {
|
||||
return $this->wp->applyFilters(self::ANALYTICS_FILTER, $this->reporter->getData());
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function isEnabled() {
|
||||
$analyticsSettings = $this->settings->get('analytics', []);
|
||||
return !empty($analyticsSettings['enabled']) === true;
|
||||
}
|
||||
|
||||
public function setPublicId($newPublicId) {
|
||||
$currentPublicId = $this->settings->get('public_id');
|
||||
if ($currentPublicId !== $newPublicId) {
|
||||
$this->settings->set('public_id', $newPublicId);
|
||||
$this->settings->set('new_public_id', 'true');
|
||||
// Force user data to be resent
|
||||
$this->settings->delete(Analytics::SETTINGS_LAST_SENT_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return string */
|
||||
public function getPublicId() {
|
||||
$publicId = $this->settings->get('public_id', '');
|
||||
if (empty($publicId)) {
|
||||
// The previous implementation used md5, so this is just to ensure consistency
|
||||
$randomId = md5(Security::generateRandomString(32));
|
||||
$this->settings->set('public_id', $randomId);
|
||||
$this->settings->set('new_public_id', 'true');
|
||||
return $randomId;
|
||||
}
|
||||
return $publicId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a the public_id was added and update new_public_id to false
|
||||
* @return bool
|
||||
*/
|
||||
public function isPublicIdNew() {
|
||||
$newPublicId = $this->settings->get('new_public_id');
|
||||
if ($newPublicId === 'true') {
|
||||
$this->settings->set('new_public_id', 'false');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function shouldSend() {
|
||||
if (!$this->isEnabled()) {
|
||||
return false;
|
||||
}
|
||||
$nextSend = $this->getNextSendDate();
|
||||
return $nextSend->isPast();
|
||||
}
|
||||
|
||||
public function getNextSendDate(): Carbon {
|
||||
$lastSent = $this->settings->get(Analytics::SETTINGS_LAST_SENT_KEY);
|
||||
if (!$lastSent) {
|
||||
return Carbon::now()->subMinute();
|
||||
}
|
||||
|
||||
return Carbon::createFromTimestamp(strtotime($lastSent))->addDays(self::SEND_AFTER_DAYS);
|
||||
}
|
||||
|
||||
public function recordDataSent() {
|
||||
$this->settings->set(Analytics::SETTINGS_LAST_SENT_KEY, Carbon::now());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Analytics;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Automation;
|
||||
use MailPoet\Automation\Engine\Data\Step;
|
||||
use MailPoet\Automation\Engine\Storage\AutomationStorage;
|
||||
use MailPoet\Captcha\CaptchaConstants;
|
||||
use MailPoet\Config\ServicesChecker;
|
||||
use MailPoet\Cron\CronTrigger;
|
||||
use MailPoet\Entities\DynamicSegmentFilterData;
|
||||
use MailPoet\Listing\ListingDefinition;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Segments\DynamicSegments\DynamicSegmentFilterRepository;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\AutomationsEvents;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\EmailAction;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\EmailActionClickAny;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\EmailOpensAbsoluteCountAction;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\EmailsReceived;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\MailPoetCustomFields;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\NumberOfClicks;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\SubscriberDateField;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\SubscriberScore;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\SubscriberSegment;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\SubscriberSubscribedViaForm;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\SubscriberTag;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\SubscriberTextField;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\UserRole;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceAverageSpent;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceCategory;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceCountry;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceCustomerTextField;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceFirstOrder;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceMembership;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceNumberOfOrders;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceNumberOfReviews;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceProduct;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommercePurchaseDate;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommercePurchasedWithAttribute;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceSingleOrderValue;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceSubscription;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceTag;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceTotalSpent;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceUsedCouponCode;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceUsedPaymentMethod;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceUsedShippingMethod;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Services\AuthorizedEmailsController;
|
||||
use MailPoet\Settings\Pages;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Settings\TrackingConfig;
|
||||
use MailPoet\Subscribers\ConfirmationEmailCustomizer;
|
||||
use MailPoet\Subscribers\NewSubscriberNotificationMailer;
|
||||
use MailPoet\Subscribers\SubscriberListingRepository;
|
||||
use MailPoet\Tags\TagRepository;
|
||||
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
|
||||
use MailPoet\WooCommerce\Helper as WooCommerceHelper;
|
||||
use MailPoet\WooCommerce\Subscription;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoet\WPCOM\DotcomHelperFunctions;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
|
||||
class Reporter {
|
||||
/** @var NewslettersRepository */
|
||||
private $newslettersRepository;
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentsRepository;
|
||||
|
||||
/** @var DynamicSegmentFilterRepository */
|
||||
private $dynamicSegmentFilterRepository;
|
||||
|
||||
/** @var TagRepository */
|
||||
private $tagRepository;
|
||||
|
||||
/** @var ServicesChecker */
|
||||
private $servicesChecker;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
/** @var WooCommerceHelper */
|
||||
private $woocommerceHelper;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var SubscribersFeature */
|
||||
private $subscribersFeature;
|
||||
|
||||
/** @var TrackingConfig */
|
||||
private $trackingConfig;
|
||||
|
||||
/** @var SubscriberListingRepository */
|
||||
private $subscriberListingRepository;
|
||||
|
||||
/** @var AutomationStorage */
|
||||
private $automationStorage;
|
||||
|
||||
/*** @var UnsubscribeReporter */
|
||||
private $unsubscribeReporter;
|
||||
|
||||
/*** @var DotcomHelperFunctions */
|
||||
private $dotcomHelperFunctions;
|
||||
|
||||
/*** @var ReporterCampaignData */
|
||||
private $reporterCampaignData;
|
||||
|
||||
public function __construct(
|
||||
NewslettersRepository $newslettersRepository,
|
||||
SegmentsRepository $segmentsRepository,
|
||||
DynamicSegmentFilterRepository $dynamicSegmentFilterRepository,
|
||||
TagRepository $tagRepository,
|
||||
ServicesChecker $servicesChecker,
|
||||
SettingsController $settings,
|
||||
WooCommerceHelper $woocommerceHelper,
|
||||
WPFunctions $wp,
|
||||
SubscribersFeature $subscribersFeature,
|
||||
TrackingConfig $trackingConfig,
|
||||
SubscriberListingRepository $subscriberListingRepository,
|
||||
AutomationStorage $automationStorage,
|
||||
UnsubscribeReporter $unsubscribeReporter,
|
||||
DotcomHelperFunctions $dotcomHelperFunctions,
|
||||
ReporterCampaignData $reporterCampaignData
|
||||
) {
|
||||
$this->newslettersRepository = $newslettersRepository;
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
$this->dynamicSegmentFilterRepository = $dynamicSegmentFilterRepository;
|
||||
$this->tagRepository = $tagRepository;
|
||||
$this->servicesChecker = $servicesChecker;
|
||||
$this->settings = $settings;
|
||||
$this->woocommerceHelper = $woocommerceHelper;
|
||||
$this->wp = $wp;
|
||||
$this->subscribersFeature = $subscribersFeature;
|
||||
$this->trackingConfig = $trackingConfig;
|
||||
$this->subscriberListingRepository = $subscriberListingRepository;
|
||||
$this->automationStorage = $automationStorage;
|
||||
$this->unsubscribeReporter = $unsubscribeReporter;
|
||||
$this->dotcomHelperFunctions = $dotcomHelperFunctions;
|
||||
$this->reporterCampaignData = $reporterCampaignData;
|
||||
}
|
||||
|
||||
public function getData() {
|
||||
global $wpdb, $wp_version, $woocommerce; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$mta = $this->settings->get('mta', []);
|
||||
$newsletters = $this->newslettersRepository->getAnalytics();
|
||||
$isCronTriggerMethodWP = $this->settings->get('cron_trigger.method') === CronTrigger::METHOD_WORDPRESS;
|
||||
$bounceAddress = $this->settings->get('bounce.address');
|
||||
$segments = $this->segmentsRepository->getCountsPerType();
|
||||
$hasWc = $this->woocommerceHelper->isWooCommerceActive();
|
||||
$inactiveSubscribersMonths = (int)round((int)$this->settings->get('deactivate_subscriber_after_inactive_days') / 30);
|
||||
$inactiveSubscribersStatus = $inactiveSubscribersMonths === 0 ? 'never' : "$inactiveSubscribersMonths months";
|
||||
|
||||
$result = [
|
||||
'PHP version' => PHP_VERSION,
|
||||
'MySQL version' => $wpdb->db_version(),
|
||||
'WordPress version' => $wp_version, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
'Multisite environment' => $this->wp->isMultisite() ? 'yes' : 'no',
|
||||
'RTL' => $this->wp->isRtl() ? 'yes' : 'no',
|
||||
'WP_MEMORY_LIMIT' => WP_MEMORY_LIMIT,
|
||||
'WP_MAX_MEMORY_LIMIT' => WP_MAX_MEMORY_LIMIT,
|
||||
'PHP memory_limit' => ini_get('memory_limit'),
|
||||
'PHP max_execution_time' => ini_get('max_execution_time'),
|
||||
'MailPoet Free version' => MAILPOET_VERSION,
|
||||
'MailPoet Premium version' => (defined('MAILPOET_PREMIUM_VERSION')) ? MAILPOET_PREMIUM_VERSION : 'N/A',
|
||||
'Total number of subscribers' => $this->subscribersFeature->getSubscribersCount(),
|
||||
'Sending Method' => $mta['method'] ?? null,
|
||||
"Send all site's emails with" => $this->settings->get('send_transactional_emails') ? 'current sending method' : 'default WordPress sending method',
|
||||
'Date of plugin installation' => $this->settings->get('installed_at'),
|
||||
'Subscribe in comments' => (boolean)$this->settings->get('subscribe.on_comment.enabled', false),
|
||||
'Subscribe in registration form' => (boolean)$this->settings->get('subscribe.on_register.enabled', false),
|
||||
'Manage Subscription page > MailPoet page' => (boolean)Pages::isMailpoetPage(intval($this->settings->get('subscription.pages.manage'))),
|
||||
'Unsubscribe page > MailPoet page' => (boolean)Pages::isMailpoetPage(intval($this->settings->get('subscription.pages.unsubscribe'))),
|
||||
'Sign-up confirmation' => (boolean)$this->settings->get('signup_confirmation.enabled', false),
|
||||
'Sign-up confirmation: Confirmation page > MailPoet page' => (boolean)Pages::isMailpoetPage(intval($this->settings->get('subscription.pages.confirmation'))),
|
||||
'Bounce email address' => !empty($bounceAddress),
|
||||
'Newsletter task scheduler (cron)' => $isCronTriggerMethodWP ? 'visitors' : 'script',
|
||||
'Open and click tracking' => $this->trackingConfig->isEmailTrackingEnabled(),
|
||||
'Tracking level' => $this->settings->get('tracking.level', TrackingConfig::LEVEL_FULL),
|
||||
'Premium key valid' => $this->servicesChecker->isPremiumKeyValid(),
|
||||
'New subscriber notifications' => NewSubscriberNotificationMailer::isDisabled($this->settings->get(NewSubscriberNotificationMailer::SETTINGS_KEY)),
|
||||
'Number of active post notifications' => $newsletters['notifications_count'],
|
||||
'Number of active welcome emails' => $newsletters['welcome_newsletters_count'],
|
||||
'Total number of standard newsletters sent' => $newsletters['sent_newsletters_count'],
|
||||
'Total number of block editor gutenberg newsletters' => $newsletters['total_gutenberg_newsletter_count'],
|
||||
'Number of block editor gutenberg newsletters sent' => $newsletters['sent_gutenberg_newsletter_count'],
|
||||
'Number of segments' => isset($segments['dynamic']) ? (int)$segments['dynamic'] : 0,
|
||||
'Number of lists' => isset($segments['default']) ? (int)$segments['default'] : 0,
|
||||
'Number of subscriber tags' => $this->tagRepository->countBy([]),
|
||||
'Site is using block theme' => $this->wp->wpIsBlockTheme(),
|
||||
'Stop sending to inactive subscribers' => $inactiveSubscribersStatus,
|
||||
'CAPTCHA setting' => $this->settings->get(CaptchaConstants::TYPE_SETTING_NAME, '') ?: 'disabled',
|
||||
'Is CAPTCHA on register forms enabled' => $this->settings->get(CaptchaConstants::ON_REGISTER_FORMS_SETTING_NAME, false) ? 'yes' : 'no',
|
||||
'users_can_register' => $this->wp->getOption('users_can_register') ? 'yes' : 'no',
|
||||
'Is WooCommerce account creation on "My account" enabled' => $this->wp->getOption('woocommerce_enable_myaccount_registration') ?? 'no',
|
||||
'Plugin > MailPoet Premium' => $this->wp->isPluginActive('mailpoet-premium/mailpoet-premium.php'),
|
||||
'Plugin > bounce add-on' => $this->wp->isPluginActive('mailpoet-bounce-handler/mailpoet-bounce-handler.php'),
|
||||
'Plugin > Bloom' => $this->wp->isPluginActive('bloom-for-publishers/bloom.php'),
|
||||
'Plugin > WP Holler' => $this->wp->isPluginActive('holler-box/holler-box.php'),
|
||||
'Plugin > WP-SMTP' => $this->wp->isPluginActive('wp-mail-smtp/wp_mail_smtp.php'),
|
||||
'Plugin > WooCommerce' => $hasWc,
|
||||
'Plugin > WooCommerce Subscription' => $this->wp->isPluginActive('woocommerce-subscriptions/woocommerce-subscriptions.php'),
|
||||
'Plugin > WooCommerce Follow Up Emails' => $this->wp->isPluginActive('woocommerce-follow-up-emails/woocommerce-follow-up-emails.php'),
|
||||
'Plugin > WooCommerce Email Customizer' => $this->wp->isPluginActive('woocommerce-email-customizer/woocommerce-email-customizer.php'),
|
||||
'Plugin > WooCommerce Memberships' => $this->wp->isPluginActive('woocommerce-memberships/woocommerce-memberships.php'),
|
||||
'Plugin > WooCommerce MailChimp' => $this->wp->isPluginActive('woocommerce-mailchimp/woocommerce-mailchimp.php'),
|
||||
'Plugin > MailChimp for WooCommerce' => $this->wp->isPluginActive('mailchimp-for-woocommerce/mailchimp-woocommerce.php'),
|
||||
'Plugin > The Event Calendar' => $this->wp->isPluginActive('the-events-calendar/the-events-calendar.php'),
|
||||
'Plugin > Gravity Forms' => $this->wp->isPluginActive('gravityforms/gravityforms.php'),
|
||||
'Plugin > Ninja Forms' => $this->wp->isPluginActive('ninja-forms/ninja-forms.php'),
|
||||
'Plugin > WPForms' => $this->wp->isPluginActive('wpforms-lite/wpforms.php'),
|
||||
'Plugin > Formidable Forms' => $this->wp->isPluginActive('formidable/formidable.php'),
|
||||
'Plugin > Contact Form 7' => $this->wp->isPluginActive('contact-form-7/wp-contact-form-7.php'),
|
||||
'Plugin > Easy Digital Downloads' => $this->wp->isPluginActive('easy-digital-downloads/easy-digital-downloads.php'),
|
||||
'Plugin > WooCommerce Multi-Currency' => $this->wp->isPluginActive('woocommerce-multi-currency/woocommerce-multi-currency.php'),
|
||||
'Plugin > Multi Currency for WooCommerce' => $this->wp->isPluginActive('woo-multi-currency/woo-multi-currency.php'),
|
||||
'Web host' => $this->settings->get('mta_group') == 'website' ? $this->settings->get('web_host') : null,
|
||||
// Dynamic segment filters tracking -- start. If you extend segments tracking, please extend mapping in analytics.js
|
||||
'Segment > number of machine-opens' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_EMAIL, EmailOpensAbsoluteCountAction::MACHINE_TYPE),
|
||||
'Segment > number of opens' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_EMAIL, EmailOpensAbsoluteCountAction::TYPE),
|
||||
'Segment > number of orders' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommerceNumberOfOrders::ACTION_NUMBER_OF_ORDERS),
|
||||
'Segment > clicked' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_EMAIL, EmailAction::ACTION_CLICKED),
|
||||
'Segment > clicked any email' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_EMAIL, EmailActionClickAny::TYPE),
|
||||
'Segment > score' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_USER_ROLE, SubscriberScore::TYPE),
|
||||
'Segment > subscribed to list' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_USER_ROLE, SubscriberSegment::TYPE),
|
||||
'Segment > opened' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_EMAIL, EmailAction::ACTION_OPENED),
|
||||
'Segment > machine-opened' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_EMAIL, EmailAction::ACTION_MACHINE_OPENED),
|
||||
'Segment > is active member of' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE_MEMBERSHIP, WooCommerceMembership::ACTION_MEMBER_OF),
|
||||
'Segment > has an active subscription' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE_SUBSCRIPTION, WooCommerceSubscription::ACTION_HAS_ACTIVE),
|
||||
'Segment > is in country' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommerceCountry::ACTION_CUSTOMER_COUNTRY),
|
||||
'Segment > MailPoet custom field' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_USER_ROLE, MailPoetCustomFields::TYPE),
|
||||
'Segment > purchased in category' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommerceCategory::ACTION_CATEGORY),
|
||||
'Segment > purchased product' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommerceProduct::ACTION_PRODUCT),
|
||||
'Segment > purchased with tag' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommerceTag::ACTION),
|
||||
'Segment > subscribed date' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_USER_ROLE, SubscriberDateField::SUBSCRIBED_DATE),
|
||||
'Segment > total spent' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommerceTotalSpent::ACTION_TOTAL_SPENT),
|
||||
'Segment > first order' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommerceFirstOrder::ACTION),
|
||||
'Segment > WordPress user role' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_USER_ROLE, UserRole::TYPE),
|
||||
'Segment > subscriber tags' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_USER_ROLE, SubscriberTag::TYPE),
|
||||
'Segment > number of emails received' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_EMAIL, EmailsReceived::ACTION),
|
||||
'Segment > purchase date' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommercePurchaseDate::ACTION),
|
||||
'Segment > average order value' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommerceAverageSpent::ACTION),
|
||||
'Segment > single order value' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommerceSingleOrderValue::ACTION_SINGLE_ORDER_VALUE),
|
||||
'Segment > last engagement date' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_USER_ROLE, SubscriberDateField::LAST_ENGAGEMENT_DATE),
|
||||
'Segment > last click date' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_USER_ROLE, SubscriberDateField::LAST_CLICK_DATE),
|
||||
'Segment > last purchase date' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_USER_ROLE, SubscriberDateField::LAST_PURCHASE_DATE),
|
||||
'Segment > last open date' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_USER_ROLE, SubscriberDateField::LAST_OPEN_DATE),
|
||||
'Segment > last page view date' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_USER_ROLE, SubscriberDateField::LAST_PAGE_VIEW_DATE),
|
||||
'Segment > last sending date' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_USER_ROLE, SubscriberDateField::LAST_SENDING_DATE),
|
||||
'Segment > first name' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_USER_ROLE, SubscriberTextField::FIRST_NAME),
|
||||
'Segment > last name' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_USER_ROLE, SubscriberTextField::LAST_NAME),
|
||||
'Segment > email' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_USER_ROLE, SubscriberTextField::EMAIL),
|
||||
'Segment > city' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommerceCustomerTextField::CITY),
|
||||
'Segment > postal code' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommerceCustomerTextField::POSTAL_CODE),
|
||||
'Segment > purchased with attribute' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommercePurchasedWithAttribute::ACTION),
|
||||
'Segment > used payment method' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommerceUsedPaymentMethod::ACTION),
|
||||
'Segment > used shipping method' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommerceUsedShippingMethod::ACTION),
|
||||
'Segment > number of reviews' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommerceNumberOfReviews::ACTION),
|
||||
'Segment > used coupon code' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_WOOCOMMERCE, WooCommerceUsedCouponCode::ACTION),
|
||||
'Segment > entered automation' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_AUTOMATIONS, AutomationsEvents::ENTERED_ACTION),
|
||||
'Segment > exited automation' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_AUTOMATIONS, AutomationsEvents::EXITED_ACTION),
|
||||
'Segment > was sent' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_EMAIL, EmailAction::ACTION_WAS_SENT),
|
||||
'Segment > subscribed via form' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_USER_ROLE, SubscriberSubscribedViaForm::TYPE),
|
||||
'Segment > number of clicks' => $this->isFilterTypeActive(DynamicSegmentFilterData::TYPE_EMAIL, NumberOfClicks::ACTION),
|
||||
// Dynamic segment filters tracking -- end. If you extend segments tracking, please extend mapping in analytics.js
|
||||
'Number of segments with multiple conditions' => $this->segmentsRepository->getSegmentCountWithMultipleFilters(),
|
||||
'Support tier' => $this->subscribersFeature->hasPremiumSupport() ? 'premium' : 'free',
|
||||
'Unauthorized email notice shown' => !empty($this->settings->get(AuthorizedEmailsController::AUTHORIZED_EMAIL_ADDRESSES_ERROR_SETTING)),
|
||||
'Sign-up confirmation: Confirmation Template > using html email editor template' => (boolean)$this->settings->get(ConfirmationEmailCustomizer::SETTING_ENABLE_EMAIL_CUSTOMIZER, false),
|
||||
'Is WordPress.com' => $this->dotcomHelperFunctions->isDotcom() ? 'yes' : 'no',
|
||||
'WordPress.com plan' => $this->dotcomHelperFunctions->getDotcomPlan(),
|
||||
];
|
||||
|
||||
$result = array_merge(
|
||||
$result,
|
||||
$this->subscriberProperties(),
|
||||
$this->automationProperties(),
|
||||
$this->reporterCampaignData->getCampaignAnalyticsProperties(),
|
||||
$this->unsubscribeReporter->getProperties()
|
||||
);
|
||||
if ($hasWc) {
|
||||
$result['WooCommerce version'] = $woocommerce->version;
|
||||
$result['Number of WooCommerce subscribers'] = isset($segments['woocommerce_users']) ? (int)$segments['woocommerce_users'] : 0;
|
||||
$result['WooCommerce: opt-in on checkout is active'] = $this->settings->get(Subscription::OPTIN_ENABLED_SETTING_NAME) ?: false;
|
||||
$result['WooCommerce: opt-in on checkout position'] = $this->settings->get(Subscription::OPTIN_POSITION_SETTING_NAME) ?: '';
|
||||
$result['WooCommerce: set old customers as subscribed'] = $this->settings->get('mailpoet_subscribe_old_woocommerce_customers.enabled') ?: false;
|
||||
$result['WooCommerce email customizer is active'] = $this->settings->get('woocommerce.use_mailpoet_editor') ?: false;
|
||||
|
||||
$result['Number of active WooCommerce first purchase emails'] = $newsletters['first_purchase_emails_count'];
|
||||
$result['Number of active WooCommerce purchased this product emails'] = $newsletters['product_purchased_emails_count'];
|
||||
$result['Number of active purchased in this category'] = $newsletters['product_purchased_in_category_emails_count'];
|
||||
$result['Number of active abandoned cart'] = $newsletters['abandoned_cart_emails_count'];
|
||||
|
||||
$result['Installed via WooCommerce onboarding wizard'] = $this->woocommerceHelper->wasMailPoetInstalledViaWooCommerceOnboardingWizard();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function automationProperties(): array {
|
||||
$automations = $this->automationStorage->getAutomations();
|
||||
$activeAutomations = array_filter(
|
||||
$automations,
|
||||
function(Automation $automation): bool {
|
||||
return $automation->getStatus() === Automation::STATUS_ACTIVE;
|
||||
}
|
||||
);
|
||||
$activeAutomationCount = count($activeAutomations);
|
||||
$draftAutomations = array_filter(
|
||||
$automations,
|
||||
function(Automation $automation): bool {
|
||||
return $automation->getStatus() === Automation::STATUS_DRAFT;
|
||||
}
|
||||
);
|
||||
$automationsWithCustomTrigger = array_filter(
|
||||
$activeAutomations,
|
||||
function(Automation $automation): bool {
|
||||
return $automation->getTrigger('mailpoet:custom-trigger') !== null;
|
||||
}
|
||||
);
|
||||
$automationsWithWordPressUserSubscribesTrigger = array_filter(
|
||||
$activeAutomations,
|
||||
function(Automation $automation): bool {
|
||||
return $automation->getTrigger('mailpoet:wp-user-registered') !== null;
|
||||
}
|
||||
);
|
||||
$automationsWithSomeoneSubscribesTrigger = array_filter(
|
||||
$activeAutomations,
|
||||
function(Automation $automation): bool {
|
||||
return $automation->getTrigger('mailpoet:someone-subscribes') !== null;
|
||||
}
|
||||
);
|
||||
$automationsWithOrderStatusChangedTrigger = array_filter(
|
||||
$activeAutomations,
|
||||
function(Automation $automation): bool {
|
||||
return $automation->getTrigger('woocommerce:order-status-changed') !== null;
|
||||
}
|
||||
);
|
||||
$automationsWithAbandonedCartTrigger = array_filter(
|
||||
$activeAutomations,
|
||||
function(Automation $automation): bool {
|
||||
return $automation->getTrigger('woocommerce:abandoned-cart') !== null;
|
||||
}
|
||||
);
|
||||
|
||||
$totalSteps = 0;
|
||||
$minSteps = null;
|
||||
$maxSteps = 0;
|
||||
foreach ($activeAutomations as $automation) {
|
||||
$steps = array_filter(
|
||||
$automation->getSteps(),
|
||||
function(Step $step): bool {
|
||||
return $step->getType() === Step::TYPE_ACTION;
|
||||
}
|
||||
);
|
||||
$stepCount = count($steps);
|
||||
$minSteps = $minSteps !== null ? min($stepCount, $minSteps) : $stepCount;
|
||||
$maxSteps = max($maxSteps, $stepCount);
|
||||
$totalSteps += $stepCount;
|
||||
}
|
||||
$averageSteps = $activeAutomationCount > 0 ? $totalSteps / $activeAutomationCount : 0;
|
||||
|
||||
$customTriggerHooks = array_unique(array_values(array_map(
|
||||
function(Automation $automation): string {
|
||||
$trigger = $automation->getTrigger('mailpoet:custom-trigger');
|
||||
return $trigger ? (string)$trigger->getArgs()['hook'] : '';
|
||||
},
|
||||
$automationsWithCustomTrigger
|
||||
)));
|
||||
$customActionHooks = array_unique(array_values(array_map(
|
||||
function(Automation $automation): array {
|
||||
$customActionSteps = array_filter(
|
||||
$automation->getSteps(),
|
||||
function(Step $step): bool {
|
||||
return $step->getKey() === 'mailpoet:custom-action';
|
||||
}
|
||||
);
|
||||
if (!$customActionSteps) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function(Step $step): string {
|
||||
return (string)$step->getArgs()['hook'];
|
||||
},
|
||||
$customActionSteps
|
||||
);
|
||||
|
||||
},
|
||||
$activeAutomations
|
||||
)));
|
||||
$customActionHooks = array_values(array_filter(array_merge(...$customActionHooks)));
|
||||
return [
|
||||
'Automation > Number of active automations' => $activeAutomationCount,
|
||||
'Automation > Number of draft automations' => count($draftAutomations),
|
||||
'Automation > Number of "WordPress user registers" active automations' => count($automationsWithWordPressUserSubscribesTrigger),
|
||||
'Automation > Number of "Someone subscribes" active automations ' => count($automationsWithSomeoneSubscribesTrigger),
|
||||
'Automation > Number of "Order status changed" active automations ' => count($automationsWithOrderStatusChangedTrigger),
|
||||
'Automation > Number of "Subscriber abandons cart" active automations' => count($automationsWithAbandonedCartTrigger),
|
||||
'Automation > Number of steps in shortest active automation' => $minSteps,
|
||||
'Automation > Number of steps in longest active automation' => $maxSteps,
|
||||
'Automation > Average number of steps in active automations' => $averageSteps,
|
||||
'Automation > Custom Trigger Hooks' => $customTriggerHooks,
|
||||
'Automation > Custom Action Hooks' => $customActionHooks,
|
||||
];
|
||||
}
|
||||
|
||||
private function subscriberProperties(): array {
|
||||
$definition = new ListingDefinition();
|
||||
$groups = $this->subscriberListingRepository->getGroups($definition);
|
||||
$properties = [];
|
||||
foreach ($groups as $group) {
|
||||
$properties['Subscribers > ' . $group['name']] = (int)$group['count'];
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
public function getTrackingData() {
|
||||
$newsletters = $this->newslettersRepository->getAnalytics();
|
||||
$segments = $this->segmentsRepository->getCountsPerType();
|
||||
$mta = $this->settings->get('mta', []);
|
||||
$installedAt = new Carbon($this->settings->get('installed_at'));
|
||||
return [
|
||||
'installedAtIso' => $installedAt->format(Carbon::ISO8601),
|
||||
'newslettersSent' => $newsletters['sent_newsletters_count'],
|
||||
'welcomeEmails' => $newsletters['welcome_newsletters_count'],
|
||||
'postnotificationEmails' => $newsletters['notifications_count'],
|
||||
'woocommerceEmails' => $newsletters['automatic_emails_count'],
|
||||
'subscribers' => $this->subscribersFeature->getSubscribersCount(),
|
||||
'lists' => isset($segments['default']) ? (int)$segments['default'] : 0,
|
||||
'sendingMethod' => isset($mta['method']) ? $mta['method'] : null,
|
||||
'woocommerceIsInstalled' => $this->woocommerceHelper->isWooCommerceActive(),
|
||||
];
|
||||
}
|
||||
|
||||
private function isFilterTypeActive(string $filterType, string $action): bool {
|
||||
if ($this->dynamicSegmentFilterRepository->findOnyByFilterTypeAndAction($filterType, $action)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,469 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Analytics;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Newsletter\Sending\SendingQueuesRepository;
|
||||
use MailPoet\UnexpectedValueException;
|
||||
|
||||
class ReporterCampaignData {
|
||||
|
||||
|
||||
const STANDARD_7_DAYS = 'Number of standard newsletters sent in last 7 days';
|
||||
const STANDARD_30_DAYS = 'Number of standard newsletters sent in last 30 days';
|
||||
const STANDARD_3_MONTHS = 'Number of standard newsletters sent in last 3 months';
|
||||
const STANDARD_SEGMENT_7_DAYS = 'Number of standard newsletters sent to segment in last 7 days';
|
||||
const STANDARD_SEGMENT_30_DAYS = 'Number of standard newsletters sent to segment in last 30 days';
|
||||
const STANDARD_SEGMENT_3_MONTHS = 'Number of standard newsletters sent to segment in last 3 months';
|
||||
const STANDARD_FILTERED_SEGMENT_7_DAYS = 'Number of standard newsletters filtered by segment in last 7 days';
|
||||
const STANDARD_FILTERED_SEGMENT_30_DAYS = 'Number of standard newsletters filtered by segment in last 30 days';
|
||||
const STANDARD_FILTERED_SEGMENT_3_MONTHS = 'Number of standard newsletters filtered by segment in last 3 months';
|
||||
const AUTOMATION_7_DAYS = 'Number of automations campaigns sent in the last 7 days';
|
||||
const AUTOMATION_30_DAYS = 'Number of automations campaigns sent in the last 30 days';
|
||||
const AUTOMATION_3_MONTHS = 'Number of automations campaigns sent in the last 3 months';
|
||||
const RE_ENGAGEMENT_7_DAYS = 'Number of re-engagement campaigns sent in the last 7 days';
|
||||
const RE_ENGAGEMENT_30_DAYS = 'Number of re-engagement campaigns sent in the last 30 days';
|
||||
const RE_ENGAGEMENT_3_MONTHS = 'Number of re-engagement campaigns sent in the last 3 months';
|
||||
const RE_ENGAGEMENT_SEGMENT_7_DAYS = 'Number of re-engagement campaigns sent to segment in the last 7 days';
|
||||
const RE_ENGAGEMENT_SEGMENT_30_DAYS = 'Number of re-engagement campaigns sent to segment in the last 30 days';
|
||||
const RE_ENGAGEMENT_SEGMENT_3_MONTHS = 'Number of re-engagement campaigns sent to segment in the last 3 months';
|
||||
const RE_ENGAGEMENT_FILTERED_SEGMENT_7_DAYS = 'Number of re-engagement campaigns filtered by segment in the last 7 days';
|
||||
const RE_ENGAGEMENT_FILTERED_SEGMENT_30_DAYS = 'Number of re-engagement campaigns filtered by segment in the last 30 days';
|
||||
const RE_ENGAGEMENT_FILTERED_SEGMENT_3_MONTHS = 'Number of re-engagement campaigns filtered by segment in the last 3 months';
|
||||
const POST_NOTIFICATION_7_DAYS = 'Number of post notification campaigns sent in the last 7 days';
|
||||
const POST_NOTIFICATION_30_DAYS = 'Number of post notification campaigns sent in the last 30 days';
|
||||
const POST_NOTIFICATION_3_MONTHS = 'Number of post notification campaigns sent in the last 3 months';
|
||||
const POST_NOTIFICATION_SEGMENT_7_DAYS = 'Number of post notification campaigns sent to segment in the last 7 days';
|
||||
const POST_NOTIFICATION_SEGMENT_30_DAYS = 'Number of post notification campaigns sent to segment in the last 30 days';
|
||||
const POST_NOTIFICATION_SEGMENT_3_MONTHS = 'Number of post notification campaigns sent to segment in the last 3 months';
|
||||
const POST_NOTIFICATION_FILTERED_SEGMENT_7_DAYS = 'Number of post notification campaigns filtered by segment in the last 7 days';
|
||||
const POST_NOTIFICATION_FILTERED_SEGMENT_30_DAYS = 'Number of post notification campaigns filtered by segment in the last 30 days';
|
||||
const POST_NOTIFICATION_FILTERED_SEGMENT_3_MONTHS = 'Number of post notification campaigns filtered by segment in the last 3 months';
|
||||
|
||||
const LEGACY_WELCOME_7_DAYS = 'Number of legacy welcome email campaigns sent in the last 7 days';
|
||||
const LEGACY_WELCOME_30_DAYS = 'Number of legacy welcome email campaigns sent in the last 30 days';
|
||||
const LEGACY_WELCOME_3_MONTHS = 'Number of legacy welcome email campaigns sent in the last 3 months';
|
||||
|
||||
const LEGACY_ABANDONED_CART_7_DAYS = 'Number of legacy abandoned cart campaigns sent in the last 7 days';
|
||||
const LEGACY_ABANDONED_CART_30_DAYS = 'Number of legacy abandoned cart campaigns sent in the last 30 days';
|
||||
const LEGACY_ABANDONED_CART_3_MONTHS = 'Number of legacy abandoned cart campaigns sent in the last 3 months';
|
||||
|
||||
const LEGACY_FIRST_PURCHASE_7_DAYS = 'Number of legacy first purchase campaigns sent in the last 7 days';
|
||||
const LEGACY_FIRST_PURCHASE_30_DAYS = 'Number of legacy first purchase campaigns sent in the last 30 days';
|
||||
const LEGACY_FIRST_PURCHASE_3_MONTHS = 'Number of legacy first purchase campaigns sent in the last 3 months';
|
||||
|
||||
const LEGACY_PURCHASED_IN_CATEGORY_7_DAYS = 'Number of legacy purchased in category campaigns sent in the last 7 days';
|
||||
const LEGACY_PURCHASED_IN_CATEGORY_30_DAYS = 'Number of legacy purchased in category campaigns sent in the last 30 days';
|
||||
const LEGACY_PURCHASED_IN_CATEGORY_3_MONTHS = 'Number of legacy purchased in category campaigns sent in the last 3 months';
|
||||
|
||||
const LEGACY_PURCHASED_PRODUCT_7_DAYS = 'Number of legacy purchased product campaigns sent in the last 7 days';
|
||||
const LEGACY_PURCHASED_PRODUCT_30_DAYS = 'Number of legacy purchased product campaigns sent in the last 30 days';
|
||||
const LEGACY_PURCHASED_PRODUCT_3_MONTHS = 'Number of legacy purchased product campaigns sent in the last 3 months';
|
||||
|
||||
const TOTAL_CAMPAIGNS_7_DAYS = 'Number of campaigns sent in the last 7 days';
|
||||
const TOTAL_CAMPAIGNS_30_DAYS = 'Number of campaigns sent in the last 30 days';
|
||||
const TOTAL_CAMPAIGNS_3_MONTHS = 'Number of campaigns sent in the last 3 months';
|
||||
|
||||
|
||||
const TOTAL_CAMPAIGNS_SEGMENT_7_DAYS = 'Number of campaigns sent to segment in the last 7 days';
|
||||
const TOTAL_CAMPAIGNS_SEGMENT_30_DAYS = 'Number of campaigns sent to segment in the last 30 days';
|
||||
const TOTAL_CAMPAIGNS_SEGMENT_3_MONTHS = 'Number of campaigns sent to segment in the last 3 months';
|
||||
|
||||
const TOTAL_CAMPAIGNS_FILTERED_SEGMENT_7_DAYS = 'Number of campaigns filtered by segment in the last 7 days';
|
||||
const TOTAL_CAMPAIGNS_FILTERED_SEGMENT_30_DAYS = 'Number of campaigns filtered by segment in the last 30 days';
|
||||
const TOTAL_CAMPAIGNS_FILTERED_SEGMENT_3_MONTHS = 'Number of campaigns filtered by segment in the last 3 months';
|
||||
|
||||
/** @var SendingQueuesRepository */
|
||||
private $sendingQueuesRepository;
|
||||
|
||||
public function __construct(
|
||||
SendingQueuesRepository $sendingQueuesRepository
|
||||
) {
|
||||
$this->sendingQueuesRepository = $sendingQueuesRepository;
|
||||
}
|
||||
|
||||
public function getCampaignAnalyticsProperties(): array {
|
||||
$returnData = [
|
||||
self::STANDARD_7_DAYS => 0,
|
||||
self::STANDARD_30_DAYS => 0,
|
||||
self::STANDARD_3_MONTHS => 0,
|
||||
|
||||
self::STANDARD_SEGMENT_7_DAYS => 0,
|
||||
self::STANDARD_SEGMENT_30_DAYS => 0,
|
||||
self::STANDARD_SEGMENT_3_MONTHS => 0,
|
||||
|
||||
self::STANDARD_FILTERED_SEGMENT_7_DAYS => 0,
|
||||
self::STANDARD_FILTERED_SEGMENT_30_DAYS => 0,
|
||||
self::STANDARD_FILTERED_SEGMENT_3_MONTHS => 0,
|
||||
|
||||
self::AUTOMATION_7_DAYS => 0,
|
||||
self::AUTOMATION_30_DAYS => 0,
|
||||
self::AUTOMATION_3_MONTHS => 0,
|
||||
|
||||
self::RE_ENGAGEMENT_7_DAYS => 0,
|
||||
self::RE_ENGAGEMENT_30_DAYS => 0,
|
||||
self::RE_ENGAGEMENT_3_MONTHS => 0,
|
||||
|
||||
self::RE_ENGAGEMENT_SEGMENT_7_DAYS => 0,
|
||||
self::RE_ENGAGEMENT_SEGMENT_30_DAYS => 0,
|
||||
self::RE_ENGAGEMENT_SEGMENT_3_MONTHS => 0,
|
||||
|
||||
self::RE_ENGAGEMENT_FILTERED_SEGMENT_7_DAYS => 0,
|
||||
self::RE_ENGAGEMENT_FILTERED_SEGMENT_30_DAYS => 0,
|
||||
self::RE_ENGAGEMENT_FILTERED_SEGMENT_3_MONTHS => 0,
|
||||
|
||||
self::POST_NOTIFICATION_7_DAYS => 0,
|
||||
self::POST_NOTIFICATION_30_DAYS => 0,
|
||||
self::POST_NOTIFICATION_3_MONTHS => 0,
|
||||
|
||||
self::POST_NOTIFICATION_SEGMENT_7_DAYS => 0,
|
||||
self::POST_NOTIFICATION_SEGMENT_30_DAYS => 0,
|
||||
self::POST_NOTIFICATION_SEGMENT_3_MONTHS => 0,
|
||||
|
||||
self::POST_NOTIFICATION_FILTERED_SEGMENT_7_DAYS => 0,
|
||||
self::POST_NOTIFICATION_FILTERED_SEGMENT_30_DAYS => 0,
|
||||
self::POST_NOTIFICATION_FILTERED_SEGMENT_3_MONTHS => 0,
|
||||
|
||||
// Legacy
|
||||
self::LEGACY_WELCOME_7_DAYS => 0,
|
||||
self::LEGACY_WELCOME_30_DAYS => 0,
|
||||
self::LEGACY_WELCOME_3_MONTHS => 0,
|
||||
|
||||
self::LEGACY_ABANDONED_CART_7_DAYS => 0,
|
||||
self::LEGACY_ABANDONED_CART_30_DAYS => 0,
|
||||
self::LEGACY_ABANDONED_CART_3_MONTHS => 0,
|
||||
|
||||
self::LEGACY_FIRST_PURCHASE_7_DAYS => 0,
|
||||
self::LEGACY_FIRST_PURCHASE_30_DAYS => 0,
|
||||
self::LEGACY_FIRST_PURCHASE_3_MONTHS => 0,
|
||||
|
||||
self::LEGACY_PURCHASED_IN_CATEGORY_7_DAYS => 0,
|
||||
self::LEGACY_PURCHASED_IN_CATEGORY_30_DAYS => 0,
|
||||
self::LEGACY_PURCHASED_IN_CATEGORY_3_MONTHS => 0,
|
||||
|
||||
self::LEGACY_PURCHASED_PRODUCT_7_DAYS => 0,
|
||||
self::LEGACY_PURCHASED_PRODUCT_30_DAYS => 0,
|
||||
self::LEGACY_PURCHASED_PRODUCT_3_MONTHS => 0,
|
||||
|
||||
// Totals
|
||||
self::TOTAL_CAMPAIGNS_7_DAYS => 0,
|
||||
self::TOTAL_CAMPAIGNS_30_DAYS => 0,
|
||||
self::TOTAL_CAMPAIGNS_3_MONTHS => 0,
|
||||
self::TOTAL_CAMPAIGNS_SEGMENT_7_DAYS => 0,
|
||||
self::TOTAL_CAMPAIGNS_SEGMENT_30_DAYS => 0,
|
||||
self::TOTAL_CAMPAIGNS_SEGMENT_3_MONTHS => 0,
|
||||
self::TOTAL_CAMPAIGNS_FILTERED_SEGMENT_7_DAYS => 0,
|
||||
self::TOTAL_CAMPAIGNS_FILTERED_SEGMENT_30_DAYS => 0,
|
||||
self::TOTAL_CAMPAIGNS_FILTERED_SEGMENT_3_MONTHS => 0,
|
||||
];
|
||||
|
||||
$processedResults = $this->getProcessedCampaignAnalytics();
|
||||
|
||||
foreach ($processedResults as $campaignId => $processedResult) {
|
||||
$isNewerThan7DaysAgo = $processedResult['sentLast7Days'] ?? false;
|
||||
$isNewerThan30DaysAgo = $processedResult['sentLast30Days'] ?? false;
|
||||
$isNewerThan3MonthsAgo = $processedResult['sentLast3Months'] ?? false;
|
||||
|
||||
$newsletterType = $processedResult['newsletterType'];
|
||||
|
||||
$wasSentToDynamicSegment = $processedResult['sentToSegment'] ?? false;
|
||||
$wasFilteredBySegment = $processedResult['filteredBySegment'] ?? false;
|
||||
|
||||
// Totals
|
||||
if ($isNewerThan7DaysAgo) {
|
||||
$returnData[self::TOTAL_CAMPAIGNS_7_DAYS]++;
|
||||
$returnData[self::TOTAL_CAMPAIGNS_30_DAYS]++;
|
||||
$returnData[self::TOTAL_CAMPAIGNS_3_MONTHS]++;
|
||||
if ($wasSentToDynamicSegment) {
|
||||
$returnData[self::TOTAL_CAMPAIGNS_SEGMENT_7_DAYS]++;
|
||||
$returnData[self::TOTAL_CAMPAIGNS_SEGMENT_30_DAYS]++;
|
||||
$returnData[self::TOTAL_CAMPAIGNS_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
if ($wasFilteredBySegment) {
|
||||
$returnData[self::TOTAL_CAMPAIGNS_FILTERED_SEGMENT_7_DAYS]++;
|
||||
$returnData[self::TOTAL_CAMPAIGNS_FILTERED_SEGMENT_30_DAYS]++;
|
||||
$returnData[self::TOTAL_CAMPAIGNS_FILTERED_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
} elseif ($isNewerThan30DaysAgo) {
|
||||
$returnData[self::TOTAL_CAMPAIGNS_30_DAYS]++;
|
||||
$returnData[self::TOTAL_CAMPAIGNS_3_MONTHS]++;
|
||||
if ($wasSentToDynamicSegment) {
|
||||
$returnData[self::TOTAL_CAMPAIGNS_SEGMENT_30_DAYS]++;
|
||||
$returnData[self::TOTAL_CAMPAIGNS_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
if ($wasFilteredBySegment) {
|
||||
$returnData[self::TOTAL_CAMPAIGNS_FILTERED_SEGMENT_30_DAYS]++;
|
||||
$returnData[self::TOTAL_CAMPAIGNS_FILTERED_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
} elseif ($isNewerThan3MonthsAgo) {
|
||||
$returnData[self::TOTAL_CAMPAIGNS_3_MONTHS]++;
|
||||
if ($wasSentToDynamicSegment) {
|
||||
$returnData[self::TOTAL_CAMPAIGNS_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
if ($wasFilteredBySegment) {
|
||||
$returnData[self::TOTAL_CAMPAIGNS_FILTERED_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($newsletterType) {
|
||||
case NewsletterEntity::TYPE_STANDARD:
|
||||
if ($isNewerThan7DaysAgo) {
|
||||
$returnData[self::STANDARD_7_DAYS]++;
|
||||
$returnData[self::STANDARD_30_DAYS]++;
|
||||
$returnData[self::STANDARD_3_MONTHS]++;
|
||||
if ($wasFilteredBySegment) {
|
||||
$returnData[self::STANDARD_FILTERED_SEGMENT_7_DAYS]++;
|
||||
$returnData[self::STANDARD_FILTERED_SEGMENT_30_DAYS]++;
|
||||
$returnData[self::STANDARD_FILTERED_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
if ($wasSentToDynamicSegment) {
|
||||
$returnData[self::STANDARD_SEGMENT_7_DAYS]++;
|
||||
$returnData[self::STANDARD_SEGMENT_30_DAYS]++;
|
||||
$returnData[self::STANDARD_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
} elseif ($isNewerThan30DaysAgo) {
|
||||
$returnData[self::STANDARD_30_DAYS]++;
|
||||
$returnData[self::STANDARD_3_MONTHS]++;
|
||||
if ($wasFilteredBySegment) {
|
||||
$returnData[self::STANDARD_FILTERED_SEGMENT_30_DAYS]++;
|
||||
$returnData[self::STANDARD_FILTERED_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
if ($wasSentToDynamicSegment) {
|
||||
$returnData[self::STANDARD_SEGMENT_30_DAYS]++;
|
||||
$returnData[self::STANDARD_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
} elseif ($isNewerThan3MonthsAgo) {
|
||||
$returnData[self::STANDARD_3_MONTHS]++;
|
||||
if ($wasFilteredBySegment) {
|
||||
$returnData[self::STANDARD_FILTERED_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
if ($wasSentToDynamicSegment) {
|
||||
$returnData[self::STANDARD_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NewsletterEntity::TYPE_NOTIFICATION_HISTORY:
|
||||
if ($isNewerThan7DaysAgo) {
|
||||
$returnData[self::POST_NOTIFICATION_7_DAYS]++;
|
||||
$returnData[self::POST_NOTIFICATION_30_DAYS]++;
|
||||
$returnData[self::POST_NOTIFICATION_3_MONTHS]++;
|
||||
if ($wasSentToDynamicSegment) {
|
||||
$returnData[self::POST_NOTIFICATION_SEGMENT_7_DAYS]++;
|
||||
$returnData[self::POST_NOTIFICATION_SEGMENT_30_DAYS]++;
|
||||
$returnData[self::POST_NOTIFICATION_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
if ($wasFilteredBySegment) {
|
||||
$returnData[self::POST_NOTIFICATION_FILTERED_SEGMENT_7_DAYS]++;
|
||||
$returnData[self::POST_NOTIFICATION_FILTERED_SEGMENT_30_DAYS]++;
|
||||
$returnData[self::POST_NOTIFICATION_FILTERED_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
} elseif ($isNewerThan30DaysAgo) {
|
||||
$returnData[self::POST_NOTIFICATION_30_DAYS]++;
|
||||
$returnData[self::POST_NOTIFICATION_3_MONTHS]++;
|
||||
if ($wasSentToDynamicSegment) {
|
||||
$returnData[self::POST_NOTIFICATION_SEGMENT_30_DAYS]++;
|
||||
$returnData[self::POST_NOTIFICATION_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
if ($wasFilteredBySegment) {
|
||||
$returnData[self::POST_NOTIFICATION_FILTERED_SEGMENT_30_DAYS]++;
|
||||
$returnData[self::POST_NOTIFICATION_FILTERED_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
} elseif ($isNewerThan3MonthsAgo) {
|
||||
$returnData[self::POST_NOTIFICATION_3_MONTHS]++;
|
||||
if ($wasSentToDynamicSegment) {
|
||||
$returnData[self::POST_NOTIFICATION_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
if ($wasFilteredBySegment) {
|
||||
$returnData[self::POST_NOTIFICATION_FILTERED_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NewsletterEntity::TYPE_RE_ENGAGEMENT:
|
||||
if ($isNewerThan7DaysAgo) {
|
||||
$returnData[self::RE_ENGAGEMENT_7_DAYS]++;
|
||||
$returnData[self::RE_ENGAGEMENT_30_DAYS]++;
|
||||
$returnData[self::RE_ENGAGEMENT_3_MONTHS]++;
|
||||
if ($wasSentToDynamicSegment) {
|
||||
$returnData[self::RE_ENGAGEMENT_SEGMENT_7_DAYS]++;
|
||||
$returnData[self::RE_ENGAGEMENT_SEGMENT_30_DAYS]++;
|
||||
$returnData[self::RE_ENGAGEMENT_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
if ($wasFilteredBySegment) {
|
||||
$returnData[self::RE_ENGAGEMENT_FILTERED_SEGMENT_7_DAYS]++;
|
||||
$returnData[self::RE_ENGAGEMENT_FILTERED_SEGMENT_30_DAYS]++;
|
||||
$returnData[self::RE_ENGAGEMENT_FILTERED_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
} elseif ($isNewerThan30DaysAgo) {
|
||||
$returnData[self::RE_ENGAGEMENT_30_DAYS]++;
|
||||
$returnData[self::RE_ENGAGEMENT_3_MONTHS]++;
|
||||
if ($wasSentToDynamicSegment) {
|
||||
$returnData[self::RE_ENGAGEMENT_SEGMENT_30_DAYS]++;
|
||||
$returnData[self::RE_ENGAGEMENT_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
if ($wasFilteredBySegment) {
|
||||
$returnData[self::RE_ENGAGEMENT_FILTERED_SEGMENT_30_DAYS]++;
|
||||
$returnData[self::RE_ENGAGEMENT_FILTERED_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
} elseif ($isNewerThan3MonthsAgo) {
|
||||
$returnData[self::RE_ENGAGEMENT_3_MONTHS]++;
|
||||
if ($wasSentToDynamicSegment) {
|
||||
$returnData[self::RE_ENGAGEMENT_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
if ($wasFilteredBySegment) {
|
||||
$returnData[self::RE_ENGAGEMENT_FILTERED_SEGMENT_3_MONTHS]++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NewsletterEntity::TYPE_WELCOME:
|
||||
if ($isNewerThan7DaysAgo) {
|
||||
$returnData[self::LEGACY_WELCOME_7_DAYS]++;
|
||||
$returnData[self::LEGACY_WELCOME_30_DAYS]++;
|
||||
$returnData[self::LEGACY_WELCOME_3_MONTHS]++;
|
||||
} elseif ($isNewerThan30DaysAgo) {
|
||||
$returnData[self::LEGACY_WELCOME_30_DAYS]++;
|
||||
$returnData[self::LEGACY_WELCOME_3_MONTHS]++;
|
||||
} elseif ($isNewerThan3MonthsAgo) {
|
||||
$returnData[self::LEGACY_WELCOME_3_MONTHS]++;
|
||||
}
|
||||
break;
|
||||
case NewsletterEntity::TYPE_AUTOMATION:
|
||||
if ($isNewerThan7DaysAgo) {
|
||||
$returnData[self::AUTOMATION_7_DAYS]++;
|
||||
$returnData[self::AUTOMATION_30_DAYS]++;
|
||||
$returnData[self::AUTOMATION_3_MONTHS]++;
|
||||
} elseif ($isNewerThan30DaysAgo) {
|
||||
$returnData[self::AUTOMATION_30_DAYS]++;
|
||||
$returnData[self::AUTOMATION_3_MONTHS]++;
|
||||
} elseif ($isNewerThan3MonthsAgo) {
|
||||
$returnData[self::AUTOMATION_3_MONTHS]++;
|
||||
}
|
||||
break;
|
||||
// Legacy automatic emails.
|
||||
case 'purchasedProduct':
|
||||
if ($isNewerThan7DaysAgo) {
|
||||
$returnData[self::LEGACY_PURCHASED_PRODUCT_7_DAYS]++;
|
||||
$returnData[self::LEGACY_PURCHASED_PRODUCT_30_DAYS]++;
|
||||
$returnData[self::LEGACY_PURCHASED_PRODUCT_3_MONTHS]++;
|
||||
} elseif ($isNewerThan30DaysAgo) {
|
||||
$returnData[self::LEGACY_PURCHASED_PRODUCT_30_DAYS]++;
|
||||
$returnData[self::LEGACY_PURCHASED_PRODUCT_3_MONTHS]++;
|
||||
} elseif ($isNewerThan3MonthsAgo) {
|
||||
$returnData[self::LEGACY_PURCHASED_PRODUCT_3_MONTHS]++;
|
||||
}
|
||||
break;
|
||||
case 'purchasedInCategory':
|
||||
if ($isNewerThan7DaysAgo) {
|
||||
$returnData[self::LEGACY_PURCHASED_IN_CATEGORY_7_DAYS]++;
|
||||
$returnData[self::LEGACY_PURCHASED_IN_CATEGORY_30_DAYS]++;
|
||||
$returnData[self::LEGACY_PURCHASED_IN_CATEGORY_3_MONTHS]++;
|
||||
} elseif ($isNewerThan30DaysAgo) {
|
||||
$returnData[self::LEGACY_PURCHASED_IN_CATEGORY_30_DAYS]++;
|
||||
$returnData[self::LEGACY_PURCHASED_IN_CATEGORY_3_MONTHS]++;
|
||||
} elseif ($isNewerThan3MonthsAgo) {
|
||||
$returnData[self::LEGACY_PURCHASED_IN_CATEGORY_3_MONTHS]++;
|
||||
}
|
||||
break;
|
||||
case 'abandonedCart':
|
||||
if ($isNewerThan7DaysAgo) {
|
||||
$returnData[self::LEGACY_ABANDONED_CART_7_DAYS]++;
|
||||
$returnData[self::LEGACY_ABANDONED_CART_30_DAYS]++;
|
||||
$returnData[self::LEGACY_ABANDONED_CART_3_MONTHS]++;
|
||||
} elseif ($isNewerThan30DaysAgo) {
|
||||
$returnData[self::LEGACY_ABANDONED_CART_30_DAYS]++;
|
||||
$returnData[self::LEGACY_ABANDONED_CART_3_MONTHS]++;
|
||||
} elseif ($isNewerThan3MonthsAgo) {
|
||||
$returnData[self::LEGACY_ABANDONED_CART_3_MONTHS]++;
|
||||
}
|
||||
break;
|
||||
case 'firstPurchase':
|
||||
if ($isNewerThan7DaysAgo) {
|
||||
$returnData[self::LEGACY_FIRST_PURCHASE_7_DAYS]++;
|
||||
$returnData[self::LEGACY_FIRST_PURCHASE_30_DAYS]++;
|
||||
$returnData[self::LEGACY_FIRST_PURCHASE_3_MONTHS]++;
|
||||
} elseif ($isNewerThan30DaysAgo) {
|
||||
$returnData[self::LEGACY_FIRST_PURCHASE_30_DAYS]++;
|
||||
$returnData[self::LEGACY_FIRST_PURCHASE_3_MONTHS]++;
|
||||
} elseif ($isNewerThan3MonthsAgo) {
|
||||
$returnData[self::LEGACY_FIRST_PURCHASE_3_MONTHS]++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $returnData;
|
||||
}
|
||||
|
||||
public function getProcessedCampaignAnalytics(): array {
|
||||
$rawData = $this->sendingQueuesRepository->getCampaignAnalyticsQuery()->getArrayResult();
|
||||
$processedResults = [];
|
||||
|
||||
foreach ($rawData as $sendingInfo) {
|
||||
$meta = $sendingInfo['sendingQueueMeta'];
|
||||
$campaignId = $meta['campaignId'] ?? null;
|
||||
|
||||
if (!is_string($campaignId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($processedResults[$campaignId])) {
|
||||
$newsletterType = $sendingInfo['newsletterType'];
|
||||
$processedData = [
|
||||
'campaignId' => $campaignId,
|
||||
'newsletterType' => $newsletterType,
|
||||
'automaticSubType' => null,
|
||||
'sentToSegment' => (bool)$sendingInfo['sentToSegment'],
|
||||
'sentLast7Days' => (bool)$sendingInfo['sentLast7Days'],
|
||||
'sentLast30Days' => (bool)$sendingInfo['sentLast30Days'],
|
||||
'sentLast3Months' => (bool)$sendingInfo['sentLast3Months'],
|
||||
'filteredBySegment' => !!($meta['filterSegment'] ?? null),
|
||||
];
|
||||
$processedResults[$campaignId] = $processedData;
|
||||
if ($newsletterType === NewsletterEntity::TYPE_AUTOMATIC) {
|
||||
try {
|
||||
// Although we could determine the subtype by joining the appropriate newsletter option field, using
|
||||
// the meta should be just as reliable, and we need the meta anyway, so this keeps our query simpler.
|
||||
$subType = $this->getLegacyAutomaticEmailSubtypeFromMeta($meta);
|
||||
$processedResults[$campaignId]['newsletterType'] = $subType;
|
||||
} catch (UnexpectedValueException $e) {
|
||||
// Ignore this error, the `automatic` email type won't be counted
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($sendingInfo['sentLast7Days']) {
|
||||
$processedResults[$campaignId]['sentLast7Days'] = true;
|
||||
}
|
||||
if ($sendingInfo['sentLast30Days']) {
|
||||
$processedResults[$campaignId]['sentLast30Days'] = true;
|
||||
}
|
||||
if ($sendingInfo['sentLast3Months']) {
|
||||
$processedResults[$campaignId]['sentLast3Months'] = true;
|
||||
}
|
||||
if ($sendingInfo['sentToSegment']) {
|
||||
$processedResults[$campaignId]['sentToSegment'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $processedResults;
|
||||
}
|
||||
|
||||
private function getLegacyAutomaticEmailSubtypeFromMeta(array $meta): string {
|
||||
if (array_key_exists('orderedProducts', $meta)) {
|
||||
return 'purchasedProduct';
|
||||
}
|
||||
if (array_key_exists('orderedProductCategories', $meta)) {
|
||||
return 'purchasedInCategory';
|
||||
}
|
||||
if (array_key_exists('cart_product_ids', $meta)) {
|
||||
return 'abandonedCart';
|
||||
}
|
||||
if (array_key_exists('order_amount', $meta) && array_key_exists('order_date', $meta) && array_key_exists('order_id', $meta)) {
|
||||
return 'firstPurchase';
|
||||
}
|
||||
|
||||
throw new UnexpectedValueException('Unknown automatic email type based on meta data');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Analytics;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\StatisticsUnsubscribeEntity;
|
||||
use MailPoet\Statistics\StatisticsUnsubscribesRepository;
|
||||
|
||||
class UnsubscribeReporter {
|
||||
|
||||
public const TOTAL = 'Unsubscribe > Total in last 6 months';
|
||||
public const COUNT_PER_METHOD_PATTERN = 'Unsubscribe > Count in last 6 months with method: %s';
|
||||
|
||||
/*** @var StatisticsUnsubscribesRepository */
|
||||
private $statisticsUnsubscribesRepository;
|
||||
|
||||
public function __construct(
|
||||
StatisticsUnsubscribesRepository $statisticsUnsubscribesRepository
|
||||
) {
|
||||
$this->statisticsUnsubscribesRepository = $statisticsUnsubscribesRepository;
|
||||
}
|
||||
|
||||
public function getProperties(): array {
|
||||
$properties = [
|
||||
self::TOTAL => $this->statisticsUnsubscribesRepository->getTotalForMonths(6),
|
||||
];
|
||||
|
||||
foreach ($this->statisticsUnsubscribesRepository->getCountPerMethodForMonths(6) as $methodStats) {
|
||||
$properties[sprintf(self::COUNT_PER_METHOD_PATTERN, $this->getMethodName($methodStats['method']))] = $methodStats['count'];
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
private function getMethodName(?string $methodKey): string {
|
||||
if ($methodKey === StatisticsUnsubscribeEntity::METHOD_ONE_CLICK) {
|
||||
return '1 Click';
|
||||
}
|
||||
|
||||
if ($methodKey === StatisticsUnsubscribeEntity::METHOD_LINK) {
|
||||
return 'Link';
|
||||
}
|
||||
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\AutomaticEmails;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AutomaticEmails\WooCommerce\WooCommerce;
|
||||
use MailPoet\DI\ContainerWrapper;
|
||||
|
||||
class AutomaticEmailFactory {
|
||||
/** @var ContainerWrapper */
|
||||
private $container;
|
||||
|
||||
public function __construct(
|
||||
ContainerWrapper $container
|
||||
) {
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public function createWooCommerceEmail(): WooCommerce {
|
||||
return $this->container->get(WooCommerce::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AutomaticEmails;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class AutomaticEmails {
|
||||
const FILTER_PREFIX = 'mailpoet_automatic_email_';
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var array|null */
|
||||
private $automaticEmails;
|
||||
|
||||
/** @var AutomaticEmailFactory */
|
||||
private $automaticEmailFactory;
|
||||
|
||||
public function __construct(
|
||||
WPFunctions $wp,
|
||||
AutomaticEmailFactory $automaticEmailFactory
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->automaticEmailFactory = $automaticEmailFactory;
|
||||
}
|
||||
|
||||
public function init() {
|
||||
$instance = $this->automaticEmailFactory->createWooCommerceEmail();
|
||||
$instance->init();
|
||||
}
|
||||
|
||||
public function getAutomaticEmails() {
|
||||
global $wp_filter; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
|
||||
if ($this->automaticEmails) {
|
||||
return $this->automaticEmails;
|
||||
}
|
||||
|
||||
// phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$registeredGroups = preg_grep('!^' . self::FILTER_PREFIX . '(.*?)$!', array_keys($wp_filter));
|
||||
|
||||
if (empty($registeredGroups)) return null;
|
||||
|
||||
$automaticEmails = [];
|
||||
foreach ($registeredGroups as $group) {
|
||||
$automaticEmail = $this->wp->applyFilters($group, []);
|
||||
|
||||
if (
|
||||
!$this->validateAutomaticEmailDataFields($automaticEmail) ||
|
||||
!$this->validateAutomaticEmailEventsDataFields($automaticEmail['events'])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// keys associative events array by slug
|
||||
$automaticEmail['events'] = array_column($automaticEmail['events'], null, 'slug');
|
||||
// keys associative automatic email array by slug
|
||||
$automaticEmails[$automaticEmail['slug']] = $automaticEmail;
|
||||
}
|
||||
|
||||
$this->automaticEmails = $automaticEmails;
|
||||
|
||||
return $automaticEmails;
|
||||
}
|
||||
|
||||
public function getAutomaticEmailBySlug($emailSlug) {
|
||||
$automaticEmails = $this->getAutomaticEmails();
|
||||
|
||||
if (empty($automaticEmails)) return null;
|
||||
|
||||
foreach ($automaticEmails as $email) {
|
||||
if (!empty($email['slug']) && $email['slug'] === $emailSlug) return $email;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getAutomaticEmailEventBySlug($emailSlug, $eventSlug) {
|
||||
$automaticEmail = $this->getAutomaticEmailBySlug($emailSlug);
|
||||
|
||||
if (empty($automaticEmail)) return null;
|
||||
|
||||
foreach ($automaticEmail['events'] as $event) {
|
||||
if (!empty($event['slug']) && $event['slug'] === $eventSlug) return $event;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function validateAutomaticEmailDataFields(array $automaticEmail) {
|
||||
$requiredFields = [
|
||||
'slug',
|
||||
'title',
|
||||
'description',
|
||||
'events',
|
||||
];
|
||||
|
||||
foreach ($requiredFields as $field) {
|
||||
if (empty($automaticEmail[$field])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function validateAutomaticEmailEventsDataFields(array $automaticEmailEvents) {
|
||||
$requiredFields = [
|
||||
'slug',
|
||||
'title',
|
||||
'description',
|
||||
'listingScheduleDisplayText',
|
||||
];
|
||||
|
||||
foreach ($automaticEmailEvents as $event) {
|
||||
$validEvent = array_diff($requiredFields, array_keys($event));
|
||||
if (!empty($validEvent)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function unregisterAutomaticEmails() {
|
||||
global $wp_filter; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
|
||||
// phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$registeredGroups = preg_grep('!^' . self::FILTER_PREFIX . '(.*?)$!', array_keys($wp_filter));
|
||||
|
||||
if (empty($registeredGroups)) return null;
|
||||
|
||||
$self = $this;
|
||||
array_map(function($group) use($self) {
|
||||
$self->wp->removeAllFilters($group);
|
||||
}, $registeredGroups);
|
||||
|
||||
$this->automaticEmails = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\AutomaticEmails\WooCommerce\Events;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AutomaticEmails\WooCommerce\WooCommerce as WooCommerceEmail;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Newsletter\Scheduler\AutomaticEmailScheduler;
|
||||
use MailPoet\Statistics\Track\SubscriberActivityTracker;
|
||||
use MailPoet\Statistics\Track\SubscriberCookie;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\WooCommerce\Helper as WooCommerceHelper;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class AbandonedCart {
|
||||
const SLUG = 'woocommerce_abandoned_shopping_cart';
|
||||
const TASK_META_NAME = 'cart_product_ids';
|
||||
|
||||
|
||||
const HOOK_SCHEDULE = 'mailpoet_abandoned_cart_schedule';
|
||||
const HOOK_RE_SCHEDULE = 'mailpoet_abandoned_cart_reschedule';
|
||||
const HOOK_CANCEL = 'mailpoet_abandoned_cart_cancel';
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var WooCommerceHelper */
|
||||
private $wooCommerceHelper;
|
||||
|
||||
/** @var SubscriberCookie */
|
||||
private $subscriberCookie;
|
||||
|
||||
/** @var AutomaticEmailScheduler */
|
||||
private $scheduler;
|
||||
|
||||
/** @var SubscriberActivityTracker */
|
||||
private $subscriberActivityTracker;
|
||||
|
||||
/** @var SubscribersRepository */
|
||||
private $subscribersRepository;
|
||||
|
||||
/** @var bool */
|
||||
private $loadSavedCartAfterLogin;
|
||||
|
||||
public function __construct(
|
||||
WPFunctions $wp,
|
||||
WooCommerceHelper $wooCommerceHelper,
|
||||
SubscriberCookie $subscriberCookie,
|
||||
SubscriberActivityTracker $subscriberActivityTracker,
|
||||
AutomaticEmailScheduler $scheduler,
|
||||
SubscribersRepository $subscribersRepository
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->wooCommerceHelper = $wooCommerceHelper;
|
||||
$this->subscriberCookie = $subscriberCookie;
|
||||
$this->subscriberActivityTracker = $subscriberActivityTracker;
|
||||
$this->scheduler = $scheduler;
|
||||
$this->subscribersRepository = $subscribersRepository;
|
||||
}
|
||||
|
||||
public function getEventDetails() {
|
||||
return [
|
||||
'slug' => self::SLUG,
|
||||
'title' => _x('Abandoned Shopping Cart', 'This is the name of a type of automatic email for ecommerce. Those emails are sent automatically when a customer adds product to his shopping cart but never complete the checkout process.', 'mailpoet'),
|
||||
'description' => __('Send an email to logged-in visitors who have items in their shopping carts but left your website without checking out. Can convert up to 5% of abandoned carts.', 'mailpoet'),
|
||||
'listingScheduleDisplayText' => _x('Send the email when a customer abandons their cart.', 'Description of Abandoned Shopping Cart email', 'mailpoet'),
|
||||
'afterDelayText' => __('after abandoning the cart', 'mailpoet'),
|
||||
'badge' => [
|
||||
'text' => __('Must-have', 'mailpoet'),
|
||||
'style' => 'red',
|
||||
],
|
||||
'timeDelayValues' => [
|
||||
'minutes' => [
|
||||
'text' => __('minute(s)', 'mailpoet'),
|
||||
'displayAfterTimeNumberField' => true,
|
||||
],
|
||||
'hours' => [
|
||||
'text' => __('hour(s)', 'mailpoet'),
|
||||
'displayAfterTimeNumberField' => true,
|
||||
],
|
||||
'days' => [
|
||||
'text' => __('day(s)', 'mailpoet'),
|
||||
'displayAfterTimeNumberField' => true,
|
||||
],
|
||||
'weeks' => [
|
||||
'text' => __('week(s)', 'mailpoet'),
|
||||
'displayAfterTimeNumberField' => true,
|
||||
],
|
||||
],
|
||||
'defaultAfterTimeType' => 'minutes',
|
||||
'schedulingReadMoreLink' => [
|
||||
'link' => 'https://www.mailpoet.com/blog/abandoned-cart-woocommerce',
|
||||
'text' => __('We recommend setting up 3 abandoned cart emails. Here’s why.', 'mailpoet'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function init() {
|
||||
if (!$this->wooCommerceHelper->isWooCommerceActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// item added to cart (not fired on quantity changes)
|
||||
$this->wp->addAction(
|
||||
'woocommerce_add_to_cart',
|
||||
[$this, 'handleCartChange'],
|
||||
10
|
||||
);
|
||||
|
||||
// item removed from cart (not fired on quantity changes, not even change to zero)
|
||||
$this->wp->addAction(
|
||||
'woocommerce_cart_item_removed',
|
||||
[$this, 'handleCartChange'],
|
||||
10
|
||||
);
|
||||
|
||||
// item quantity updated (not fired when quantity updated to zero)
|
||||
$this->wp->addAction(
|
||||
'woocommerce_after_cart_item_quantity_update',
|
||||
[$this, 'handleCartChange'],
|
||||
10
|
||||
);
|
||||
|
||||
// item quantity set to zero
|
||||
$this->wp->addAction(
|
||||
'woocommerce_remove_cart_item',
|
||||
[$this, 'handleCartChange'],
|
||||
10
|
||||
);
|
||||
|
||||
// cart emptied (not called when all items removed)
|
||||
$this->wp->addAction(
|
||||
'woocommerce_cart_emptied',
|
||||
[$this, 'handleCartChange'],
|
||||
10
|
||||
);
|
||||
|
||||
// undo removal of item from cart or cart emptying (does not fire any other cart change hook)
|
||||
$this->wp->addAction(
|
||||
'woocommerce_cart_item_restored',
|
||||
[$this, 'handleCartChange'],
|
||||
10
|
||||
);
|
||||
|
||||
// we should handle loading cart from session if user logs in
|
||||
$this->wp->addAction(
|
||||
'woocommerce_load_cart_from_session',
|
||||
[$this, 'handleUserLogin'],
|
||||
10
|
||||
);
|
||||
|
||||
// cart loaded from session (only processed after login, not on every page load)
|
||||
$this->wp->addAction(
|
||||
'woocommerce_cart_loaded_from_session',
|
||||
[$this, 'handleCartChangeOnLogin'],
|
||||
10
|
||||
);
|
||||
|
||||
$this->subscriberActivityTracker->registerCallback(
|
||||
'mailpoet_abandoned_cart',
|
||||
[$this, 'handleSubscriberActivity']
|
||||
);
|
||||
}
|
||||
|
||||
public function handleCartChange() {
|
||||
$cart = $this->wooCommerceHelper->WC()->cart;
|
||||
|
||||
$currentAction = current_action();
|
||||
if ($currentAction !== 'woocommerce_cart_emptied' && $cart && !$cart->is_empty()) {
|
||||
$this->scheduleAbandonedCartEmail($this->getCartProductIds($cart));
|
||||
} else {
|
||||
$this->cancelAbandonedCartEmail();
|
||||
}
|
||||
}
|
||||
|
||||
public function handleUserLogin() {
|
||||
$wpUserId = $this->wp->getCurrentUserId();
|
||||
if (!$wpUserId) {
|
||||
return false;
|
||||
}
|
||||
$this->loadSavedCartAfterLogin = (bool)$this->wp->getUserMeta($wpUserId, '_woocommerce_load_saved_cart_after_login', true);
|
||||
}
|
||||
|
||||
public function handleCartChangeOnLogin() {
|
||||
if (!$this->loadSavedCartAfterLogin) {
|
||||
return false;
|
||||
}
|
||||
$this->handleCartChange();
|
||||
}
|
||||
|
||||
public function handleSubscriberActivity(SubscriberEntity $subscriber) {
|
||||
// on subscriber activity on site reschedule all currently scheduled (not yet sent) emails for given subscriber
|
||||
// (it tracks at most once per minute to avoid processing many calls at the same time, i.e. AJAX)
|
||||
$this->rescheduleAbandonedCartEmail($subscriber);
|
||||
}
|
||||
|
||||
private function getCartProductIds($cart) {
|
||||
$cartItems = $cart->get_cart() ?: [];
|
||||
return array_column($cartItems, 'product_id');
|
||||
}
|
||||
|
||||
private function scheduleAbandonedCartEmail(array $cartProductIds = []) {
|
||||
$subscriber = $this->getSubscriber();
|
||||
if (!$subscriber) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->wp->doAction(self::HOOK_SCHEDULE, $subscriber, $cartProductIds);
|
||||
$meta = [self::TASK_META_NAME => $cartProductIds];
|
||||
$this->scheduler->scheduleOrRescheduleAutomaticEmail(WooCommerceEmail::SLUG, self::SLUG, $subscriber, $meta);
|
||||
}
|
||||
|
||||
private function rescheduleAbandonedCartEmail(SubscriberEntity $subscriber) {
|
||||
$this->wp->doAction(self::HOOK_RE_SCHEDULE, $subscriber);
|
||||
$this->scheduler->rescheduleAutomaticEmail(WooCommerceEmail::SLUG, self::SLUG, $subscriber);
|
||||
}
|
||||
|
||||
private function cancelAbandonedCartEmail() {
|
||||
$subscriber = $this->getSubscriber();
|
||||
if (!$subscriber) {
|
||||
return;
|
||||
}
|
||||
$this->wp->doAction(self::HOOK_CANCEL, $subscriber);
|
||||
$this->scheduler->cancelAutomaticEmail(WooCommerceEmail::SLUG, self::SLUG, $subscriber);
|
||||
}
|
||||
|
||||
private function getSubscriber(): ?SubscriberEntity {
|
||||
$wpUser = $this->wp->wpGetCurrentUser();
|
||||
if ($wpUser->exists()) {
|
||||
return $this->subscribersRepository->findOneBy(['wpUserId' => $wpUser->ID]);
|
||||
}
|
||||
|
||||
// if user not logged in, try to find subscriber by cookie
|
||||
$subscriberId = $this->subscriberCookie->getSubscriberId();
|
||||
if ($subscriberId) {
|
||||
return $this->subscribersRepository->findOneById($subscriberId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AutomaticEmails\WooCommerce\Events;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AutomaticEmails\WooCommerce\WooCommerce;
|
||||
use MailPoet\DI\ContainerWrapper;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Logging\LoggerFactory;
|
||||
use MailPoet\Newsletter\AutomaticEmailsRepository;
|
||||
use MailPoet\Newsletter\Scheduler\AutomaticEmailScheduler;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\WooCommerce\Helper as WCHelper;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class FirstPurchase {
|
||||
const SLUG = 'woocommerce_first_purchase';
|
||||
const ORDER_TOTAL_SHORTCODE = '[woocommerce:order_total]';
|
||||
const ORDER_DATE_SHORTCODE = '[woocommerce:order_date]';
|
||||
/**
|
||||
* @var \MailPoet\WooCommerce\Helper
|
||||
*/
|
||||
private $helper;
|
||||
|
||||
/** @var AutomaticEmailScheduler */
|
||||
private $scheduler;
|
||||
|
||||
/** @var LoggerFactory */
|
||||
private $loggerFactory;
|
||||
|
||||
/** @var AutomaticEmailsRepository */
|
||||
private $automaticEmailsRepository;
|
||||
|
||||
/** @var SubscribersRepository */
|
||||
private $subscribersRepository;
|
||||
|
||||
public function __construct(
|
||||
WCHelper $helper = null
|
||||
) {
|
||||
if ($helper === null) {
|
||||
$helper = ContainerWrapper::getInstance()->get(WCHelper::class);
|
||||
}
|
||||
$this->helper = $helper;
|
||||
$this->scheduler = ContainerWrapper::getInstance()->get(AutomaticEmailScheduler::class);
|
||||
$this->loggerFactory = LoggerFactory::getInstance();
|
||||
$this->automaticEmailsRepository = ContainerWrapper::getInstance()->get(AutomaticEmailsRepository::class);
|
||||
$this->subscribersRepository = ContainerWrapper::getInstance()->get(SubscribersRepository::class);
|
||||
}
|
||||
|
||||
public function init() {
|
||||
WPFunctions::get()->addFilter('mailpoet_newsletter_shortcode', [
|
||||
$this,
|
||||
'handleOrderTotalShortcode',
|
||||
], 10, 4);
|
||||
WPFunctions::get()->addFilter('mailpoet_newsletter_shortcode', [
|
||||
$this,
|
||||
'handleOrderDateShortcode',
|
||||
], 10, 4);
|
||||
|
||||
// We have to use a set of states because an order state after checkout differs for different payment methods
|
||||
$acceptedOrderStates = WPFunctions::get()->applyFilters('mailpoet_first_purchase_order_states', ['completed', 'processing']);
|
||||
|
||||
foreach ($acceptedOrderStates as $state) {
|
||||
WPFunctions::get()->addAction('woocommerce_order_status_' . $state, [
|
||||
$this,
|
||||
'scheduleEmailWhenOrderIsPlaced',
|
||||
], 10, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public function getEventDetails() {
|
||||
return [
|
||||
'slug' => self::SLUG,
|
||||
'title' => __('First Purchase', 'mailpoet'),
|
||||
'description' => __('Let MailPoet send an email to customers who make their first purchase.', 'mailpoet'),
|
||||
'listingScheduleDisplayText' => __('Email sent when a customer makes their first purchase.', 'mailpoet'),
|
||||
'afterDelayText' => __('after the first purchase', 'mailpoet'),
|
||||
'badge' => [
|
||||
'text' => __('Must-have', 'mailpoet'),
|
||||
'style' => 'red',
|
||||
],
|
||||
'timeDelayValues' => [
|
||||
'immediate' => [
|
||||
'text' => __('immediately', 'mailpoet'),
|
||||
'displayAfterTimeNumberField' => false,
|
||||
],
|
||||
'minutes' => [
|
||||
'text' => __('minute(s)', 'mailpoet'),
|
||||
'displayAfterTimeNumberField' => true,
|
||||
],
|
||||
'hours' => [
|
||||
'text' => __('hour(s)', 'mailpoet'),
|
||||
'displayAfterTimeNumberField' => true,
|
||||
],
|
||||
'days' => [
|
||||
'text' => __('day(s)', 'mailpoet'),
|
||||
'displayAfterTimeNumberField' => true,
|
||||
],
|
||||
'weeks' => [
|
||||
'text' => __('week(s)', 'mailpoet'),
|
||||
'displayAfterTimeNumberField' => true,
|
||||
],
|
||||
],
|
||||
'shortcodes' => [
|
||||
[
|
||||
'text' => __('Order amount', 'mailpoet'),
|
||||
'shortcode' => self::ORDER_TOTAL_SHORTCODE,
|
||||
],
|
||||
[
|
||||
'text' => __('Order date', 'mailpoet'),
|
||||
'shortcode' => self::ORDER_DATE_SHORTCODE,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function handleOrderDateShortcode($shortcode, $newsletter, $subscriber, $queue) {
|
||||
$result = $shortcode;
|
||||
if ($shortcode === self::ORDER_DATE_SHORTCODE) {
|
||||
$defaultValue = WPFunctions::get()->dateI18n(get_option('date_format'));
|
||||
if (!$queue) {
|
||||
$result = $defaultValue;
|
||||
} else {
|
||||
$meta = $queue->getMeta();
|
||||
$result = (!empty($meta['order_date'])) ? WPFunctions::get()->dateI18n(get_option('date_format'), $meta['order_date']) : $defaultValue;
|
||||
}
|
||||
}
|
||||
$this->loggerFactory->getLogger(self::SLUG)->info(
|
||||
'handleOrderDateShortcode called',
|
||||
[
|
||||
'newsletter_id' => ($newsletter instanceof NewsletterEntity) ? $newsletter->getId() : null,
|
||||
'subscriber_id' => ($subscriber instanceof SubscriberEntity) ? $subscriber->getId() : null,
|
||||
'task_id' => ($queue instanceof SendingQueueEntity) ? (($task = $queue->getTask()) ? $task->getId() : null) : null,
|
||||
'shortcode' => $shortcode,
|
||||
'result' => $result,
|
||||
]
|
||||
);
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function handleOrderTotalShortcode($shortcode, $newsletter, $subscriber, $queue) {
|
||||
$result = $shortcode;
|
||||
if ($shortcode === self::ORDER_TOTAL_SHORTCODE) {
|
||||
$defaultValue = $this->helper->wcPrice(0);
|
||||
if (!$queue) {
|
||||
$result = $defaultValue;
|
||||
} else {
|
||||
$meta = $queue->getMeta();
|
||||
$result = (!empty($meta['order_amount'])) ? $this->helper->wcPrice($meta['order_amount']) : $defaultValue;
|
||||
}
|
||||
}
|
||||
$this->loggerFactory->getLogger(self::SLUG)->info(
|
||||
'handleOrderTotalShortcode called',
|
||||
[
|
||||
'newsletter_id' => ($newsletter instanceof NewsletterEntity) ? $newsletter->getId() : null,
|
||||
'subscriber_id' => ($subscriber instanceof SubscriberEntity) ? $subscriber->getId() : null,
|
||||
'task_id' => ($queue instanceof SendingQueueEntity) ? (($task = $queue->getTask()) ? $task->getId() : null) : null,
|
||||
'shortcode' => $shortcode,
|
||||
'result' => $result,
|
||||
]
|
||||
);
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function scheduleEmailWhenOrderIsPlaced($orderId) {
|
||||
$orderDetails = $this->helper->wcGetOrder($orderId);
|
||||
if (!$orderDetails || !$orderDetails->get_billing_email()) {
|
||||
$this->loggerFactory->getLogger(self::SLUG)->info(
|
||||
'Email not scheduled because the order customer was not found',
|
||||
['order_id' => $orderId]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$customerEmail = $orderDetails->get_billing_email();
|
||||
$customerOrderCount = $this->getCustomerOrderCount($customerEmail);
|
||||
if ($customerOrderCount > 1) {
|
||||
$this->loggerFactory->getLogger(self::SLUG)->info(
|
||||
'Email not scheduled because this is not the first order of the customer',
|
||||
[
|
||||
'order_id' => $orderId,
|
||||
'customer_email' => $customerEmail,
|
||||
'order_count' => $customerOrderCount,
|
||||
]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$meta = [
|
||||
'order_amount' => $orderDetails->get_total(),
|
||||
'order_date' => $orderDetails->get_date_created()->getTimestamp(),
|
||||
'order_id' => $orderDetails->get_id(),
|
||||
];
|
||||
|
||||
$subscriber = $this->subscribersRepository->getWooCommerceSegmentSubscriber($customerEmail);
|
||||
|
||||
if (!$subscriber instanceof SubscriberEntity) {
|
||||
$this->loggerFactory->getLogger(self::SLUG)->info(
|
||||
'Email not scheduled because the customer was not found as WooCommerce list subscriber',
|
||||
['order_id' => $orderId, 'customer_email' => $customerEmail]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$checkEmailWasNotScheduled = function (NewsletterEntity $newsletter) use ($subscriber) {
|
||||
return !$this->automaticEmailsRepository->wasScheduledForSubscriber((int)$newsletter->getId(), (int)$subscriber->getId());
|
||||
};
|
||||
|
||||
$this->loggerFactory->getLogger(self::SLUG)->info(
|
||||
'Email scheduled',
|
||||
[
|
||||
'order_id' => $orderId,
|
||||
'customer_email' => $customerEmail,
|
||||
'subscriber_id' => $subscriber->getId(),
|
||||
]
|
||||
);
|
||||
$this->scheduler->scheduleAutomaticEmail(WooCommerce::SLUG, self::SLUG, $checkEmailWasNotScheduled, $subscriber, $meta);
|
||||
}
|
||||
|
||||
public function getCustomerOrderCount($customerEmail) {
|
||||
// registered user
|
||||
$user = WPFunctions::get()->getUserBy('email', $customerEmail);
|
||||
if ($user) {
|
||||
return $this->helper->wcGetCustomerOrderCount($user->ID);
|
||||
}
|
||||
// guest user
|
||||
return $this->getGuestCustomerOrderCountByEmail($customerEmail);
|
||||
}
|
||||
|
||||
private function getGuestCustomerOrderCountByEmail(string $customerEmail): int {
|
||||
$ordersCount = $this->helper->wcGetOrders(
|
||||
[
|
||||
'status' => 'all',
|
||||
'type' => 'shop_order',
|
||||
'billing_email' => $customerEmail,
|
||||
'limit' => 1,
|
||||
'return' => 'ids',
|
||||
'paginate' => true,
|
||||
]
|
||||
)->total;
|
||||
return intval($ordersCount);
|
||||
}
|
||||
}
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\AutomaticEmails\WooCommerce\Events;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AutomaticEmails\WooCommerce\WooCommerce;
|
||||
use MailPoet\DI\ContainerWrapper;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\NewsletterOptionFieldEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Logging\LoggerFactory;
|
||||
use MailPoet\Newsletter\AutomaticEmailsRepository;
|
||||
use MailPoet\Newsletter\Scheduler\AutomaticEmailScheduler;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\WooCommerce\Helper as WCHelper;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class PurchasedInCategory {
|
||||
const SLUG = 'woocommerce_product_purchased_in_category';
|
||||
|
||||
/** @var WCHelper */
|
||||
private $woocommerceHelper;
|
||||
|
||||
/** @var AutomaticEmailScheduler */
|
||||
private $scheduler;
|
||||
|
||||
/** @var LoggerFactory */
|
||||
private $loggerFactory;
|
||||
|
||||
/** @var AutomaticEmailsRepository */
|
||||
private $repository;
|
||||
|
||||
/** @var SubscribersRepository */
|
||||
private $subscribersRepository;
|
||||
|
||||
public function __construct(
|
||||
WCHelper $woocommerceHelper = null
|
||||
) {
|
||||
if ($woocommerceHelper === null) {
|
||||
$woocommerceHelper = ContainerWrapper::getInstance()->get(WCHelper::class);
|
||||
}
|
||||
$this->woocommerceHelper = $woocommerceHelper;
|
||||
$this->scheduler = ContainerWrapper::getInstance()->get(AutomaticEmailScheduler::class);
|
||||
$this->loggerFactory = LoggerFactory::getInstance();
|
||||
$this->repository = ContainerWrapper::getInstance()->get(AutomaticEmailsRepository::class);
|
||||
$this->subscribersRepository = ContainerWrapper::getInstance()->get(SubscribersRepository::class);
|
||||
}
|
||||
|
||||
public function getEventDetails() {
|
||||
return [
|
||||
'slug' => self::SLUG,
|
||||
'title' => _x('Purchased In This Category', 'This is the name of a type for automatic email for ecommerce. Those emails are sent automatically every time a customer buys for the first time a product in a given category', 'mailpoet'),
|
||||
'description' => __('Let MailPoet send an email to customers who purchase a product for the first time in a specific category.', 'mailpoet'),
|
||||
// translators: %s is the name of the category.
|
||||
'listingScheduleDisplayText' => __('Email sent when a customer buys a product in category: %s', 'mailpoet'),
|
||||
// translators: %s is the name of the category.
|
||||
'listingScheduleDisplayTextPlural' => __('Email sent when a customer buys a product in categories: %s', 'mailpoet'),
|
||||
'afterDelayText' => __('after a purchase', 'mailpoet'),
|
||||
'timeDelayValues' => [
|
||||
'immediate' => [
|
||||
'text' => __('immediately', 'mailpoet'),
|
||||
'displayAfterTimeNumberField' => false,
|
||||
],
|
||||
'minutes' => [
|
||||
'text' => __('minute(s)', 'mailpoet'),
|
||||
'displayAfterTimeNumberField' => true,
|
||||
],
|
||||
'hours' => [
|
||||
'text' => __('hour(s)', 'mailpoet'),
|
||||
'displayAfterTimeNumberField' => true,
|
||||
],
|
||||
'days' => [
|
||||
'text' => __('day(s)', 'mailpoet'),
|
||||
'displayAfterTimeNumberField' => true,
|
||||
],
|
||||
'weeks' => [
|
||||
'text' => __('week(s)', 'mailpoet'),
|
||||
'displayAfterTimeNumberField' => true,
|
||||
],
|
||||
],
|
||||
'options' => [
|
||||
'multiple' => true,
|
||||
'endpoint' => 'product_categories',
|
||||
'placeholder' => _x('Search category', 'Search input for product category (ecommerce)', 'mailpoet'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function init() {
|
||||
WPFunctions::get()->removeAllFilters('woocommerce_product_purchased_get_categories');
|
||||
WPFunctions::get()->addFilter(
|
||||
'woocommerce_product_purchased_get_categories',
|
||||
[$this, 'getCategories']
|
||||
);
|
||||
|
||||
$acceptedOrderStates = WPFunctions::get()->applyFilters('mailpoet_first_purchase_order_states', ['completed', 'processing']);
|
||||
foreach ($acceptedOrderStates as $state) {
|
||||
WPFunctions::get()->addAction(
|
||||
'woocommerce_order_status_' . $state,
|
||||
[$this, 'scheduleEmail'],
|
||||
10,
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function getCategories($searchQuery) {
|
||||
$args = [
|
||||
'taxonomy' => 'product_cat',
|
||||
'search' => $searchQuery,
|
||||
'orderby' => 'name',
|
||||
'hierarchical' => 0,
|
||||
'hide_empty' => 1,
|
||||
'order' => 'ASC',
|
||||
];
|
||||
$allCategories = get_categories($args);
|
||||
|
||||
return array_map(function($category) {
|
||||
return [
|
||||
'id' => $category->term_id, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
'name' => $category->name,
|
||||
];
|
||||
}, $allCategories);
|
||||
}
|
||||
|
||||
public function scheduleEmail($orderId) {
|
||||
$orderDetails = $this->woocommerceHelper->wcGetOrder($orderId);
|
||||
if (!$orderDetails || !$orderDetails->get_billing_email()) {
|
||||
$this->loggerFactory->getLogger(self::SLUG)->info(
|
||||
'Email not scheduled because the order customer was not found',
|
||||
['order_id' => $orderId]
|
||||
);
|
||||
return;
|
||||
}
|
||||
$customerEmail = $orderDetails->get_billing_email();
|
||||
|
||||
$subscriber = $this->subscribersRepository->getWooCommerceSegmentSubscriber($customerEmail);
|
||||
|
||||
if (!$subscriber instanceof SubscriberEntity) {
|
||||
$this->loggerFactory->getLogger(self::SLUG)->info(
|
||||
'Email not scheduled because the customer was not found as WooCommerce list subscriber',
|
||||
['order_id' => $orderId, 'customer_email' => $customerEmail]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$orderedProductCategories = [];
|
||||
foreach ($orderDetails->get_items() as $orderItemProduct) {
|
||||
$product = $orderItemProduct->get_product();
|
||||
if (!$product instanceof \WC_Product) {
|
||||
continue;
|
||||
}
|
||||
if ($product->get_type() === 'variation') {
|
||||
// WooCommerce returns a empty list when get_category_ids() is called for a product variation,
|
||||
// so we need to get the parent product
|
||||
$product = $this->woocommerceHelper->wcGetProduct($product->get_parent_id());
|
||||
}
|
||||
$orderedProductCategories = array_merge($orderedProductCategories, $product->get_category_ids());
|
||||
}
|
||||
|
||||
$schedulingCondition = function(NewsletterEntity $automaticEmail) use ($orderedProductCategories, $subscriber) {
|
||||
$matchedCategories = $this->getProductCategoryIdsMatchingNewsletterTrigger($automaticEmail, $orderedProductCategories);
|
||||
if (empty($matchedCategories)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->repository->wasScheduledForSubscriber((int)$automaticEmail->getId(), (int)$subscriber->getId())) {
|
||||
$sentAllProducts = $this->repository->alreadySentAllProducts((int)$automaticEmail->getId(), (int)$subscriber->getId(), 'orderedProductCategories', $matchedCategories);
|
||||
if ($sentAllProducts) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
$this->loggerFactory->getLogger(self::SLUG)->info(
|
||||
'Email scheduled',
|
||||
[
|
||||
'order_id' => $orderId,
|
||||
'customer_email' => $customerEmail,
|
||||
'subscriber_id' => $subscriber->getId(),
|
||||
]
|
||||
);
|
||||
$this->scheduler->scheduleAutomaticEmail(
|
||||
WooCommerce::SLUG,
|
||||
self::SLUG,
|
||||
$schedulingCondition,
|
||||
$subscriber,
|
||||
['orderedProductCategories' => $orderedProductCategories],
|
||||
[$this, 'metaModifier']
|
||||
);
|
||||
}
|
||||
|
||||
public function metaModifier(NewsletterEntity $automaticEmail, array $meta): array {
|
||||
$orderedProductCategoryIds = $meta['orderedProductCategories'] ?? null;
|
||||
if (empty($orderedProductCategoryIds)) {
|
||||
return $meta;
|
||||
}
|
||||
$meta['orderedProductCategories'] = $this->getProductCategoryIdsMatchingNewsletterTrigger($automaticEmail, $orderedProductCategoryIds);
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
private function getProductCategoryIdsMatchingNewsletterTrigger(NewsletterEntity $automaticEmail, array $orderedCategoryIds): array {
|
||||
$automaticEmailMetaValue = $automaticEmail->getOptionValue(NewsletterOptionFieldEntity::NAME_META);
|
||||
$optionValue = Helpers::isJson($automaticEmailMetaValue) ? json_decode($automaticEmailMetaValue, true) : $automaticEmailMetaValue;
|
||||
|
||||
if (!is_array($optionValue) || empty($optionValue['option'])) {
|
||||
return [];
|
||||
}
|
||||
$emailTriggeringCategoryIds = array_column($optionValue['option'], 'id');
|
||||
|
||||
return array_intersect($emailTriggeringCategoryIds, $orderedCategoryIds);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user