init
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Settings;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
class Charsets {
|
||||
public static function getAll() {
|
||||
return [
|
||||
'UTF-8', 'UTF-7', 'BIG5', 'ISO-2022-JP',
|
||||
'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3',
|
||||
'ISO-8859-4', 'ISO-8859-5', 'ISO-8859-6',
|
||||
'ISO-8859-7', 'ISO-8859-8', 'ISO-8859-9',
|
||||
'ISO-8859-10', 'ISO-8859-13', 'ISO-8859-14',
|
||||
'ISO-8859-15', 'Windows-1251', 'Windows-1252',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Settings;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
class Hosts {
|
||||
private static $smtp = [
|
||||
'AmazonSES' => [
|
||||
'name' => 'Amazon SES',
|
||||
'emails' => 100,
|
||||
'interval' => 5,
|
||||
'fields' => [
|
||||
'region',
|
||||
'access_key',
|
||||
'secret_key',
|
||||
],
|
||||
'regions' => [
|
||||
'US East (N. Virginia)' => 'us-east-1',
|
||||
'US East (Ohio)' => 'us-east-2',
|
||||
'US West (N. California)' => 'us-west-1',
|
||||
'US West (Oregon)' => 'us-west-2',
|
||||
'EU (Ireland)' => 'eu-west-1',
|
||||
'EU (London)' => 'eu-west-2',
|
||||
'EU (Paris)' => 'eu-west-3',
|
||||
'EU (Milan)' => 'eu-south-1',
|
||||
'EU (Frankfurt)' => 'eu-central-1',
|
||||
'EU (Stockholm)' => 'eu-north-1',
|
||||
'Canada (Central)' => 'ca-central-1',
|
||||
'China (Beijing)' => 'cn-north-1',
|
||||
'China (Ningxia)' => 'cn-northwest-1',
|
||||
'Africa (Cape Town)' => 'af-south-1',
|
||||
'Asia Pacific (Hong Kong)' => 'ap-east-1',
|
||||
'Asia Pacific (Jakarta)' => 'ap-southeast-3',
|
||||
'Asia Pacific (Mumbai)' => 'ap-south-1',
|
||||
'Asia Pacific (Seoul)' => 'ap-northeast-2',
|
||||
'Asia Pacific (Osaka)' => 'ap-northeast-3',
|
||||
'Asia Pacific (Singapore)' => 'ap-southeast-1',
|
||||
'Asia Pacific (Sydney)' => 'ap-southeast-2',
|
||||
'Asia Pacific (Tokyo)' => 'ap-northeast-1',
|
||||
'Middle East (Bahrain)' => 'me-south-1',
|
||||
'South America (Sao Paulo)' => 'sa-east-1',
|
||||
'AWS GovCloud (US)' => 'us-gov-west-1',
|
||||
],
|
||||
],
|
||||
'SendGrid' => [
|
||||
'name' => 'SendGrid',
|
||||
'emails' => 100,
|
||||
'interval' => 5,
|
||||
'fields' => [
|
||||
'api_key',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
private static $web = [
|
||||
'bluehost' => [
|
||||
'name' => 'BlueHost',
|
||||
'emails' => 70,
|
||||
'interval' => 30,
|
||||
],
|
||||
'df' => [
|
||||
'name' => 'Df.eu',
|
||||
'emails' => 115,
|
||||
'interval' => 15,
|
||||
],
|
||||
'dreamhost' => [
|
||||
'name' => 'DreamHost',
|
||||
'emails' => 25,
|
||||
'interval' => 15,
|
||||
],
|
||||
'free' => [
|
||||
'name' => 'Free.fr',
|
||||
'emails' => 18,
|
||||
'interval' => 15,
|
||||
],
|
||||
'froghost' => [
|
||||
'name' => 'FrogHost.com',
|
||||
'emails' => 490,
|
||||
'interval' => 30,
|
||||
],
|
||||
'godaddy' => [
|
||||
'name' => 'GoDaddy',
|
||||
'emails' => 5,
|
||||
'interval' => 30,
|
||||
],
|
||||
'goneo' => [
|
||||
'name' => 'Goneo',
|
||||
'emails' => 60,
|
||||
'interval' => 15,
|
||||
],
|
||||
'googleapps' => [
|
||||
'name' => 'Google Apps',
|
||||
'emails' => 20,
|
||||
'interval' => 60,
|
||||
],
|
||||
'greengeeks' => [
|
||||
'name' => 'GreenGeeks',
|
||||
'emails' => 45,
|
||||
'interval' => 30,
|
||||
],
|
||||
'hawkhost' => [
|
||||
'name' => 'Hawkhost.com',
|
||||
'emails' => 500,
|
||||
'interval' => 15,
|
||||
],
|
||||
'hivetec' => [
|
||||
'name' => 'Hivetec',
|
||||
'emails' => 20,
|
||||
'interval' => 15,
|
||||
],
|
||||
'hostgator' => [
|
||||
'name' => 'Host Gator',
|
||||
'emails' => 115,
|
||||
'interval' => 15,
|
||||
],
|
||||
'hosting2go' => [
|
||||
'name' => 'Hosting 2GO',
|
||||
'emails' => 45,
|
||||
'interval' => 15,
|
||||
],
|
||||
'hostmonster' => [
|
||||
'name' => 'Host Monster',
|
||||
'emails' => 115,
|
||||
'interval' => 15,
|
||||
],
|
||||
'infomaniak' => [
|
||||
'name' => 'Infomaniak',
|
||||
'emails' => 20,
|
||||
'interval' => 15,
|
||||
],
|
||||
'1and1' => [
|
||||
'name' => 'IONOS by 1&1',
|
||||
'emails' => 30,
|
||||
'interval' => 5,
|
||||
],
|
||||
'justhost' => [
|
||||
'name' => 'JustHost',
|
||||
'emails' => 70,
|
||||
'interval' => 30,
|
||||
],
|
||||
'laughingsquid' => [
|
||||
'name' => 'Laughing Squid',
|
||||
'emails' => 20,
|
||||
'interval' => 15,
|
||||
],
|
||||
'lunarpages' => [
|
||||
'name' => 'Lunarpages',
|
||||
'emails' => 19,
|
||||
'interval' => 15,
|
||||
],
|
||||
'mediatemple' => [
|
||||
'name' => 'Media Temple',
|
||||
'emails' => 115,
|
||||
'interval' => 15,
|
||||
],
|
||||
'netfirms' => [
|
||||
'name' => 'Netfirms',
|
||||
'emails' => 200,
|
||||
'interval' => 60,
|
||||
],
|
||||
'netissime' => [
|
||||
'name' => 'Netissime',
|
||||
'emails' => 100,
|
||||
'interval' => 15,
|
||||
],
|
||||
'one' => [
|
||||
'name' => 'One.com',
|
||||
'emails' => 100,
|
||||
'interval' => 15,
|
||||
],
|
||||
'ovh' => [
|
||||
'name' => 'OVH',
|
||||
'emails' => 50,
|
||||
'interval' => 15,
|
||||
],
|
||||
'phpnet' => [
|
||||
'name' => 'PHPNet',
|
||||
'emails' => 15,
|
||||
'interval' => 15,
|
||||
],
|
||||
'planethoster' => [
|
||||
'name' => 'PlanetHoster',
|
||||
'emails' => 90,
|
||||
'interval' => 30,
|
||||
],
|
||||
'rochen' => [
|
||||
'name' => 'Rochen',
|
||||
'emails' => 40,
|
||||
'interval' => 15,
|
||||
],
|
||||
'site5' => [
|
||||
'name' => 'Site5',
|
||||
'emails' => 40,
|
||||
'interval' => 15,
|
||||
],
|
||||
'siteground' => [
|
||||
'name' => 'Siteground',
|
||||
'emails' => 95,
|
||||
'interval' => 15,
|
||||
],
|
||||
'synthesis' => [
|
||||
'name' => 'Synthesis',
|
||||
'emails' => 250,
|
||||
'interval' => 15,
|
||||
],
|
||||
'techark' => [
|
||||
'name' => 'Techark',
|
||||
'emails' => 60,
|
||||
'interval' => 15,
|
||||
],
|
||||
'vexxhost' => [
|
||||
'name' => 'Vexxhost',
|
||||
'emails' => 60,
|
||||
'interval' => 15,
|
||||
],
|
||||
'vps' => [
|
||||
'name' => 'VPS.net',
|
||||
'emails' => 90,
|
||||
'interval' => 30,
|
||||
],
|
||||
'webcity' => [
|
||||
'name' => 'Webcity',
|
||||
'emails' => 19,
|
||||
'interval' => 15,
|
||||
],
|
||||
'westhost' => [
|
||||
'name' => 'Westhost',
|
||||
'emails' => 225,
|
||||
'interval' => 15,
|
||||
],
|
||||
'wpwebhost' => [
|
||||
'name' => 'Wpwebhost.com',
|
||||
'emails' => 95,
|
||||
'interval' => 30,
|
||||
],
|
||||
];
|
||||
|
||||
public static function getWebHosts() {
|
||||
return static::$web;
|
||||
}
|
||||
|
||||
public static function getSMTPHosts() {
|
||||
return static::$smtp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Settings;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Subscription;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class Pages {
|
||||
const PAGE_SUBSCRIPTIONS = 'subscriptions';
|
||||
const PAGE_CAPTCHA = 'captcha';
|
||||
|
||||
public function __construct() {
|
||||
}
|
||||
|
||||
public function init() {
|
||||
WPFunctions::get()->registerPostType('mailpoet_page', [
|
||||
'labels' => [
|
||||
'name' => __('MailPoet Page', 'mailpoet'),
|
||||
'singular_name' => __('MailPoet Page', 'mailpoet'),
|
||||
],
|
||||
'public' => true,
|
||||
'has_archive' => false,
|
||||
'show_ui' => false,
|
||||
'show_in_menu' => false,
|
||||
'rewrite' => false,
|
||||
'show_in_nav_menus' => false,
|
||||
'can_export' => false,
|
||||
'publicly_queryable' => true,
|
||||
'exclude_from_search' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function createMailPoetPage($postName) {
|
||||
WPFunctions::get()->removeAllActions('pre_post_update');
|
||||
WPFunctions::get()->removeAllActions('save_post');
|
||||
WPFunctions::get()->removeAllActions('wp_insert_post');
|
||||
|
||||
$id = WPFunctions::get()->wpInsertPost([
|
||||
'post_status' => 'publish',
|
||||
'post_type' => 'mailpoet_page',
|
||||
'post_author' => 1,
|
||||
'post_content' => '[mailpoet_page]',
|
||||
'post_title' => __('MailPoet Page', 'mailpoet'),
|
||||
'post_name' => $postName,
|
||||
]);
|
||||
|
||||
return ((int)$id > 0) ? (int)$id : false;
|
||||
}
|
||||
|
||||
public static function getMailPoetPage($postName) {
|
||||
$wp = WPFunctions::get();
|
||||
$pages = $wp->getPosts([
|
||||
'posts_per_page' => 1,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'post_type' => 'mailpoet_page',
|
||||
'post_name__in' => [$postName],
|
||||
]);
|
||||
|
||||
$page = null;
|
||||
if (!empty($pages)) {
|
||||
$page = array_shift($pages);
|
||||
if (strpos($page->post_content, '[mailpoet_page]') === false) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$page = null;
|
||||
}
|
||||
}
|
||||
return $page;
|
||||
}
|
||||
|
||||
public static function getMailPoetPages() {
|
||||
return WPFunctions::get()->getPosts([
|
||||
'post_type' => 'mailpoet_page',
|
||||
'post_name__in' => [self::PAGE_SUBSCRIPTIONS],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isMailpoetPage($id) {
|
||||
$mailpoetPages = static::getMailPoetPages();
|
||||
foreach ($mailpoetPages as $mailpoetPage) {
|
||||
if ($mailpoetPage->ID === $id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function getAll() {
|
||||
$allPages = array_merge(
|
||||
static::getMailPoetPages(),
|
||||
WPFunctions::get()->getPages()
|
||||
);
|
||||
|
||||
$pages = [];
|
||||
foreach ($allPages as $page) {
|
||||
$pages[] = static::getPageData($page);
|
||||
}
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
public static function getPageData($page) {
|
||||
$subscriptionUrlFactory = Subscription\SubscriptionUrlFactory::getInstance();
|
||||
return [
|
||||
'id' => $page->ID,
|
||||
'title' => $page->post_title, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
'url' => [
|
||||
'unsubscribe' => $subscriptionUrlFactory->getSubscriptionUrl($page, 'unsubscribe'),
|
||||
'manage' => $subscriptionUrlFactory->getSubscriptionUrl($page, 'manage'),
|
||||
'confirm' => $subscriptionUrlFactory->getSubscriptionUrl($page, 'confirm'),
|
||||
'confirm_unsubscribe' => $subscriptionUrlFactory->getSubscriptionUrl($page, 'confirm_unsubscribe'),
|
||||
're_engagement' => $subscriptionUrlFactory->getSubscriptionUrl($page, 're_engagement'),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Settings;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Cron\Workers\InactiveSubscribers;
|
||||
use MailPoet\Cron\Workers\WooCommerceSync;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Mailer\Mailer;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
|
||||
use MailPoet\Services\Bridge;
|
||||
use MailPoet\Services\SubscribersCountReporter;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
|
||||
class SettingsChangeHandler {
|
||||
|
||||
/** @var ScheduledTasksRepository */
|
||||
private $scheduledTasksRepository;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settingsController;
|
||||
|
||||
/** @var Bridge */
|
||||
private $bridge;
|
||||
|
||||
/** @var SubscribersCountReporter */
|
||||
private $subscribersCountReporter;
|
||||
|
||||
public function __construct(
|
||||
ScheduledTasksRepository $scheduledTasksRepository,
|
||||
SettingsController $settingsController,
|
||||
Bridge $bridge,
|
||||
SubscribersCountReporter $subscribersCountReporter
|
||||
) {
|
||||
$this->scheduledTasksRepository = $scheduledTasksRepository;
|
||||
$this->settingsController = $settingsController;
|
||||
$this->bridge = $bridge;
|
||||
$this->subscribersCountReporter = $subscribersCountReporter;
|
||||
}
|
||||
|
||||
public function onSubscribeOldWoocommerceCustomersChange(): void {
|
||||
$task = $this->scheduledTasksRepository->findOneBy([
|
||||
'type' => WooCommerceSync::TASK_TYPE,
|
||||
'status' => ScheduledTaskEntity::STATUS_SCHEDULED,
|
||||
'deletedAt' => null,
|
||||
], ['createdAt' => 'DESC']);
|
||||
if (!($task instanceof ScheduledTaskEntity)) {
|
||||
$task = $this->createScheduledTask(WooCommerceSync::TASK_TYPE);
|
||||
}
|
||||
$datetime = Carbon::now()->millisecond(0);
|
||||
$task->setScheduledAt($datetime->subMinute());
|
||||
$this->scheduledTasksRepository->persist($task);
|
||||
$this->scheduledTasksRepository->flush();
|
||||
}
|
||||
|
||||
public function onInactiveSubscribersIntervalChange(): void {
|
||||
$task = $this->scheduledTasksRepository->findOneBy([
|
||||
'type' => InactiveSubscribers::TASK_TYPE,
|
||||
'status' => ScheduledTaskEntity::STATUS_SCHEDULED,
|
||||
'deletedAt' => null,
|
||||
], ['createdAt' => 'DESC']);
|
||||
if (!($task instanceof ScheduledTaskEntity)) {
|
||||
$task = $this->createScheduledTask(InactiveSubscribers::TASK_TYPE);
|
||||
}
|
||||
$datetime = Carbon::now()->millisecond(0);
|
||||
$task->setScheduledAt($datetime->subMinute());
|
||||
$this->scheduledTasksRepository->persist($task);
|
||||
$this->scheduledTasksRepository->flush();
|
||||
}
|
||||
|
||||
public function onMSSActivate($newSettings) {
|
||||
// see mailpoet/assets/js/src/wizard/create_sender_settings.jsx:freeAddress
|
||||
$httpHost = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : '';
|
||||
$domain = str_replace('www.', '', $httpHost);
|
||||
if (
|
||||
isset($newSettings['sender']['address'])
|
||||
&& !empty($newSettings['reply_to']['address'])
|
||||
&& ($newSettings['sender']['address'] === ('wordpress@' . $domain))
|
||||
) {
|
||||
$sender = [
|
||||
'name' => $newSettings['reply_to']['name'] ?? '',
|
||||
'address' => $newSettings['reply_to']['address'],
|
||||
];
|
||||
$this->settingsController->set('sender', $sender);
|
||||
$this->settingsController->set('reply_to', null);
|
||||
}
|
||||
}
|
||||
|
||||
private function createScheduledTask(string $type): ScheduledTaskEntity {
|
||||
$task = new ScheduledTaskEntity();
|
||||
$task->setType($type);
|
||||
$task->setStatus(ScheduledTaskEntity::STATUS_SCHEDULED);
|
||||
return $task;
|
||||
}
|
||||
|
||||
public function updateApiKeyState($settings) {
|
||||
$apiKey = $settings[Mailer::MAILER_CONFIG_SETTING_NAME]['mailpoet_api_key'] ?? null;
|
||||
$premiumKey = $settings['premium']['premium_key'] ?? null;
|
||||
if (!empty($apiKey)) {
|
||||
$apiKeyState = $this->bridge->checkMSSKey($apiKey);
|
||||
$this->bridge->storeMSSKeyAndState($apiKey, $apiKeyState);
|
||||
}
|
||||
if (!empty($premiumKey)) {
|
||||
$premiumState = $this->bridge->checkPremiumKey($premiumKey);
|
||||
$this->bridge->storePremiumKeyAndState($premiumKey, $premiumState);
|
||||
}
|
||||
if ($apiKey && !empty($apiKeyState) && in_array($apiKeyState['state'], [Bridge::KEY_VALID, Bridge::KEY_VALID_UNDERPRIVILEGED], true)) {
|
||||
return $this->subscribersCountReporter->report($apiKey);
|
||||
}
|
||||
if ($premiumKey && !empty($premiumState) && in_array($premiumState['state'], [Bridge::KEY_VALID, Bridge::KEY_VALID_UNDERPRIVILEGED], true)) {
|
||||
return $this->subscribersCountReporter->report($premiumKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Settings;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Cron\CronTrigger;
|
||||
use MailPoet\DI\ContainerWrapper;
|
||||
|
||||
class SettingsController {
|
||||
|
||||
const DEFAULT_SENDING_METHOD_GROUP = 'website';
|
||||
const DEFAULT_SENDING_METHOD = 'PHPMail';
|
||||
const DEFAULT_SENDING_FREQUENCY_EMAILS = 25;
|
||||
const DEFAULT_SENDING_FREQUENCY_INTERVAL = 5; // in minutes
|
||||
const DEFAULT_DEACTIVATE_SUBSCRIBER_AFTER_INACTIVE_DAYS = 365;
|
||||
|
||||
private $loaded = false;
|
||||
|
||||
private $settings = [];
|
||||
|
||||
private $defaults = null;
|
||||
|
||||
/** @var SettingsRepository */
|
||||
private $settingsRepository;
|
||||
|
||||
private static $instance;
|
||||
|
||||
public function __construct(
|
||||
SettingsRepository $settingsRepository
|
||||
) {
|
||||
$this->settingsRepository = $settingsRepository;
|
||||
}
|
||||
|
||||
public function get($key, $default = null) {
|
||||
$this->ensureLoaded();
|
||||
$keyParts = explode('.', $key);
|
||||
$setting = $this->settings;
|
||||
if ($default === null) {
|
||||
$default = $this->getDefaultValue($keyParts);
|
||||
}
|
||||
foreach ($keyParts as $keyPart) {
|
||||
if (is_array($setting) && array_key_exists($keyPart, $setting)) {
|
||||
$setting = $setting[$keyPart];
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
if (is_array($setting) && is_array($default)) {
|
||||
return array_replace_recursive($default, $setting);
|
||||
}
|
||||
return $setting;
|
||||
}
|
||||
|
||||
public function getAllDefaults() {
|
||||
if ($this->defaults === null) {
|
||||
$this->defaults = [
|
||||
'mta_group' => self::DEFAULT_SENDING_METHOD_GROUP,
|
||||
'mta' => [
|
||||
'method' => self::DEFAULT_SENDING_METHOD,
|
||||
'frequency' => [
|
||||
'emails' => self::DEFAULT_SENDING_FREQUENCY_EMAILS,
|
||||
'interval' => self::DEFAULT_SENDING_FREQUENCY_INTERVAL,
|
||||
],
|
||||
],
|
||||
CronTrigger::SETTING_NAME => [
|
||||
'method' => CronTrigger::DEFAULT_METHOD,
|
||||
],
|
||||
'signup_confirmation' => [
|
||||
'enabled' => true,
|
||||
'use_mailpoet_editor' => true,
|
||||
'subject' => __('Confirm your subscription to [site:title]', 'mailpoet'),
|
||||
'body' => __("Hello [subscriber:firstname | default:there],\n\nYou've received this message because you subscribed to [site:title]. Please confirm your subscription to receive emails from us:\n\n[activation_link]Click here to confirm your subscription.[/activation_link] \n\nIf you received this email by mistake, simply delete it. You won't receive any more emails from us unless you confirm your subscription using the link above.\n\nThank you,\n\n<a target=\"_blank\" href=\"[site:homepage_url]\">[site:title]</a>", 'mailpoet'),
|
||||
],
|
||||
'tracking' => [
|
||||
'level' => TrackingConfig::LEVEL_FULL,
|
||||
],
|
||||
'analytics' => [
|
||||
'enabled' => false,
|
||||
],
|
||||
'display_nps_poll' => true,
|
||||
'deactivate_subscriber_after_inactive_days' => self::DEFAULT_DEACTIVATE_SUBSCRIBER_AFTER_INACTIVE_DAYS,
|
||||
];
|
||||
}
|
||||
return $this->defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the value from DB and update in cache
|
||||
* This is required for sync settings between parallel processes e.g. cron
|
||||
*/
|
||||
public function fetch($key, $default = null) {
|
||||
$keys = explode('.', $key);
|
||||
$mainKey = $keys[0];
|
||||
$this->settings[$mainKey] = $this->fetchValue($mainKey);
|
||||
return $this->get($key, $default);
|
||||
}
|
||||
|
||||
public function getAll() {
|
||||
$this->ensureLoaded();
|
||||
return array_replace_recursive($this->getAllDefaults(), $this->settings);
|
||||
}
|
||||
|
||||
public function set($key, $value) {
|
||||
$this->ensureLoaded();
|
||||
$keyParts = explode('.', $key);
|
||||
$mainKey = $keyParts[0];
|
||||
$lastKey = array_pop($keyParts);
|
||||
$setting =& $this->settings;
|
||||
foreach ($keyParts as $keyPart) {
|
||||
$setting =& $setting[$keyPart];
|
||||
if (!is_array($setting)) {
|
||||
$setting = [];
|
||||
}
|
||||
}
|
||||
$setting[$lastKey] = $value;
|
||||
$this->settingsRepository->createOrUpdateByName($mainKey, $this->settings[$mainKey]);
|
||||
}
|
||||
|
||||
public function delete($key) {
|
||||
$setting = $this->settingsRepository->findOneByName($key);
|
||||
if ($setting) {
|
||||
$this->settingsRepository->remove($setting);
|
||||
$this->settingsRepository->flush();
|
||||
}
|
||||
unset($this->settings[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a value is stored in the database for the given key
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSavedValue(string $key): bool {
|
||||
return $this->get($key, 'unset') !== 'unset';
|
||||
}
|
||||
|
||||
private function ensureLoaded() {
|
||||
if ($this->loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->settings = [];
|
||||
foreach ($this->settingsRepository->findAll() as $setting) {
|
||||
$this->settings[$setting->getName()] = $setting->getValue();
|
||||
}
|
||||
$this->loaded = true;
|
||||
}
|
||||
|
||||
private function getDefaultValue($keys) {
|
||||
$default = $this->getAllDefaults();
|
||||
foreach ($keys as $key) {
|
||||
if (array_key_exists($key, $default)) {
|
||||
$default = $default[$key];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
private function fetchValue($key) {
|
||||
$setting = $this->settingsRepository->findOneByName($key);
|
||||
return $setting ? $setting->getValue() : null;
|
||||
}
|
||||
|
||||
public function resetCache() {
|
||||
$this->settings = [];
|
||||
$this->loaded = false;
|
||||
}
|
||||
|
||||
public static function setInstance($instance) {
|
||||
self::$instance = $instance;
|
||||
}
|
||||
|
||||
/** @return SettingsController */
|
||||
public static function getInstance() {
|
||||
if (isset(self::$instance)) return self::$instance;
|
||||
return ContainerWrapper::getInstance()->get(SettingsController::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Settings;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Doctrine\Repository;
|
||||
use MailPoet\Entities\SettingEntity;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
use MailPoetVendor\Doctrine\ORM\Query;
|
||||
|
||||
/**
|
||||
* @extends Repository<SettingEntity>
|
||||
*/
|
||||
class SettingsRepository extends Repository {
|
||||
public function findOneByName(string $name): ?SettingEntity {
|
||||
// Always fetch fresh entity data (= don't use "findOneBy()"). See also further below.
|
||||
$result = (array)$this->doctrineRepository->createQueryBuilder('s')
|
||||
->where('s.name = :name')
|
||||
->setParameter('name', $name)
|
||||
->getQuery()
|
||||
->setHint(Query::HINT_REFRESH, true)
|
||||
->getResult();
|
||||
|
||||
return isset($result[0]) && $result[0] instanceof SettingEntity ? $result[0] : null;
|
||||
}
|
||||
|
||||
public function createOrUpdateByName($name, $value) {
|
||||
// Temporarily use low-level INSERT ... ON DUPLICATE KEY UPDATE query to avoid race conditions
|
||||
// between entity fetch and creation with multiple concurrent requests. This will be replaced
|
||||
// by a code solving atomicity of create-or-update on entity (ORM) level in a follow-up ticket.
|
||||
$now = Carbon::now()->millisecond(0);
|
||||
$tableName = $this->entityManager->getClassMetadata(SettingEntity::class)->getTableName();
|
||||
$this->entityManager->getConnection()->executeStatement("
|
||||
INSERT INTO $tableName (name, value, created_at, updated_at)
|
||||
VALUES (:name, :value, :now, :now)
|
||||
ON DUPLICATE KEY UPDATE value = :value, updated_at = :now
|
||||
", [
|
||||
'name' => $name,
|
||||
'value' => is_array($value) ? serialize($value) : $value,
|
||||
'now' => $now,
|
||||
]);
|
||||
|
||||
// Ensure entity data is up-to-date in memory.
|
||||
// - We can't use "refresh()"; we don't have the entity instance, and it can be a new record.
|
||||
// - We can't use "findOneBy()"; it could return a cached entity instance.
|
||||
$this->findOneByName($name);
|
||||
}
|
||||
|
||||
protected function getEntityClassName() {
|
||||
return SettingEntity::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Settings;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
class TrackingConfig {
|
||||
const LEVEL_FULL = 'full';
|
||||
const LEVEL_PARTIAL = 'partial';
|
||||
const LEVEL_BASIC = 'basic';
|
||||
|
||||
const OPENS_MERGED = 'merged';
|
||||
const OPENS_SEPARATED = 'separated';
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
public function __construct(
|
||||
SettingsController $settings
|
||||
) {
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function isEmailTrackingEnabled(string $level = null): bool {
|
||||
$level = $level ?? $this->settings->get('tracking.level', self::LEVEL_FULL);
|
||||
return in_array($level, [self::LEVEL_PARTIAL, self::LEVEL_FULL], true);
|
||||
}
|
||||
|
||||
public function isCookieTrackingEnabled(string $level = null): bool {
|
||||
$level = $level ?? $this->settings->get('tracking.level', self::LEVEL_FULL);
|
||||
return $level === self::LEVEL_FULL;
|
||||
}
|
||||
|
||||
public function areOpensMerged(string $opens = null): bool {
|
||||
$opens = $opens ?? $this->settings->get('tracking.opens', self::OPENS_MERGED);
|
||||
return $opens !== self::OPENS_SEPARATED;
|
||||
}
|
||||
|
||||
public function areOpensSeparated(string $opens = null): bool {
|
||||
return !$this->areOpensMerged($opens);
|
||||
}
|
||||
|
||||
public function getConfig(): array {
|
||||
return [
|
||||
'level' => $this->settings->get('tracking.level', self::LEVEL_FULL),
|
||||
'emailTrackingEnabled' => $this->isEmailTrackingEnabled(),
|
||||
'cookieTrackingEnabled' => $this->isCookieTrackingEnabled(),
|
||||
'opens' => $this->settings->get('tracking.opens', self::OPENS_MERGED),
|
||||
'opensMerged' => $this->areOpensMerged(),
|
||||
'opensSeparated' => $this->areOpensSeparated(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Settings;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\UserFlagEntity;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class UserFlagsController {
|
||||
const EMAIL_EDITOR_SURVEY = 'email_editor_survey_seen';
|
||||
|
||||
/** @var array|null */
|
||||
private $data = null;
|
||||
|
||||
/** @var array */
|
||||
private $defaults;
|
||||
|
||||
/** @var UserFlagsRepository */
|
||||
private $userFlagsRepository;
|
||||
|
||||
public function __construct(
|
||||
UserFlagsRepository $userFlagsRepository
|
||||
) {
|
||||
$this->defaults = [
|
||||
'editor_tutorial_seen' => false,
|
||||
'form_editor_tutorial_seen' => false,
|
||||
'display_new_form_editor_nps_survey' => false,
|
||||
'transactional_emails_opt_in_notice_dismissed' => false,
|
||||
'legacy_automations_notice_dismissed' => false,
|
||||
'legacy_automatic_emails_notice_dismissed' => false,
|
||||
self::EMAIL_EDITOR_SURVEY => null,
|
||||
];
|
||||
$this->userFlagsRepository = $userFlagsRepository;
|
||||
}
|
||||
|
||||
public function get($name) {
|
||||
$this->ensureLoaded();
|
||||
if (!isset($this->data[$name])) {
|
||||
return $this->defaults[$name];
|
||||
}
|
||||
return $this->data[$name];
|
||||
}
|
||||
|
||||
public function getAll() {
|
||||
$this->ensureLoaded();
|
||||
$data = $this->data;
|
||||
if (!is_array($data)) {
|
||||
$data = [];
|
||||
}
|
||||
return array_merge($this->defaults, $data);
|
||||
}
|
||||
|
||||
public function set($name, $value) {
|
||||
$currentUserId = WPFunctions::get()->getCurrentUserId();
|
||||
$flag = $this->userFlagsRepository->findOneBy([
|
||||
'userId' => $currentUserId,
|
||||
'name' => $name,
|
||||
]);
|
||||
|
||||
if (!$flag) {
|
||||
$flag = new UserFlagEntity();
|
||||
$flag->setUserId($currentUserId);
|
||||
$flag->setName($name);
|
||||
$this->userFlagsRepository->persist($flag);
|
||||
}
|
||||
$flag->setValue($value);
|
||||
$this->userFlagsRepository->flush();
|
||||
|
||||
if ($this->isLoaded()) {
|
||||
$this->data[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function delete($name) {
|
||||
$currentUserId = WPFunctions::get()->getCurrentUserId();
|
||||
$flag = $this->userFlagsRepository->findOneBy([
|
||||
'userId' => $currentUserId,
|
||||
'name' => $name,
|
||||
]);
|
||||
|
||||
if (!$flag) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->userFlagsRepository->remove($flag);
|
||||
$this->userFlagsRepository->flush();
|
||||
|
||||
if ($this->isLoaded()) {
|
||||
unset($this->data[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
private function load() {
|
||||
$currentUserId = WPFunctions::get()->getCurrentUserId();
|
||||
$flags = $this->userFlagsRepository->findBy(['userId' => $currentUserId]);
|
||||
$this->data = [];
|
||||
foreach ($flags as $flag) {
|
||||
$this->data[$flag->getName()] = $flag->getValue();
|
||||
}
|
||||
}
|
||||
|
||||
private function isLoaded() {
|
||||
return $this->data !== null;
|
||||
}
|
||||
|
||||
private function ensureLoaded() {
|
||||
if (!$this->isLoaded()) {
|
||||
$this->load();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Settings;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Doctrine\Repository;
|
||||
use MailPoet\Entities\UserFlagEntity;
|
||||
|
||||
/**
|
||||
* @extends Repository<UserFlagEntity>
|
||||
*/
|
||||
class UserFlagsRepository extends Repository {
|
||||
protected function getEntityClassName() {
|
||||
return UserFlagEntity::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
Reference in New Issue
Block a user