init
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
// autoload.php @generated by Composer
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
return ComposerAutoloaderInit9957467448f215809ac9dc8fbcab2b00::getLoader();
|
||||
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
namespace Composer\Autoload;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ClassLoader
|
||||
{
|
||||
private static $includeFile;
|
||||
private $vendorDir;
|
||||
// PSR-4
|
||||
private $prefixLengthsPsr4 = array();
|
||||
private $prefixDirsPsr4 = array();
|
||||
private $fallbackDirsPsr4 = array();
|
||||
// PSR-0
|
||||
private $prefixesPsr0 = array();
|
||||
private $fallbackDirsPsr0 = array();
|
||||
private $useIncludePath = false;
|
||||
private $classMap = array();
|
||||
private $classMapAuthoritative = false;
|
||||
private $missingClasses = array();
|
||||
private $apcuPrefix;
|
||||
private static $registeredLoaders = array();
|
||||
public function __construct($vendorDir = null)
|
||||
{
|
||||
$this->vendorDir = $vendorDir;
|
||||
self::initializeIncludeClosure();
|
||||
}
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
||||
}
|
||||
return array();
|
||||
}
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = $paths;
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
$paths
|
||||
);
|
||||
}
|
||||
}
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
$paths
|
||||
);
|
||||
}
|
||||
}
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
||||
}
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
if (null === $this->vendorDir) {
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
|
||||
} else {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
self::$registeredLoaders[$this->vendorDir] = $this;
|
||||
}
|
||||
}
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
if (null !== $this->vendorDir) {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
}
|
||||
}
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
$includeFile = self::$includeFile;
|
||||
$includeFile($file);
|
||||
return true;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
public static function getRegisteredLoaders()
|
||||
{
|
||||
return self::$registeredLoaders;
|
||||
}
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath . '\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
if (file_exists($file = $dir . $pathEnd)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private static function initializeIncludeClosure()
|
||||
{
|
||||
if (self::$includeFile !== null) {
|
||||
return;
|
||||
}
|
||||
self::$includeFile = \Closure::bind(static function($file) {
|
||||
include $file;
|
||||
}, null, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
namespace Composer;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Composer\Semver\VersionParser;
|
||||
class InstalledVersions
|
||||
{
|
||||
private static $installed;
|
||||
private static $canGetVendors;
|
||||
private static $installedByVendor = array();
|
||||
public static function getInstalledPackages()
|
||||
{
|
||||
$packages = array();
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
$packages[] = array_keys($installed['versions']);
|
||||
}
|
||||
if (1 === \count($packages)) {
|
||||
return $packages[0];
|
||||
}
|
||||
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
|
||||
}
|
||||
public static function getInstalledPackagesByType($type)
|
||||
{
|
||||
$packagesByType = array();
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
foreach ($installed['versions'] as $name => $package) {
|
||||
if (isset($package['type']) && $package['type'] === $type) {
|
||||
$packagesByType[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $packagesByType;
|
||||
}
|
||||
public static function isInstalled($packageName, $includeDevRequirements = true)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (isset($installed['versions'][$packageName])) {
|
||||
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
||||
{
|
||||
$constraint = $parser->parseConstraints((string) $constraint);
|
||||
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
||||
return $provided->matches($constraint);
|
||||
}
|
||||
public static function getVersionRanges($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
$ranges = array();
|
||||
if (isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
|
||||
}
|
||||
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
|
||||
}
|
||||
if (array_key_exists('provided', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
|
||||
}
|
||||
return implode(' || ', $ranges);
|
||||
}
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
public static function getVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($installed['versions'][$packageName]['version'])) {
|
||||
return null;
|
||||
}
|
||||
return $installed['versions'][$packageName]['version'];
|
||||
}
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
public static function getPrettyVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
return null;
|
||||
}
|
||||
return $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
public static function getReference($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($installed['versions'][$packageName]['reference'])) {
|
||||
return null;
|
||||
}
|
||||
return $installed['versions'][$packageName]['reference'];
|
||||
}
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
public static function getInstallPath($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
|
||||
}
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
public static function getRootPackage()
|
||||
{
|
||||
$installed = self::getInstalled();
|
||||
return $installed[0]['root'];
|
||||
}
|
||||
public static function getRawData()
|
||||
{
|
||||
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
|
||||
if (null === self::$installed) {
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
self::$installed = include __DIR__ . '/installed.php';
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
return self::$installed;
|
||||
}
|
||||
public static function getAllRawData()
|
||||
{
|
||||
return self::getInstalled();
|
||||
}
|
||||
public static function reload($data)
|
||||
{
|
||||
self::$installed = $data;
|
||||
self::$installedByVendor = array();
|
||||
}
|
||||
private static function getInstalled()
|
||||
{
|
||||
if (null === self::$canGetVendors) {
|
||||
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
|
||||
}
|
||||
$installed = array();
|
||||
if (self::$canGetVendors) {
|
||||
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir];
|
||||
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||
$required = require $vendorDir.'/composer/installed.php';
|
||||
$installed[] = self::$installedByVendor[$vendorDir] = $required;
|
||||
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
|
||||
self::$installed = $installed[count($installed) - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (null === self::$installed) {
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
$required = require __DIR__ . '/installed.php';
|
||||
self::$installed = $required;
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
if (self::$installed !== array()) {
|
||||
$installed[] = self::$installed;
|
||||
}
|
||||
return $installed;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
// autoload_files.php @generated by Composer
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
return array(
|
||||
'1f155afceeab454b94183cdd8d5248a6' => $vendorDir . '/mixpanel/mixpanel-php/lib/Mixpanel.php',
|
||||
'e65c52a9094ad098d9369d5cad5421fa' => $baseDir . '/lib/exceptions.php',
|
||||
'4407d0da6f795e11445a92772e35fd6a' => $baseDir . '/vendor-prefixed/symfony/polyfill-ctype/bootstrap.php',
|
||||
'766fdd1f275feb3306ba0045c4fcf600' => $baseDir . '/vendor-prefixed/symfony/polyfill-iconv/bootstrap.php',
|
||||
'24027dad1c10324ab40c497c832a3fa2' => $baseDir . '/vendor-prefixed/symfony/polyfill-mbstring/bootstrap.php',
|
||||
'30cb785a37f15f59296ced870db7effb' => $baseDir . '/vendor-prefixed/symfony/polyfill-php80/bootstrap.php',
|
||||
'01435227b0ecc712eca8e297034a558a' => $baseDir . '/vendor-prefixed/symfony/polyfill-php81/bootstrap.php',
|
||||
'720b32d405dc3da98c8232fcdb9b60ba' => $baseDir . '/vendor-prefixed/symfony/polyfill-intl-idn/bootstrap.php',
|
||||
'a39f667d2b6290b6a94a9a0abd6bc10e' => $baseDir . '/vendor-prefixed/symfony/polyfill-intl-normalizer/bootstrap.php',
|
||||
);
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
return array(
|
||||
);
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
// autoload_psr4.php @generated by Composer
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
return array(
|
||||
'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
|
||||
'MailPoet\\' => array($baseDir . '/lib'),
|
||||
'MailPoetVendor\\' => array($baseDir . '/vendor-prefixed'),
|
||||
'MailPoetGenerated\\' => array($baseDir . '/generated'),
|
||||
'Cron\\' => array($vendorDir . '/dragonmantank/cron-expression/src/Cron'),
|
||||
);
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
// autoload_real.php @generated by Composer
|
||||
class ComposerAutoloaderInit9957467448f215809ac9dc8fbcab2b00
|
||||
{
|
||||
private static $loader;
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
require __DIR__ . '/platform_check.php';
|
||||
spl_autoload_register(array('ComposerAutoloaderInit9957467448f215809ac9dc8fbcab2b00', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit9957467448f215809ac9dc8fbcab2b00', 'loadClassLoader'));
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit9957467448f215809ac9dc8fbcab2b00::getInitializer($loader));
|
||||
$loader->register(true);
|
||||
$filesToLoad = \Composer\Autoload\ComposerStaticInit9957467448f215809ac9dc8fbcab2b00::$files;
|
||||
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
|
||||
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
|
||||
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
|
||||
require $file;
|
||||
}
|
||||
}, null, null);
|
||||
foreach ($filesToLoad as $fileIdentifier => $file) {
|
||||
$requireFile($fileIdentifier, $file);
|
||||
}
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,273 @@
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"name": "dragonmantank/cron-expression",
|
||||
"version": "v3.3.3",
|
||||
"version_normalized": "3.3.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dragonmantank/cron-expression.git",
|
||||
"reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/adfb1f505deb6384dc8b39804c5065dd3c8c8c0a",
|
||||
"reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2|^8.0",
|
||||
"webmozart/assert": "^1.0"
|
||||
},
|
||||
"replace": {
|
||||
"mtdowling/cron-expression": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "^1.0",
|
||||
"phpstan/phpstan-webmozart-assert": "^1.0",
|
||||
"phpunit/phpunit": "^7.0|^8.0|^9.0"
|
||||
},
|
||||
"time": "2023-08-10T19:36:49+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Cron\\": "src/Cron/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Chris Tankersley",
|
||||
"email": "chris@ctankersley.com",
|
||||
"homepage": "https://github.com/dragonmantank"
|
||||
}
|
||||
],
|
||||
"description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
|
||||
"keywords": [
|
||||
"cron",
|
||||
"schedule"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/dragonmantank/cron-expression/issues",
|
||||
"source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/dragonmantank",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"install-path": "../dragonmantank/cron-expression"
|
||||
},
|
||||
{
|
||||
"name": "mailpoet/email-editor",
|
||||
"version": "dev-trunk",
|
||||
"version_normalized": "dev-trunk",
|
||||
"dist": {
|
||||
"type": "path",
|
||||
"url": "../packages/php/email-editor",
|
||||
"reference": "311798cfd57b26bb5df1fc7f97b5732e45603419"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.4"
|
||||
},
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"classmap": [
|
||||
"tests/unit/"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"unit-test": [
|
||||
"../../../tests_env/vendor/bin/codecept run unit"
|
||||
],
|
||||
"integration-test": [
|
||||
"cd ../../../tests_env/docker && COMPOSE_HTTP_TIMEOUT=200 docker compose run -e SKIP_DEPS=1 -e SKIP_PLUGINS=1 -e PACKAGE_NAME=email-editor codeception_integration"
|
||||
],
|
||||
"code-style": [
|
||||
"../../../mailpoet/tasks/code_sniffer/vendor/bin/phpcs -ps"
|
||||
],
|
||||
"code-style-fix": [
|
||||
"../../../mailpoet/tasks/code_sniffer/vendor/bin/phpcbf -p"
|
||||
]
|
||||
},
|
||||
"description": "Email editor based on WordPress Gutenberg package.",
|
||||
"transport-options": {
|
||||
"relative": true
|
||||
},
|
||||
"install-path": "../mailpoet/email-editor"
|
||||
},
|
||||
{
|
||||
"name": "mixpanel/mixpanel-php",
|
||||
"version": "2.11.0",
|
||||
"version_normalized": "2.11.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mixpanel/mixpanel-php.git",
|
||||
"reference": "4b0fafacf2129eff5d50721e129b07f0c32687e7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mixpanel/mixpanel-php/zipball/4b0fafacf2129eff5d50721e129b07f0c32687e7",
|
||||
"reference": "4b0fafacf2129eff5d50721e129b07f0c32687e7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpdocumentor/phpdocumentor": "2.9.*",
|
||||
"phpunit/phpunit": "5.6.*"
|
||||
},
|
||||
"time": "2023-04-11T23:03:57+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"lib/Mixpanel.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Mixpanel <dev@mixpanel.com>",
|
||||
"homepage": "https://mixpanel.com/"
|
||||
}
|
||||
],
|
||||
"description": "The Official PHP library for Mixpanel",
|
||||
"homepage": "https://mixpanel.com/help/reference/php",
|
||||
"keywords": [
|
||||
"mixpanel",
|
||||
"mixpanel php"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/mixpanel/mixpanel-php/issues",
|
||||
"source": "https://github.com/mixpanel/mixpanel-php/tree/2.11.0"
|
||||
},
|
||||
"install-path": "../mixpanel/mixpanel-php"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
"version": "1.11.0",
|
||||
"version_normalized": "1.11.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/webmozarts/assert.git",
|
||||
"reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991",
|
||||
"reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-ctype": "*",
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"phpstan/phpstan": "<0.12.20",
|
||||
"vimeo/psalm": "<4.6.1 || 4.6.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5.13"
|
||||
},
|
||||
"time": "2022-06-03T18:03:27+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.10-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Webmozart\\Assert\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Bernhard Schussek",
|
||||
"email": "bschussek@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Assertions to validate method input/output with nice error messages.",
|
||||
"keywords": [
|
||||
"assert",
|
||||
"check",
|
||||
"validate"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/webmozarts/assert/issues",
|
||||
"source": "https://github.com/webmozarts/assert/tree/1.11.0"
|
||||
},
|
||||
"install-path": "../webmozart/assert"
|
||||
},
|
||||
{
|
||||
"name": "woocommerce/action-scheduler",
|
||||
"version": "3.8.0",
|
||||
"version_normalized": "3.8.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/woocommerce/action-scheduler.git",
|
||||
"reference": "99cd7981f51c98883082534d4852491858d72834"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/99cd7981f51c98883082534d4852491858d72834",
|
||||
"reference": "99cd7981f51c98883082534d4852491858d72834",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.5",
|
||||
"woocommerce/woocommerce-sniffs": "0.1.0",
|
||||
"wp-cli/wp-cli": "~2.5.0",
|
||||
"yoast/phpunit-polyfills": "^2.0"
|
||||
},
|
||||
"time": "2024-05-22T13:50:29+00:00",
|
||||
"type": "wordpress-plugin",
|
||||
"extra": {
|
||||
"scripts-description": {
|
||||
"test": "Run unit tests",
|
||||
"phpcs": "Analyze code against the WordPress coding standards with PHP_CodeSniffer",
|
||||
"phpcbf": "Fix coding standards warnings/errors automatically with PHP Code Beautifier"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"GPL-3.0-or-later"
|
||||
],
|
||||
"description": "Action Scheduler for WordPress and WooCommerce",
|
||||
"homepage": "https://actionscheduler.org/",
|
||||
"support": {
|
||||
"issues": "https://github.com/woocommerce/action-scheduler/issues",
|
||||
"source": "https://github.com/woocommerce/action-scheduler/tree/3.8.0"
|
||||
},
|
||||
"install-path": "../woocommerce/action-scheduler"
|
||||
}
|
||||
],
|
||||
"dev": false,
|
||||
"dev-package-names": []
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
return array(
|
||||
'root' => array(
|
||||
'name' => '__root__',
|
||||
'pretty_version' => 'dev-trunk',
|
||||
'version' => 'dev-trunk',
|
||||
'reference' => '6b7f6af6aa82d76ed2d7b4772298c76ae1bac7f4',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev' => false,
|
||||
),
|
||||
'versions' => array(
|
||||
'__root__' => array(
|
||||
'pretty_version' => 'dev-trunk',
|
||||
'version' => 'dev-trunk',
|
||||
'reference' => '6b7f6af6aa82d76ed2d7b4772298c76ae1bac7f4',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'dragonmantank/cron-expression' => array(
|
||||
'pretty_version' => 'v3.3.3',
|
||||
'version' => '3.3.3.0',
|
||||
'reference' => 'adfb1f505deb6384dc8b39804c5065dd3c8c8c0a',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../dragonmantank/cron-expression',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'mailpoet/email-editor' => array(
|
||||
'pretty_version' => 'dev-trunk',
|
||||
'version' => 'dev-trunk',
|
||||
'reference' => '311798cfd57b26bb5df1fc7f97b5732e45603419',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../mailpoet/email-editor',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'mixpanel/mixpanel-php' => array(
|
||||
'pretty_version' => '2.11.0',
|
||||
'version' => '2.11.0.0',
|
||||
'reference' => '4b0fafacf2129eff5d50721e129b07f0c32687e7',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../mixpanel/mixpanel-php',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'mtdowling/cron-expression' => array(
|
||||
'dev_requirement' => false,
|
||||
'replaced' => array(
|
||||
0 => '^1.0',
|
||||
),
|
||||
),
|
||||
'webmozart/assert' => array(
|
||||
'pretty_version' => '1.11.0',
|
||||
'version' => '1.11.0.0',
|
||||
'reference' => '11cb2199493b2f8a3b53e7f19068fc6aac760991',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../webmozart/assert',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'woocommerce/action-scheduler' => array(
|
||||
'pretty_version' => '3.8.0',
|
||||
'version' => '3.8.0.0',
|
||||
'reference' => '99cd7981f51c98883082534d4852491858d72834',
|
||||
'type' => 'wordpress-plugin',
|
||||
'install_path' => __DIR__ . '/../woocommerce/action-scheduler',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
// platform_check.php @generated by Composer
|
||||
$issues = array();
|
||||
if (!(PHP_VERSION_ID >= 70400)) {
|
||||
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.';
|
||||
}
|
||||
if ($issues) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
|
||||
} elseif (!headers_sent()) {
|
||||
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues),
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
Vendored
+204
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace Cron;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use DateTimeInterface;
|
||||
abstract class AbstractField implements FieldInterface
|
||||
{
|
||||
protected $fullRange = [];
|
||||
protected $literals = [];
|
||||
protected $rangeStart;
|
||||
protected $rangeEnd;
|
||||
public function __construct()
|
||||
{
|
||||
$this->fullRange = range($this->rangeStart, $this->rangeEnd);
|
||||
}
|
||||
public function isSatisfied(int $dateValue, string $value): bool
|
||||
{
|
||||
if ($this->isIncrementsOfRanges($value)) {
|
||||
return $this->isInIncrementsOfRanges($dateValue, $value);
|
||||
}
|
||||
if ($this->isRange($value)) {
|
||||
return $this->isInRange($dateValue, $value);
|
||||
}
|
||||
return '*' === $value || $dateValue === (int) $value;
|
||||
}
|
||||
public function isRange(string $value): bool
|
||||
{
|
||||
return false !== strpos($value, '-');
|
||||
}
|
||||
public function isIncrementsOfRanges(string $value): bool
|
||||
{
|
||||
return false !== strpos($value, '/');
|
||||
}
|
||||
public function isInRange(int $dateValue, $value): bool
|
||||
{
|
||||
$parts = array_map(
|
||||
function ($value) {
|
||||
$value = trim($value);
|
||||
return $this->convertLiterals($value);
|
||||
},
|
||||
explode('-', $value, 2)
|
||||
);
|
||||
return $dateValue >= $parts[0] && $dateValue <= $parts[1];
|
||||
}
|
||||
public function isInIncrementsOfRanges(int $dateValue, string $value): bool
|
||||
{
|
||||
$chunks = array_map('trim', explode('/', $value, 2));
|
||||
$range = $chunks[0];
|
||||
$step = $chunks[1] ?? 0;
|
||||
// No step or 0 steps aren't cool
|
||||
if (null === $step || '0' === $step || 0 === $step) {
|
||||
return false;
|
||||
}
|
||||
// Expand the * to a full range
|
||||
if ('*' === $range) {
|
||||
$range = $this->rangeStart . '-' . $this->rangeEnd;
|
||||
}
|
||||
// Generate the requested small range
|
||||
$rangeChunks = explode('-', $range, 2);
|
||||
$rangeStart = (int) $rangeChunks[0];
|
||||
$rangeEnd = $rangeChunks[1] ?? $rangeStart;
|
||||
$rangeEnd = (int) $rangeEnd;
|
||||
if ($rangeStart < $this->rangeStart || $rangeStart > $this->rangeEnd || $rangeStart > $rangeEnd) {
|
||||
throw new \OutOfRangeException('Invalid range start requested');
|
||||
}
|
||||
if ($rangeEnd < $this->rangeStart || $rangeEnd > $this->rangeEnd || $rangeEnd < $rangeStart) {
|
||||
throw new \OutOfRangeException('Invalid range end requested');
|
||||
}
|
||||
// Steps larger than the range need to wrap around and be handled
|
||||
// slightly differently than smaller steps
|
||||
// UPDATE - This is actually false. The C implementation will allow a
|
||||
// larger step as valid syntax, it never wraps around. It will stop
|
||||
// once it hits the end. Unfortunately this means in future versions
|
||||
// we will not wrap around. However, because the logic exists today
|
||||
// per the above documentation, fixing the bug from #89
|
||||
if ($step > $this->rangeEnd) {
|
||||
$thisRange = [$this->fullRange[$step % \count($this->fullRange)]];
|
||||
} else {
|
||||
if ($step > ($rangeEnd - $rangeStart)) {
|
||||
$thisRange[$rangeStart] = (int) $rangeStart;
|
||||
} else {
|
||||
$thisRange = range($rangeStart, $rangeEnd, (int) $step);
|
||||
}
|
||||
}
|
||||
return \in_array($dateValue, $thisRange, true);
|
||||
}
|
||||
public function getRangeForExpression(string $expression, int $max): array
|
||||
{
|
||||
$values = [];
|
||||
$expression = $this->convertLiterals($expression);
|
||||
if (false !== strpos($expression, ',')) {
|
||||
$ranges = explode(',', $expression);
|
||||
$values = [];
|
||||
foreach ($ranges as $range) {
|
||||
$expanded = $this->getRangeForExpression($range, $this->rangeEnd);
|
||||
$values = array_merge($values, $expanded);
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
if ($this->isRange($expression) || $this->isIncrementsOfRanges($expression)) {
|
||||
if (!$this->isIncrementsOfRanges($expression)) {
|
||||
[$offset, $to] = explode('-', $expression);
|
||||
$offset = $this->convertLiterals($offset);
|
||||
$to = $this->convertLiterals($to);
|
||||
$stepSize = 1;
|
||||
} else {
|
||||
$range = array_map('trim', explode('/', $expression, 2));
|
||||
$stepSize = $range[1] ?? 0;
|
||||
$range = $range[0];
|
||||
$range = explode('-', $range, 2);
|
||||
$offset = $range[0];
|
||||
$to = $range[1] ?? $max;
|
||||
}
|
||||
$offset = '*' === $offset ? $this->rangeStart : $offset;
|
||||
if ($stepSize >= $this->rangeEnd) {
|
||||
$values = [$this->fullRange[$stepSize % \count($this->fullRange)]];
|
||||
} else {
|
||||
for ($i = $offset; $i <= $to; $i += $stepSize) {
|
||||
$values[] = (int) $i;
|
||||
}
|
||||
}
|
||||
sort($values);
|
||||
} else {
|
||||
$values = [$expression];
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
protected function convertLiterals(string $value): string
|
||||
{
|
||||
if (\count($this->literals)) {
|
||||
$key = array_search(strtoupper($value), $this->literals, true);
|
||||
if (false !== $key) {
|
||||
return (string) $key;
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
public function validate(string $value): bool
|
||||
{
|
||||
$value = $this->convertLiterals($value);
|
||||
// All fields allow * as a valid value
|
||||
if ('*' === $value) {
|
||||
return true;
|
||||
}
|
||||
// Validate each chunk of a list individually
|
||||
if (false !== strpos($value, ',')) {
|
||||
foreach (explode(',', $value) as $listItem) {
|
||||
if (!$this->validate($listItem)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (false !== strpos($value, '/')) {
|
||||
[$range, $step] = explode('/', $value);
|
||||
// Don't allow numeric ranges
|
||||
if (is_numeric($range)) {
|
||||
return false;
|
||||
}
|
||||
return $this->validate($range) && filter_var($step, FILTER_VALIDATE_INT);
|
||||
}
|
||||
if (false !== strpos($value, '-')) {
|
||||
if (substr_count($value, '-') > 1) {
|
||||
return false;
|
||||
}
|
||||
$chunks = explode('-', $value);
|
||||
$chunks[0] = $this->convertLiterals($chunks[0]);
|
||||
$chunks[1] = $this->convertLiterals($chunks[1]);
|
||||
if ('*' === $chunks[0] || '*' === $chunks[1]) {
|
||||
return false;
|
||||
}
|
||||
return $this->validate($chunks[0]) && $this->validate($chunks[1]);
|
||||
}
|
||||
if (!is_numeric($value)) {
|
||||
return false;
|
||||
}
|
||||
if (false !== strpos($value, '.')) {
|
||||
return false;
|
||||
}
|
||||
// We should have a numeric by now, so coerce this into an integer
|
||||
$value = (int) $value;
|
||||
return \in_array($value, $this->fullRange, true);
|
||||
}
|
||||
protected function timezoneSafeModify(DateTimeInterface $dt, string $modification): DateTimeInterface
|
||||
{
|
||||
$timezone = $dt->getTimezone();
|
||||
$dt = $dt->setTimezone(new \DateTimeZone("UTC"));
|
||||
$dt = $dt->modify($modification);
|
||||
$dt = $dt->setTimezone($timezone);
|
||||
return $dt;
|
||||
}
|
||||
protected function setTimeHour(DateTimeInterface $date, bool $invert, int $originalTimestamp): DateTimeInterface
|
||||
{
|
||||
$date = $date->setTime((int)$date->format('H'), ($invert ? 59 : 0));
|
||||
// setTime caused the offset to change, moving time in the wrong direction
|
||||
$actualTimestamp = $date->format('U');
|
||||
if ((! $invert) && ($actualTimestamp <= $originalTimestamp)) {
|
||||
$date = $this->timezoneSafeModify($date, "+1 hour");
|
||||
} elseif ($invert && ($actualTimestamp >= $originalTimestamp)) {
|
||||
$date = $this->timezoneSafeModify($date, "-1 hour");
|
||||
}
|
||||
return $date;
|
||||
}
|
||||
}
|
||||
Vendored
+308
@@ -0,0 +1,308 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace Cron;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use DateTimeZone;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
use Webmozart\Assert\Assert;
|
||||
class CronExpression
|
||||
{
|
||||
public const MINUTE = 0;
|
||||
public const HOUR = 1;
|
||||
public const DAY = 2;
|
||||
public const MONTH = 3;
|
||||
public const WEEKDAY = 4;
|
||||
public const YEAR = 5;
|
||||
public const MAPPINGS = [
|
||||
'@yearly' => '0 0 1 1 *',
|
||||
'@annually' => '0 0 1 1 *',
|
||||
'@monthly' => '0 0 1 * *',
|
||||
'@weekly' => '0 0 * * 0',
|
||||
'@daily' => '0 0 * * *',
|
||||
'@midnight' => '0 0 * * *',
|
||||
'@hourly' => '0 * * * *',
|
||||
];
|
||||
protected $cronParts;
|
||||
protected $fieldFactory;
|
||||
protected $maxIterationCount = 1000;
|
||||
protected static $order = [
|
||||
self::YEAR,
|
||||
self::MONTH,
|
||||
self::DAY,
|
||||
self::WEEKDAY,
|
||||
self::HOUR,
|
||||
self::MINUTE,
|
||||
];
|
||||
private static $registeredAliases = self::MAPPINGS;
|
||||
public static function registerAlias(string $alias, string $expression): void
|
||||
{
|
||||
try {
|
||||
new self($expression);
|
||||
} catch (InvalidArgumentException $exception) {
|
||||
throw new LogicException("The expression `$expression` is invalid", 0, $exception);
|
||||
}
|
||||
$shortcut = strtolower($alias);
|
||||
if (1 !== preg_match('/^@\w+$/', $shortcut)) {
|
||||
throw new LogicException("The alias `$alias` is invalid. It must start with an `@` character and contain alphanumeric (letters, numbers, regardless of case) plus underscore (_).");
|
||||
}
|
||||
if (isset(self::$registeredAliases[$shortcut])) {
|
||||
throw new LogicException("The alias `$alias` is already registered.");
|
||||
}
|
||||
self::$registeredAliases[$shortcut] = $expression;
|
||||
}
|
||||
public static function unregisterAlias(string $alias): bool
|
||||
{
|
||||
$shortcut = strtolower($alias);
|
||||
if (isset(self::MAPPINGS[$shortcut])) {
|
||||
throw new LogicException("The alias `$alias` is a built-in alias; it can not be unregistered.");
|
||||
}
|
||||
if (!isset(self::$registeredAliases[$shortcut])) {
|
||||
return false;
|
||||
}
|
||||
unset(self::$registeredAliases[$shortcut]);
|
||||
return true;
|
||||
}
|
||||
public static function supportsAlias(string $alias): bool
|
||||
{
|
||||
return isset(self::$registeredAliases[strtolower($alias)]);
|
||||
}
|
||||
public static function getAliases(): array
|
||||
{
|
||||
return self::$registeredAliases;
|
||||
}
|
||||
public static function factory(string $expression, FieldFactoryInterface $fieldFactory = null): CronExpression
|
||||
{
|
||||
return new static($expression, $fieldFactory);
|
||||
}
|
||||
public static function isValidExpression(string $expression): bool
|
||||
{
|
||||
try {
|
||||
new CronExpression($expression);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public function __construct(string $expression, FieldFactoryInterface $fieldFactory = null)
|
||||
{
|
||||
$shortcut = strtolower($expression);
|
||||
$expression = self::$registeredAliases[$shortcut] ?? $expression;
|
||||
$this->fieldFactory = $fieldFactory ?: new FieldFactory();
|
||||
$this->setExpression($expression);
|
||||
}
|
||||
public function setExpression(string $value): CronExpression
|
||||
{
|
||||
$split = preg_split('/\s/', $value, -1, PREG_SPLIT_NO_EMPTY);
|
||||
Assert::isArray($split);
|
||||
$notEnoughParts = \count($split) < 5;
|
||||
$questionMarkInInvalidPart = array_key_exists(0, $split) && $split[0] === '?'
|
||||
|| array_key_exists(1, $split) && $split[1] === '?'
|
||||
|| array_key_exists(3, $split) && $split[3] === '?';
|
||||
$tooManyQuestionMarks = array_key_exists(2, $split) && $split[2] === '?'
|
||||
&& array_key_exists(4, $split) && $split[4] === '?';
|
||||
if ($notEnoughParts || $questionMarkInInvalidPart || $tooManyQuestionMarks) {
|
||||
throw new InvalidArgumentException(
|
||||
$value . ' is not a valid CRON expression'
|
||||
);
|
||||
}
|
||||
$this->cronParts = $split;
|
||||
foreach ($this->cronParts as $position => $part) {
|
||||
$this->setPart($position, $part);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
public function setPart(int $position, string $value): CronExpression
|
||||
{
|
||||
if (!$this->fieldFactory->getField($position)->validate($value)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Invalid CRON field value ' . $value . ' at position ' . $position
|
||||
);
|
||||
}
|
||||
$this->cronParts[$position] = $value;
|
||||
return $this;
|
||||
}
|
||||
public function setMaxIterationCount(int $maxIterationCount): CronExpression
|
||||
{
|
||||
$this->maxIterationCount = $maxIterationCount;
|
||||
return $this;
|
||||
}
|
||||
public function getNextRunDate($currentTime = 'now', int $nth = 0, bool $allowCurrentDate = false, $timeZone = null): DateTime
|
||||
{
|
||||
return $this->getRunDate($currentTime, $nth, false, $allowCurrentDate, $timeZone);
|
||||
}
|
||||
public function getPreviousRunDate($currentTime = 'now', int $nth = 0, bool $allowCurrentDate = false, $timeZone = null): DateTime
|
||||
{
|
||||
return $this->getRunDate($currentTime, $nth, true, $allowCurrentDate, $timeZone);
|
||||
}
|
||||
public function getMultipleRunDates(int $total, $currentTime = 'now', bool $invert = false, bool $allowCurrentDate = false, $timeZone = null): array
|
||||
{
|
||||
$timeZone = $this->determineTimeZone($currentTime, $timeZone);
|
||||
if ('now' === $currentTime) {
|
||||
$currentTime = new DateTime();
|
||||
} elseif ($currentTime instanceof DateTime) {
|
||||
$currentTime = clone $currentTime;
|
||||
} elseif ($currentTime instanceof DateTimeImmutable) {
|
||||
$currentTime = DateTime::createFromFormat('U', $currentTime->format('U'));
|
||||
} elseif (\is_string($currentTime)) {
|
||||
$currentTime = new DateTime($currentTime);
|
||||
}
|
||||
Assert::isInstanceOf($currentTime, DateTime::class);
|
||||
$currentTime->setTimezone(new DateTimeZone($timeZone));
|
||||
$matches = [];
|
||||
for ($i = 0; $i < $total; ++$i) {
|
||||
try {
|
||||
$result = $this->getRunDate($currentTime, 0, $invert, $allowCurrentDate, $timeZone);
|
||||
} catch (RuntimeException $e) {
|
||||
break;
|
||||
}
|
||||
$allowCurrentDate = false;
|
||||
$currentTime = clone $result;
|
||||
$matches[] = $result;
|
||||
}
|
||||
return $matches;
|
||||
}
|
||||
public function getExpression($part = null): ?string
|
||||
{
|
||||
if (null === $part) {
|
||||
return implode(' ', $this->cronParts);
|
||||
}
|
||||
if (array_key_exists($part, $this->cronParts)) {
|
||||
return $this->cronParts[$part];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function getParts()
|
||||
{
|
||||
return $this->cronParts;
|
||||
}
|
||||
public function __toString(): string
|
||||
{
|
||||
return (string) $this->getExpression();
|
||||
}
|
||||
public function isDue($currentTime = 'now', $timeZone = null): bool
|
||||
{
|
||||
$timeZone = $this->determineTimeZone($currentTime, $timeZone);
|
||||
if ('now' === $currentTime) {
|
||||
$currentTime = new DateTime();
|
||||
} elseif ($currentTime instanceof DateTime) {
|
||||
$currentTime = clone $currentTime;
|
||||
} elseif ($currentTime instanceof DateTimeImmutable) {
|
||||
$currentTime = DateTime::createFromFormat('U', $currentTime->format('U'));
|
||||
} elseif (\is_string($currentTime)) {
|
||||
$currentTime = new DateTime($currentTime);
|
||||
}
|
||||
Assert::isInstanceOf($currentTime, DateTime::class);
|
||||
$currentTime->setTimezone(new DateTimeZone($timeZone));
|
||||
// drop the seconds to 0
|
||||
$currentTime->setTime((int) $currentTime->format('H'), (int) $currentTime->format('i'), 0);
|
||||
try {
|
||||
return $this->getNextRunDate($currentTime, 0, true)->getTimestamp() === $currentTime->getTimestamp();
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
protected function getRunDate($currentTime = null, int $nth = 0, bool $invert = false, bool $allowCurrentDate = false, $timeZone = null): DateTime
|
||||
{
|
||||
$timeZone = $this->determineTimeZone($currentTime, $timeZone);
|
||||
if ($currentTime instanceof DateTime) {
|
||||
$currentDate = clone $currentTime;
|
||||
} elseif ($currentTime instanceof DateTimeImmutable) {
|
||||
$currentDate = DateTime::createFromFormat('U', $currentTime->format('U'));
|
||||
} elseif (\is_string($currentTime)) {
|
||||
$currentDate = new DateTime($currentTime);
|
||||
} else {
|
||||
$currentDate = new DateTime('now');
|
||||
}
|
||||
Assert::isInstanceOf($currentDate, DateTime::class);
|
||||
$currentDate->setTimezone(new DateTimeZone($timeZone));
|
||||
// Workaround for setTime causing an offset change: https://bugs.php.net/bug.php?id=81074
|
||||
$currentDate = DateTime::createFromFormat("!Y-m-d H:iO", $currentDate->format("Y-m-d H:iP"), $currentDate->getTimezone());
|
||||
if ($currentDate === false) {
|
||||
throw new \RuntimeException('Unable to create date from format');
|
||||
}
|
||||
$currentDate->setTimezone(new DateTimeZone($timeZone));
|
||||
$nextRun = clone $currentDate;
|
||||
// We don't have to satisfy * or null fields
|
||||
$parts = [];
|
||||
$fields = [];
|
||||
foreach (self::$order as $position) {
|
||||
$part = $this->getExpression($position);
|
||||
if (null === $part || '*' === $part) {
|
||||
continue;
|
||||
}
|
||||
$parts[$position] = $part;
|
||||
$fields[$position] = $this->fieldFactory->getField($position);
|
||||
}
|
||||
if (isset($parts[self::DAY]) && isset($parts[self::WEEKDAY])) {
|
||||
$domExpression = sprintf('%s %s %s %s *', $this->getExpression(0), $this->getExpression(1), $this->getExpression(2), $this->getExpression(3));
|
||||
$dowExpression = sprintf('%s %s * %s %s', $this->getExpression(0), $this->getExpression(1), $this->getExpression(3), $this->getExpression(4));
|
||||
$domExpression = new self($domExpression);
|
||||
$dowExpression = new self($dowExpression);
|
||||
$domRunDates = $domExpression->getMultipleRunDates($nth + 1, $currentTime, $invert, $allowCurrentDate, $timeZone);
|
||||
$dowRunDates = $dowExpression->getMultipleRunDates($nth + 1, $currentTime, $invert, $allowCurrentDate, $timeZone);
|
||||
if ($parts[self::DAY] === '?' || $parts[self::DAY] === '*') {
|
||||
$domRunDates = [];
|
||||
}
|
||||
if ($parts[self::WEEKDAY] === '?' || $parts[self::WEEKDAY] === '*') {
|
||||
$dowRunDates = [];
|
||||
}
|
||||
$combined = array_merge($domRunDates, $dowRunDates);
|
||||
usort($combined, function ($a, $b) {
|
||||
return $a->format('Y-m-d H:i:s') <=> $b->format('Y-m-d H:i:s');
|
||||
});
|
||||
if ($invert) {
|
||||
$combined = array_reverse($combined);
|
||||
}
|
||||
return $combined[$nth];
|
||||
}
|
||||
// Set a hard limit to bail on an impossible date
|
||||
for ($i = 0; $i < $this->maxIterationCount; ++$i) {
|
||||
foreach ($parts as $position => $part) {
|
||||
$satisfied = false;
|
||||
// Get the field object used to validate this part
|
||||
$field = $fields[$position];
|
||||
// Check if this is singular or a list
|
||||
if (false === strpos($part, ',')) {
|
||||
$satisfied = $field->isSatisfiedBy($nextRun, $part, $invert);
|
||||
} else {
|
||||
foreach (array_map('trim', explode(',', $part)) as $listPart) {
|
||||
if ($field->isSatisfiedBy($nextRun, $listPart, $invert)) {
|
||||
$satisfied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the field is not satisfied, then start over
|
||||
if (!$satisfied) {
|
||||
$field->increment($nextRun, $invert, $part);
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
// Skip this match if needed
|
||||
if ((!$allowCurrentDate && $nextRun == $currentDate) || --$nth > -1) {
|
||||
$this->fieldFactory->getField(self::MINUTE)->increment($nextRun, $invert, $parts[self::MINUTE] ?? null);
|
||||
continue;
|
||||
}
|
||||
return $nextRun;
|
||||
}
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new RuntimeException('Impossible CRON expression');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
protected function determineTimeZone($currentTime, ?string $timeZone): string
|
||||
{
|
||||
if (null !== $timeZone) {
|
||||
return $timeZone;
|
||||
}
|
||||
if ($currentTime instanceof DateTimeInterface) {
|
||||
return $currentTime->getTimezone()->getName();
|
||||
}
|
||||
return date_default_timezone_get();
|
||||
}
|
||||
}
|
||||
Vendored
+94
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace Cron;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use DateTime;
|
||||
use DateTimeInterface;
|
||||
class DayOfMonthField extends AbstractField
|
||||
{
|
||||
protected $rangeStart = 1;
|
||||
protected $rangeEnd = 31;
|
||||
private static function getNearestWeekday(int $currentYear, int $currentMonth, int $targetDay): ?DateTime
|
||||
{
|
||||
$tday = str_pad((string) $targetDay, 2, '0', STR_PAD_LEFT);
|
||||
$target = DateTime::createFromFormat('Y-m-d', "{$currentYear}-{$currentMonth}-{$tday}");
|
||||
if ($target === false) {
|
||||
return null;
|
||||
}
|
||||
$currentWeekday = (int) $target->format('N');
|
||||
if ($currentWeekday < 6) {
|
||||
return $target;
|
||||
}
|
||||
$lastDayOfMonth = $target->format('t');
|
||||
foreach ([-1, 1, -2, 2] as $i) {
|
||||
$adjusted = $targetDay + $i;
|
||||
if ($adjusted > 0 && $adjusted <= $lastDayOfMonth) {
|
||||
$target->setDate($currentYear, $currentMonth, $adjusted);
|
||||
if ((int) $target->format('N') < 6 && (int) $target->format('m') === $currentMonth) {
|
||||
return $target;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function isSatisfiedBy(DateTimeInterface $date, $value, bool $invert): bool
|
||||
{
|
||||
// ? states that the field value is to be skipped
|
||||
if ('?' === $value) {
|
||||
return true;
|
||||
}
|
||||
$fieldValue = $date->format('d');
|
||||
// Check to see if this is the last day of the month
|
||||
if ('L' === $value) {
|
||||
return $fieldValue === $date->format('t');
|
||||
}
|
||||
// Check to see if this is the nearest weekday to a particular value
|
||||
if ($wPosition = strpos($value, 'W')) {
|
||||
// Parse the target day
|
||||
$targetDay = (int) substr($value, 0, $wPosition);
|
||||
// Find out if the current day is the nearest day of the week
|
||||
$nearest = self::getNearestWeekday(
|
||||
(int) $date->format('Y'),
|
||||
(int) $date->format('m'),
|
||||
$targetDay
|
||||
);
|
||||
if ($nearest) {
|
||||
return $date->format('j') === $nearest->format('j');
|
||||
}
|
||||
throw new \RuntimeException('Unable to return nearest weekday');
|
||||
}
|
||||
return $this->isSatisfied((int) $date->format('d'), $value);
|
||||
}
|
||||
public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface
|
||||
{
|
||||
if (! $invert) {
|
||||
$date = $date->add(new \DateInterval('P1D'));
|
||||
$date = $date->setTime(0, 0);
|
||||
} else {
|
||||
$date = $date->sub(new \DateInterval('P1D'));
|
||||
$date = $date->setTime(23, 59);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
public function validate(string $value): bool
|
||||
{
|
||||
$basicChecks = parent::validate($value);
|
||||
// Validate that a list don't have W or L
|
||||
if (false !== strpos($value, ',') && (false !== strpos($value, 'W') || false !== strpos($value, 'L'))) {
|
||||
return false;
|
||||
}
|
||||
if (!$basicChecks) {
|
||||
if ('?' === $value) {
|
||||
return true;
|
||||
}
|
||||
if ('L' === $value) {
|
||||
return true;
|
||||
}
|
||||
if (preg_match('/^(.*)W$/', $value, $matches)) {
|
||||
return $this->validate($matches[1]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return $basicChecks;
|
||||
}
|
||||
}
|
||||
Vendored
+124
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace Cron;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use DateTimeInterface;
|
||||
use InvalidArgumentException;
|
||||
class DayOfWeekField extends AbstractField
|
||||
{
|
||||
protected $rangeStart = 0;
|
||||
protected $rangeEnd = 7;
|
||||
protected $nthRange;
|
||||
protected $literals = [1 => 'MON', 2 => 'TUE', 3 => 'WED', 4 => 'THU', 5 => 'FRI', 6 => 'SAT', 7 => 'SUN'];
|
||||
public function __construct()
|
||||
{
|
||||
$this->nthRange = range(1, 5);
|
||||
parent::__construct();
|
||||
}
|
||||
public function isSatisfiedBy(DateTimeInterface $date, $value, bool $invert): bool
|
||||
{
|
||||
if ('?' === $value) {
|
||||
return true;
|
||||
}
|
||||
// Convert text day of the week values to integers
|
||||
$value = $this->convertLiterals($value);
|
||||
$currentYear = (int) $date->format('Y');
|
||||
$currentMonth = (int) $date->format('m');
|
||||
$lastDayOfMonth = (int) $date->format('t');
|
||||
// Find out if this is the last specific weekday of the month
|
||||
if ($lPosition = strpos($value, 'L')) {
|
||||
$weekday = $this->convertLiterals(substr($value, 0, $lPosition));
|
||||
$weekday %= 7;
|
||||
$daysInMonth = (int) $date->format('t');
|
||||
$remainingDaysInMonth = $daysInMonth - (int) $date->format('d');
|
||||
return (($weekday === (int) $date->format('w')) && ($remainingDaysInMonth < 7));
|
||||
}
|
||||
// Handle # hash tokens
|
||||
if (strpos($value, '#')) {
|
||||
[$weekday, $nth] = explode('#', $value);
|
||||
if (!is_numeric($nth)) {
|
||||
throw new InvalidArgumentException("Hashed weekdays must be numeric, {$nth} given");
|
||||
} else {
|
||||
$nth = (int) $nth;
|
||||
}
|
||||
// 0 and 7 are both Sunday, however 7 matches date('N') format ISO-8601
|
||||
if ('0' === $weekday) {
|
||||
$weekday = 7;
|
||||
}
|
||||
$weekday = (int) $this->convertLiterals((string) $weekday);
|
||||
// Validate the hash fields
|
||||
if ($weekday < 0 || $weekday > 7) {
|
||||
throw new InvalidArgumentException("Weekday must be a value between 0 and 7. {$weekday} given");
|
||||
}
|
||||
if (!\in_array($nth, $this->nthRange, true)) {
|
||||
throw new InvalidArgumentException("There are never more than 5 or less than 1 of a given weekday in a month, {$nth} given");
|
||||
}
|
||||
// The current weekday must match the targeted weekday to proceed
|
||||
if ((int) $date->format('N') !== $weekday) {
|
||||
return false;
|
||||
}
|
||||
$tdate = clone $date;
|
||||
$tdate = $tdate->setDate($currentYear, $currentMonth, 1);
|
||||
$dayCount = 0;
|
||||
$currentDay = 1;
|
||||
while ($currentDay < $lastDayOfMonth + 1) {
|
||||
if ((int) $tdate->format('N') === $weekday) {
|
||||
if (++$dayCount >= $nth) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$tdate = $tdate->setDate($currentYear, $currentMonth, ++$currentDay);
|
||||
}
|
||||
return (int) $date->format('j') === $currentDay;
|
||||
}
|
||||
// Handle day of the week values
|
||||
if (false !== strpos($value, '-')) {
|
||||
$parts = explode('-', $value);
|
||||
if ('7' === $parts[0]) {
|
||||
$parts[0] = 0;
|
||||
} elseif ('0' === $parts[1]) {
|
||||
$parts[1] = 7;
|
||||
}
|
||||
$value = implode('-', $parts);
|
||||
}
|
||||
// Test to see which Sunday to use -- 0 == 7 == Sunday
|
||||
$format = \in_array(7, array_map(function ($value) {
|
||||
return (int) $value;
|
||||
}, str_split($value)), true) ? 'N' : 'w';
|
||||
$fieldValue = (int) $date->format($format);
|
||||
return $this->isSatisfied($fieldValue, $value);
|
||||
}
|
||||
public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface
|
||||
{
|
||||
if (! $invert) {
|
||||
$date = $date->add(new \DateInterval('P1D'));
|
||||
$date = $date->setTime(0, 0);
|
||||
} else {
|
||||
$date = $date->sub(new \DateInterval('P1D'));
|
||||
$date = $date->setTime(23, 59);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
public function validate(string $value): bool
|
||||
{
|
||||
$basicChecks = parent::validate($value);
|
||||
if (!$basicChecks) {
|
||||
if ('?' === $value) {
|
||||
return true;
|
||||
}
|
||||
// Handle the # value
|
||||
if (false !== strpos($value, '#')) {
|
||||
$chunks = explode('#', $value);
|
||||
$chunks[0] = $this->convertLiterals($chunks[0]);
|
||||
if (parent::validate($chunks[0]) && is_numeric($chunks[1]) && \in_array((int) $chunks[1], $this->nthRange, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (preg_match('/^(.*)L$/', $value, $matches)) {
|
||||
return $this->validate($matches[1]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return $basicChecks;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace Cron;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use InvalidArgumentException;
|
||||
class FieldFactory implements FieldFactoryInterface
|
||||
{
|
||||
private $fields = [];
|
||||
public function getField(int $position): FieldInterface
|
||||
{
|
||||
return $this->fields[$position] ?? $this->fields[$position] = $this->instantiateField($position);
|
||||
}
|
||||
private function instantiateField(int $position): FieldInterface
|
||||
{
|
||||
switch ($position) {
|
||||
case CronExpression::MINUTE:
|
||||
return new MinutesField();
|
||||
case CronExpression::HOUR:
|
||||
return new HoursField();
|
||||
case CronExpression::DAY:
|
||||
return new DayOfMonthField();
|
||||
case CronExpression::MONTH:
|
||||
return new MonthField();
|
||||
case CronExpression::WEEKDAY:
|
||||
return new DayOfWeekField();
|
||||
}
|
||||
throw new InvalidArgumentException(
|
||||
($position + 1) . ' is not a valid position'
|
||||
);
|
||||
}
|
||||
}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
namespace Cron;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
interface FieldFactoryInterface
|
||||
{
|
||||
public function getField(int $position): FieldInterface;
|
||||
}
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace Cron;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use DateTimeInterface;
|
||||
interface FieldInterface
|
||||
{
|
||||
public function isSatisfiedBy(DateTimeInterface $date, $value, bool $invert): bool;
|
||||
public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface;
|
||||
public function validate(string $value): bool;
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace Cron;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use DateTimeInterface;
|
||||
use DateTimeZone;
|
||||
class HoursField extends AbstractField
|
||||
{
|
||||
protected $rangeStart = 0;
|
||||
protected $rangeEnd = 23;
|
||||
protected $transitions = [];
|
||||
protected $transitionsStart = null;
|
||||
protected $transitionsEnd = null;
|
||||
public function isSatisfiedBy(DateTimeInterface $date, $value, bool $invert): bool
|
||||
{
|
||||
$checkValue = (int) $date->format('H');
|
||||
$retval = $this->isSatisfied($checkValue, $value);
|
||||
if ($retval) {
|
||||
return $retval;
|
||||
}
|
||||
// Are we on the edge of a transition
|
||||
$lastTransition = $this->getPastTransition($date);
|
||||
if (($lastTransition !== null) && ($lastTransition["ts"] > ((int) $date->format('U') - 3600))) {
|
||||
$dtLastOffset = clone $date;
|
||||
$this->timezoneSafeModify($dtLastOffset, "-1 hour");
|
||||
$lastOffset = $dtLastOffset->getOffset();
|
||||
$dtNextOffset = clone $date;
|
||||
$this->timezoneSafeModify($dtNextOffset, "+1 hour");
|
||||
$nextOffset = $dtNextOffset->getOffset();
|
||||
$offsetChange = $nextOffset - $lastOffset;
|
||||
if ($offsetChange >= 3600) {
|
||||
$checkValue -= 1;
|
||||
return $this->isSatisfied($checkValue, $value);
|
||||
}
|
||||
if ((! $invert) && ($offsetChange <= -3600)) {
|
||||
$checkValue += 1;
|
||||
return $this->isSatisfied($checkValue, $value);
|
||||
}
|
||||
}
|
||||
return $retval;
|
||||
}
|
||||
public function getPastTransition(DateTimeInterface $date): ?array
|
||||
{
|
||||
$currentTimestamp = (int) $date->format('U');
|
||||
if (
|
||||
($this->transitions === null)
|
||||
|| ($this->transitionsStart < ($currentTimestamp + 86400))
|
||||
|| ($this->transitionsEnd > ($currentTimestamp - 86400))
|
||||
) {
|
||||
// We start a day before current time so we can differentiate between the first transition entry
|
||||
// and a change that happens now
|
||||
$dtLimitStart = clone $date;
|
||||
$dtLimitStart = $dtLimitStart->modify("-12 months");
|
||||
$dtLimitEnd = clone $date;
|
||||
$dtLimitEnd = $dtLimitEnd->modify('+12 months');
|
||||
$this->transitions = $date->getTimezone()->getTransitions(
|
||||
$dtLimitStart->getTimestamp(),
|
||||
$dtLimitEnd->getTimestamp()
|
||||
);
|
||||
if (empty($this->transitions)) {
|
||||
return null;
|
||||
}
|
||||
$this->transitionsStart = $dtLimitStart->getTimestamp();
|
||||
$this->transitionsEnd = $dtLimitEnd->getTimestamp();
|
||||
}
|
||||
$nextTransition = null;
|
||||
foreach ($this->transitions as $transition) {
|
||||
if ($transition["ts"] > $currentTimestamp) {
|
||||
continue;
|
||||
}
|
||||
if (($nextTransition !== null) && ($transition["ts"] < $nextTransition["ts"])) {
|
||||
continue;
|
||||
}
|
||||
$nextTransition = $transition;
|
||||
}
|
||||
return ($nextTransition ?? null);
|
||||
}
|
||||
public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface
|
||||
{
|
||||
$originalTimestamp = (int) $date->format('U');
|
||||
// Change timezone to UTC temporarily. This will
|
||||
// allow us to go back or forwards and hour even
|
||||
// if DST will be changed between the hours.
|
||||
if (null === $parts || '*' === $parts) {
|
||||
if ($invert) {
|
||||
$date = $date->sub(new \DateInterval('PT1H'));
|
||||
} else {
|
||||
$date = $date->add(new \DateInterval('PT1H'));
|
||||
}
|
||||
$date = $this->setTimeHour($date, $invert, $originalTimestamp);
|
||||
return $this;
|
||||
}
|
||||
$parts = false !== strpos($parts, ',') ? explode(',', $parts) : [$parts];
|
||||
$hours = [];
|
||||
foreach ($parts as $part) {
|
||||
$hours = array_merge($hours, $this->getRangeForExpression($part, 23));
|
||||
}
|
||||
$current_hour = (int) $date->format('H');
|
||||
$position = $invert ? \count($hours) - 1 : 0;
|
||||
$countHours = \count($hours);
|
||||
if ($countHours > 1) {
|
||||
for ($i = 0; $i < $countHours - 1; ++$i) {
|
||||
if ((!$invert && $current_hour >= $hours[$i] && $current_hour < $hours[$i + 1]) ||
|
||||
($invert && $current_hour > $hours[$i] && $current_hour <= $hours[$i + 1])) {
|
||||
$position = $invert ? $i : $i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$target = (int) $hours[$position];
|
||||
$originalHour = (int)$date->format('H');
|
||||
$originalDay = (int)$date->format('d');
|
||||
$previousOffset = $date->getOffset();
|
||||
if (! $invert) {
|
||||
if ($originalHour >= $target) {
|
||||
$distance = 24 - $originalHour;
|
||||
$date = $this->timezoneSafeModify($date, "+{$distance} hours");
|
||||
$actualDay = (int)$date->format('d');
|
||||
$actualHour = (int)$date->format('H');
|
||||
if (($actualDay !== ($originalDay + 1)) && ($actualHour !== 0)) {
|
||||
$offsetChange = ($previousOffset - $date->getOffset());
|
||||
$date = $this->timezoneSafeModify($date, "+{$offsetChange} seconds");
|
||||
}
|
||||
$originalHour = (int)$date->format('H');
|
||||
}
|
||||
$distance = $target - $originalHour;
|
||||
$date = $this->timezoneSafeModify($date, "+{$distance} hours");
|
||||
} else {
|
||||
if ($originalHour <= $target) {
|
||||
$distance = ($originalHour + 1);
|
||||
$date = $this->timezoneSafeModify($date, "-" . $distance . " hours");
|
||||
$actualDay = (int)$date->format('d');
|
||||
$actualHour = (int)$date->format('H');
|
||||
if (($actualDay !== ($originalDay - 1)) && ($actualHour !== 23)) {
|
||||
$offsetChange = ($previousOffset - $date->getOffset());
|
||||
$date = $this->timezoneSafeModify($date, "+{$offsetChange} seconds");
|
||||
}
|
||||
$originalHour = (int)$date->format('H');
|
||||
}
|
||||
$distance = $originalHour - $target;
|
||||
$date = $this->timezoneSafeModify($date, "-{$distance} hours");
|
||||
}
|
||||
$date = $this->setTimeHour($date, $invert, $originalTimestamp);
|
||||
$actualHour = (int)$date->format('H');
|
||||
if ($invert && ($actualHour === ($target - 1) || (($actualHour === 23) && ($target === 0)))) {
|
||||
$date = $this->timezoneSafeModify($date, "+1 hour");
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace Cron;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use DateTimeInterface;
|
||||
class MinutesField extends AbstractField
|
||||
{
|
||||
protected $rangeStart = 0;
|
||||
protected $rangeEnd = 59;
|
||||
public function isSatisfiedBy(DateTimeInterface $date, $value, bool $invert):bool
|
||||
{
|
||||
if ($value === '?') {
|
||||
return true;
|
||||
}
|
||||
return $this->isSatisfied((int)$date->format('i'), $value);
|
||||
}
|
||||
public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface
|
||||
{
|
||||
if (is_null($parts)) {
|
||||
$date = $this->timezoneSafeModify($date, ($invert ? "-" : "+") ."1 minute");
|
||||
return $this;
|
||||
}
|
||||
$current_minute = (int) $date->format('i');
|
||||
$parts = false !== strpos($parts, ',') ? explode(',', $parts) : [$parts];
|
||||
sort($parts);
|
||||
$minutes = [];
|
||||
foreach ($parts as $part) {
|
||||
$minutes = array_merge($minutes, $this->getRangeForExpression($part, 59));
|
||||
}
|
||||
$position = $invert ? \count($minutes) - 1 : 0;
|
||||
if (\count($minutes) > 1) {
|
||||
for ($i = 0; $i < \count($minutes) - 1; ++$i) {
|
||||
if ((!$invert && $current_minute >= $minutes[$i] && $current_minute < $minutes[$i + 1]) ||
|
||||
($invert && $current_minute > $minutes[$i] && $current_minute <= $minutes[$i + 1])) {
|
||||
$position = $invert ? $i : $i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$target = (int) $minutes[$position];
|
||||
$originalMinute = (int) $date->format("i");
|
||||
if (! $invert) {
|
||||
if ($originalMinute >= $target) {
|
||||
$distance = 60 - $originalMinute;
|
||||
$date = $this->timezoneSafeModify($date, "+{$distance} minutes");
|
||||
$originalMinute = (int) $date->format("i");
|
||||
}
|
||||
$distance = $target - $originalMinute;
|
||||
$date = $this->timezoneSafeModify($date, "+{$distance} minutes");
|
||||
} else {
|
||||
if ($originalMinute <= $target) {
|
||||
$distance = ($originalMinute + 1);
|
||||
$date = $this->timezoneSafeModify($date, "-{$distance} minutes");
|
||||
$originalMinute = (int) $date->format("i");
|
||||
}
|
||||
$distance = $originalMinute - $target;
|
||||
$date = $this->timezoneSafeModify($date, "-{$distance} minutes");
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace Cron;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use DateTimeInterface;
|
||||
class MonthField extends AbstractField
|
||||
{
|
||||
protected $rangeStart = 1;
|
||||
protected $rangeEnd = 12;
|
||||
protected $literals = [1 => 'JAN', 2 => 'FEB', 3 => 'MAR', 4 => 'APR', 5 => 'MAY', 6 => 'JUN', 7 => 'JUL',
|
||||
8 => 'AUG', 9 => 'SEP', 10 => 'OCT', 11 => 'NOV', 12 => 'DEC', ];
|
||||
public function isSatisfiedBy(DateTimeInterface $date, $value, bool $invert): bool
|
||||
{
|
||||
if ($value === '?') {
|
||||
return true;
|
||||
}
|
||||
$value = $this->convertLiterals($value);
|
||||
return $this->isSatisfied((int) $date->format('m'), $value);
|
||||
}
|
||||
public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface
|
||||
{
|
||||
if (! $invert) {
|
||||
$date = $date->modify('first day of next month');
|
||||
$date = $date->setTime(0, 0);
|
||||
} else {
|
||||
$date = $date->modify('last day of previous month');
|
||||
$date = $date->setTime(23, 59);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"*.php": [
|
||||
"../../../mailpoet/tools/vendor/composer.phar code-style-fix",
|
||||
"../../../mailpoet/tools/vendor/composer.phar phpstan"
|
||||
],
|
||||
"*": "../../../mailpoet/tools/vendor/composer.phar code-style"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
paths:
|
||||
tests: tests
|
||||
output: tests/_output
|
||||
data: tests/_data
|
||||
support: tests/_support
|
||||
envs: tests/_envs
|
||||
settings:
|
||||
colors: true
|
||||
memory_limit: 1024M
|
||||
log: true
|
||||
strict_xml: true
|
||||
extensions:
|
||||
enabled:
|
||||
- Codeception\Extension\RunFailed
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0"?>
|
||||
<ruleset name="Custom WordPress Standards">
|
||||
<!-- Set the base standard to WordPress -->
|
||||
<rule ref="WordPress"/>
|
||||
|
||||
<!-- Define files and folders to scan -->
|
||||
<file>.</file>
|
||||
|
||||
<!-- Exclude test files from FileName rules because we need to match Codeception autoload -->
|
||||
<rule ref="WordPress.Files.FileName">
|
||||
<exclude-pattern>tests/*</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<!-- We should use WP_Filesystem or another safer solution but this change can be more complex -->
|
||||
<rule ref="WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents">
|
||||
<exclude-pattern>src</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<!-- Exclude bootstrap from the SeparateFunctionsFromOO.Mixed rule -->
|
||||
<rule ref="Universal.Files.SeparateFunctionsFromOO.Mixed">
|
||||
<exclude-pattern>tests/unit/_bootstrap.php</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<!-- Exclude test files from the PSR2.Methods.MethodDeclaration.Underscore rule due to methods _after() and _before() -->
|
||||
<rule ref="PSR2.Methods.MethodDeclaration.Underscore">
|
||||
<exclude-pattern>tests/integration</exclude-pattern>
|
||||
<exclude-pattern>tests/unit</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<!-- Skip the vendor directory -->
|
||||
<exclude-pattern>vendor/*</exclude-pattern>
|
||||
</ruleset>
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Patterns;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
abstract class Abstract_Pattern {
|
||||
protected $name = '';
|
||||
protected $namespace = '';
|
||||
protected $block_types = array();
|
||||
protected $template_types = array();
|
||||
protected $inserter = true;
|
||||
protected $source = 'plugin';
|
||||
protected $categories = array();
|
||||
protected $viewport_width = 620;
|
||||
public function get_name(): string {
|
||||
return $this->name;
|
||||
}
|
||||
public function get_namespace(): string {
|
||||
return $this->namespace;
|
||||
}
|
||||
public function get_properties(): array {
|
||||
return array(
|
||||
'title' => $this->get_title(),
|
||||
'content' => $this->get_content(),
|
||||
'description' => $this->get_description(),
|
||||
'categories' => $this->categories,
|
||||
'inserter' => $this->inserter,
|
||||
'blockTypes' => $this->block_types,
|
||||
'templateTypes' => $this->template_types,
|
||||
'source' => $this->source,
|
||||
'viewportWidth' => $this->viewport_width,
|
||||
);
|
||||
}
|
||||
abstract protected function get_content(): string;
|
||||
abstract protected function get_title(): string;
|
||||
protected function get_description(): string {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Patterns;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class Patterns {
|
||||
public function initialize(): void {
|
||||
$this->register_block_pattern_categories();
|
||||
}
|
||||
private function register_block_pattern_categories(): void {
|
||||
$categories = array(
|
||||
array(
|
||||
'name' => 'email-contents',
|
||||
'label' => _x( 'Email Contents', 'Block pattern category', 'mailpoet' ),
|
||||
'description' => __( 'A collection of email content layouts.', 'mailpoet' ),
|
||||
),
|
||||
);
|
||||
foreach ( $categories as $category ) {
|
||||
register_block_pattern_category(
|
||||
$category['name'],
|
||||
array(
|
||||
'label' => $category['label'],
|
||||
'description' => $category['description'] ?? '',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\PersonalizationTags;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use WP_HTML_Tag_Processor;
|
||||
use WP_HTML_Text_Replacement;
|
||||
class HTML_Tag_Processor extends WP_HTML_Tag_Processor {
|
||||
private $deferred_updates = array();
|
||||
public function replace_token( string $new_content ): void {
|
||||
$this->set_bookmark( 'here' );
|
||||
$here = $this->bookmarks['here'];
|
||||
$this->deferred_updates[] = new WP_HTML_Text_Replacement(
|
||||
$here->start,
|
||||
$here->length,
|
||||
$new_content
|
||||
);
|
||||
}
|
||||
public function flush_updates(): void {
|
||||
foreach ( $this->deferred_updates as $key => $update ) {
|
||||
$this->lexical_updates[] = $update;
|
||||
unset( $this->deferred_updates[ $key ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\PersonalizationTags;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class Personalization_Tag {
|
||||
private string $name;
|
||||
private string $token;
|
||||
private string $category;
|
||||
private $callback;
|
||||
private array $attributes;
|
||||
private string $value_to_insert;
|
||||
public function __construct(
|
||||
string $name,
|
||||
string $token,
|
||||
string $category,
|
||||
callable $callback,
|
||||
array $attributes = array(),
|
||||
?string $value_to_insert = null
|
||||
) {
|
||||
$this->name = $name;
|
||||
// Because Gutenberg does not wrap the token with square brackets, we need to add them here.
|
||||
$this->token = strpos( $token, '[' ) === 0 ? $token : "[$token]";
|
||||
$this->category = $category;
|
||||
$this->callback = $callback;
|
||||
$this->attributes = $attributes;
|
||||
// Composing token to insert based on the token and attributes if it is not set.
|
||||
if ( ! $value_to_insert ) {
|
||||
if ( $this->attributes ) {
|
||||
$value_to_insert = substr( $this->token, 0, -1 ) . ' ' .
|
||||
implode(
|
||||
' ',
|
||||
array_map(
|
||||
function ( $key ) {
|
||||
return $key . '="' . esc_attr( $this->attributes[ $key ] ) . '"';
|
||||
},
|
||||
array_keys( $this->attributes )
|
||||
)
|
||||
) . ']';
|
||||
} else {
|
||||
$value_to_insert = $this->token;
|
||||
}
|
||||
}
|
||||
$this->value_to_insert = $value_to_insert;
|
||||
}
|
||||
public function get_name(): string {
|
||||
return $this->name;
|
||||
}
|
||||
public function get_token(): string {
|
||||
return $this->token;
|
||||
}
|
||||
public function get_category(): string {
|
||||
return $this->category;
|
||||
}
|
||||
public function get_attributes(): array {
|
||||
return $this->attributes;
|
||||
}
|
||||
public function get_value_to_insert(): string {
|
||||
return $this->value_to_insert;
|
||||
}
|
||||
public function execute_callback( $context, $args = array() ): string {
|
||||
return call_user_func( $this->callback, ...array_merge( array( $context ), array( $args ) ) );
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\PersonalizationTags;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class Personalization_Tags_Registry {
|
||||
private $tags = array();
|
||||
public function initialize(): void {
|
||||
apply_filters( 'mailpoet_email_editor_register_personalization_tags', $this );
|
||||
}
|
||||
public function register( Personalization_Tag $tag ): void {
|
||||
if ( isset( $this->tags[ $tag->get_token() ] ) ) {
|
||||
return;
|
||||
}
|
||||
$this->tags[ $tag->get_token() ] = $tag;
|
||||
}
|
||||
public function get_by_token( string $token ): ?Personalization_Tag {
|
||||
return $this->tags[ $token ] ?? null;
|
||||
}
|
||||
public function get_all() {
|
||||
return $this->tags;
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Layout;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
class Flex_Layout_Renderer {
|
||||
public function render_inner_blocks_in_layout( array $parsed_block, Settings_Controller $settings_controller ): string {
|
||||
$theme_styles = $settings_controller->get_email_styles();
|
||||
$flex_gap = $theme_styles['spacing']['blockGap'] ?? '0px';
|
||||
$flex_gap_number = $settings_controller->parse_number_from_string_with_pixels( $flex_gap );
|
||||
$margin_top = $parsed_block['email_attrs']['margin-top'] ?? '0px';
|
||||
$justify = $parsed_block['attrs']['layout']['justifyContent'] ?? 'left';
|
||||
$styles = wp_style_engine_get_styles( $parsed_block['attrs']['style'] ?? array() )['css'] ?? '';
|
||||
$styles .= 'margin-top: ' . $margin_top . ';';
|
||||
$styles .= 'text-align: ' . $justify;
|
||||
// MS Outlook doesn't support style attribute in divs so we conditionally wrap the buttons in a table and repeat styles.
|
||||
$output_html = sprintf(
|
||||
'<!--[if mso | IE]><table align="%2$s" role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%%"><tr><td style="%1$s" ><![endif]-->
|
||||
<div style="%1$s"><table class="layout-flex-wrapper" style="display:inline-block"><tbody><tr>',
|
||||
esc_attr( $styles ),
|
||||
esc_attr( $justify )
|
||||
);
|
||||
$inner_blocks = $this->compute_widths_for_flex_layout( $parsed_block, $settings_controller, $flex_gap_number );
|
||||
foreach ( $inner_blocks as $key => $block ) {
|
||||
$styles = array();
|
||||
if ( $block['email_attrs']['layout_width'] ?? null ) {
|
||||
$styles['width'] = $block['email_attrs']['layout_width'];
|
||||
}
|
||||
if ( $key > 0 ) {
|
||||
$styles['padding-left'] = $flex_gap;
|
||||
}
|
||||
$output_html .= '<td class="layout-flex-item" style="' . esc_attr( \WP_Style_Engine::compile_css( $styles, '' ) ) . '">' . render_block( $block ) . '</td>';
|
||||
}
|
||||
$output_html .= '</tr></table></div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->';
|
||||
return $output_html;
|
||||
}
|
||||
private function compute_widths_for_flex_layout( array $parsed_block, Settings_Controller $settings_controller, float $flex_gap ): array {
|
||||
// When there is no parent width we can't compute widths so auto width will be used.
|
||||
if ( ! isset( $parsed_block['email_attrs']['width'] ) ) {
|
||||
return $parsed_block['innerBlocks'] ?? array();
|
||||
}
|
||||
$blocks_count = count( $parsed_block['innerBlocks'] );
|
||||
$total_used_width = 0; // Total width assuming items without set width would consume proportional width.
|
||||
$parent_width = $settings_controller->parse_number_from_string_with_pixels( $parsed_block['email_attrs']['width'] );
|
||||
$inner_blocks = $parsed_block['innerBlocks'] ?? array();
|
||||
foreach ( $inner_blocks as $key => $block ) {
|
||||
$block_width_percent = ( $block['attrs']['width'] ?? 0 ) ? intval( $block['attrs']['width'] ) : 0;
|
||||
$block_width = floor( $parent_width * ( $block_width_percent / 100 ) );
|
||||
// If width is not set, we assume it's 25% of the parent width.
|
||||
$total_used_width += $block_width ? $block_width : floor( $parent_width * ( 25 / 100 ) );
|
||||
if ( ! $block_width ) {
|
||||
$inner_blocks[ $key ]['email_attrs']['layout_width'] = null; // Will be rendered as auto.
|
||||
continue;
|
||||
}
|
||||
$inner_blocks[ $key ]['email_attrs']['layout_width'] = $this->get_width_without_gap( $block_width, $flex_gap, $block_width_percent ) . 'px';
|
||||
}
|
||||
// When there is only one block, or percentage is set reasonably we don't need to adjust and just render as set by user.
|
||||
if ( $blocks_count <= 1 || ( $total_used_width <= $parent_width ) ) {
|
||||
return $inner_blocks;
|
||||
}
|
||||
foreach ( $inner_blocks as $key => $block ) {
|
||||
$proportional_space_overflow = $parent_width / $total_used_width;
|
||||
$block_width = $block['email_attrs']['layout_width'] ? $settings_controller->parse_number_from_string_with_pixels( $block['email_attrs']['layout_width'] ) : 0;
|
||||
$block_proportional_width = $block_width * $proportional_space_overflow;
|
||||
$block_proportional_percentage = ( $block_proportional_width / $parent_width ) * 100;
|
||||
$inner_blocks[ $key ]['email_attrs']['layout_width'] = $block_width ? $this->get_width_without_gap( $block_proportional_width, $flex_gap, $block_proportional_percentage ) . 'px' : null;
|
||||
}
|
||||
return $inner_blocks;
|
||||
}
|
||||
private function get_width_without_gap( float $block_width, float $flex_gap, float $block_width_percent ): int {
|
||||
$width_gap_reduction = $flex_gap * ( ( 100 - $block_width_percent ) / 100 );
|
||||
return intval( floor( $block_width - $width_gap_reduction ) );
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Postprocessors;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class Highlighting_Postprocessor implements Postprocessor {
|
||||
public function postprocess( string $html ): string {
|
||||
return str_replace(
|
||||
array( '<mark', '</mark>' ),
|
||||
array( '<span', '</span>' ),
|
||||
$html
|
||||
);
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Postprocessors;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Theme_Controller;
|
||||
class Variables_Postprocessor implements Postprocessor {
|
||||
private Theme_Controller $theme_controller;
|
||||
public function __construct(
|
||||
Theme_Controller $theme_controller
|
||||
) {
|
||||
$this->theme_controller = $theme_controller;
|
||||
}
|
||||
public function postprocess( string $html ): string {
|
||||
$variables = $this->theme_controller->get_variables_values_map();
|
||||
$replacements = array();
|
||||
foreach ( $variables as $name => $value ) {
|
||||
$var_pattern = '/' . preg_quote( 'var(' . $name . ')', '/' ) . '/i';
|
||||
$replacements[ $var_pattern ] = $value;
|
||||
}
|
||||
// Pattern to match style attributes and their values.
|
||||
$callback = function ( $matches ) use ( $replacements ) {
|
||||
// For each match, replace CSS variables with their values.
|
||||
$style = $matches[1];
|
||||
$style = preg_replace( array_keys( $replacements ), array_values( $replacements ), $style );
|
||||
return 'style="' . esc_attr( $style ) . '"';
|
||||
};
|
||||
// We want to replace the CSS variables only in the style attributes to avoid replacing the actual content.
|
||||
$style_pattern = '/style="(.*?)"/i';
|
||||
$style_pattern_alt = "/style='(.*?)'/i";
|
||||
$html = (string) preg_replace_callback( $style_pattern, $callback, $html );
|
||||
$html = (string) preg_replace_callback( $style_pattern_alt, $callback, $html );
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Postprocessors;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
interface Postprocessor {
|
||||
public function postprocess( string $html ): string;
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class Blocks_Width_Preprocessor implements Preprocessor {
|
||||
public function preprocess( array $parsed_blocks, array $layout, array $styles ): array {
|
||||
foreach ( $parsed_blocks as $key => $block ) {
|
||||
// Layout width is recalculated for each block because full-width blocks don't exclude padding.
|
||||
$layout_width = $this->parse_number_from_string_with_pixels( $layout['contentSize'] );
|
||||
$alignment = $block['attrs']['align'] ?? null;
|
||||
// Subtract padding from the block width if it's not full-width.
|
||||
if ( 'full' !== $alignment ) {
|
||||
$layout_width -= $this->parse_number_from_string_with_pixels( $styles['spacing']['padding']['left'] ?? '0px' );
|
||||
$layout_width -= $this->parse_number_from_string_with_pixels( $styles['spacing']['padding']['right'] ?? '0px' );
|
||||
}
|
||||
$width_input = $block['attrs']['width'] ?? '100%';
|
||||
// Currently we support only % and px units in case only the number is provided we assume it's %
|
||||
// because editor saves percent values as a number.
|
||||
$width_input = is_numeric( $width_input ) ? "$width_input%" : $width_input;
|
||||
$width = $this->convert_width_to_pixels( $width_input, $layout_width );
|
||||
if ( 'core/columns' === $block['blockName'] ) {
|
||||
// Calculate width of the columns based on the layout width and padding.
|
||||
$columns_width = $layout_width;
|
||||
$columns_width -= $this->parse_number_from_string_with_pixels( $block['attrs']['style']['spacing']['padding']['left'] ?? '0px' );
|
||||
$columns_width -= $this->parse_number_from_string_with_pixels( $block['attrs']['style']['spacing']['padding']['right'] ?? '0px' );
|
||||
$border_width = $block['attrs']['style']['border']['width'] ?? '0px';
|
||||
$columns_width -= $this->parse_number_from_string_with_pixels( $block['attrs']['style']['border']['left']['width'] ?? $border_width );
|
||||
$columns_width -= $this->parse_number_from_string_with_pixels( $block['attrs']['style']['border']['right']['width'] ?? $border_width );
|
||||
$block['innerBlocks'] = $this->add_missing_column_widths( $block['innerBlocks'], $columns_width );
|
||||
}
|
||||
// Copy layout styles and update width and padding.
|
||||
$modified_layout = $layout;
|
||||
$modified_layout['contentSize'] = "{$width}px";
|
||||
$modified_styles = $styles;
|
||||
$modified_styles['spacing']['padding']['left'] = $block['attrs']['style']['spacing']['padding']['left'] ?? '0px';
|
||||
$modified_styles['spacing']['padding']['right'] = $block['attrs']['style']['spacing']['padding']['right'] ?? '0px';
|
||||
$block['email_attrs']['width'] = "{$width}px";
|
||||
$block['innerBlocks'] = $this->preprocess( $block['innerBlocks'], $modified_layout, $modified_styles );
|
||||
$parsed_blocks[ $key ] = $block;
|
||||
}
|
||||
return $parsed_blocks;
|
||||
}
|
||||
// TODO: We could add support for other units like em, rem, etc.
|
||||
private function convert_width_to_pixels( string $current_width, float $layout_width ): float {
|
||||
$width = $layout_width;
|
||||
if ( strpos( $current_width, '%' ) !== false ) {
|
||||
$width = (float) str_replace( '%', '', $current_width );
|
||||
$width = round( $width / 100 * $layout_width );
|
||||
} elseif ( strpos( $current_width, 'px' ) !== false ) {
|
||||
$width = $this->parse_number_from_string_with_pixels( $current_width );
|
||||
}
|
||||
return $width;
|
||||
}
|
||||
private function parse_number_from_string_with_pixels( string $value ): float {
|
||||
return (float) str_replace( 'px', '', $value );
|
||||
}
|
||||
private function add_missing_column_widths( array $columns, float $columns_width ): array {
|
||||
$columns_count_with_defined_width = 0;
|
||||
$defined_column_width = 0;
|
||||
$columns_count = count( $columns );
|
||||
foreach ( $columns as $column ) {
|
||||
if ( isset( $column['attrs']['width'] ) && ! empty( $column['attrs']['width'] ) ) {
|
||||
++$columns_count_with_defined_width;
|
||||
$defined_column_width += $this->convert_width_to_pixels( $column['attrs']['width'], $columns_width );
|
||||
} else {
|
||||
// When width is not set we need to add padding to the defined column width for better ratio accuracy.
|
||||
$defined_column_width += $this->parse_number_from_string_with_pixels( $column['attrs']['style']['spacing']['padding']['left'] ?? '0px' );
|
||||
$defined_column_width += $this->parse_number_from_string_with_pixels( $column['attrs']['style']['spacing']['padding']['right'] ?? '0px' );
|
||||
$border_width = $column['attrs']['style']['border']['width'] ?? '0px';
|
||||
$defined_column_width += $this->parse_number_from_string_with_pixels( $column['attrs']['style']['border']['left']['width'] ?? $border_width );
|
||||
$defined_column_width += $this->parse_number_from_string_with_pixels( $column['attrs']['style']['border']['right']['width'] ?? $border_width );
|
||||
}
|
||||
}
|
||||
if ( $columns_count - $columns_count_with_defined_width > 0 ) {
|
||||
$default_columns_width = round( ( $columns_width - $defined_column_width ) / ( $columns_count - $columns_count_with_defined_width ), 2 );
|
||||
foreach ( $columns as $key => $column ) {
|
||||
if ( ! isset( $column['attrs']['width'] ) || empty( $column['attrs']['width'] ) ) {
|
||||
// Add padding to the specific column width because it's not included in the default width.
|
||||
$column_width = $default_columns_width;
|
||||
$column_width += $this->parse_number_from_string_with_pixels( $column['attrs']['style']['spacing']['padding']['left'] ?? '0px' );
|
||||
$column_width += $this->parse_number_from_string_with_pixels( $column['attrs']['style']['spacing']['padding']['right'] ?? '0px' );
|
||||
$border_width = $column['attrs']['style']['border']['width'] ?? '0px';
|
||||
$column_width += $this->parse_number_from_string_with_pixels( $column['attrs']['style']['border']['left']['width'] ?? $border_width );
|
||||
$column_width += $this->parse_number_from_string_with_pixels( $column['attrs']['style']['border']['right']['width'] ?? $border_width );
|
||||
$columns[ $key ]['attrs']['width'] = "{$column_width}px";
|
||||
}
|
||||
}
|
||||
}
|
||||
return $columns;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class Cleanup_Preprocessor implements Preprocessor {
|
||||
public function preprocess( array $parsed_blocks, array $layout, array $styles ): array {
|
||||
foreach ( $parsed_blocks as $key => $block ) {
|
||||
// https://core.trac.wordpress.org/ticket/45312
|
||||
// \WP_Block_Parser::parse_blocks() sometimes add a block with name null that can cause unexpected spaces in rendered content
|
||||
// This behavior was reported as an issue, but it was closed as won't fix.
|
||||
if ( null === $block['blockName'] ) {
|
||||
unset( $parsed_blocks[ $key ] );
|
||||
}
|
||||
}
|
||||
return array_values( $parsed_blocks );
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class Spacing_Preprocessor implements Preprocessor {
|
||||
public function preprocess( array $parsed_blocks, array $layout, array $styles ): array {
|
||||
$parsed_blocks = $this->add_block_gaps( $parsed_blocks, $styles['spacing']['blockGap'] ?? '', null );
|
||||
return $parsed_blocks;
|
||||
}
|
||||
private function add_block_gaps( array $parsed_blocks, string $gap = '', $parent_block = null ): array {
|
||||
foreach ( $parsed_blocks as $key => $block ) {
|
||||
$parent_block_name = $parent_block['blockName'] ?? '';
|
||||
// Ensure that email_attrs are set.
|
||||
$block['email_attrs'] = $block['email_attrs'] ?? array();
|
||||
if ( 0 !== $key && $gap && 'core/buttons' !== $parent_block_name ) {
|
||||
$block['email_attrs']['margin-top'] = $gap;
|
||||
}
|
||||
$block['innerBlocks'] = $this->add_block_gaps( $block['innerBlocks'] ?? array(), $gap, $block );
|
||||
$parsed_blocks[ $key ] = $block;
|
||||
}
|
||||
return $parsed_blocks;
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
class Typography_Preprocessor implements Preprocessor {
|
||||
private const TYPOGRAPHY_STYLES = array(
|
||||
'color',
|
||||
'font-size',
|
||||
'text-decoration',
|
||||
);
|
||||
private $settings_controller;
|
||||
public function __construct(
|
||||
Settings_Controller $settings_controller
|
||||
) {
|
||||
$this->settings_controller = $settings_controller;
|
||||
}
|
||||
public function preprocess( array $parsed_blocks, array $layout, array $styles ): array {
|
||||
foreach ( $parsed_blocks as $key => $block ) {
|
||||
$block = $this->preprocess_parent( $block );
|
||||
// Set defaults from theme - this needs to be done on top level blocks only.
|
||||
$block = $this->set_defaults_from_theme( $block );
|
||||
$block['innerBlocks'] = $this->copy_typography_from_parent( $block['innerBlocks'], $block );
|
||||
$parsed_blocks[ $key ] = $block;
|
||||
}
|
||||
return $parsed_blocks;
|
||||
}
|
||||
private function copy_typography_from_parent( array $children, array $parent_block ): array {
|
||||
foreach ( $children as $key => $child ) {
|
||||
$child = $this->preprocess_parent( $child );
|
||||
$child['email_attrs'] = array_merge( $this->filterStyles( $parent_block['email_attrs'] ), $child['email_attrs'] );
|
||||
$child['innerBlocks'] = $this->copy_typography_from_parent( $child['innerBlocks'] ?? array(), $child );
|
||||
$children[ $key ] = $child;
|
||||
}
|
||||
return $children;
|
||||
}
|
||||
private function preprocess_parent( array $block ): array {
|
||||
// Build styles that should be copied to children.
|
||||
$email_attrs = array();
|
||||
if ( isset( $block['attrs']['style']['color']['text'] ) ) {
|
||||
$email_attrs['color'] = $block['attrs']['style']['color']['text'];
|
||||
}
|
||||
// In case the fontSize is set via a slug (small, medium, large, etc.) we translate it to a number
|
||||
// The font size slug is set in $block['attrs']['fontSize'] and value in $block['attrs']['style']['typography']['fontSize'].
|
||||
if ( isset( $block['attrs']['fontSize'] ) ) {
|
||||
$block['attrs']['style']['typography']['fontSize'] = $this->settings_controller->translate_slug_to_font_size( $block['attrs']['fontSize'] );
|
||||
}
|
||||
// Pass font size to email_attrs.
|
||||
if ( isset( $block['attrs']['style']['typography']['fontSize'] ) ) {
|
||||
$email_attrs['font-size'] = $block['attrs']['style']['typography']['fontSize'];
|
||||
}
|
||||
if ( isset( $block['attrs']['style']['typography']['textDecoration'] ) ) {
|
||||
$email_attrs['text-decoration'] = $block['attrs']['style']['typography']['textDecoration'];
|
||||
}
|
||||
$block['email_attrs'] = array_merge( $email_attrs, $block['email_attrs'] ?? array() );
|
||||
return $block;
|
||||
}
|
||||
private function filterStyles( array $styles ): array {
|
||||
return array_intersect_key( $styles, array_flip( self::TYPOGRAPHY_STYLES ) );
|
||||
}
|
||||
private function set_defaults_from_theme( array $block ): array {
|
||||
$theme_data = $this->settings_controller->get_theme()->get_data();
|
||||
if ( ! ( $block['email_attrs']['color'] ?? '' ) ) {
|
||||
$block['email_attrs']['color'] = $theme_data['styles']['color']['text'] ?? null;
|
||||
}
|
||||
if ( ! ( $block['email_attrs']['font-size'] ?? '' ) ) {
|
||||
$block['email_attrs']['font-size'] = $theme_data['styles']['typography']['fontSize'];
|
||||
}
|
||||
return $block;
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
interface Preprocessor {
|
||||
public function preprocess( array $parsed_blocks, array $layout, array $styles ): array;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer\ContentRenderer;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
interface Block_Renderer {
|
||||
public function render( string $block_content, array $parsed_block, Settings_Controller $settings_controller ): string;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer\ContentRenderer;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use WP_Block_Parser;
|
||||
class Blocks_Parser extends WP_Block_Parser {
|
||||
public $output;
|
||||
public function parse( $document ) {
|
||||
parent::parse( $document );
|
||||
return apply_filters( 'mailpoet_blocks_renderer_parsed_blocks', $this->output );
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer\ContentRenderer;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class Blocks_Registry {
|
||||
private $fallback_renderer = null;
|
||||
private array $block_renderers_map = array();
|
||||
public function add_block_renderer( string $block_name, Block_Renderer $renderer ): void {
|
||||
$this->block_renderers_map[ $block_name ] = $renderer;
|
||||
}
|
||||
public function add_fallback_renderer( Block_Renderer $renderer ): void {
|
||||
$this->fallback_renderer = $renderer;
|
||||
}
|
||||
public function has_block_renderer( string $block_name ): bool {
|
||||
return isset( $this->block_renderers_map[ $block_name ] );
|
||||
}
|
||||
public function get_block_renderer( string $block_name ): ?Block_Renderer {
|
||||
return $this->block_renderers_map[ $block_name ] ?? null;
|
||||
}
|
||||
public function get_fallback_renderer(): ?Block_Renderer {
|
||||
return $this->fallback_renderer;
|
||||
}
|
||||
public function remove_all_block_renderers(): void {
|
||||
foreach ( array_keys( $this->block_renderers_map ) as $block_name ) {
|
||||
$this->remove_block_renderer( $block_name );
|
||||
}
|
||||
}
|
||||
private function remove_block_renderer( string $block_name ): void {
|
||||
unset( $this->block_renderers_map[ $block_name ] );
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer\ContentRenderer;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\Css_Inliner;
|
||||
use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
use MailPoet\EmailEditor\Engine\Theme_Controller;
|
||||
use WP_Block_Template;
|
||||
use WP_Post;
|
||||
class Content_Renderer {
|
||||
private Blocks_Registry $blocks_registry;
|
||||
private Process_Manager $process_manager;
|
||||
private Settings_Controller $settings_controller;
|
||||
private Theme_Controller $theme_controller;
|
||||
const CONTENT_STYLES_FILE = 'content.css';
|
||||
private Css_Inliner $css_inliner;
|
||||
public function __construct(
|
||||
Process_Manager $preprocess_manager,
|
||||
Blocks_Registry $blocks_registry,
|
||||
Settings_Controller $settings_controller,
|
||||
Css_Inliner $css_inliner,
|
||||
Theme_Controller $theme_controller
|
||||
) {
|
||||
$this->process_manager = $preprocess_manager;
|
||||
$this->blocks_registry = $blocks_registry;
|
||||
$this->settings_controller = $settings_controller;
|
||||
$this->theme_controller = $theme_controller;
|
||||
$this->css_inliner = $css_inliner;
|
||||
}
|
||||
private function initialize() {
|
||||
add_filter( 'render_block', array( $this, 'render_block' ), 10, 2 );
|
||||
add_filter( 'block_parser_class', array( $this, 'block_parser' ) );
|
||||
add_filter( 'mailpoet_blocks_renderer_parsed_blocks', array( $this, 'preprocess_parsed_blocks' ) );
|
||||
do_action( 'mailpoet_blocks_renderer_initialized', $this->blocks_registry );
|
||||
}
|
||||
public function render( WP_Post $post, WP_Block_Template $template ): string {
|
||||
$this->set_template_globals( $post, $template );
|
||||
$this->initialize();
|
||||
$rendered_html = get_the_block_template_html();
|
||||
$this->reset();
|
||||
return $this->process_manager->postprocess( $this->inline_styles( $rendered_html, $post, $template ) );
|
||||
}
|
||||
public function block_parser() {
|
||||
return 'MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Blocks_Parser';
|
||||
}
|
||||
public function preprocess_parsed_blocks( array $parsed_blocks ): array {
|
||||
return $this->process_manager->preprocess( $parsed_blocks, $this->theme_controller->get_layout_settings(), $this->theme_controller->get_styles() );
|
||||
}
|
||||
public function render_block( string $block_content, array $parsed_block ): string {
|
||||
$renderer = $this->blocks_registry->get_block_renderer( $parsed_block['blockName'] );
|
||||
if ( ! $renderer ) {
|
||||
$renderer = $this->blocks_registry->get_fallback_renderer();
|
||||
}
|
||||
return $renderer ? $renderer->render( $block_content, $parsed_block, $this->settings_controller ) : $block_content;
|
||||
}
|
||||
private function set_template_globals( WP_Post $post, WP_Block_Template $template ) {
|
||||
global $_wp_current_template_content, $_wp_current_template_id;
|
||||
$_wp_current_template_id = $template->id;
|
||||
$_wp_current_template_content = $template->content;
|
||||
$GLOBALS['post'] = $post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- I have not found a better way to set the post object for the block renderer.
|
||||
}
|
||||
private function reset(): void {
|
||||
$this->blocks_registry->remove_all_block_renderers();
|
||||
remove_filter( 'render_block', array( $this, 'render_block' ) );
|
||||
remove_filter( 'block_parser_class', array( $this, 'block_parser' ) );
|
||||
remove_filter( 'mailpoet_blocks_renderer_parsed_blocks', array( $this, 'preprocess_parsed_blocks' ) );
|
||||
}
|
||||
private function inline_styles( $html, WP_Post $post, $template = null ) {
|
||||
$styles = (string) file_get_contents( __DIR__ . '/' . self::CONTENT_STYLES_FILE );
|
||||
$styles .= (string) file_get_contents( __DIR__ . '/../../content-shared.css' );
|
||||
// Apply default contentWidth to constrained blocks.
|
||||
$layout = $this->theme_controller->get_layout_settings();
|
||||
$styles .= sprintf(
|
||||
'
|
||||
.is-layout-constrained > *:not(.alignleft):not(.alignright):not(.alignfull) {
|
||||
max-width: %1$s;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
}
|
||||
.is-layout-constrained > .alignwide {
|
||||
max-width: %2$s;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
}
|
||||
',
|
||||
$layout['contentSize'],
|
||||
$layout['wideSize']
|
||||
);
|
||||
// Get styles from theme.
|
||||
$styles .= $this->theme_controller->get_stylesheet_for_rendering( $post, $template );
|
||||
$block_support_styles = $this->theme_controller->get_stylesheet_from_context( 'block-supports', array() );
|
||||
// Get styles from block-supports stylesheet. This includes rules such as layout (contentWidth) that some blocks use.
|
||||
// @see https://github.com/WordPress/WordPress/blob/3c5da9c74344aaf5bf8097f2e2c6a1a781600e03/wp-includes/script-loader.php#L3134
|
||||
// @internal :where is not supported by emogrifier, so we need to replace it with *.
|
||||
$block_support_styles = str_replace(
|
||||
':where(:not(.alignleft):not(.alignright):not(.alignfull))',
|
||||
'*:not(.alignleft):not(.alignright):not(.alignfull)',
|
||||
$block_support_styles
|
||||
);
|
||||
$block_support_styles = preg_replace(
|
||||
'/group-is-layout-(\d+) >/',
|
||||
'group-is-layout-$1 > tbody tr td >',
|
||||
$block_support_styles
|
||||
);
|
||||
$styles .= $block_support_styles;
|
||||
$styles = '<style>' . wp_strip_all_tags( (string) apply_filters( 'mailpoet_email_content_renderer_styles', $styles, $post ) ) . '</style>';
|
||||
return $this->css_inliner->from_html( $styles . $html )->inline_css()->render();
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer\ContentRenderer;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Postprocessors\Highlighting_Postprocessor;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Postprocessors\Postprocessor;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Postprocessors\Variables_Postprocessor;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors\Blocks_Width_Preprocessor;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors\Cleanup_Preprocessor;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors\Preprocessor;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors\Spacing_Preprocessor;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors\Typography_Preprocessor;
|
||||
class Process_Manager {
|
||||
private $preprocessors = array();
|
||||
private $postprocessors = array();
|
||||
public function __construct(
|
||||
Cleanup_Preprocessor $cleanup_preprocessor,
|
||||
Blocks_Width_Preprocessor $blocks_width_preprocessor,
|
||||
Typography_Preprocessor $typography_preprocessor,
|
||||
Spacing_Preprocessor $spacing_preprocessor,
|
||||
Highlighting_Postprocessor $highlighting_postprocessor,
|
||||
Variables_Postprocessor $variables_postprocessor
|
||||
) {
|
||||
$this->register_preprocessor( $cleanup_preprocessor );
|
||||
$this->register_preprocessor( $blocks_width_preprocessor );
|
||||
$this->register_preprocessor( $typography_preprocessor );
|
||||
$this->register_preprocessor( $spacing_preprocessor );
|
||||
$this->register_postprocessor( $highlighting_postprocessor );
|
||||
$this->register_postprocessor( $variables_postprocessor );
|
||||
}
|
||||
public function preprocess( array $parsed_blocks, array $layout, array $styles ): array {
|
||||
foreach ( $this->preprocessors as $preprocessor ) {
|
||||
$parsed_blocks = $preprocessor->preprocess( $parsed_blocks, $layout, $styles );
|
||||
}
|
||||
return $parsed_blocks;
|
||||
}
|
||||
public function postprocess( string $html ): string {
|
||||
foreach ( $this->postprocessors as $postprocessor ) {
|
||||
$html = $postprocessor->postprocess( $html );
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
public function register_preprocessor( Preprocessor $preprocessor ): void {
|
||||
$this->preprocessors[] = $preprocessor;
|
||||
}
|
||||
public function register_postprocessor( Postprocessor $postprocessor ): void {
|
||||
$this->postprocessors[] = $postprocessor;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
CSS reset for email clients for elements used in email content
|
||||
StyleLint is disabled because some rules contain properties that linter marks as unknown (e.g. mso- prefix), but they are valid for email rendering
|
||||
*/
|
||||
/* stylelint-disable property-no-unknown */
|
||||
table,
|
||||
td {
|
||||
border-collapse: collapse;
|
||||
mso-table-lspace: 0;
|
||||
mso-table-rspace: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
height: auto;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
line-height: 100%;
|
||||
max-width: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* Ensure border style is set when a block has a border */
|
||||
.has-border-color {
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
/* We want ensure the same design for all email clients */
|
||||
ul,
|
||||
ol {
|
||||
/* When margin attribute is set to zero, Outlook doesn't render the list properly. As a possible workaround, we can reset only margin for top and bottom */
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
padding: 0 0 0 40px;
|
||||
}
|
||||
/* Outlook was adding weird spaces around lists in some versions. Resetting vertical margin for list items solved it */
|
||||
li {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
require_once __DIR__ . '/../../../vendor/autoload.php';
|
||||
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Content_Renderer;
|
||||
use MailPoet\EmailEditor\Engine\Templates\Templates;
|
||||
use MailPoet\EmailEditor\Engine\Theme_Controller;
|
||||
use Soundasleep\Html2Text;
|
||||
use WP_Style_Engine;
|
||||
class Renderer {
|
||||
private Theme_Controller $theme_controller;
|
||||
private Content_Renderer $content_renderer;
|
||||
private Templates $templates;
|
||||
private Css_Inliner $css_inliner;
|
||||
const TEMPLATE_FILE = 'template-canvas.php';
|
||||
const TEMPLATE_STYLES_FILE = 'template-canvas.css';
|
||||
public function __construct(
|
||||
Content_Renderer $content_renderer,
|
||||
Templates $templates,
|
||||
Css_Inliner $css_inliner,
|
||||
Theme_Controller $theme_controller
|
||||
) {
|
||||
$this->content_renderer = $content_renderer;
|
||||
$this->templates = $templates;
|
||||
$this->theme_controller = $theme_controller;
|
||||
$this->css_inliner = $css_inliner;
|
||||
}
|
||||
public function render( \WP_Post $post, string $subject, string $pre_header, string $language, $meta_robots = '' ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
$template_slug = get_page_template_slug( $post ) ? get_page_template_slug( $post ) : 'email-general';
|
||||
// phpcs:ignore Generic.Commenting.DocComment.MissingShort -- used for phpstan
|
||||
$template = $this->templates->get_block_template( $template_slug );
|
||||
$email_styles = $this->theme_controller->get_styles();
|
||||
$template_html = $this->content_renderer->render( $post, $template );
|
||||
$layout = $this->theme_controller->get_layout_settings();
|
||||
ob_start();
|
||||
include self::TEMPLATE_FILE;
|
||||
$rendered_template = (string) ob_get_clean();
|
||||
$template_styles =
|
||||
WP_Style_Engine::compile_css(
|
||||
array(
|
||||
'background-color' => $email_styles['color']['background'] ?? 'inherit',
|
||||
'color' => $email_styles['color']['text'] ?? 'inherit',
|
||||
'padding-top' => $email_styles['spacing']['padding']['top'] ?? '0px',
|
||||
'padding-bottom' => $email_styles['spacing']['padding']['bottom'] ?? '0px',
|
||||
'padding-left' => $email_styles['spacing']['padding']['left'] ?? '0px',
|
||||
'padding-right' => $email_styles['spacing']['padding']['right'] ?? '0px',
|
||||
'font-family' => $email_styles['typography']['fontFamily'] ?? 'inherit',
|
||||
'line-height' => $email_styles['typography']['lineHeight'] ?? '1.5',
|
||||
'font-size' => $email_styles['typography']['fontSize'] ?? 'inherit',
|
||||
),
|
||||
'body, .email_layout_wrapper'
|
||||
);
|
||||
$template_styles .= '.email_layout_wrapper { box-sizing: border-box;}';
|
||||
$template_styles .= file_get_contents( __DIR__ . '/' . self::TEMPLATE_STYLES_FILE );
|
||||
$template_styles = '<style>' . wp_strip_all_tags( (string) apply_filters( 'mailpoet_email_renderer_styles', $template_styles, $post ) ) . '</style>';
|
||||
$rendered_template = $this->inline_css_styles( $template_styles . $rendered_template );
|
||||
// This is a workaround to support link :hover in some clients. Ideally we would remove the ability to set :hover
|
||||
// however this is not possible using the color panel from Gutenberg.
|
||||
if ( isset( $email_styles['elements']['link'][':hover']['color']['text'] ) ) {
|
||||
$rendered_template = str_replace( '<!-- Forced Styles -->', '<style>a:hover { color: ' . esc_attr( $email_styles['elements']['link'][':hover']['color']['text'] ) . ' !important; }</style>', $rendered_template );
|
||||
}
|
||||
return array(
|
||||
'html' => $rendered_template,
|
||||
'text' => $this->render_text_version( $rendered_template ),
|
||||
);
|
||||
}
|
||||
private function inline_css_styles( $template ) {
|
||||
return $this->css_inliner->from_html( $template )->inline_css()->render();
|
||||
}
|
||||
private function render_text_version( $template ) {
|
||||
$template = ( mb_detect_encoding( $template, 'UTF-8', true ) ) ? $template : mb_convert_encoding( $template, 'UTF-8', mb_list_encodings() );
|
||||
$result = Html2Text::convert( $template );
|
||||
if ( ! $result ) {
|
||||
return '';
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
interface Css_Inliner {
|
||||
public function from_html( string $unprocessed_html ): self;
|
||||
public function inline_css( string $css = '' ): self;
|
||||
public function render(): string;
|
||||
}
|
||||
Vendored
+91
@@ -0,0 +1,91 @@
|
||||
/* Base CSS rules to be applied to all emails */
|
||||
/* Created based on original MailPoet template for rendering emails */
|
||||
/* StyleLint is disabled because some rules contain properties that linter marks as unknown (e.g. mso- prefix), but they are valid for email rendering */
|
||||
/* stylelint-disable property-no-unknown */
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%; /* From MJMJ - Automatic test adjustment on mobile max to 100% */
|
||||
-ms-text-size-adjust: 100%; /* From MJMJ - Automatic test adjustment on mobile max to 100% */
|
||||
word-spacing: normal;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.email_layout_wrapper {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.email_content_wrapper {
|
||||
direction: ltr;
|
||||
font-size: inherit;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.email_footer {
|
||||
direction: ltr;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* https://www.emailonacid.com/blog/article/email-development/tips-for-coding-email-preheaders */
|
||||
.email_preheader,
|
||||
.email_preheader * {
|
||||
color: #fff;
|
||||
display: none;
|
||||
font-size: 1px;
|
||||
line-height: 1px;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
mso-hide: all;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
-webkit-text-size-adjust: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 660px) {
|
||||
.email-block-column-content {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
.block {
|
||||
display: block;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* Ensure proper width of columns on mobile when we set 100% and a border is set */
|
||||
.email-block-column {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* We set width to some tables e.g. for wrappers of horizontally aligned images and we force width 100% on mobile */
|
||||
.email-table-with-width {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* Flex Layout */
|
||||
.layout-flex-wrapper,
|
||||
.layout-flex-wrapper tbody,
|
||||
.layout-flex-wrapper tr {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.layout-flex-item {
|
||||
display: block !important;
|
||||
padding-bottom: 8px !important; /* Half of the flex gap between blocks */
|
||||
padding-left: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.layout-flex-item table,
|
||||
.layout-flex-item td {
|
||||
box-sizing: border-box !important;
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
/* Flex Layout End */
|
||||
}
|
||||
/* stylelint-enable property-no-unknown */
|
||||
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
if (!defined('ABSPATH')) exit;
|
||||
// phpcs:disable Generic.Files.InlineHTML.Found
|
||||
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
?><!DOCTYPE html>
|
||||
<html <?php language_attributes(); ?>>
|
||||
<head>
|
||||
<title><?php echo esc_html( $subject ); // @phpstan-ignore-line ?></title>
|
||||
<meta charset="<?php bloginfo( 'charset' ); ?>" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<?php echo $meta_robots; // @phpstan-ignore-line HTML defined by MailPoet--do not escape. ?>
|
||||
<!-- Forced Styles -->
|
||||
</head>
|
||||
<body>
|
||||
<!--[if mso | IE]><table align="center" role="presentation" border="0" cellpadding="0" cellspacing="0" width="<?php echo esc_attr( $layout['contentSize'] ); // @phpstan-ignore-line ?>" style="width:<?php echo esc_attr( $layout['contentSize'] ); // @phpstan-ignore-line ?>"><tr><td><![endif]-->
|
||||
<div class="email_layout_wrapper" style="max-width: <?php echo esc_attr( $layout['contentSize'] ); // @phpstan-ignore-line ?>">
|
||||
<table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="email_preheader" height="1">
|
||||
<?php echo esc_html( wp_strip_all_tags( $pre_header ) ); // @phpstan-ignore-line ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="email_content_wrapper">
|
||||
<?php echo $template_html; // @phpstan-ignore-line ?>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</body>
|
||||
</html>
|
||||
Vendored
+87
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Templates;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Validator\Builder;
|
||||
use WP_Block_Template;
|
||||
class Templates {
|
||||
private string $template_prefix = 'mailpoet';
|
||||
private array $post_types = array();
|
||||
private string $template_directory = __DIR__ . DIRECTORY_SEPARATOR;
|
||||
public function initialize( array $post_types ): void {
|
||||
$this->post_types = $post_types;
|
||||
add_filter( 'theme_templates', array( $this, 'add_theme_templates' ), 10, 4 ); // Workaround needed when saving post – template association.
|
||||
$this->register_templates();
|
||||
$this->register_post_types_to_api();
|
||||
}
|
||||
public function get_block_template( $template_slug ) {
|
||||
// Template id is always prefixed by active theme and get_stylesheet returns the active theme slug.
|
||||
$template_id = get_stylesheet() . '//' . $template_slug;
|
||||
return get_block_template( $template_id );
|
||||
}
|
||||
private function register_templates(): void {
|
||||
// The function was added in WordPress 6.7. We can remove this check after we drop support for WordPress 6.6.
|
||||
if ( ! function_exists( 'register_block_template' ) ) {
|
||||
return;
|
||||
}
|
||||
// Register basic blank template.
|
||||
$general_email = array(
|
||||
'title' => __( 'General Email', 'mailpoet' ),
|
||||
'description' => __( 'A general template for emails.', 'mailpoet' ),
|
||||
'slug' => 'email-general',
|
||||
);
|
||||
$template_filename = $general_email['slug'] . '.html';
|
||||
$template_name = $this->template_prefix . '//' . $general_email['slug'];
|
||||
if ( ! \WP_Block_Templates_Registry::get_instance()->is_registered( $template_name ) ) {
|
||||
// skip registration if the template was already registered.
|
||||
register_block_template(
|
||||
$template_name,
|
||||
array(
|
||||
'title' => $general_email['title'],
|
||||
'description' => $general_email['description'],
|
||||
'content' => (string) file_get_contents( $this->template_directory . $template_filename ),
|
||||
'post_types' => $this->post_types,
|
||||
)
|
||||
);
|
||||
}
|
||||
do_action( 'mailpoet_email_editor_register_templates' );
|
||||
}
|
||||
public function register_post_types_to_api(): void {
|
||||
$controller = new \WP_REST_Templates_Controller( 'wp_template' );
|
||||
$schema = $controller->get_item_schema();
|
||||
// Future compatibility check if the post_types property is already registered.
|
||||
if ( isset( $schema['properties']['post_types'] ) ) {
|
||||
return;
|
||||
}
|
||||
register_rest_field(
|
||||
'wp_template',
|
||||
'post_types',
|
||||
array(
|
||||
'get_callback' => array( $this, 'get_post_types' ),
|
||||
'update_callback' => null,
|
||||
'schema' => Builder::string()->to_array(),
|
||||
)
|
||||
);
|
||||
}
|
||||
public function get_post_types( $response_object ): array {
|
||||
if ( isset( $response_object['plugin'] ) && $response_object['plugin'] === $this->template_prefix ) {
|
||||
return $this->post_types;
|
||||
}
|
||||
return $response_object['post_types'] ?? array();
|
||||
}
|
||||
public function add_theme_templates( $templates, $theme, $post, $post_type ) {
|
||||
if ( $post_type && ! in_array( $post_type, $this->post_types, true ) ) {
|
||||
return $templates;
|
||||
}
|
||||
$block_templates = get_block_templates();
|
||||
foreach ( $block_templates as $block_template ) {
|
||||
// Ideally we could check for supported post_types but there seems to be a bug and once a template has some edits and is stored in DB
|
||||
// the core returns null for post_types.
|
||||
if ( $block_template->plugin !== $this->template_prefix ) {
|
||||
continue;
|
||||
}
|
||||
$templates[ $block_template->slug ] = $block_template;
|
||||
}
|
||||
return $templates;
|
||||
}
|
||||
}
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
<!-- wp:core/post-content {"lock":{"move":true,"remove":true},"layout":{"type":"default"}} /-->
|
||||
<!-- wp:mailpoet/powered-by-mailpoet {"lock":{"move":true,"remove":true}} /-->
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
// get the rendered post HTML content.
|
||||
$template_html = apply_filters( 'mailpoet_email_editor_preview_post_template_html', get_post() );
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $template_html;
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class Dependency_Check {
|
||||
public const MIN_WP_VERSION = '6.7';
|
||||
public function are_dependencies_met(): bool {
|
||||
if ( ! $this->is_wp_version_compatible() ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private function is_wp_version_compatible(): bool {
|
||||
return version_compare( get_bloginfo( 'version' ), self::MIN_WP_VERSION, '>=' );
|
||||
}
|
||||
}
|
||||
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\PersonalizationTags\Personalization_Tag;
|
||||
use MailPoet\EmailEditor\Engine\PersonalizationTags\Personalization_Tags_Registry;
|
||||
use MailPoet\EmailEditor\Validator\Builder;
|
||||
use WP_Post;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
class Email_Api_Controller {
|
||||
private Personalization_Tags_Registry $personalization_tags_registry;
|
||||
public function __construct( Personalization_Tags_Registry $personalization_tags_registry ) {
|
||||
$this->personalization_tags_registry = $personalization_tags_registry;
|
||||
}
|
||||
public function get_email_data(): array {
|
||||
// Here comes code getting Email specific data that will be passed on 'email_data' attribute.
|
||||
return array();
|
||||
}
|
||||
public function save_email_data( array $data, WP_Post $email_post ): void {
|
||||
// Here comes code saving of Email specific data that will be passed on 'email_data' attribute.
|
||||
}
|
||||
public function send_preview_email_data( WP_REST_Request $request ): WP_REST_Response {
|
||||
$data = $request->get_params();
|
||||
try {
|
||||
$result = apply_filters( 'mailpoet_email_editor_send_preview_email', $data );
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'success' => (bool) $result,
|
||||
'result' => $result,
|
||||
),
|
||||
$result ? 200 : 400
|
||||
);
|
||||
} catch ( \Exception $exception ) {
|
||||
return new WP_REST_Response( array( 'error' => $exception->getMessage() ), 400 );
|
||||
}
|
||||
}
|
||||
public function get_personalization_tags(): WP_REST_Response {
|
||||
$tags = $this->personalization_tags_registry->get_all();
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'success' => true,
|
||||
'result' => array_values(
|
||||
array_map(
|
||||
function ( Personalization_Tag $tag ) {
|
||||
return array(
|
||||
'name' => $tag->get_name(),
|
||||
'token' => $tag->get_token(),
|
||||
'category' => $tag->get_category(),
|
||||
'attributes' => $tag->get_attributes(),
|
||||
'valueToInsert' => $tag->get_value_to_insert(),
|
||||
);
|
||||
},
|
||||
$tags
|
||||
),
|
||||
),
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
public function get_email_data_schema(): array {
|
||||
return Builder::object()->to_array();
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Patterns\Patterns;
|
||||
use MailPoet\EmailEditor\Engine\PersonalizationTags\Personalization_Tags_Registry;
|
||||
use MailPoet\EmailEditor\Engine\Templates\Templates;
|
||||
use WP_Post;
|
||||
use WP_Theme_JSON;
|
||||
class Email_Editor {
|
||||
public const MAILPOET_EMAIL_META_THEME_TYPE = 'mailpoet_email_theme';
|
||||
private Email_Api_Controller $email_api_controller;
|
||||
private Templates $templates;
|
||||
private Patterns $patterns;
|
||||
private Send_Preview_Email $send_preview_email;
|
||||
private Personalization_Tags_Registry $personalization_tags_registry;
|
||||
public function __construct(
|
||||
Email_Api_Controller $email_api_controller,
|
||||
Templates $templates,
|
||||
Patterns $patterns,
|
||||
Send_Preview_Email $send_preview_email,
|
||||
Personalization_Tags_Registry $personalization_tags_controller
|
||||
) {
|
||||
$this->email_api_controller = $email_api_controller;
|
||||
$this->templates = $templates;
|
||||
$this->patterns = $patterns;
|
||||
$this->send_preview_email = $send_preview_email;
|
||||
$this->personalization_tags_registry = $personalization_tags_controller;
|
||||
}
|
||||
public function initialize(): void {
|
||||
do_action( 'mailpoet_email_editor_initialized' );
|
||||
add_filter( 'mailpoet_email_editor_rendering_theme_styles', array( $this, 'extend_email_theme_styles' ), 10, 2 );
|
||||
$this->register_block_patterns();
|
||||
$this->register_email_post_types();
|
||||
$this->register_block_templates();
|
||||
$this->register_email_post_send_status();
|
||||
$this->register_personalization_tags();
|
||||
$is_editor_page = apply_filters( 'mailpoet_is_email_editor_page', false );
|
||||
if ( $is_editor_page ) {
|
||||
$this->extend_email_post_api();
|
||||
}
|
||||
add_action( 'rest_api_init', array( $this, 'register_email_editor_api_routes' ) );
|
||||
add_filter( 'mailpoet_email_editor_send_preview_email', array( $this->send_preview_email, 'send_preview_email' ), 11, 1 ); // allow for other filter methods to take precedent.
|
||||
add_filter( 'single_template', array( $this, 'load_email_preview_template' ) );
|
||||
}
|
||||
private function register_block_templates(): void {
|
||||
// Since we cannot currently disable blocks in the editor for specific templates, disable templates when viewing site editor. @see https://github.com/WordPress/gutenberg/issues/41062.
|
||||
if ( strstr( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ?? '' ) ), 'site-editor.php' ) === false ) {
|
||||
$post_types = array_column( $this->get_post_types(), 'name' );
|
||||
$this->templates->initialize( $post_types );
|
||||
}
|
||||
}
|
||||
private function register_block_patterns(): void {
|
||||
$this->patterns->initialize();
|
||||
}
|
||||
private function register_email_post_types(): void {
|
||||
foreach ( $this->get_post_types() as $post_type ) {
|
||||
register_post_type(
|
||||
$post_type['name'],
|
||||
array_merge( $this->get_default_email_post_args(), $post_type['args'] )
|
||||
);
|
||||
}
|
||||
}
|
||||
private function register_personalization_tags(): void {
|
||||
$this->personalization_tags_registry->initialize();
|
||||
}
|
||||
private function get_post_types(): array {
|
||||
$post_types = array();
|
||||
return apply_filters( 'mailpoet_email_editor_post_types', $post_types );
|
||||
}
|
||||
private function get_default_email_post_args(): array {
|
||||
return array(
|
||||
'public' => false,
|
||||
'hierarchical' => false,
|
||||
'show_ui' => true,
|
||||
'show_in_menu' => false,
|
||||
'show_in_nav_menus' => false,
|
||||
'supports' => array( 'editor', 'title', 'custom-fields' ), // 'custom-fields' is required for loading meta fields via API.
|
||||
'has_archive' => true,
|
||||
'show_in_rest' => true, // Important to enable Gutenberg editor.
|
||||
'default_rendering_mode' => 'template-locked',
|
||||
'publicly_queryable' => true, // required by the preview in new tab feature.
|
||||
);
|
||||
}
|
||||
private function register_email_post_send_status(): void {
|
||||
register_post_status(
|
||||
'sent',
|
||||
array(
|
||||
'public' => false,
|
||||
'exclude_from_search' => true,
|
||||
'internal' => true, // for now, we hide it, if we use the status in the listings we may flip this and following values.
|
||||
'show_in_admin_all_list' => false,
|
||||
'show_in_admin_status_list' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
public function extend_email_post_api() {
|
||||
$email_post_types = array_column( $this->get_post_types(), 'name' );
|
||||
register_rest_field(
|
||||
$email_post_types,
|
||||
'email_data',
|
||||
array(
|
||||
'get_callback' => array( $this->email_api_controller, 'get_email_data' ),
|
||||
'update_callback' => array( $this->email_api_controller, 'save_email_data' ),
|
||||
'schema' => $this->email_api_controller->get_email_data_schema(),
|
||||
)
|
||||
);
|
||||
}
|
||||
public function register_email_editor_api_routes() {
|
||||
register_rest_route(
|
||||
'mailpoet-email-editor/v1',
|
||||
'/send_preview_email',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( $this->email_api_controller, 'send_preview_email_data' ),
|
||||
'permission_callback' => function () {
|
||||
return current_user_can( 'edit_posts' );
|
||||
},
|
||||
)
|
||||
);
|
||||
register_rest_route(
|
||||
'mailpoet-email-editor/v1',
|
||||
'/get_personalization_tags',
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this->email_api_controller, 'get_personalization_tags' ),
|
||||
'permission_callback' => function () {
|
||||
return current_user_can( 'edit_posts' );
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
public function extend_email_theme_styles( WP_Theme_JSON $theme, WP_Post $post ): WP_Theme_JSON {
|
||||
$email_theme = get_post_meta( $post->ID, self::MAILPOET_EMAIL_META_THEME_TYPE, true );
|
||||
if ( $email_theme && is_array( $email_theme ) ) {
|
||||
$theme->merge( new WP_Theme_JSON( $email_theme ) );
|
||||
}
|
||||
return $theme;
|
||||
}
|
||||
public function get_current_post() {
|
||||
if ( isset( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$current_post = get_post( intval( $_GET['post'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- data valid
|
||||
} else {
|
||||
$current_post = $GLOBALS['post'];
|
||||
}
|
||||
return $current_post;
|
||||
}
|
||||
public function load_email_preview_template( string $template ): string {
|
||||
$post = $this->get_current_post();
|
||||
if ( ! $post instanceof \WP_Post ) {
|
||||
return $template;
|
||||
}
|
||||
$current_post_type = $post->post_type;
|
||||
$email_post_types = array_column( $this->get_post_types(), 'name' );
|
||||
if ( ! in_array( $current_post_type, $email_post_types, true ) ) {
|
||||
return $template;
|
||||
}
|
||||
add_filter(
|
||||
'mailpoet_email_editor_preview_post_template_html',
|
||||
function () use ( $post ) {
|
||||
// Generate HTML content for email editor post.
|
||||
return $this->send_preview_email->render_html( $post );
|
||||
}
|
||||
);
|
||||
return __DIR__ . '/Templates/single-email-post-template.php';
|
||||
}
|
||||
}
|
||||
Vendored
+99
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Validator\Builder;
|
||||
class Email_Styles_Schema {
|
||||
public function get_schema(): array {
|
||||
$typography_props = Builder::object(
|
||||
array(
|
||||
'fontFamily' => Builder::string()->nullable(),
|
||||
'fontSize' => Builder::string()->nullable(),
|
||||
'fontStyle' => Builder::string()->nullable(),
|
||||
'fontWeight' => Builder::string()->nullable(),
|
||||
'letterSpacing' => Builder::string()->nullable(),
|
||||
'lineHeight' => Builder::string()->nullable(),
|
||||
'textTransform' => Builder::string()->nullable(),
|
||||
'textDecoration' => Builder::string()->nullable(),
|
||||
)
|
||||
)->nullable();
|
||||
return Builder::object(
|
||||
array(
|
||||
'version' => Builder::integer(),
|
||||
'styles' => Builder::object(
|
||||
array(
|
||||
'spacing' => Builder::object(
|
||||
array(
|
||||
'padding' => Builder::object(
|
||||
array(
|
||||
'top' => Builder::string(),
|
||||
'right' => Builder::string(),
|
||||
'bottom' => Builder::string(),
|
||||
'left' => Builder::string(),
|
||||
)
|
||||
)->nullable(),
|
||||
'blockGap' => Builder::string()->nullable(),
|
||||
)
|
||||
)->nullable(),
|
||||
'color' => Builder::object(
|
||||
array(
|
||||
'background' => Builder::string()->nullable(),
|
||||
'text' => Builder::string()->nullable(),
|
||||
)
|
||||
)->nullable(),
|
||||
'typography' => $typography_props,
|
||||
'elements' => Builder::object(
|
||||
array(
|
||||
'heading' => Builder::object(
|
||||
array(
|
||||
'typography' => $typography_props,
|
||||
)
|
||||
)->nullable(),
|
||||
'button' => Builder::object(
|
||||
array(
|
||||
'typography' => $typography_props,
|
||||
)
|
||||
)->nullable(),
|
||||
'link' => Builder::object(
|
||||
array(
|
||||
'typography' => $typography_props,
|
||||
)
|
||||
)->nullable(),
|
||||
'h1' => Builder::object(
|
||||
array(
|
||||
'typography' => $typography_props,
|
||||
)
|
||||
)->nullable(),
|
||||
'h2' => Builder::object(
|
||||
array(
|
||||
'typography' => $typography_props,
|
||||
)
|
||||
)->nullable(),
|
||||
'h3' => Builder::object(
|
||||
array(
|
||||
'typography' => $typography_props,
|
||||
)
|
||||
)->nullable(),
|
||||
'h4' => Builder::object(
|
||||
array(
|
||||
'typography' => $typography_props,
|
||||
)
|
||||
)->nullable(),
|
||||
'h5' => Builder::object(
|
||||
array(
|
||||
'typography' => $typography_props,
|
||||
)
|
||||
)->nullable(),
|
||||
'h6' => Builder::object(
|
||||
array(
|
||||
'typography' => $typography_props,
|
||||
)
|
||||
)->nullable(),
|
||||
)
|
||||
)->nullable(),
|
||||
)
|
||||
)->nullable(),
|
||||
)
|
||||
)->to_array();
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\PersonalizationTags\HTML_Tag_Processor;
|
||||
use MailPoet\EmailEditor\Engine\PersonalizationTags\Personalization_Tags_Registry;
|
||||
class Personalizer {
|
||||
private Personalization_Tags_Registry $tags_registry;
|
||||
private array $context;
|
||||
public function __construct( Personalization_Tags_Registry $tags_registry ) {
|
||||
$this->tags_registry = $tags_registry;
|
||||
$this->context = array();
|
||||
}
|
||||
public function set_context( array $context ) {
|
||||
$this->context = $context;
|
||||
}
|
||||
public function personalize_content( string $content ): string {
|
||||
$content_processor = new HTML_Tag_Processor( $content );
|
||||
while ( $content_processor->next_token() ) {
|
||||
if ( $content_processor->get_token_type() === '#comment' ) {
|
||||
$token = $this->parse_token( $content_processor->get_modifiable_text() );
|
||||
$tag = $this->tags_registry->get_by_token( $token['token'] );
|
||||
if ( ! $tag ) {
|
||||
continue;
|
||||
}
|
||||
$value = $tag->execute_callback( $this->context, $token['arguments'] );
|
||||
$content_processor->replace_token( $value );
|
||||
} elseif ( $content_processor->get_token_type() === '#tag' && $content_processor->get_tag() === 'TITLE' ) {
|
||||
// The title tag contains the subject of the email which should be personalized. HTML_Tag_Processor does parse the header tags.
|
||||
$title = $this->personalize_content( $content_processor->get_modifiable_text() );
|
||||
$content_processor->set_modifiable_text( $title );
|
||||
} elseif ( $content_processor->get_token_type() === '#tag' && $content_processor->get_tag() === 'A' && $content_processor->get_attribute( 'data-link-href' ) ) {
|
||||
// The anchor tag contains the data-link-href attribute which should be personalized.
|
||||
$href = $content_processor->get_attribute( 'data-link-href' );
|
||||
$token = $this->parse_token( $href );
|
||||
$tag = $this->tags_registry->get_by_token( $token['token'] );
|
||||
if ( ! $tag ) {
|
||||
continue;
|
||||
}
|
||||
$value = $tag->execute_callback( $this->context, $token['arguments'] );
|
||||
$value = $this->replace_link_href( $href, $tag->get_token(), $value );
|
||||
if ( $value ) {
|
||||
$content_processor->set_attribute( 'href', $value );
|
||||
$content_processor->remove_attribute( 'data-link-href' );
|
||||
$content_processor->remove_attribute( 'contenteditable' );
|
||||
}
|
||||
}
|
||||
}
|
||||
$content_processor->flush_updates();
|
||||
return $content_processor->get_updated_html();
|
||||
}
|
||||
private function parse_token( string $token ): array {
|
||||
$result = array(
|
||||
'token' => '',
|
||||
'arguments' => array(),
|
||||
);
|
||||
// Step 1: Separate the tag and attributes.
|
||||
if ( preg_match( '/^\[([a-zA-Z0-9\-\/]+)\s*(.*?)\]$/', trim( $token ), $matches ) ) {
|
||||
$result['token'] = "[{$matches[1]}]"; // The tag part (e.g., "[mailpoet/subscriber-firstname]").
|
||||
$attributes_string = $matches[2]; // The attributes part (e.g., 'default="subscriber"').
|
||||
// Step 2: Extract attributes from the attribute string.
|
||||
if ( preg_match_all( '/(\w+)=["\']([^"\']+)["\']/', $attributes_string, $attribute_matches, PREG_SET_ORDER ) ) {
|
||||
foreach ( $attribute_matches as $attribute ) {
|
||||
$result['arguments'][ $attribute[1] ] = $attribute[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
private function replace_link_href( string $content, string $token, string $replacement ) {
|
||||
// Escape the shortcode name for safe regex usage and strip the brackets.
|
||||
$escaped_shortcode = preg_quote( substr( $token, 1, strlen( $token ) - 2 ), '/' );
|
||||
// Create a regex pattern dynamically.
|
||||
$pattern = '/\[' . $escaped_shortcode . '(?:\s+[^\]]+)?\]/';
|
||||
return trim( (string) preg_replace( $pattern, $replacement, $content ) );
|
||||
}
|
||||
}
|
||||
Vendored
+76
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
namespace MailPoet\EmailEditor\Engine;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\Renderer;
|
||||
class Send_Preview_Email {
|
||||
private Renderer $renderer;
|
||||
private Personalizer $personalizer;
|
||||
public function __construct(
|
||||
Renderer $renderer,
|
||||
Personalizer $personalizer
|
||||
) {
|
||||
$this->renderer = $renderer;
|
||||
$this->personalizer = $personalizer;
|
||||
}
|
||||
public function send_preview_email( $data ): bool {
|
||||
if ( is_bool( $data ) ) {
|
||||
// preview mail already sent. Do not process again.
|
||||
return $data;
|
||||
}
|
||||
$this->validate_data( $data );
|
||||
$email = $data['email'];
|
||||
$post_id = $data['postId'];
|
||||
$post = $this->fetch_post( $post_id );
|
||||
$subject = $post->post_title;
|
||||
$email_html_content = $this->render_html( $post );
|
||||
return $this->send_email( $email, $subject, $email_html_content );
|
||||
}
|
||||
public function render_html( $post ): string {
|
||||
$subject = $post->post_title;
|
||||
$language = get_bloginfo( 'language' );
|
||||
$rendered_data = $this->renderer->render(
|
||||
$post,
|
||||
$subject,
|
||||
__( 'Preview', 'mailpoet' ),
|
||||
$language
|
||||
);
|
||||
return $this->set_personalize_content( $rendered_data['html'] );
|
||||
}
|
||||
public function set_personalize_content( string $content ): string {
|
||||
$current_user = wp_get_current_user();
|
||||
$subscriber = ! empty( $current_user->ID ) ? $current_user : null;
|
||||
$this->personalizer->set_context(
|
||||
array(
|
||||
'recipient_email' => $subscriber ? $subscriber->user_email : null,
|
||||
'is_user_preview' => true,
|
||||
)
|
||||
);
|
||||
return $this->personalizer->personalize_content( $content );
|
||||
}
|
||||
public function send_email( string $to, string $subject, string $body ): bool {
|
||||
add_filter( 'wp_mail_content_type', array( $this, 'set_mail_content_type' ) );
|
||||
$result = wp_mail( $to, $subject, $body );
|
||||
// Reset content-type to avoid conflicts.
|
||||
remove_filter( 'wp_mail_content_type', array( $this, 'set_mail_content_type' ) );
|
||||
return $result;
|
||||
}
|
||||
public function set_mail_content_type( string $content_type ): string { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
|
||||
return 'text/html';
|
||||
}
|
||||
private function validate_data( array $data ) {
|
||||
if ( empty( $data['email'] ) || empty( $data['postId'] ) ) {
|
||||
throw new \InvalidArgumentException( esc_html__( 'Missing required data', 'mailpoet' ) );
|
||||
}
|
||||
if ( ! is_email( $data['email'] ) ) {
|
||||
throw new \InvalidArgumentException( esc_html__( 'Invalid email', 'mailpoet' ) );
|
||||
}
|
||||
}
|
||||
private function fetch_post( $post_id ): \WP_Post {
|
||||
$post = get_post( intval( $post_id ) );
|
||||
if ( ! $post instanceof \WP_Post ) {
|
||||
throw new \Exception( esc_html__( 'Invalid post', 'mailpoet' ) );
|
||||
}
|
||||
return $post;
|
||||
}
|
||||
}
|
||||
Vendored
+110
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class Settings_Controller {
|
||||
const ALLOWED_BLOCK_TYPES = array(
|
||||
'core/button',
|
||||
'core/buttons',
|
||||
'core/paragraph',
|
||||
'core/heading',
|
||||
'core/column',
|
||||
'core/columns',
|
||||
'core/image',
|
||||
'core/list',
|
||||
'core/list-item',
|
||||
'core/group',
|
||||
'core/spacer',
|
||||
);
|
||||
const DEFAULT_SETTINGS = array(
|
||||
'enableCustomUnits' => array( 'px', '%' ),
|
||||
);
|
||||
private Theme_Controller $theme_controller;
|
||||
private array $iframe_assets = array();
|
||||
public function __construct(
|
||||
Theme_Controller $theme_controller
|
||||
) {
|
||||
$this->theme_controller = $theme_controller;
|
||||
}
|
||||
public function get_settings(): array {
|
||||
$this->init_iframe_assets();
|
||||
$core_default_settings = \get_default_block_editor_settings();
|
||||
$theme_settings = $this->theme_controller->get_settings();
|
||||
$settings = array_merge( $core_default_settings, self::DEFAULT_SETTINGS );
|
||||
$settings['allowedBlockTypes'] = self::ALLOWED_BLOCK_TYPES;
|
||||
// Assets for iframe editor (component styles, scripts, etc.).
|
||||
$settings['__unstableResolvedAssets'] = $this->iframe_assets;
|
||||
$editor_content_styles = file_get_contents( __DIR__ . '/content-editor.css' );
|
||||
$shares_content_styles = file_get_contents( __DIR__ . '/content-shared.css' );
|
||||
$settings['styles'] = array(
|
||||
array( 'css' => $editor_content_styles ),
|
||||
array( 'css' => $shares_content_styles ),
|
||||
);
|
||||
$settings['__experimentalFeatures'] = $theme_settings;
|
||||
// Controls which alignment options are available for blocks.
|
||||
$settings['supportsLayout'] = true; // Allow using default layouts.
|
||||
$settings['__unstableIsBlockBasedTheme'] = true; // For default setting this to true disables wide and full alignments.
|
||||
return $settings;
|
||||
}
|
||||
public function get_layout(): array {
|
||||
$layout_settings = $this->theme_controller->get_layout_settings();
|
||||
return array(
|
||||
'contentSize' => $layout_settings['contentSize'],
|
||||
'wideSize' => $layout_settings['wideSize'],
|
||||
);
|
||||
}
|
||||
public function get_email_styles(): array {
|
||||
$theme = $this->get_theme();
|
||||
return $theme->get_data()['styles'];
|
||||
}
|
||||
public function get_layout_width_without_padding(): string {
|
||||
$styles = $this->get_email_styles();
|
||||
$layout = $this->get_layout();
|
||||
$width = $this->parse_number_from_string_with_pixels( $layout['contentSize'] );
|
||||
$width -= $this->parse_number_from_string_with_pixels( $styles['spacing']['padding']['left'] );
|
||||
$width -= $this->parse_number_from_string_with_pixels( $styles['spacing']['padding']['right'] );
|
||||
return "{$width}px";
|
||||
}
|
||||
public function parse_styles_to_array( string $styles ): array {
|
||||
$styles = explode( ';', $styles );
|
||||
$parsed_styles = array();
|
||||
foreach ( $styles as $style ) {
|
||||
$style = explode( ':', $style );
|
||||
if ( count( $style ) === 2 ) {
|
||||
$parsed_styles[ trim( $style[0] ) ] = trim( $style[1] );
|
||||
}
|
||||
}
|
||||
return $parsed_styles;
|
||||
}
|
||||
public function parse_number_from_string_with_pixels( string $value ): float {
|
||||
return (float) str_replace( 'px', '', $value );
|
||||
}
|
||||
public function get_theme(): \WP_Theme_JSON {
|
||||
return $this->theme_controller->get_theme();
|
||||
}
|
||||
public function translate_slug_to_font_size( string $font_size ): string {
|
||||
return $this->theme_controller->translate_slug_to_font_size( $font_size );
|
||||
}
|
||||
public function translate_slug_to_color( string $color_slug ): string {
|
||||
return $this->theme_controller->translate_slug_to_color( $color_slug );
|
||||
}
|
||||
private function init_iframe_assets(): void {
|
||||
if ( ! empty( $this->iframe_assets ) ) {
|
||||
return;
|
||||
}
|
||||
$this->iframe_assets = _wp_get_iframed_editor_assets();
|
||||
// Remove layout styles and block library for classic themes. They are added only when a classic theme is active
|
||||
// and they add unwanted margins and paddings in the editor content.
|
||||
$cleaned_styles = array();
|
||||
foreach ( explode( "\n", (string) $this->iframe_assets['styles'] ) as $asset ) {
|
||||
if ( strpos( $asset, 'wp-editor-classic-layout-styles-css' ) !== false ) {
|
||||
continue;
|
||||
}
|
||||
if ( strpos( $asset, 'wp-block-library-theme-css' ) !== false ) {
|
||||
continue;
|
||||
}
|
||||
$cleaned_styles[] = $asset;
|
||||
}
|
||||
$this->iframe_assets['styles'] = implode( "\n", $cleaned_styles );
|
||||
}
|
||||
}
|
||||
Vendored
+181
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use WP_Block_Template;
|
||||
use WP_Post;
|
||||
use WP_Theme_JSON;
|
||||
use WP_Theme_JSON_Resolver;
|
||||
class Theme_Controller {
|
||||
private WP_Theme_JSON $core_theme;
|
||||
private WP_Theme_JSON $base_theme;
|
||||
private User_Theme $user_theme;
|
||||
public function __construct() {
|
||||
$this->core_theme = WP_Theme_JSON_Resolver::get_core_data();
|
||||
$this->base_theme = new WP_Theme_JSON( (array) json_decode( (string) file_get_contents( __DIR__ . '/theme.json' ), true ), 'default' );
|
||||
$this->user_theme = new User_Theme();
|
||||
}
|
||||
public function get_theme(): WP_Theme_JSON {
|
||||
$theme = $this->get_base_theme();
|
||||
$theme->merge( $this->user_theme->get_theme() );
|
||||
return $theme;
|
||||
}
|
||||
public function get_base_theme(): WP_Theme_JSON {
|
||||
$theme = new WP_Theme_JSON();
|
||||
$theme->merge( $this->core_theme );
|
||||
$theme->merge( $this->base_theme );
|
||||
return apply_filters( 'mailpoet_email_editor_theme_json', $theme );
|
||||
}
|
||||
private function recursive_replace_presets( $values, $presets ) {
|
||||
foreach ( $values as $key => $value ) {
|
||||
if ( is_array( $value ) ) {
|
||||
$values[ $key ] = $this->recursive_replace_presets( $value, $presets );
|
||||
} elseif ( is_string( $value ) ) {
|
||||
$values[ $key ] = preg_replace( array_keys( $presets ), array_values( $presets ), $value );
|
||||
} else {
|
||||
$values[ $key ] = $value;
|
||||
}
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
private function recursive_extract_preset_variables( $styles ) {
|
||||
foreach ( $styles as $key => $style_value ) {
|
||||
if ( is_array( $style_value ) ) {
|
||||
$styles[ $key ] = $this->recursive_extract_preset_variables( $style_value );
|
||||
} elseif ( strpos( $style_value, 'var:preset|' ) === 0 ) {
|
||||
// phpcs:ignore Generic.Commenting.DocComment.MissingShort
|
||||
$styles[ $key ] = 'var(--wp--' . str_replace( '|', '--', str_replace( 'var:', '', $style_value ) ) . ')';
|
||||
} else {
|
||||
$styles[ $key ] = $style_value;
|
||||
}
|
||||
}
|
||||
return $styles;
|
||||
}
|
||||
public function get_styles(): array {
|
||||
$theme_styles = $this->get_theme()->get_data()['styles'];
|
||||
// Extract preset variables.
|
||||
$theme_styles = $this->recursive_extract_preset_variables( $theme_styles );
|
||||
// Replace preset values.
|
||||
$variables = $this->get_variables_values_map();
|
||||
$presets = array();
|
||||
foreach ( $variables as $name => $value ) {
|
||||
$pattern = '/var\(' . preg_quote( $name, '/' ) . '\)/i';
|
||||
$presets[ $pattern ] = $value;
|
||||
}
|
||||
return $this->recursive_replace_presets( $theme_styles, $presets );
|
||||
}
|
||||
public function get_settings(): array {
|
||||
$email_editor_theme_settings = $this->get_theme()->get_settings();
|
||||
$site_theme_settings = WP_Theme_JSON_Resolver::get_theme_data()->get_settings();
|
||||
$email_editor_theme_settings['color']['palette']['theme'] = array();
|
||||
if ( isset( $site_theme_settings['color']['palette']['theme'] ) ) {
|
||||
$email_editor_theme_settings['color']['palette']['theme'] = $site_theme_settings['color']['palette']['theme'];
|
||||
}
|
||||
return $email_editor_theme_settings;
|
||||
}
|
||||
public function get_layout_settings(): array {
|
||||
return $this->get_theme()->get_settings()['layout'];
|
||||
}
|
||||
public function get_stylesheet_from_context( $context, $options = array() ): string {
|
||||
return function_exists( 'gutenberg_style_engine_get_stylesheet_from_context' ) ? gutenberg_style_engine_get_stylesheet_from_context( $context, $options ) : wp_style_engine_get_stylesheet_from_context( $context, $options );
|
||||
}
|
||||
public function get_stylesheet_for_rendering( ?WP_Post $post = null, $template = null ): string {
|
||||
$email_theme_settings = $this->get_settings();
|
||||
$css_presets = '';
|
||||
// Font family classes.
|
||||
foreach ( $email_theme_settings['typography']['fontFamilies']['default'] as $font_family ) {
|
||||
$css_presets .= ".has-{$font_family['slug']}-font-family { font-family: {$font_family['fontFamily']}; } \n";
|
||||
}
|
||||
// Font size classes.
|
||||
foreach ( $email_theme_settings['typography']['fontSizes']['default'] as $font_size ) {
|
||||
$css_presets .= ".has-{$font_size['slug']}-font-size { font-size: {$font_size['size']}; } \n";
|
||||
}
|
||||
// Color palette classes.
|
||||
$color_definitions = array_merge( $email_theme_settings['color']['palette']['theme'], $email_theme_settings['color']['palette']['default'] );
|
||||
foreach ( $color_definitions as $color ) {
|
||||
$css_presets .= ".has-{$color['slug']}-color { color: {$color['color']}; } \n";
|
||||
$css_presets .= ".has-{$color['slug']}-background-color { background-color: {$color['color']}; } \n";
|
||||
$css_presets .= ".has-{$color['slug']}-border-color { border-color: {$color['color']}; } \n";
|
||||
}
|
||||
// Block specific styles.
|
||||
$css_blocks = '';
|
||||
$blocks = $this->get_theme()->get_styles_block_nodes();
|
||||
foreach ( $blocks as $block_metadata ) {
|
||||
$css_blocks .= $this->get_theme()->get_styles_for_block( $block_metadata );
|
||||
}
|
||||
// Element specific styles.
|
||||
$elements_styles = $this->get_theme()->get_raw_data()['styles']['elements'] ?? array();
|
||||
// Because the section styles is not a part of the output the `get_styles_block_nodes` method, we need to get it separately.
|
||||
if ( $template && $template->wp_id ) {
|
||||
$template_theme = (array) get_post_meta( $template->wp_id, 'mailpoet_email_theme', true );
|
||||
$template_styles = (array) ( $template_theme['styles'] ?? array() );
|
||||
$template_elements = $template_styles['elements'] ?? array();
|
||||
$elements_styles = array_replace_recursive( (array) $elements_styles, (array) $template_elements );
|
||||
}
|
||||
if ( $post ) {
|
||||
$post_theme = (array) get_post_meta( $post->ID, 'mailpoet_email_theme', true );
|
||||
$post_styles = (array) ( $post_theme['styles'] ?? array() );
|
||||
$post_elements = $post_styles['elements'] ?? array();
|
||||
$elements_styles = array_replace_recursive( (array) $elements_styles, (array) $post_elements );
|
||||
}
|
||||
$css_elements = '';
|
||||
foreach ( $elements_styles as $key => $elements_style ) {
|
||||
$selector = $key;
|
||||
if ( 'button' === $key ) {
|
||||
$selector = '.wp-block-button';
|
||||
$css_elements .= wp_style_engine_get_styles( $elements_style, array( 'selector' => '.wp-block-button' ) )['css'];
|
||||
// Add color to link element.
|
||||
$css_elements .= wp_style_engine_get_styles( array( 'color' => array( 'text' => $elements_style['color']['text'] ?? '' ) ), array( 'selector' => '.wp-block-button a' ) )['css'];
|
||||
continue;
|
||||
}
|
||||
switch ( $key ) {
|
||||
case 'heading':
|
||||
$selector = 'h1, h2, h3, h4, h5, h6';
|
||||
break;
|
||||
case 'link':
|
||||
$selector = 'a:not(.button-link)';
|
||||
break;
|
||||
}
|
||||
$css_elements .= wp_style_engine_get_styles( $elements_style, array( 'selector' => $selector ) )['css'];
|
||||
}
|
||||
$result = $css_presets . $css_blocks . $css_elements;
|
||||
// Because font-size can by defined by the clamp() function that is not supported in the e-mail clients, we need to replace it to the value.
|
||||
// Regular expression to match clamp() function and capture its max value.
|
||||
$pattern = '/clamp\([^,]+,\s*[^,]+,\s*([^)]+)\)/';
|
||||
// Replace clamp() with its maximum value.
|
||||
$result = (string) preg_replace( $pattern, '$1', $result );
|
||||
return $result;
|
||||
}
|
||||
public function translate_slug_to_font_size( string $font_size ): string {
|
||||
$settings = $this->get_settings();
|
||||
foreach ( $settings['typography']['fontSizes']['default'] as $font_size_definition ) {
|
||||
if ( $font_size_definition['slug'] === $font_size ) {
|
||||
return $font_size_definition['size'];
|
||||
}
|
||||
}
|
||||
return $font_size;
|
||||
}
|
||||
public function translate_slug_to_color( string $color_slug ): string {
|
||||
$settings = $this->get_settings();
|
||||
$color_definitions = array_merge( $settings['color']['palette']['theme'], $settings['color']['palette']['default'] );
|
||||
foreach ( $color_definitions as $color_definition ) {
|
||||
if ( $color_definition['slug'] === $color_slug ) {
|
||||
return strtolower( $color_definition['color'] );
|
||||
}
|
||||
}
|
||||
return $color_slug;
|
||||
}
|
||||
public function get_variables_values_map(): array {
|
||||
$variables_css = $this->get_theme()->get_stylesheet( array( 'variables' ) );
|
||||
$map = array();
|
||||
// Regular expression to match CSS variable definitions.
|
||||
$pattern = '/--(.*?):\s*(.*?);/';
|
||||
if ( preg_match_all( $pattern, $variables_css, $matches, PREG_SET_ORDER ) ) {
|
||||
foreach ( $matches as $match ) {
|
||||
// '--' . $match[1] is the variable name, $match[2] is the variable value.
|
||||
$map[ '--' . $match[1] ] = $match[2];
|
||||
}
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use WP_Post;
|
||||
use WP_Theme_JSON;
|
||||
class User_Theme {
|
||||
private const USER_THEME_POST_NAME = 'wp-global-styles-mailpoet-email';
|
||||
private const INITIAL_THEME_DATA = array(
|
||||
'version' => 3,
|
||||
'isGlobalStylesUserThemeJSON' => true,
|
||||
);
|
||||
private ?WP_Post $user_theme_post = null;
|
||||
public function get_theme(): WP_Theme_JSON {
|
||||
$post = $this->get_user_theme_post();
|
||||
$theme_data = json_decode( $post->post_content, true );
|
||||
if ( ! is_array( $theme_data ) ) {
|
||||
$theme_data = self::INITIAL_THEME_DATA;
|
||||
}
|
||||
return new WP_Theme_JSON( $theme_data, 'custom' );
|
||||
}
|
||||
public function get_user_theme_post(): WP_Post {
|
||||
$this->ensure_theme_post();
|
||||
if ( ! $this->user_theme_post instanceof WP_Post ) {
|
||||
throw new \Exception( 'Error creating user theme post' );
|
||||
}
|
||||
return $this->user_theme_post;
|
||||
}
|
||||
private function ensure_theme_post(): void {
|
||||
if ( $this->user_theme_post ) {
|
||||
return;
|
||||
}
|
||||
$this->user_theme_post = get_page_by_path( self::USER_THEME_POST_NAME, OBJECT, 'wp_global_styles' );
|
||||
if ( $this->user_theme_post instanceof WP_Post ) {
|
||||
return;
|
||||
}
|
||||
$post_data = array(
|
||||
'post_title' => __( 'Custom Email Styles', 'mailpoet' ),
|
||||
'post_name' => self::USER_THEME_POST_NAME,
|
||||
'post_content' => (string) wp_json_encode( self::INITIAL_THEME_DATA, JSON_FORCE_OBJECT ),
|
||||
'post_status' => 'publish',
|
||||
'post_type' => 'wp_global_styles',
|
||||
);
|
||||
$post_id = wp_insert_post( $post_data );
|
||||
if ( is_wp_error( $post_id ) ) {
|
||||
throw new \Exception( 'Error creating user theme post: ' . esc_html( $post_id->get_error_message() ) );
|
||||
}
|
||||
$this->user_theme_post = get_post( $post_id );
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Styles for the email editor.
|
||||
*/
|
||||
/*
|
||||
* Flex layout used for buttons block for email editor.
|
||||
*/
|
||||
.is-layout-email-flex {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
:where(body .is-layout-flex) {
|
||||
gap: var(--wp--style--block-gap, 16px);
|
||||
}
|
||||
|
||||
.is-mobile-preview .is-layout-email-flex {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.is-mobile-preview .is-layout-email-flex .block-editor-block-list__block {
|
||||
padding: 5px 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.is-mobile-preview .is-layout-email-flex .wp-block-button__link {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/*
|
||||
* Email Editor specific styles for vertical gap between blocks in column and group.
|
||||
* This is needed because we disable layout for core/group, core/column and core/columns blocks, and .is-layout-flex is not applied.
|
||||
*/
|
||||
.wp-block-columns:not(.is-not-stacked-on-mobile)
|
||||
> .wp-block-column
|
||||
> .wp-block:first-child,
|
||||
.wp-block-group > .wp-block:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.wp-block-columns:not(.is-not-stacked-on-mobile)
|
||||
> .wp-block-column
|
||||
> .wp-block,
|
||||
.wp-block-group > .wp-block {
|
||||
margin-bottom: var(--wp--style--block-gap, 16px);
|
||||
margin-top: var(--wp--style--block-gap, 16px);
|
||||
}
|
||||
|
||||
.wp-block-columns:not(.is-not-stacked-on-mobile)
|
||||
> .wp-block-column
|
||||
> .wp-block:not([aria-hidden="true"]):last-of-type,
|
||||
.wp-block-group > .wp-block:not([aria-hidden="true"]):last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Use box sizing border box for columns that have defined a width (they have flex-basis set).
|
||||
*/
|
||||
.wp-block-columns:not(.is-not-stacked-on-mobile)
|
||||
> .wp-block-column[style*='flex-basis'] {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/*
|
||||
* For the WYSIWYG experience we don't want to display any margins between blocks in the editor
|
||||
*/
|
||||
.wp-block {
|
||||
clear: both; // for ensuring that floated elements (images) are cleared
|
||||
}
|
||||
|
||||
/*
|
||||
* Image block enhancements
|
||||
*/
|
||||
.wp-block-image figcaption {
|
||||
/* Resetting the margin for images in the editor to avoid unexpected spacing */
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wp-block-image.alignleft,
|
||||
.wp-block-image.alignright {
|
||||
margin-inline: 0 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wp-block-image.aligncenter {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.wp-block-image.alignright {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set default padding-left to have consistent default look in editor and in email
|
||||
* This also overrides the default values in browsers for padding-inline-start
|
||||
*/
|
||||
ul,
|
||||
ol,
|
||||
ul.has-background,
|
||||
ol.has-background {
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Override default button border radius which is set in core to 9999px
|
||||
*/
|
||||
.wp-block-button__link {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Mobile preview fixes
|
||||
*/
|
||||
.is-mobile-preview figure > div {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Styles for both the email editor and renderer.
|
||||
*/
|
||||
|
||||
/* Automatic padding for blocks with background color */
|
||||
.email-text-block.has-background,
|
||||
p.has-background,
|
||||
h1.has-background,
|
||||
h2.has-background,
|
||||
h3.has-background,
|
||||
h4.has-background,
|
||||
h5.has-background,
|
||||
h6.has-background {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/theme.json",
|
||||
"version": 3,
|
||||
"settings": {
|
||||
"color": {
|
||||
"customGradient": false,
|
||||
"defaultGradients": false,
|
||||
"gradients": [],
|
||||
"background": true,
|
||||
"text": true,
|
||||
"link": true
|
||||
},
|
||||
"layout": {
|
||||
"contentSize": "660px",
|
||||
"wideSize": "",
|
||||
"allowEditing": false,
|
||||
"allowCustomContentAndWideSize": false
|
||||
},
|
||||
"background": {
|
||||
"backgroundImage": true
|
||||
},
|
||||
"spacing": {
|
||||
"units": ["px"],
|
||||
"blockGap": false,
|
||||
"padding": true,
|
||||
"margin": false,
|
||||
"spacingSizes": [
|
||||
{
|
||||
"name": "1",
|
||||
"size": "10px",
|
||||
"slug": "10"
|
||||
},
|
||||
{
|
||||
"name": "2",
|
||||
"size": "20px",
|
||||
"slug": "20"
|
||||
},
|
||||
{
|
||||
"name": "3",
|
||||
"size": "30px",
|
||||
"slug": "30"
|
||||
},
|
||||
{
|
||||
"name": "4",
|
||||
"size": "40px",
|
||||
"slug": "40"
|
||||
},
|
||||
{
|
||||
"name": "5",
|
||||
"size": "50px",
|
||||
"slug": "50"
|
||||
},
|
||||
{
|
||||
"name": "6",
|
||||
"size": "60px",
|
||||
"slug": "60"
|
||||
}
|
||||
]
|
||||
},
|
||||
"border": {
|
||||
"radius": true,
|
||||
"color": true,
|
||||
"style": true,
|
||||
"width": true
|
||||
},
|
||||
"typography": {
|
||||
"dropCap": false,
|
||||
"fontWeight": true,
|
||||
"lineHeight": true,
|
||||
"defaultFontSizes": true,
|
||||
"fontFamilies": [
|
||||
{
|
||||
"name": "Arial",
|
||||
"slug": "arial",
|
||||
"fontFamily": "Arial, 'Helvetica Neue', Helvetica, sans-serif"
|
||||
},
|
||||
{
|
||||
"name": "Comic Sans MS",
|
||||
"slug": "comic-sans-ms",
|
||||
"fontFamily": "'Comic Sans MS', 'Marker Felt-Thin', Arial, sans-serif"
|
||||
},
|
||||
{
|
||||
"name": "Courier New",
|
||||
"slug": "courier-new",
|
||||
"fontFamily": "'Courier New', Courier, 'Lucida Sans Typewriter', 'Lucida Typewriter', monospace"
|
||||
},
|
||||
{
|
||||
"name": "Georgia",
|
||||
"slug": "georgia",
|
||||
"fontFamily": "Georgia, Times, 'Times New Roman', serif"
|
||||
},
|
||||
{
|
||||
"name": "Lucida",
|
||||
"slug": "lucida",
|
||||
"fontFamily": "'Lucida Sans Unicode', 'Lucida Grande', sans-serif"
|
||||
},
|
||||
{
|
||||
"name": "Tahoma",
|
||||
"slug": "tahoma",
|
||||
"fontFamily": "'Tahoma, Verdana, Segoe, sans-serif'"
|
||||
},
|
||||
{
|
||||
"name": "Times New Roman",
|
||||
"slug": "times-new-roman",
|
||||
"fontFamily": "'Times New Roman', Times, Baskerville, Georgia, serif"
|
||||
},
|
||||
{
|
||||
"name": "Trebuchet MS",
|
||||
"slug": "trebuchet-ms",
|
||||
"fontFamily": "'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif"
|
||||
},
|
||||
{
|
||||
"name": "Verdana",
|
||||
"slug": "verdana",
|
||||
"fontFamily": "'Verdana, Geneva, sans-serif'"
|
||||
},
|
||||
{
|
||||
"name": "Arvo",
|
||||
"slug": "arvo",
|
||||
"fontFamily": "'arvo, courier, georgia, serif'"
|
||||
},
|
||||
{
|
||||
"name": "Lato",
|
||||
"slug": "lato",
|
||||
"fontFamily": "lato, 'helvetica neue', helvetica, arial, sans-serif"
|
||||
},
|
||||
{
|
||||
"name": "Lora",
|
||||
"slug": "lora",
|
||||
"fontFamily": "lora, georgia, 'times new roman', serif"
|
||||
},
|
||||
{
|
||||
"name": "Merriweather",
|
||||
"slug": "merriweather",
|
||||
"fontFamily": "merriweather, georgia, 'times new roman', serif"
|
||||
},
|
||||
{
|
||||
"name": "Merriweather Sans",
|
||||
"slug": "merriweather-sans",
|
||||
"fontFamily": "'merriweather sans', 'helvetica neue', helvetica, arial, sans-serif"
|
||||
},
|
||||
{
|
||||
"name": "Noticia Text",
|
||||
"slug": "noticia-text",
|
||||
"fontFamily": "'noticia text', georgia, 'times new roman', serif"
|
||||
},
|
||||
{
|
||||
"name": "Open Sans",
|
||||
"slug": "open-sans",
|
||||
"fontFamily": "'open sans', 'helvetica neue', helvetica, arial, sans-serif"
|
||||
},
|
||||
{
|
||||
"name": "Playfair Display",
|
||||
"slug": "playfair-display",
|
||||
"fontFamily": "'playfair display', georgia, 'times new roman', serif"
|
||||
},
|
||||
{
|
||||
"name": "Roboto",
|
||||
"slug": "roboto",
|
||||
"fontFamily": "roboto, 'helvetica neue', helvetica, arial, sans-serif"
|
||||
},
|
||||
{
|
||||
"name": "Source Sans Pro",
|
||||
"slug": "source-sans-pro",
|
||||
"fontFamily": "'source sans pro', 'helvetica neue', helvetica, arial, sans-serif"
|
||||
},
|
||||
{
|
||||
"name": "Oswald",
|
||||
"slug": "oswald",
|
||||
"fontFamily": "Oswald, 'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif"
|
||||
},
|
||||
{
|
||||
"name": "Raleway",
|
||||
"slug": "raleway",
|
||||
"fontFamily": "Raleway, 'Century Gothic', CenturyGothic, AppleGothic, sans-serif"
|
||||
},
|
||||
{
|
||||
"name": "Permanent Marker",
|
||||
"slug": "permanent-marker",
|
||||
"fontFamily": "'Permanent Marker', Tahoma, Verdana, Segoe, sans-serif"
|
||||
},
|
||||
{
|
||||
"name": "Pacifico",
|
||||
"slug": "pacifico",
|
||||
"fontFamily": "Pacifico, 'Arial Narrow', Arial, sans-serif"
|
||||
}
|
||||
],
|
||||
"fontSizes": [
|
||||
{
|
||||
"name": "small",
|
||||
"size": "13px",
|
||||
"slug": "small"
|
||||
},
|
||||
{
|
||||
"name": "medium",
|
||||
"size": "16px",
|
||||
"slug": "medium"
|
||||
},
|
||||
{
|
||||
"name": "large",
|
||||
"size": "28px",
|
||||
"slug": "large"
|
||||
},
|
||||
{
|
||||
"name": "extra-large",
|
||||
"size": "42px",
|
||||
"slug": "x-large"
|
||||
}
|
||||
]
|
||||
},
|
||||
"useRootPaddingAwareAlignments": true
|
||||
},
|
||||
"styles": {
|
||||
"spacing": {
|
||||
"blockGap": "16px",
|
||||
"padding": {
|
||||
"bottom": "20px",
|
||||
"left": "20px",
|
||||
"right": "20px",
|
||||
"top": "20px"
|
||||
}
|
||||
},
|
||||
"color": {
|
||||
"background": "#ffffff",
|
||||
"text": "#1e1e1e"
|
||||
},
|
||||
"typography": {
|
||||
"fontFamily": "Arial, 'Helvetica Neue', Helvetica, sans-serif",
|
||||
"fontSize": "16px",
|
||||
"fontWeight": "400",
|
||||
"fontStyle": "normal",
|
||||
"letterSpacing": "0",
|
||||
"lineHeight": "1.5",
|
||||
"textDecoration": "none",
|
||||
"textTransform": "none"
|
||||
},
|
||||
"elements": {
|
||||
"heading": {
|
||||
"typography": {
|
||||
"fontFamily": "Arial, 'Helvetica Neue', Helvetica, sans-serif",
|
||||
"fontWeight": "400",
|
||||
"fontStyle": "normal",
|
||||
"lineHeight": "1.5"
|
||||
},
|
||||
"color": {
|
||||
"text": "#000000"
|
||||
}
|
||||
},
|
||||
"h1": {
|
||||
"typography": {
|
||||
"fontSize": "42px",
|
||||
"fontWeight": "700",
|
||||
"fontStyle": "normal"
|
||||
}
|
||||
},
|
||||
"h2": {
|
||||
"typography": {
|
||||
"fontSize": "42px"
|
||||
}
|
||||
},
|
||||
"h3": {
|
||||
"typography": {
|
||||
"fontSize": "28px"
|
||||
}
|
||||
},
|
||||
"h4": {
|
||||
"typography": {
|
||||
"fontSize": "16px"
|
||||
}
|
||||
},
|
||||
"h5": {
|
||||
"typography": {
|
||||
"fontSize": "13px"
|
||||
}
|
||||
},
|
||||
"h6": {
|
||||
"typography": {
|
||||
"fontSize": "13px"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
namespace MailPoet\EmailEditor\Integrations\Core\Renderer\Blocks;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Block_Renderer;
|
||||
use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
use WP_Style_Engine;
|
||||
abstract class Abstract_Block_Renderer implements Block_Renderer {
|
||||
protected function get_styles_from_block( array $block_styles, $skip_convert_vars = false ) {
|
||||
$styles = wp_style_engine_get_styles( $block_styles, array( 'convert_vars_to_classnames' => $skip_convert_vars ) );
|
||||
return wp_parse_args(
|
||||
$styles,
|
||||
array(
|
||||
'css' => '',
|
||||
'declarations' => array(),
|
||||
'classnames' => '',
|
||||
)
|
||||
);
|
||||
}
|
||||
protected function compile_css( ...$styles ): string {
|
||||
return WP_Style_Engine::compile_css( array_merge( ...$styles ), '' );
|
||||
}
|
||||
protected function add_spacer( $content, $email_attrs ): string {
|
||||
$gap_style = WP_Style_Engine::compile_css( array_intersect_key( $email_attrs, array_flip( array( 'margin-top' ) ) ), '' );
|
||||
$padding_style = WP_Style_Engine::compile_css( array_intersect_key( $email_attrs, array_flip( array( 'padding-left', 'padding-right' ) ) ), '' );
|
||||
if ( ! $gap_style && ! $padding_style ) {
|
||||
return $content;
|
||||
}
|
||||
return sprintf(
|
||||
'<!--[if mso | IE]><table align="left" role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%%" style="%2$s"><tr><td style="%3$s"><![endif]-->
|
||||
<div class="email-block-layout" style="%2$s %3$s">%1$s</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->',
|
||||
$content,
|
||||
esc_attr( $gap_style ),
|
||||
esc_attr( $padding_style )
|
||||
);
|
||||
}
|
||||
public function render( string $block_content, array $parsed_block, Settings_Controller $settings_controller ): string {
|
||||
return $this->add_spacer(
|
||||
$this->render_content( $block_content, $parsed_block, $settings_controller ),
|
||||
$parsed_block['email_attrs'] ?? array()
|
||||
);
|
||||
}
|
||||
abstract protected function render_content( string $block_content, array $parsed_block, Settings_Controller $settings_controller ): string;
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
namespace MailPoet\EmailEditor\Integrations\Core\Renderer\Blocks;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
use MailPoet\EmailEditor\Integrations\Utils\Dom_Document_Helper;
|
||||
class Button extends Abstract_Block_Renderer {
|
||||
private function get_wrapper_styles( array $block_styles ) {
|
||||
$properties = array( 'border', 'color', 'typography', 'spacing' );
|
||||
$styles = $this->get_styles_from_block( array_intersect_key( $block_styles, array_flip( $properties ) ) );
|
||||
return (object) array(
|
||||
'css' => $this->compile_css(
|
||||
$styles['declarations'],
|
||||
array(
|
||||
'word-break' => 'break-word',
|
||||
'display' => 'block',
|
||||
)
|
||||
),
|
||||
'classname' => $styles['classnames'],
|
||||
);
|
||||
}
|
||||
private function get_link_styles( array $block_styles ) {
|
||||
$styles = $this->get_styles_from_block(
|
||||
array(
|
||||
'color' => array(
|
||||
'text' => $block_styles['color']['text'] ?? '',
|
||||
),
|
||||
'typography' => $block_styles['typography'] ?? array(),
|
||||
)
|
||||
);
|
||||
return (object) array(
|
||||
'css' => $this->compile_css( $styles['declarations'], array( 'display' => 'block' ) ),
|
||||
'classname' => $styles['classnames'],
|
||||
);
|
||||
}
|
||||
public function render( string $block_content, array $parsed_block, Settings_Controller $settings_controller ): string {
|
||||
return $this->render_content( $block_content, $parsed_block, $settings_controller );
|
||||
}
|
||||
protected function render_content( $block_content, array $parsed_block, Settings_Controller $settings_controller ): string {
|
||||
if ( empty( $parsed_block['innerHTML'] ) ) {
|
||||
return '';
|
||||
}
|
||||
$dom_helper = new Dom_Document_Helper( $parsed_block['innerHTML'] );
|
||||
$block_classname = $dom_helper->get_attribute_value_by_tag_name( 'div', 'class' ) ?? '';
|
||||
$button_link = $dom_helper->find_element( 'a' );
|
||||
if ( ! $button_link ) {
|
||||
return '';
|
||||
}
|
||||
$button_text = $dom_helper->get_element_inner_html( $button_link ) ? $dom_helper->get_element_inner_html( $button_link ) : '';
|
||||
$button_url = $button_link->getAttribute( 'href' ) ? $button_link->getAttribute( 'href' ) : '#';
|
||||
$block_attributes = wp_parse_args(
|
||||
$parsed_block['attrs'] ?? array(),
|
||||
array(
|
||||
'width' => '',
|
||||
'style' => array(),
|
||||
'textAlign' => 'center',
|
||||
'backgroundColor' => '',
|
||||
'textColor' => '',
|
||||
)
|
||||
);
|
||||
$block_styles = array_replace_recursive(
|
||||
array(
|
||||
'color' => array_filter(
|
||||
array(
|
||||
'background' => $block_attributes['backgroundColor'] ? $settings_controller->translate_slug_to_color( $block_attributes['backgroundColor'] ) : null,
|
||||
'text' => $block_attributes['textColor'] ? $settings_controller->translate_slug_to_color( $block_attributes['textColor'] ) : null,
|
||||
)
|
||||
),
|
||||
),
|
||||
$block_attributes['style'] ?? array()
|
||||
);
|
||||
if ( ! empty( $block_styles['border'] ) && empty( $block_styles['border']['style'] ) ) {
|
||||
$block_styles['border']['style'] = 'solid';
|
||||
}
|
||||
$wrapper_styles = $this->get_wrapper_styles( $block_styles );
|
||||
$link_styles = $this->get_link_styles( $block_styles );
|
||||
return sprintf(
|
||||
'<table border="0" cellspacing="0" cellpadding="0" role="presentation" style="width:%1$s;">
|
||||
<tr>
|
||||
<td align="%2$s" valign="middle" role="presentation" class="%3$s" style="%4$s">
|
||||
<a class="button-link %5$s" style="%6$s" href="%7$s" target="_blank">%8$s</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>',
|
||||
esc_attr( $block_attributes['width'] ? '100%' : 'auto' ),
|
||||
esc_attr( $block_attributes['textAlign'] ),
|
||||
esc_attr( $wrapper_styles->classname . ' ' . $block_classname ),
|
||||
esc_attr( $wrapper_styles->css ),
|
||||
esc_attr( $link_styles->classname ),
|
||||
esc_attr( $link_styles->css ),
|
||||
esc_url( $button_url ),
|
||||
$button_text,
|
||||
);
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
namespace MailPoet\EmailEditor\Integrations\Core\Renderer\Blocks;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Layout\Flex_Layout_Renderer;
|
||||
use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
class Buttons extends Abstract_Block_Renderer {
|
||||
private $flex_layout_renderer;
|
||||
public function __construct(
|
||||
Flex_Layout_Renderer $flex_layout_renderer
|
||||
) {
|
||||
$this->flex_layout_renderer = $flex_layout_renderer;
|
||||
}
|
||||
protected function render_content( $block_content, array $parsed_block, Settings_Controller $settings_controller ): string {
|
||||
// Ignore font size set on the buttons block.
|
||||
// We rely on TypographyPreprocessor to set the font size on the buttons.
|
||||
// Rendering font size on the wrapper causes unwanted whitespace below the buttons.
|
||||
if ( isset( $parsed_block['attrs']['style']['typography']['fontSize'] ) ) {
|
||||
unset( $parsed_block['attrs']['style']['typography']['fontSize'] );
|
||||
}
|
||||
return $this->flex_layout_renderer->render_inner_blocks_in_layout( $parsed_block, $settings_controller );
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
namespace MailPoet\EmailEditor\Integrations\Core\Renderer\Blocks;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
use MailPoet\EmailEditor\Integrations\Utils\Dom_Document_Helper;
|
||||
use WP_Style_Engine;
|
||||
class Column extends Abstract_Block_Renderer {
|
||||
protected function add_spacer( $content, $email_attrs ): string {
|
||||
return $content;
|
||||
}
|
||||
protected function render_content( string $block_content, array $parsed_block, Settings_Controller $settings_controller ): string {
|
||||
$content = '';
|
||||
foreach ( $parsed_block['innerBlocks'] ?? array() as $block ) {
|
||||
$content .= render_block( $block );
|
||||
}
|
||||
return str_replace(
|
||||
'{column_content}',
|
||||
$content,
|
||||
$this->get_block_wrapper( $block_content, $parsed_block, $settings_controller )
|
||||
);
|
||||
}
|
||||
private function get_block_wrapper( string $block_content, array $parsed_block, Settings_Controller $settings_controller ): string {
|
||||
$original_wrapper_classname = ( new Dom_Document_Helper( $block_content ) )->get_attribute_value_by_tag_name( 'div', 'class' ) ?? '';
|
||||
$block_attributes = wp_parse_args(
|
||||
$parsed_block['attrs'] ?? array(),
|
||||
array(
|
||||
'verticalAlignment' => 'stretch',
|
||||
'width' => $settings_controller->get_layout_width_without_padding(),
|
||||
'style' => array(),
|
||||
)
|
||||
);
|
||||
// The default column alignment is `stretch to fill` which means that we need to set the background color to the main cell
|
||||
// to create a feeling of a stretched column. This also needs to apply to CSS classnames which can also apply styles.
|
||||
$is_stretched = empty( $block_attributes['verticalAlignment'] ) || 'stretch' === $block_attributes['verticalAlignment'];
|
||||
$padding_css = $this->get_styles_from_block( array( 'spacing' => array( 'padding' => $block_attributes['style']['spacing']['padding'] ?? array() ) ) )['css'];
|
||||
$cell_styles = $this->get_styles_from_block(
|
||||
array(
|
||||
'color' => $block_attributes['style']['color'] ?? array(),
|
||||
'background' => $block_attributes['style']['background'] ?? array(),
|
||||
)
|
||||
)['declarations'];
|
||||
$border_styles = $this->get_styles_from_block( array( 'border' => $block_attributes['style']['border'] ?? array() ) )['declarations'];
|
||||
if ( ! empty( $border_styles ) ) {
|
||||
$cell_styles = array_merge( $cell_styles, array( 'border-style' => 'solid' ), $border_styles );
|
||||
}
|
||||
if ( ! empty( $cell_styles['background-image'] ) && empty( $cell_styles['background-size'] ) ) {
|
||||
$cell_styles['background-size'] = 'cover';
|
||||
}
|
||||
$wrapper_classname = 'block wp-block-column email-block-column';
|
||||
$content_classname = 'email-block-column-content';
|
||||
$wrapper_css = WP_Style_Engine::compile_css(
|
||||
array(
|
||||
'vertical-align' => $is_stretched ? 'top' : $block_attributes['verticalAlignment'],
|
||||
),
|
||||
''
|
||||
);
|
||||
$content_css = 'vertical-align: top;';
|
||||
if ( $is_stretched ) {
|
||||
$wrapper_classname .= ' ' . $original_wrapper_classname;
|
||||
$wrapper_css .= ' ' . WP_Style_Engine::compile_css( $cell_styles, '' );
|
||||
} else {
|
||||
$content_classname .= ' ' . $original_wrapper_classname;
|
||||
$content_css .= ' ' . WP_Style_Engine::compile_css( $cell_styles, '' );
|
||||
}
|
||||
return '
|
||||
<td class="' . esc_attr( $wrapper_classname ) . '" style="' . esc_attr( $wrapper_css ) . '" width="' . esc_attr( $block_attributes['width'] ) . '">
|
||||
<table class="' . esc_attr( $content_classname ) . '" style="' . esc_attr( $content_css ) . '" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="text-align:left;' . esc_attr( $padding_css ) . '">
|
||||
{column_content}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
';
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
namespace MailPoet\EmailEditor\Integrations\Core\Renderer\Blocks;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
use MailPoet\EmailEditor\Integrations\Utils\Dom_Document_Helper;
|
||||
use WP_Style_Engine;
|
||||
class Columns extends Abstract_Block_Renderer {
|
||||
protected function render_content( string $block_content, array $parsed_block, Settings_Controller $settings_controller ): string {
|
||||
$content = '';
|
||||
foreach ( $parsed_block['innerBlocks'] ?? array() as $block ) {
|
||||
$content .= render_block( $block );
|
||||
}
|
||||
return str_replace(
|
||||
'{columns_content}',
|
||||
$content,
|
||||
$this->getBlockWrapper( $block_content, $parsed_block, $settings_controller )
|
||||
);
|
||||
}
|
||||
private function getBlockWrapper( string $block_content, array $parsed_block, Settings_Controller $settings_controller ): string {
|
||||
$original_wrapper_classname = ( new Dom_Document_Helper( $block_content ) )->get_attribute_value_by_tag_name( 'div', 'class' ) ?? '';
|
||||
$block_attributes = wp_parse_args(
|
||||
$parsed_block['attrs'] ?? array(),
|
||||
array(
|
||||
'align' => null,
|
||||
'width' => $settings_controller->get_layout_width_without_padding(),
|
||||
'style' => array(),
|
||||
)
|
||||
);
|
||||
$columns_styles = $this->get_styles_from_block(
|
||||
array(
|
||||
'spacing' => array( 'padding' => $block_attributes['style']['spacing']['padding'] ?? array() ),
|
||||
'color' => $block_attributes['style']['color'] ?? array(),
|
||||
'background' => $block_attributes['style']['background'] ?? array(),
|
||||
)
|
||||
)['declarations'];
|
||||
$border_styles = $this->get_styles_from_block( array( 'border' => $block_attributes['style']['border'] ?? array() ) )['declarations'];
|
||||
if ( ! empty( $border_styles ) ) {
|
||||
$columns_styles = array_merge( $columns_styles, array( 'border-style' => 'solid' ), $border_styles );
|
||||
}
|
||||
if ( empty( $columns_styles['background-size'] ) ) {
|
||||
$columns_styles['background-size'] = 'cover';
|
||||
}
|
||||
$rendered_columns = '<table class="' . esc_attr( 'email-block-columns ' . $original_wrapper_classname ) . '" style="width:100%;border-collapse:separate;text-align:left;' . esc_attr( WP_Style_Engine::compile_css( $columns_styles, '' ) ) . '" align="center" border="0" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tbody>
|
||||
<tr>{columns_content}</tr>
|
||||
</tbody>
|
||||
</table>';
|
||||
// Margins are not supported well in outlook for tables, so wrap in another table.
|
||||
$margins = $block_attributes['style']['spacing']['margin'] ?? array();
|
||||
if ( ! empty( $margins ) ) {
|
||||
$margin_to_padding_styles = $this->get_styles_from_block(
|
||||
array(
|
||||
'spacing' => array( 'margin' => $margins ),
|
||||
)
|
||||
)['css'];
|
||||
$rendered_columns = '<table class="email-block-columns-wrapper" style="width:100%;border-collapse:separate;text-align:left;' . esc_attr( $margin_to_padding_styles ) . '" align="center" border="0" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>' . $rendered_columns . '</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>';
|
||||
}
|
||||
return $rendered_columns;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
namespace MailPoet\EmailEditor\Integrations\Core\Renderer\Blocks;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
class Fallback extends Abstract_Block_Renderer {
|
||||
protected function render_content( $block_content, array $parsed_block, Settings_Controller $settings_controller ): string {
|
||||
return $block_content;
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
namespace MailPoet\EmailEditor\Integrations\Core\Renderer\Blocks;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
use MailPoet\EmailEditor\Integrations\Utils\Dom_Document_Helper;
|
||||
use WP_Style_Engine;
|
||||
class Group extends Abstract_Block_Renderer {
|
||||
protected function render_content( string $block_content, array $parsed_block, Settings_Controller $settings_controller ): string {
|
||||
$content = '';
|
||||
$inner_blocks = $parsed_block['innerBlocks'] ?? array();
|
||||
foreach ( $inner_blocks as $block ) {
|
||||
$content .= render_block( $block );
|
||||
}
|
||||
return str_replace(
|
||||
'{group_content}',
|
||||
$content,
|
||||
$this->get_block_wrapper( $block_content, $parsed_block, $settings_controller )
|
||||
);
|
||||
}
|
||||
private function get_block_wrapper( string $block_content, array $parsed_block, Settings_Controller $settings_controller ): string {
|
||||
$original_classname = ( new Dom_Document_Helper( $block_content ) )->get_attribute_value_by_tag_name( 'div', 'class' ) ?? '';
|
||||
$block_attributes = wp_parse_args(
|
||||
$parsed_block['attrs'] ?? array(),
|
||||
array(
|
||||
'style' => array(),
|
||||
'backgroundColor' => '',
|
||||
'textColor' => '',
|
||||
'borderColor' => '',
|
||||
'layout' => array(),
|
||||
)
|
||||
);
|
||||
// Layout, background, borders need to be on the outer table element.
|
||||
$table_styles = $this->get_styles_from_block(
|
||||
array(
|
||||
'color' => array_filter(
|
||||
array(
|
||||
'background' => $block_attributes['backgroundColor'] ? $settings_controller->translate_slug_to_color( $block_attributes['backgroundColor'] ) : null,
|
||||
'text' => $block_attributes['textColor'] ? $settings_controller->translate_slug_to_color( $block_attributes['textColor'] ) : null,
|
||||
'border' => $block_attributes['borderColor'] ? $settings_controller->translate_slug_to_color( $block_attributes['borderColor'] ) : null,
|
||||
)
|
||||
),
|
||||
'background' => $block_attributes['style']['background'] ?? array(),
|
||||
'border' => $block_attributes['style']['border'] ?? array(),
|
||||
'spacing' => array( 'padding' => $block_attributes['style']['spacing']['margin'] ?? array() ),
|
||||
)
|
||||
)['declarations'];
|
||||
$table_styles['border-collapse'] = 'separate'; // Needed for the border radius to work.
|
||||
// Padding properties need to be added to the table cell.
|
||||
$cell_styles = $this->get_styles_from_block(
|
||||
array(
|
||||
'spacing' => array( 'padding' => $block_attributes['style']['spacing']['padding'] ?? array() ),
|
||||
)
|
||||
)['declarations'];
|
||||
$table_styles['background-size'] = empty( $table_styles['background-size'] ) ? 'cover' : $table_styles['background-size'];
|
||||
$width = $parsed_block['email_attrs']['width'] ?? '100%';
|
||||
return sprintf(
|
||||
'<table class="email-block-group %3$s" style="%1$s" width="100%%" border="0" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="email-block-group-content" style="%2$s" width="%4$s">
|
||||
{group_content}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>',
|
||||
esc_attr( WP_Style_Engine::compile_css( $table_styles, '' ) ),
|
||||
esc_attr( WP_Style_Engine::compile_css( $cell_styles, '' ) ),
|
||||
esc_attr( $original_classname ),
|
||||
esc_attr( $width ),
|
||||
);
|
||||
}
|
||||
}
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
namespace MailPoet\EmailEditor\Integrations\Core\Renderer\Blocks;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
use MailPoet\EmailEditor\Integrations\Utils\Dom_Document_Helper;
|
||||
class Image extends Abstract_Block_Renderer {
|
||||
protected function render_content( $block_content, array $parsed_block, Settings_Controller $settings_controller ): string {
|
||||
$parsed_html = $this->parse_block_content( $block_content );
|
||||
if ( ! $parsed_html ) {
|
||||
return '';
|
||||
}
|
||||
$image_url = $parsed_html['imageUrl'];
|
||||
$image = $parsed_html['image'];
|
||||
$caption = $parsed_html['caption'];
|
||||
$class = $parsed_html['class'];
|
||||
$parsed_block = $this->add_image_size_when_missing( $parsed_block, $image_url, $settings_controller );
|
||||
$image = $this->addImageDimensions( $image, $parsed_block, $settings_controller );
|
||||
$image = $this->apply_image_border_style( $image, $parsed_block, $caption );
|
||||
$image = $this->apply_rounded_style( $image, $parsed_block );
|
||||
$image_with_wrapper = str_replace(
|
||||
array( '{image_content}', '{caption_content}' ),
|
||||
array( $image, $caption ),
|
||||
$this->get_block_wrapper( $parsed_block, $settings_controller, $caption )
|
||||
);
|
||||
$image_with_wrapper = $this->apply_rounded_style( $image_with_wrapper, $parsed_block );
|
||||
$image_with_wrapper = $this->apply_image_border_style( $image_with_wrapper, $parsed_block, $class );
|
||||
return $image_with_wrapper;
|
||||
}
|
||||
private function apply_rounded_style( string $block_content, array $parsed_block ): string {
|
||||
// Because the isn't an attribute for definition of rounded style, we have to check the class name.
|
||||
if ( isset( $parsed_block['attrs']['className'] ) && strpos( $parsed_block['attrs']['className'], 'is-style-rounded' ) !== false ) {
|
||||
// If the image should be in a circle, we need to set the border-radius to 9999px to make it the same as is in the editor
|
||||
// This style is applied to both wrapper and the image.
|
||||
$block_content = $this->remove_style_attribute_from_element(
|
||||
$block_content,
|
||||
array(
|
||||
'tag_name' => 'td',
|
||||
'class_name' => 'email-image-cell',
|
||||
),
|
||||
'border-radius'
|
||||
);
|
||||
$block_content = $this->add_style_to_element(
|
||||
$block_content,
|
||||
array(
|
||||
'tag_name' => 'td',
|
||||
'class_name' => 'email-image-cell',
|
||||
),
|
||||
'border-radius: 9999px;'
|
||||
);
|
||||
$block_content = $this->remove_style_attribute_from_element( $block_content, array( 'tag_name' => 'img' ), 'border-radius' );
|
||||
$block_content = $this->add_style_to_element( $block_content, array( 'tag_name' => 'img' ), 'border-radius: 9999px;' );
|
||||
}
|
||||
return $block_content;
|
||||
}
|
||||
private function add_image_size_when_missing( array $parsed_block, string $image_url, Settings_Controller $settings_controller ): array {
|
||||
if ( isset( $parsed_block['attrs']['width'] ) ) {
|
||||
return $parsed_block;
|
||||
}
|
||||
// Can't determine any width let's go with 100%.
|
||||
if ( ! isset( $parsed_block['email_attrs']['width'] ) ) {
|
||||
$parsed_block['attrs']['width'] = '100%';
|
||||
}
|
||||
$max_width = $settings_controller->parse_number_from_string_with_pixels( $parsed_block['email_attrs']['width'] );
|
||||
$image_size = wp_getimagesize( $image_url );
|
||||
$image_size = $image_size ? $image_size[0] : $max_width;
|
||||
$width = min( $image_size, $max_width );
|
||||
$parsed_block['attrs']['width'] = "{$width}px";
|
||||
return $parsed_block;
|
||||
}
|
||||
private function apply_image_border_style( string $block_content, array $parsed_block, string $class_name ): string {
|
||||
// Getting individual border properties.
|
||||
$border_styles = wp_style_engine_get_styles( array( 'border' => $parsed_block['attrs']['style']['border'] ?? array() ) );
|
||||
$border_styles = $border_styles['declarations'] ?? array();
|
||||
if ( ! empty( $border_styles ) ) {
|
||||
$border_styles['border-style'] = 'solid';
|
||||
$border_styles['box-sizing'] = 'border-box';
|
||||
}
|
||||
$border_element_tag = array(
|
||||
'tag_name' => 'td',
|
||||
'class_name' => 'email-image-cell',
|
||||
);
|
||||
$content_with_border_styles = $this->add_style_to_element( $block_content, $border_element_tag, \WP_Style_Engine::compile_css( $border_styles, '' ) );
|
||||
// Add Border related classes to proper element. This is required for inlined border-color styles when defined via class.
|
||||
$border_classes = array_filter(
|
||||
explode( ' ', $class_name ),
|
||||
function ( $class_name ) {
|
||||
return strpos( $class_name, 'border' ) !== false;
|
||||
}
|
||||
);
|
||||
$html = new \WP_HTML_Tag_Processor( $content_with_border_styles );
|
||||
if ( $html->next_tag( $border_element_tag ) ) {
|
||||
$class_name = $html->get_attribute( 'class' ) ?? '';
|
||||
$border_classes[] = $class_name;
|
||||
$html->set_attribute( 'class', implode( ' ', $border_classes ) );
|
||||
}
|
||||
return $html->get_updated_html();
|
||||
}
|
||||
private function addImageDimensions( $block_content, array $parsed_block, Settings_Controller $settings_controller ): string {
|
||||
$html = new \WP_HTML_Tag_Processor( $block_content );
|
||||
if ( $html->next_tag( array( 'tag_name' => 'img' ) ) ) {
|
||||
// Getting height from styles and if it's set, we set the height attribute.
|
||||
// phpcs:ignore Generic.Commenting.DocComment.MissingShort -- used for phpstan
|
||||
$styles = $html->get_attribute( 'style' ) ?? '';
|
||||
$styles = $settings_controller->parse_styles_to_array( $styles );
|
||||
$height = $styles['height'] ?? null;
|
||||
if ( $height && 'auto' !== $height && is_numeric( $settings_controller->parse_number_from_string_with_pixels( $height ) ) ) {
|
||||
$height = $settings_controller->parse_number_from_string_with_pixels( $height );
|
||||
$html->set_attribute( 'height', esc_attr( $height ) );
|
||||
}
|
||||
if ( isset( $parsed_block['attrs']['width'] ) ) {
|
||||
$width = $settings_controller->parse_number_from_string_with_pixels( $parsed_block['attrs']['width'] );
|
||||
$html->set_attribute( 'width', esc_attr( $width ) );
|
||||
}
|
||||
$block_content = $html->get_updated_html();
|
||||
}
|
||||
return $block_content;
|
||||
}
|
||||
private function get_caption_styles( Settings_Controller $settings_controller, array $parsed_block ): string {
|
||||
$theme_data = $settings_controller->get_theme()->get_data();
|
||||
$styles = array(
|
||||
'text-align' => isset( $parsed_block['attrs']['align'] ) ? 'center' : 'left',
|
||||
);
|
||||
$styles['font-size'] = $parsed_block['email_attrs']['font-size'] ?? $theme_data['styles']['typography']['fontSize'];
|
||||
return \WP_Style_Engine::compile_css( $styles, '' );
|
||||
}
|
||||
private function get_block_wrapper( array $parsed_block, Settings_Controller $settings_controller, ?string $caption ): string {
|
||||
$styles = array(
|
||||
'border-collapse' => 'collapse',
|
||||
'border-spacing' => '0px',
|
||||
'font-size' => '0px',
|
||||
'vertical-align' => 'top',
|
||||
'width' => '100%',
|
||||
);
|
||||
$width = $parsed_block['attrs']['width'] ?? '100%';
|
||||
$wrapper_width = ( $width && '100%' !== $width ) ? $width : 'auto';
|
||||
$wrapper_styles = $styles;
|
||||
$wrapper_styles['width'] = $wrapper_width;
|
||||
$wrapper_styles['border-collapse'] = 'separate'; // Needed because of border radius.
|
||||
$caption_html = '';
|
||||
if ( $caption ) {
|
||||
// When the image is not aligned, the wrapper is set to 100% width due to caption that can be longer than the image.
|
||||
$caption_width = isset( $parsed_block['attrs']['align'] ) ? ( $parsed_block['attrs']['width'] ?? '100%' ) : '100%';
|
||||
$caption_wrapper_styles = $styles;
|
||||
$caption_wrapper_styles['width'] = $caption_width;
|
||||
$caption_styles = $this->get_caption_styles( $settings_controller, $parsed_block );
|
||||
$caption_html = '
|
||||
<table
|
||||
role="presentation"
|
||||
class="email-table-with-width"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
style="' . esc_attr( \WP_Style_Engine::compile_css( $caption_wrapper_styles, '' ) ) . '"
|
||||
width="' . esc_attr( $caption_width ) . '"
|
||||
>
|
||||
<tr>
|
||||
<td style="' . esc_attr( $caption_styles ) . '">{caption_content}</td>
|
||||
</tr>
|
||||
</table>';
|
||||
}
|
||||
$styles['width'] = '100%';
|
||||
$align = $parsed_block['attrs']['align'] ?? 'left';
|
||||
return '
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
style="' . esc_attr( \WP_Style_Engine::compile_css( $styles, '' ) ) . '"
|
||||
width="100%"
|
||||
>
|
||||
<tr>
|
||||
<td align="' . esc_attr( $align ) . '">
|
||||
<table
|
||||
role="presentation"
|
||||
class="email-table-with-width"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
style="' . esc_attr( \WP_Style_Engine::compile_css( $wrapper_styles, '' ) ) . '"
|
||||
width="' . esc_attr( $wrapper_width ) . '"
|
||||
>
|
||||
<tr>
|
||||
<td class="email-image-cell">{image_content}</td>
|
||||
</tr>
|
||||
</table>' . $caption_html . '
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
';
|
||||
}
|
||||
private function add_style_to_element( $block_content, array $tag, string $style ): string {
|
||||
$html = new \WP_HTML_Tag_Processor( $block_content );
|
||||
if ( $html->next_tag( $tag ) ) {
|
||||
// phpcs:ignore Generic.Commenting.DocComment.MissingShort -- used for phpstan
|
||||
$element_style = $html->get_attribute( 'style' ) ?? '';
|
||||
$element_style = ! empty( $element_style ) ? ( rtrim( $element_style, ';' ) . ';' ) : ''; // Adding semicolon if it's missing.
|
||||
$element_style .= $style;
|
||||
$html->set_attribute( 'style', esc_attr( $element_style ) );
|
||||
$block_content = $html->get_updated_html();
|
||||
}
|
||||
return $block_content;
|
||||
}
|
||||
private function remove_style_attribute_from_element( $block_content, array $tag, string $style_name ): string {
|
||||
$html = new \WP_HTML_Tag_Processor( $block_content );
|
||||
if ( $html->next_tag( $tag ) ) {
|
||||
// phpcs:ignore Generic.Commenting.DocComment.MissingShort -- used for phpstan
|
||||
$element_style = $html->get_attribute( 'style' ) ?? '';
|
||||
$element_style = preg_replace( '/' . $style_name . ':(.?[0-9]+px)+;?/', '', $element_style );
|
||||
$html->set_attribute( 'style', esc_attr( strval( $element_style ) ) );
|
||||
$block_content = $html->get_updated_html();
|
||||
}
|
||||
return $block_content;
|
||||
}
|
||||
private function parse_block_content( string $block_content ): ?array {
|
||||
// If block's image is not set, we don't need to parse the content.
|
||||
if ( empty( $block_content ) ) {
|
||||
return null;
|
||||
}
|
||||
$dom_helper = new Dom_Document_Helper( $block_content );
|
||||
$figure_tag = $dom_helper->find_element( 'figure' );
|
||||
if ( ! $figure_tag ) {
|
||||
return null;
|
||||
}
|
||||
$img_tag = $dom_helper->find_element( 'img' );
|
||||
if ( ! $img_tag ) {
|
||||
return null;
|
||||
}
|
||||
$image_src = $dom_helper->get_attribute_value( $img_tag, 'src' );
|
||||
$image_class = $dom_helper->get_attribute_value( $img_tag, 'class' );
|
||||
$image_html = $dom_helper->get_outer_html( $img_tag );
|
||||
$figcaption = $dom_helper->find_element( 'figcaption' );
|
||||
$figcaption_html = $figcaption ? $dom_helper->get_outer_html( $figcaption ) : '';
|
||||
$figcaption_html = str_replace( array( '<figcaption', '</figcaption>' ), array( '<span', '</span>' ), $figcaption_html );
|
||||
return array(
|
||||
'imageUrl' => $image_src ? $image_src : '',
|
||||
'image' => $this->cleanup_image_html( $image_html ),
|
||||
'caption' => $figcaption_html ? $figcaption_html : '',
|
||||
'class' => $image_class ? $image_class : '',
|
||||
);
|
||||
}
|
||||
private function cleanup_image_html( string $content_html ): string {
|
||||
$html = new \WP_HTML_Tag_Processor( $content_html );
|
||||
if ( $html->next_tag( array( 'tag_name' => 'img' ) ) ) {
|
||||
$html->remove_attribute( 'srcset' );
|
||||
$html->remove_attribute( 'class' );
|
||||
}
|
||||
return $html->get_updated_html();
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
namespace MailPoet\EmailEditor\Integrations\Core\Renderer\Blocks;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
class List_Block extends Abstract_Block_Renderer {
|
||||
protected function render_content( string $block_content, array $parsed_block, Settings_Controller $settings_controller ): string {
|
||||
$html = new \WP_HTML_Tag_Processor( $block_content );
|
||||
$tag_name = ( $parsed_block['attrs']['ordered'] ?? false ) ? 'ol' : 'ul';
|
||||
if ( $html->next_tag( array( 'tag_name' => $tag_name ) ) ) {
|
||||
// phpcs:ignore Generic.Commenting.DocComment.MissingShort -- used for phpstan
|
||||
$styles = $html->get_attribute( 'style' ) ?? '';
|
||||
$styles = $settings_controller->parse_styles_to_array( $styles );
|
||||
// Font size.
|
||||
if ( isset( $parsed_block['email_attrs']['font-size'] ) ) {
|
||||
$styles['font-size'] = $parsed_block['email_attrs']['font-size'];
|
||||
} else {
|
||||
// Use font-size from email theme when those properties are not set.
|
||||
$theme_data = $settings_controller->get_theme()->get_data();
|
||||
$styles['font-size'] = $theme_data['styles']['typography']['fontSize'];
|
||||
}
|
||||
$html->set_attribute( 'style', esc_attr( \WP_Style_Engine::compile_css( $styles, '' ) ) );
|
||||
$block_content = $html->get_updated_html();
|
||||
}
|
||||
$wrapper_style = \WP_Style_Engine::compile_css(
|
||||
array(
|
||||
'margin-top' => $parsed_block['email_attrs']['margin-top'] ?? '0px',
|
||||
),
|
||||
''
|
||||
);
|
||||
// \WP_HTML_Tag_Processor escapes the content, so we have to replace it back
|
||||
$block_content = str_replace( ''', "'", $block_content );
|
||||
return sprintf(
|
||||
'<div style="%1$s">%2$s</div>',
|
||||
esc_attr( $wrapper_style ),
|
||||
$block_content
|
||||
);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
namespace MailPoet\EmailEditor\Integrations\Core\Renderer\Blocks;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
class List_Item extends Abstract_Block_Renderer {
|
||||
protected function add_spacer( $content, $email_attrs ): string {
|
||||
return $content;
|
||||
}
|
||||
protected function render_content( $block_content, array $parsed_block, Settings_Controller $settings_controller ): string {
|
||||
return $block_content;
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
namespace MailPoet\EmailEditor\Integrations\Core\Renderer\Blocks;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
class Text extends Abstract_Block_Renderer {
|
||||
protected function render_content( string $block_content, array $parsed_block, Settings_Controller $settings_controller ): string {
|
||||
// Do not render empty blocks.
|
||||
if ( empty( trim( wp_strip_all_tags( $block_content ) ) ) ) {
|
||||
return '';
|
||||
}
|
||||
$block_content = $this->adjustStyleAttribute( $block_content );
|
||||
$block_attributes = wp_parse_args(
|
||||
$parsed_block['attrs'] ?? array(),
|
||||
array(
|
||||
'textAlign' => 'left',
|
||||
'style' => array(),
|
||||
)
|
||||
);
|
||||
$html = new \WP_HTML_Tag_Processor( $block_content );
|
||||
$classes = 'email-text-block';
|
||||
if ( $html->next_tag() ) {
|
||||
// phpcs:ignore Generic.Commenting.DocComment.MissingShort -- used for phpstan
|
||||
$block_classes = $html->get_attribute( 'class' ) ?? '';
|
||||
$classes .= ' ' . $block_classes;
|
||||
// remove has-background to prevent double padding applied for wrapper and inner element.
|
||||
$block_classes = str_replace( 'has-background', '', $block_classes );
|
||||
// remove border related classes because we handle border on wrapping table cell.
|
||||
$block_classes = preg_replace( '/[a-z-]+-border-[a-z-]+/', '', $block_classes );
|
||||
// phpcs:ignore Generic.Commenting.DocComment.MissingShort -- used for phpstan
|
||||
$html->set_attribute( 'class', trim( $block_classes ) );
|
||||
$block_content = $html->get_updated_html();
|
||||
}
|
||||
$block_styles = $this->get_styles_from_block(
|
||||
array(
|
||||
'color' => $block_attributes['style']['color'] ?? array(),
|
||||
'spacing' => $block_attributes['style']['spacing'] ?? array(),
|
||||
'typography' => $block_attributes['style']['typography'] ?? array(),
|
||||
'border' => $block_attributes['style']['border'] ?? array(),
|
||||
)
|
||||
);
|
||||
$styles = array(
|
||||
'min-width' => '100%', // prevent Gmail App from shrinking the table on mobile devices.
|
||||
);
|
||||
$styles['text-align'] = 'left';
|
||||
if ( ! empty( $parsed_block['attrs']['textAlign'] ) ) { // in this case, textAlign needs to be one of 'left', 'center', 'right'.
|
||||
$styles['text-align'] = $parsed_block['attrs']['textAlign'];
|
||||
} elseif ( in_array( $parsed_block['attrs']['align'] ?? null, array( 'left', 'center', 'right' ), true ) ) {
|
||||
$styles['text-align'] = $parsed_block['attrs']['align'];
|
||||
}
|
||||
$compiled_styles = $this->compile_css( $block_styles['declarations'], $styles );
|
||||
$table_styles = 'border-collapse: separate;'; // Needed because of border radius.
|
||||
return sprintf(
|
||||
'<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
width="100%%"
|
||||
style="%1$s"
|
||||
>
|
||||
<tr>
|
||||
<td class="%2$s" style="%3$s" align="%4$s">%5$s</td>
|
||||
</tr>
|
||||
</table>',
|
||||
esc_attr( $table_styles ),
|
||||
esc_attr( $classes ),
|
||||
esc_attr( $compiled_styles ),
|
||||
esc_attr( $styles['text-align'] ),
|
||||
$block_content
|
||||
);
|
||||
}
|
||||
private function adjustStyleAttribute( string $block_content ): string {
|
||||
$html = new \WP_HTML_Tag_Processor( $block_content );
|
||||
if ( $html->next_tag() ) {
|
||||
$element_style_value = $html->get_attribute( 'style' );
|
||||
$element_style = isset( $element_style_value ) ? strval( $element_style_value ) : '';
|
||||
// Padding may contain value like 10px or variable like var(--spacing-10).
|
||||
$element_style = preg_replace( '/padding[^:]*:.?[0-9a-z-()]+;?/', '', $element_style );
|
||||
// Remove border styles. We apply border styles on the wrapping table cell.
|
||||
$element_style = preg_replace( '/border[^:]*:.?[0-9a-z-()#]+;?/', '', strval( $element_style ) );
|
||||
// We define the font-size on the wrapper element, but we need to keep font-size definition here
|
||||
// to prevent CSS Inliner from adding a default value and overriding the value set by user, which is on the wrapper element.
|
||||
// The value provided by WP uses clamp() function which is not supported in many email clients.
|
||||
$element_style = preg_replace( '/font-size:[^;]+;?/', 'font-size: inherit;', strval( $element_style ) );
|
||||
// phpcs:ignore Generic.Commenting.DocComment.MissingShort -- used for phpstan
|
||||
$html->set_attribute( 'style', esc_attr( $element_style ) );
|
||||
$block_content = $html->get_updated_html();
|
||||
}
|
||||
return $block_content;
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
wp-content/plugins/mailpoet/vendor/mailpoet/email-editor/src/Integrations/Core/class-initializer.php
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
namespace MailPoet\EmailEditor\Integrations\Core;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Blocks_Registry;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Layout\Flex_Layout_Renderer;
|
||||
class Initializer {
|
||||
public function initialize(): void {
|
||||
add_action( 'mailpoet_blocks_renderer_initialized', array( $this, 'register_core_blocks_renderers' ), 10, 1 );
|
||||
add_filter( 'mailpoet_email_editor_theme_json', array( $this, 'adjust_theme_json' ), 10, 1 );
|
||||
add_filter( 'safe_style_css', array( $this, 'allow_styles' ) );
|
||||
}
|
||||
public function register_core_blocks_renderers( Blocks_Registry $blocks_registry ): void {
|
||||
$blocks_registry->add_block_renderer( 'core/paragraph', new Renderer\Blocks\Text() );
|
||||
$blocks_registry->add_block_renderer( 'core/heading', new Renderer\Blocks\Text() );
|
||||
$blocks_registry->add_block_renderer( 'core/column', new Renderer\Blocks\Column() );
|
||||
$blocks_registry->add_block_renderer( 'core/columns', new Renderer\Blocks\Columns() );
|
||||
$blocks_registry->add_block_renderer( 'core/list', new Renderer\Blocks\List_Block() );
|
||||
$blocks_registry->add_block_renderer( 'core/list-item', new Renderer\Blocks\List_Item() );
|
||||
$blocks_registry->add_block_renderer( 'core/image', new Renderer\Blocks\Image() );
|
||||
$blocks_registry->add_block_renderer( 'core/buttons', new Renderer\Blocks\Buttons( new Flex_Layout_Renderer() ) );
|
||||
$blocks_registry->add_block_renderer( 'core/button', new Renderer\Blocks\Button() );
|
||||
$blocks_registry->add_block_renderer( 'core/group', new Renderer\Blocks\Group() );
|
||||
// Render used for all other blocks.
|
||||
$blocks_registry->add_fallback_renderer( new Renderer\Blocks\Fallback() );
|
||||
}
|
||||
public function adjust_theme_json( \WP_Theme_JSON $editor_theme_json ): \WP_Theme_JSON {
|
||||
$theme_json = (string) file_get_contents( __DIR__ . '/theme.json' );
|
||||
$theme_json = json_decode( $theme_json, true );
|
||||
$editor_theme_json->merge( new \WP_Theme_JSON( $theme_json, 'default' ) );
|
||||
return $editor_theme_json;
|
||||
}
|
||||
public function allow_styles( ?array $allowed_styles ): array {
|
||||
// The styles can be null in some cases.
|
||||
if ( ! is_array( $allowed_styles ) ) {
|
||||
$allowed_styles = array();
|
||||
}
|
||||
$allowed_styles[] = 'display';
|
||||
$allowed_styles[] = 'mso-padding-alt';
|
||||
$allowed_styles[] = 'mso-font-width';
|
||||
$allowed_styles[] = 'mso-text-raise';
|
||||
return $allowed_styles;
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/theme.json",
|
||||
"version": 3,
|
||||
"styles": {
|
||||
"blocks": {
|
||||
"core/button": {
|
||||
"variations": {}
|
||||
}
|
||||
},
|
||||
"elements": {
|
||||
"button": {
|
||||
"color": {
|
||||
"background": "#32373c",
|
||||
"text": "#ffffff"
|
||||
},
|
||||
"spacing": {
|
||||
"padding": {
|
||||
"bottom": "0.7em",
|
||||
"left": "1.4em",
|
||||
"right": "1.4em",
|
||||
"top": "0.7em"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
namespace MailPoet\EmailEditor\Integrations\Utils;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class Dom_Document_Helper {
|
||||
private \DOMDocument $dom;
|
||||
public function __construct( string $html_content ) {
|
||||
$this->load_html( $html_content );
|
||||
}
|
||||
private function load_html( string $html_content ): void {
|
||||
libxml_use_internal_errors( true );
|
||||
$this->dom = new \DOMDocument();
|
||||
if ( ! empty( $html_content ) ) {
|
||||
// prefixing the content with the XML declaration to force the input encoding to UTF-8.
|
||||
$this->dom->loadHTML( '<?xml encoding="UTF-8">' . $html_content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
|
||||
}
|
||||
libxml_clear_errors();
|
||||
}
|
||||
public function find_element( string $tag_name ): ?\DOMElement {
|
||||
$elements = $this->dom->getElementsByTagName( $tag_name );
|
||||
return $elements->item( 0 ) ? $elements->item( 0 ) : null;
|
||||
}
|
||||
public function get_attribute_value( \DOMElement $element, string $attribute ): string {
|
||||
return $element->hasAttribute( $attribute ) ? $element->getAttribute( $attribute ) : '';
|
||||
}
|
||||
public function get_attribute_value_by_tag_name( string $tag_name, string $attribute ): ?string {
|
||||
$element = $this->find_element( $tag_name );
|
||||
if ( ! $element ) {
|
||||
return null;
|
||||
}
|
||||
return $this->get_attribute_value( $element, $attribute );
|
||||
}
|
||||
public function get_outer_html( \DOMElement $element ): string {
|
||||
return (string) $this->dom->saveHTML( $element );
|
||||
}
|
||||
public function get_element_inner_html( \DOMElement $element ): string {
|
||||
$inner_html = '';
|
||||
$children = $element->childNodes; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
foreach ( $children as $child ) {
|
||||
if ( ! $child instanceof \DOMNode ) {
|
||||
continue;
|
||||
}
|
||||
$inner_html .= $this->dom->saveHTML( $child );
|
||||
}
|
||||
return $inner_html;
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
namespace MailPoet\EmailEditor\Validator\Schema;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use MailPoet\EmailEditor\Validator\Schema;
|
||||
class Any_Of_Schema extends Schema {
|
||||
protected $schema = array(
|
||||
'anyOf' => array(),
|
||||
);
|
||||
public function __construct(
|
||||
array $schemas
|
||||
) {
|
||||
foreach ( $schemas as $schema ) {
|
||||
$this->schema['anyOf'][] = $schema->to_array();
|
||||
}
|
||||
}
|
||||
public function nullable(): self {
|
||||
$null = array( 'type' => 'null' );
|
||||
$any_of = $this->schema['anyOf'];
|
||||
$value = in_array( $null, $any_of, true ) ? $any_of : array_merge( $any_of, array( $null ) );
|
||||
return $this->update_schema_property( 'anyOf', $value );
|
||||
}
|
||||
public function non_nullable(): self {
|
||||
$null = array( 'type' => 'null' );
|
||||
$any_of = $this->schema['any_of'];
|
||||
$value = array_filter(
|
||||
$any_of,
|
||||
function ( $item ) use ( $null ) {
|
||||
return $item !== $null;
|
||||
}
|
||||
);
|
||||
return $this->update_schema_property( 'any_of', $value );
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user