Compare commits
1 Commits
update-plu
...
5.6.3
Author | SHA1 | Date | |
---|---|---|---|
e4e4b5f2be |
@ -197,7 +197,7 @@ jobs:
|
||||
- run:
|
||||
name: Download additional WP Plugins for tests
|
||||
command: |
|
||||
./do download:woo-commerce-zip 9.6.1
|
||||
./do download:woo-commerce-zip 9.6.0
|
||||
./do download:woo-commerce-subscriptions-zip 7.1.0
|
||||
./do download:woo-commerce-memberships-zip 1.26.5
|
||||
./do download:automate-woo-zip 6.1.5
|
||||
|
@ -1,30 +0,0 @@
|
||||
clone:
|
||||
git:
|
||||
image: woodpeckerci/plugin-git
|
||||
settings:
|
||||
depth: 1
|
||||
|
||||
steps:
|
||||
build:
|
||||
image: node:current-bookworm-slim
|
||||
commands:
|
||||
- apt update
|
||||
- apt install php php-symfony bash -y
|
||||
- npm install pnpm
|
||||
- cd mailpoet
|
||||
- bash build.sh
|
||||
- mkdir ../output
|
||||
- mv mailpoet.zip ../output
|
||||
- cd ..
|
||||
|
||||
release:
|
||||
image: woodpeckerci/plugin-gitea-release:latest
|
||||
settings:
|
||||
base_url: https://git.cavemanon.xyz
|
||||
api_key:
|
||||
from_secret: releasesmithapikey
|
||||
files: "output/"
|
||||
prerelease: false
|
||||
title: "${CI_COMMIT_TAG}"
|
||||
when:
|
||||
- event: tag
|
@ -23,7 +23,6 @@ class RoboFile extends \Robo\Tasks {
|
||||
return $this->taskExecStack()
|
||||
->stopOnFail()
|
||||
->exec('./tools/vendor/composer.phar install')
|
||||
->exec('cd ../packages/php/email-editor && ../../../mailpoet/tools/vendor/composer.phar install && cd -')
|
||||
->exec('cd .. && pnpm install --frozen-lockfile --prefer-offline')
|
||||
->addCode([$this, 'cleanupCachedFiles'])
|
||||
->run();
|
||||
@ -33,7 +32,6 @@ class RoboFile extends \Robo\Tasks {
|
||||
return $this->taskExecStack()
|
||||
->stopOnFail()
|
||||
->exec('./tools/vendor/composer.phar install')
|
||||
->exec('cd ../packages/php/email-editor && ../../../mailpoet/tools/vendor/composer.phar install && cd -')
|
||||
->addCode([$this, 'cleanupCachedFiles'])
|
||||
->run();
|
||||
}
|
||||
|
@ -143,7 +143,6 @@ export function* activate() {
|
||||
return {
|
||||
type: 'ACTIVATE',
|
||||
automation: data?.data ?? automation,
|
||||
saved: !!data?.data,
|
||||
} as const;
|
||||
}
|
||||
|
||||
|
@ -41,15 +41,11 @@ export function reducer(state: State, action): State {
|
||||
savedState: 'saved',
|
||||
};
|
||||
case 'ACTIVATE':
|
||||
return action.saved
|
||||
? {
|
||||
...state,
|
||||
automationData: action.automation,
|
||||
savedState: 'saved',
|
||||
}
|
||||
: {
|
||||
...state,
|
||||
};
|
||||
return {
|
||||
...state,
|
||||
automationData: action.automation,
|
||||
savedState: 'saved',
|
||||
};
|
||||
case 'DEACTIVATE':
|
||||
return {
|
||||
...state,
|
||||
|
@ -3,7 +3,6 @@ import { Step } from '../../../../../editor/components/automation/types';
|
||||
import { storeName } from '../../../../../editor/store';
|
||||
|
||||
const transactionalTriggers = [
|
||||
'mailpoet:custom-trigger',
|
||||
'woocommerce:order-status-changed',
|
||||
'woocommerce:order-created',
|
||||
'woocommerce:order-completed',
|
||||
|
@ -4,27 +4,27 @@ import { Icon } from './icon';
|
||||
import { PremiumModalForStepEdit } from '../../../../components/premium-modal-steps-edit';
|
||||
|
||||
const keywords = [
|
||||
// translators: noun, used as a search keyword for "Customer posts a review" trigger
|
||||
// translators: noun, used as a search keyword for "Customer makes a review" trigger
|
||||
__('review', 'mailpoet'),
|
||||
// translators: verb, used as a search keyword for "Customer posts a review" trigger
|
||||
// translators: verb, used as a search keyword for "Customer makes a review" trigger
|
||||
__('buy', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer posts a review" trigger
|
||||
// translators: noun, used as a search keyword for "Customer makes a review" trigger
|
||||
__('comment', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer posts a review" trigger
|
||||
// translators: noun, used as a search keyword for "Customer makes a review" trigger
|
||||
__('ecommerce', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer posts a review" trigger
|
||||
// translators: noun, used as a search keyword for "Customer makes a review" trigger
|
||||
__('woocommerce', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer posts a review" trigger
|
||||
// translators: noun, used as a search keyword for "Customer makes a review" trigger
|
||||
__('product', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer posts a review" trigger
|
||||
// translators: noun, used as a search keyword for "Customer makes a review" trigger
|
||||
__('order', 'mailpoet'),
|
||||
];
|
||||
export const step: StepType = {
|
||||
key: 'woocommerce:made-a-review',
|
||||
group: 'triggers',
|
||||
title: () => __('Customer posts a review', 'mailpoet'),
|
||||
title: () => __('Customer makes a review', 'mailpoet'),
|
||||
description: () =>
|
||||
__('Start the automation when a customer posts a review.', 'mailpoet'),
|
||||
__('Start the automation when a customer makes a review.', 'mailpoet'),
|
||||
|
||||
subtitle: () => __('Trigger', 'mailpoet'),
|
||||
keywords,
|
||||
|
@ -73,7 +73,7 @@ export function ProductDiscovery({ onHide }: Props): JSX.Element {
|
||||
title={MailPoet.I18n.t('brandWooEmails')}
|
||||
description={MailPoet.I18n.t('brandWooEmailsDesc')}
|
||||
link="admin.php?page=mailpoet-settings#/woocommerce"
|
||||
imgSrc={`${MailPoet.cdnUrl}homepage/woo-transactional-email-illustration.20241219.png`}
|
||||
imgSrc={`${MailPoet.cdnUrl}homepage/woo-transactional-email-illustration.png`}
|
||||
isDone={tasksStatus.brandWooEmails}
|
||||
doneMessage={MailPoet.I18n.t('brandWooEmailsDone')}
|
||||
/>,
|
||||
|
@ -13,7 +13,7 @@ export function Resources(): JSX.Element {
|
||||
link="https://kb.mailpoet.com/article/141-create-an-email-types-of-campaigns?utm_source=plugin&utm_medium=homepage&utm_campaign=resources"
|
||||
abstract={MailPoet.I18n.t('createAnEmailAbstract')}
|
||||
title={MailPoet.I18n.t('createAnEmailTitle')}
|
||||
imgSrc={`${MailPoet.cdnUrl}homepage/resources/add_email.20241219.png`}
|
||||
imgSrc={`${MailPoet.cdnUrl}homepage/resources/add_email.png`}
|
||||
/>,
|
||||
<ResourcePost
|
||||
key="createAForm"
|
||||
|
@ -12,7 +12,7 @@ const Images = {
|
||||
icon_4: `${MailPoet.cdnUrl}landingpage/feature_icon_4.png`,
|
||||
},
|
||||
wooCommerceFeatureImages: {
|
||||
feature_1: `${MailPoet.cdnUrl}landingpage/woo_feature_automate_your_marketing.20241219.png`,
|
||||
feature_1: `${MailPoet.cdnUrl}landingpage/woo_feature_automate_your_marketing.png`,
|
||||
feature_2: `${MailPoet.cdnUrl}landingpage/woo_feature_measure_revenue_per_email.png`,
|
||||
feature_3: `${MailPoet.cdnUrl}landingpage/woo_feature_let_your_brand_shine.png`,
|
||||
feature_4: `${MailPoet.cdnUrl}landingpage/woo_feature_rescue_abandoned_carts.png`,
|
||||
|
@ -6,18 +6,11 @@ import { addFilter, addAction } from '@wordpress/hooks';
|
||||
import { MailPoet } from 'mailpoet';
|
||||
import { withSatismeterSurvey } from './satismeter-survey';
|
||||
import './index.scss';
|
||||
import { useValidationRules } from './validate-email-content';
|
||||
|
||||
addFilter('mailpoet_email_editor_wrap_editor_component', 'mailpoet', (editor) =>
|
||||
withSatismeterSurvey(editor),
|
||||
);
|
||||
|
||||
addFilter(
|
||||
'mailpoet_email_editor_content_validation_rules',
|
||||
'mailpoet',
|
||||
(validationRules: []) => [...validationRules, ...useValidationRules()],
|
||||
);
|
||||
|
||||
const EVENTS_TO_TRACK = [
|
||||
'email_editor_events_editor_layout_loaded', // email editor was opened
|
||||
'email_editor_events_template_select_modal_template_selected', // a template was selected from the template-select modal
|
||||
|
@ -1,93 +0,0 @@
|
||||
import { useMemo } from '@wordpress/element';
|
||||
import { createBlock } from '@wordpress/blocks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { dispatch, useSelect } from '@wordpress/data';
|
||||
import { store as blockEditorStore } from '@wordpress/block-editor';
|
||||
import { store as coreDataStore } from '@wordpress/core-data';
|
||||
|
||||
const emailEditorStore = 'email-editor/editor';
|
||||
|
||||
const contentLink = `<a data-link-href='[mailpoet/subscription-unsubscribe-url]' contenteditable='false' style='text-decoration: underline;' class='mailpoet-email-editor__personalization-tags-link'>${__(
|
||||
'Unsubscribe',
|
||||
'mailpoet',
|
||||
)}</a> | <a data-link-href='[mailpoet/subscription-manage-url]' contenteditable='false' style='text-decoration: underline;' class='mailpoet-email-editor__personalization-tags-link'>${__(
|
||||
'Manage subscription',
|
||||
'mailpoet',
|
||||
)}</a>`;
|
||||
|
||||
export function useValidationRules() {
|
||||
const { contentBlockId, hasFooter } = useSelect((select) => {
|
||||
const allBlocks = select(blockEditorStore).getBlocks();
|
||||
const noBodyBlocks = allBlocks.filter(
|
||||
(block) =>
|
||||
block.name !== 'mailpoet/powered-by-mailpoet' &&
|
||||
block.name !== 'core/post-content',
|
||||
);
|
||||
// @ts-expect-error getBlocksByName is not defined in types
|
||||
const blocks = select(blockEditorStore).getBlocksByName(
|
||||
'core/post-content',
|
||||
) as string[] | undefined;
|
||||
return {
|
||||
contentBlockId: blocks?.[0],
|
||||
hasFooter: noBodyBlocks.length > 0,
|
||||
};
|
||||
});
|
||||
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
const { editedTemplateContent, postTemplateId } = useSelect((mapSelect) => ({
|
||||
editedTemplateContent:
|
||||
// @ts-ignore
|
||||
mapSelect(emailEditorStore).getCurrentTemplateContent() as string,
|
||||
postTemplateId:
|
||||
// @ts-ignore
|
||||
mapSelect(emailEditorStore).getCurrentTemplate()?.id as string,
|
||||
}));
|
||||
|
||||
return useMemo(() => {
|
||||
const linksParagraphBlock = createBlock('core/paragraph', {
|
||||
align: 'center',
|
||||
fontSize: 'small',
|
||||
content: contentLink,
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'missing-unsubscribe-link',
|
||||
test: (emailContent: string) =>
|
||||
!emailContent.includes('[mailpoet/subscription-unsubscribe-url]'),
|
||||
message: __(
|
||||
'All emails must include an "Unsubscribe" link.',
|
||||
'mailpoet',
|
||||
),
|
||||
actions: [
|
||||
{
|
||||
label: __('Insert link', 'mailpoet'),
|
||||
onClick: () => {
|
||||
if (!hasFooter) {
|
||||
void dispatch(blockEditorStore).insertBlock(
|
||||
linksParagraphBlock,
|
||||
undefined,
|
||||
contentBlockId,
|
||||
);
|
||||
} else {
|
||||
void dispatch(coreDataStore).editEntityRecord(
|
||||
'postType',
|
||||
'wp_template',
|
||||
postTemplateId,
|
||||
{
|
||||
content: `
|
||||
${editedTemplateContent}
|
||||
<!-- wp:paragraph {"align":"center","fontSize":"small"} -->
|
||||
${contentLink}
|
||||
<!-- /wp:paragraph -->
|
||||
`,
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}, [contentBlockId, postTemplateId, hasFooter, editedTemplateContent]);
|
||||
}
|
@ -41,36 +41,24 @@ export const initializeSatismeterSurvey = (writeId = null) => {
|
||||
} else {
|
||||
writeKey = oldUsersPollId;
|
||||
}
|
||||
const traits = {
|
||||
name: window.mailpoet_current_wp_user.user_nicename,
|
||||
email: window.mailpoet_current_wp_user.user_email,
|
||||
mailpoetVersion: window.mailpoet_version,
|
||||
mailpoetPremiumIsActive: window.mailpoet_premium_active,
|
||||
createdAt: trackingData.installedAtIso,
|
||||
newslettersSent: trackingData.newslettersSent,
|
||||
welcomeEmails: trackingData.welcomeEmails,
|
||||
postnotificationEmails: trackingData.postnotificationEmails,
|
||||
woocommerceEmails: trackingData.woocommerceEmails,
|
||||
subscribers: trackingData.subscribers,
|
||||
lists: trackingData.lists,
|
||||
sendingMethod: trackingData.sendingMethod,
|
||||
woocommerceIsInstalled: trackingData.woocommerceIsInstalled,
|
||||
woocommerceVersion: trackingData.woocommerceVersion,
|
||||
WordPressVersion: trackingData.WordPressVersion,
|
||||
blockTheme: trackingData.blockTheme,
|
||||
themeVersion: trackingData.themeVersion,
|
||||
theme: trackingData.theme,
|
||||
};
|
||||
if (trackingData.gutenbergVersion) {
|
||||
traits.gutenbergVersion = trackingData.gutenbergVersion;
|
||||
}
|
||||
if (trackingData.wooCommerceVersion) {
|
||||
traits.wooCommerceVersion = trackingData.wooCommerceVersion;
|
||||
}
|
||||
satismeter({
|
||||
writeKey,
|
||||
userId: window.mailpoet_current_wp_user.ID + window.mailpoet_site_url,
|
||||
traits,
|
||||
traits: {
|
||||
name: window.mailpoet_current_wp_user.user_nicename,
|
||||
email: window.mailpoet_current_wp_user.user_email,
|
||||
mailpoetVersion: window.mailpoet_version,
|
||||
mailpoetPremiumIsActive: window.mailpoet_premium_active,
|
||||
createdAt: trackingData.installedAtIso,
|
||||
newslettersSent: trackingData.newslettersSent,
|
||||
welcomeEmails: trackingData.welcomeEmails,
|
||||
postnotificationEmails: trackingData.postnotificationEmails,
|
||||
woocommerceEmails: trackingData.woocommerceEmails,
|
||||
subscribers: trackingData.subscribers,
|
||||
lists: trackingData.lists,
|
||||
sendingMethod: trackingData.sendingMethod,
|
||||
woocommerceIsInstalled: trackingData.woocommerceIsInstalled,
|
||||
},
|
||||
events: {
|
||||
submit: (response) => {
|
||||
if (response.rating >= 9 && response.completed) {
|
||||
|
@ -4,7 +4,7 @@ function WelcomeWizardStepLayoutBody(props) {
|
||||
return (
|
||||
<div className="mailpoet-wizard-step">
|
||||
<div className="mailpoet-wizard-step-illustration">
|
||||
<img src={props.illustrationUrl} alt="" />
|
||||
<img src={props.illustrationUrl} width="500" alt="" />
|
||||
</div>
|
||||
<div className="mailpoet-wizard-step-content">{props.children}</div>
|
||||
</div>
|
||||
|
@ -43,9 +43,6 @@ if [ -d 'vendor-prefixed' ]; then
|
||||
mv vendor-prefixed vendor-prefixed-backup
|
||||
fi
|
||||
|
||||
echo '[BUILD] Install email editor dependencies'
|
||||
cd ../packages/php/email-editor && ../../../mailpoet/tools/vendor/composer.phar install --no-dev --prefer-dist --optimize-autoloader --no-scripts && cd -
|
||||
|
||||
# Production libraries.
|
||||
echo '[BUILD] Fetching production libraries'
|
||||
mkdir vendor-prefixed
|
||||
|
@ -61,9 +61,6 @@
|
||||
"MailPoet\\Test\\DataGenerator\\": "tests/DataGenerator"
|
||||
}
|
||||
},
|
||||
"replace": {
|
||||
"soundasleep/html2text": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"pre-install-cmd": [
|
||||
"@php tools/install.php",
|
||||
|
10
mailpoet/composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "51893b0f5ed38d130932b86b48e55b94",
|
||||
"content-hash": "9957467448f215809ac9dc8fbcab2b00",
|
||||
"packages": [
|
||||
{
|
||||
"name": "dragonmantank/cron-expression",
|
||||
@ -73,11 +73,10 @@
|
||||
"dist": {
|
||||
"type": "path",
|
||||
"url": "../packages/php/email-editor",
|
||||
"reference": "53577c5aa3a97e82c58284d48c3aa339cb2a15d4"
|
||||
"reference": "311798cfd57b26bb5df1fc7f97b5732e45603419"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.4",
|
||||
"soundasleep/html2text": "^2.1"
|
||||
"php": ">=7.4"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@ -102,9 +101,6 @@
|
||||
],
|
||||
"code-style-fix": [
|
||||
"../../../mailpoet/tasks/code_sniffer/vendor/bin/phpcbf -p"
|
||||
],
|
||||
"phpstan": [
|
||||
"php ./tasks/run-phpstan.php"
|
||||
]
|
||||
},
|
||||
"description": "Email editor based on WordPress Gutenberg package.",
|
||||
|
@ -417,15 +417,11 @@ class Reporter {
|
||||
}
|
||||
|
||||
public function getTrackingData() {
|
||||
global $wp_version, $woocommerce; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$newsletters = $this->newslettersRepository->getAnalytics();
|
||||
$segments = $this->segmentsRepository->getCountsPerType();
|
||||
$mta = $this->settings->get('mta', []);
|
||||
$installedAt = new Carbon($this->settings->get('installed_at'));
|
||||
$theme = $this->wp->wpGetTheme();
|
||||
$result = [
|
||||
'WordPressVersion' => $wp_version, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
'pluginGutenberg' => $this->wp->isPluginActive('gutenberg/gutenberg.php'),
|
||||
return [
|
||||
'installedAtIso' => $installedAt->format(Carbon::ISO8601),
|
||||
'newslettersSent' => $newsletters['sent_newsletters_count'],
|
||||
'welcomeEmails' => $newsletters['welcome_newsletters_count'],
|
||||
@ -433,19 +429,9 @@ class Reporter {
|
||||
'woocommerceEmails' => $newsletters['automatic_emails_count'],
|
||||
'subscribers' => $this->subscribersFeature->getSubscribersCount(),
|
||||
'lists' => isset($segments['default']) ? (int)$segments['default'] : 0,
|
||||
'sendingMethod' => $mta['method'] ?? null,
|
||||
'sendingMethod' => isset($mta['method']) ? $mta['method'] : null,
|
||||
'woocommerceIsInstalled' => $this->woocommerceHelper->isWooCommerceActive(),
|
||||
'blockTheme' => $this->wp->wpIsBlockTheme(),
|
||||
'theme' => $theme->Name, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
'themeVersion' => $theme->Version, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
];
|
||||
if (defined('GUTENBERG_VERSION')) {
|
||||
$result['gutenbergVersion'] = GUTENBERG_VERSION;
|
||||
}
|
||||
if ($this->woocommerceHelper->isWooCommerceActive()) {
|
||||
$result['wooCommerceVersion'] = $woocommerce->version;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function isFilterTypeActive(string $filterType, string $action): bool {
|
||||
|
@ -62,7 +62,6 @@ class SendEmailAction implements Action {
|
||||
private const OPTIN_RETRIES = 'optin_retries';
|
||||
|
||||
private const TRANSACTIONAL_TRIGGERS = [
|
||||
'mailpoet:custom-trigger',
|
||||
'woocommerce:order-status-changed',
|
||||
'woocommerce:order-created',
|
||||
'woocommerce:order-completed',
|
||||
|
@ -45,16 +45,16 @@ class AbandonedCartWorker extends SimpleWorker {
|
||||
|
||||
$subscribers = $task->getSubscribers();
|
||||
if ($subscribers->count() !== 1) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
$subscriber = isset($subscribers[0]) ? $subscribers[0]->getSubscriber() : null;
|
||||
if (!$subscriber) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
$automation = $this->automationStorage->getAutomation((int)$automationId, (int)$automationVersion);
|
||||
if (!$automation || $automation->getStatus() !== Automation::STATUS_ACTIVE) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->wp->doAction(
|
||||
|
@ -352,7 +352,6 @@ class ContainerConfigurator implements IContainerConfigurator {
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors\Typography_Preprocessor::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Renderer\Renderer::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Templates\Templates::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Templates\Templates_Registry::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Patterns\Patterns::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Content_Renderer::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Blocks_Registry::class)->setPublic(true);
|
||||
@ -361,10 +360,6 @@ class ContainerConfigurator implements IContainerConfigurator {
|
||||
$container->autowire(\MailPoet\EmailEditor\Integrations\MailPoet\Cli::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Integrations\MailPoet\EmailEditor::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Integrations\MailPoet\EditorPageRenderer::class)->setPublic(true);
|
||||
$container->register(\MailPoet\EmailEditor\Engine\Renderer\Css_Inliner::class)
|
||||
->setPublic(true)
|
||||
->setFactory([\MailPoet\EmailEditor\Integrations\MailPoet\MailpoetCssInlinerFactory::class, 'create']);
|
||||
$container->autowire(\MailPoet\EmailEditor\Integrations\MailPoet\MailPoetCssInliner::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Integrations\MailPoet\EmailApiController::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Integrations\MailPoet\EmailEditorPreviewEmail::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Integrations\MailPoet\DependencyNotice::class)->setPublic(true);
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace MailPoet\EmailEditor\Integrations\MailPoet;
|
||||
|
||||
use MailPoet\Analytics\Analytics;
|
||||
use MailPoet\API\JSON\API;
|
||||
use MailPoet\Config\Env;
|
||||
use MailPoet\Config\Installer;
|
||||
use MailPoet\Config\ServicesChecker;
|
||||
@ -74,9 +75,7 @@ class EditorPageRenderer {
|
||||
public function render() {
|
||||
$postId = isset($_GET['post']) ? intval($_GET['post']) : 0;
|
||||
$post = $this->wp->getPost($postId);
|
||||
$currentPostType = $post->post_type; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
|
||||
if (!$post instanceof \WP_Post || $currentPostType !== EditorInitController::MAILPOET_EMAIL_POST_TYPE) {
|
||||
if (!$post instanceof \WP_Post || $post->post_type !== EditorInitController::MAILPOET_EMAIL_POST_TYPE) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
return;
|
||||
}
|
||||
$newsletter = $this->newslettersRepository->findOneBy(['wpPost' => $postId]);
|
||||
@ -135,13 +134,19 @@ class EditorPageRenderer {
|
||||
$assetsParams['version']
|
||||
);
|
||||
|
||||
$jsonAPIRoot = rtrim($this->wp->escUrlRaw(admin_url('admin-ajax.php')), '/');
|
||||
$token = $this->wp->wpCreateNonce('mailpoet_token');
|
||||
$apiVersion = API::CURRENT_VERSION;
|
||||
$currentUserEmail = $this->wp->wpGetCurrentUser()->user_email;
|
||||
$this->wp->wpLocalizeScript(
|
||||
'mailpoet_email_editor',
|
||||
'MailPoetEmailEditor',
|
||||
[
|
||||
'current_post_type' => esc_js($currentPostType),
|
||||
'current_post_id' => $post->ID,
|
||||
'json_api_root' => esc_js($jsonAPIRoot),
|
||||
'api_token' => esc_js($token),
|
||||
'api_version' => esc_js($apiVersion),
|
||||
'cdn_url' => esc_js($this->cdnAssetUrl->generateCdnUrl("")),
|
||||
'is_premium_plugin_active' => (bool)$this->servicesChecker->isPremiumPluginActive(),
|
||||
'current_wp_user_email' => esc_js($currentUserEmail),
|
||||
'editor_settings' => $this->settingsController->get_settings(),
|
||||
'editor_theme' => $this->themeController->get_base_theme()->get_raw_data(),
|
||||
|
@ -1,31 +0,0 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\EmailEditor\Integrations\MailPoet;
|
||||
|
||||
use MailPoet\EmailEditor\Engine\Renderer\Css_Inliner;
|
||||
use MailPoetVendor\Pelago\Emogrifier\CssInliner;
|
||||
|
||||
class MailPoetCssInliner implements Css_Inliner {
|
||||
private CssInliner $inliner;
|
||||
|
||||
public function from_html(string $unprocessed_html): self {// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps -- we need to match the interface
|
||||
$that = new self();
|
||||
$that->inliner = CssInliner::fromHtml($unprocessed_html);
|
||||
return $that;
|
||||
}
|
||||
|
||||
public function inline_css(string $css = ''): self {// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps -- we need to match the interface
|
||||
if (!isset($this->inliner)) {
|
||||
throw new \LogicException('You must call from_html before calling inline_css');
|
||||
}
|
||||
$this->inliner->inlineCss($css);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render(): string {
|
||||
if (!isset($this->inliner)) {
|
||||
throw new \LogicException('You must call from_html before calling inline_css');
|
||||
}
|
||||
return $this->inliner->render();
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\EmailEditor\Integrations\MailPoet;
|
||||
|
||||
class MailpoetCssInlinerFactory {
|
||||
public static function create(): MailPoetCssInliner {
|
||||
return new MailPoetCssInliner();
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ use MailPoet\EmailEditor\Integrations\MailPoet\Patterns\Pattern;
|
||||
|
||||
class OneColumn extends Pattern {
|
||||
protected $name = '1-column-content';
|
||||
protected $block_types = []; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
protected $block_types = ['core/post-content']; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
protected $template_types = ['email-template']; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
protected $categories = ['email-contents'];
|
||||
|
||||
|
@ -6,7 +6,7 @@ use MailPoet\EmailEditor\Integrations\MailPoet\Patterns\Pattern;
|
||||
|
||||
class ThreeColumn extends Pattern {
|
||||
protected $name = '3-column-content';
|
||||
protected $block_types = []; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
protected $block_types = ['core/post-content']; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
protected $template_types = ['email-template']; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
protected $categories = ['email-contents'];
|
||||
|
||||
|
@ -6,7 +6,7 @@ use MailPoet\EmailEditor\Integrations\MailPoet\Patterns\Pattern;
|
||||
|
||||
class TwoColumn extends Pattern {
|
||||
protected $name = '2-column-content';
|
||||
protected $block_types = []; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
protected $block_types = ['core/post-content']; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
protected $template_types = ['email-template']; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
protected $categories = ['email-contents'];
|
||||
|
||||
|
@ -28,7 +28,7 @@ class Newsletter {
|
||||
public function getContent(): string {
|
||||
// translators: This is a text used in a footer on an email <!--[mailpoet/site-title]--> will be replaced with the site title.
|
||||
$footerText = __('You received this email because you are subscribed to the <!--[mailpoet/site-title]-->', 'mailpoet');
|
||||
return '<!-- wp:group {"backgroundColor":"white","layout":{"type":"constrained"},"lock":{"move":false,"remove":true}} -->
|
||||
return '<!-- wp:group {"backgroundColor":"white","layout":{"type":"constrained"},"lock":{"move":false,"remove":false}} -->
|
||||
<div class="wp-block-group has-white-background-color has-background">
|
||||
<!-- wp:group {"style":{"spacing":{"padding":{"top":"var:preset|spacing|30","bottom":"var:preset|spacing|10","left":"var:preset|spacing|20","right":"var:preset|spacing|20"}}}} -->
|
||||
<div
|
||||
@ -51,7 +51,7 @@ class Newsletter {
|
||||
<!-- /wp:image -->
|
||||
</div>
|
||||
<!-- /wp:group -->
|
||||
<!-- wp:post-content {"lock":{"move":false,"remove":true},"layout":{"type":"default"}} /-->
|
||||
<!-- wp:post-content {"lock":{"move":false,"remove":false},"layout":{"type":"default"}} /-->
|
||||
<!-- wp:group {"style":{"spacing":{"padding":{"right":"var:preset|spacing|20","left":"var:preset|spacing|20","top":"var:preset|spacing|10","bottom":"var:preset|spacing|10"}}}} -->
|
||||
<div
|
||||
class="wp-block-group"
|
||||
|
@ -2,8 +2,6 @@
|
||||
|
||||
namespace MailPoet\EmailEditor\Integrations\MailPoet\Templates;
|
||||
|
||||
use MailPoet\EmailEditor\Engine\Templates\Template;
|
||||
use MailPoet\EmailEditor\Engine\Templates\Templates_Registry;
|
||||
use MailPoet\EmailEditor\Integrations\MailPoet\EmailEditor;
|
||||
use MailPoet\EmailEditor\Integrations\MailPoet\Templates\Library\Newsletter;
|
||||
use MailPoet\Util\CdnAssetUrl;
|
||||
@ -23,22 +21,26 @@ class TemplatesController {
|
||||
}
|
||||
|
||||
public function initialize() {
|
||||
$this->wp->addFilter('mailpoet_email_editor_register_templates', [$this, 'registerTemplates'], 10, 1);
|
||||
$this->wp->addAction('mailpoet_email_editor_register_templates', [$this, 'registerTemplates'], 10, 0);
|
||||
}
|
||||
|
||||
public function registerTemplates(Templates_Registry $templatesRegistry): Templates_Registry {
|
||||
public function registerTemplates() {
|
||||
$newsletter = new Newsletter($this->cdnAssetUrl);
|
||||
$templateName = $this->templatePrefix . '//' . $newsletter->getSlug();
|
||||
|
||||
$template = new Template(
|
||||
$this->templatePrefix,
|
||||
$newsletter->getSlug(),
|
||||
$newsletter->getTitle(),
|
||||
$newsletter->getDescription(),
|
||||
$newsletter->getContent(),
|
||||
[EmailEditor::MAILPOET_EMAIL_POST_TYPE]
|
||||
if (\WP_Block_Templates_Registry::get_instance()->is_registered($templateName)) {
|
||||
// skip registration if the template was already registered.
|
||||
return;
|
||||
}
|
||||
|
||||
register_block_template(
|
||||
$templateName,
|
||||
[
|
||||
'title' => $newsletter->getTitle(),
|
||||
'description' => $newsletter->getDescription(),
|
||||
'content' => $newsletter->getContent(),
|
||||
'post_types' => [EmailEditor::MAILPOET_EMAIL_POST_TYPE],
|
||||
]
|
||||
);
|
||||
$templatesRegistry->register($template);
|
||||
|
||||
return $templatesRegistry;
|
||||
}
|
||||
}
|
||||
|
@ -39,13 +39,7 @@ class PostContentManager {
|
||||
if ($this->wp->hasExcerpt($post)) {
|
||||
return self::stripShortCodes($this->wp->getTheExcerpt($post));
|
||||
}
|
||||
return self::stripShortCodes(
|
||||
$this->wp->applyFilters(
|
||||
'get_the_excerpt',
|
||||
$this->generateExcerpt($post->post_content), // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$post
|
||||
)
|
||||
);
|
||||
return $this->generateExcerpt($post->post_content); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
}
|
||||
return self::stripShortCodes($post->post_content); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
}
|
||||
|
@ -167,6 +167,8 @@ class Subscribers {
|
||||
}
|
||||
|
||||
private function getFreeSubscribersLimit() {
|
||||
return 999999999;
|
||||
$installationTime = strtotime((string)$this->settings->get('installed_at'));
|
||||
$oldUser = $installationTime < strtotime(self::NEW_LIMIT_DATE);
|
||||
return $oldUser ? self::SUBSCRIBERS_OLD_LIMIT : self::SUBSCRIBERS_NEW_LIMIT;
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ class CouponPreProcessor {
|
||||
if ($preview) {
|
||||
return $blocks;
|
||||
}
|
||||
|
||||
|
||||
$generated = $this->ensureCouponForBlocks($blocks, $newsletter);
|
||||
$body = $newsletter->getBody();
|
||||
|
||||
@ -146,27 +146,18 @@ class CouponPreProcessor {
|
||||
}, $items);
|
||||
}
|
||||
|
||||
private function generateRandomSegment($length) {
|
||||
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
$segment = '';
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$randomIndex = rand(0, strlen($characters) - 1);
|
||||
$segment .= $characters[$randomIndex];
|
||||
}
|
||||
|
||||
return $segment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates Coupon code for XXXX-XXXXXX-XXXX pattern
|
||||
*/
|
||||
private function generateRandomCode(): string {
|
||||
$part1 = $this->generateRandomSegment(4);
|
||||
$part2 = $this->generateRandomSegment(6);
|
||||
$part3 = $this->generateRandomSegment(4);
|
||||
|
||||
return $part1 . '-' . $part2 . '-' . $part3;
|
||||
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
$length = strlen($chars);
|
||||
return sprintf(
|
||||
"%s-%s-%s",
|
||||
substr($chars, rand(0, $length - 5), 4),
|
||||
substr($chars, rand(0, $length - 8), 7),
|
||||
substr($chars, rand(0, $length - 5), 4)
|
||||
);
|
||||
}
|
||||
|
||||
private function shouldGenerateCoupon(array $block): bool {
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
/*
|
||||
* Plugin Name: MailPoet
|
||||
* Version: 5.7.0
|
||||
* Version: 5.6.3
|
||||
* Plugin URI: https://www.mailpoet.com
|
||||
* Description: Create and send newsletters, post notifications and welcome emails from your WordPress.
|
||||
* Author: MailPoet
|
||||
@ -11,8 +11,8 @@
|
||||
* Text Domain: mailpoet
|
||||
* Domain Path: /lang
|
||||
*
|
||||
* WC requires at least: 9.5.1
|
||||
* WC tested up to: 9.6.0
|
||||
* WC requires at least: 9.4.3
|
||||
* WC tested up to: 9.5.1
|
||||
*
|
||||
* @package WordPress
|
||||
* @author MailPoet
|
||||
@ -20,7 +20,7 @@
|
||||
*/
|
||||
|
||||
$mailpoetPlugin = [
|
||||
'version' => '5.7.0',
|
||||
'version' => '5.6.3',
|
||||
'filename' => __FILE__,
|
||||
'path' => dirname(__FILE__),
|
||||
'autoloader' => dirname(__FILE__) . '/vendor/autoload.php',
|
||||
@ -28,7 +28,7 @@ $mailpoetPlugin = [
|
||||
];
|
||||
|
||||
const MAILPOET_MINIMUM_REQUIRED_WP_VERSION = '6.6'; // L-1 version, not the latest
|
||||
const MAILPOET_MINIMUM_REQUIRED_WOOCOMMERCE_VERSION = '9.5'; // L-1 version, not the latest
|
||||
const MAILPOET_MINIMUM_REQUIRED_WOOCOMMERCE_VERSION = '9.4'; // L-1 version, not the latest
|
||||
|
||||
|
||||
// Display WP version error notice
|
||||
|
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 139 KiB |
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 104 KiB |
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 102 KiB |
@ -3,7 +3,7 @@ Contributors: mailpoet, woocommerce, automattic
|
||||
Tags: email marketing, post notification, woocommerce emails, email automation, newsletter
|
||||
Requires at least: 6.6
|
||||
Tested up to: 6.7
|
||||
Stable tag: 5.7.0
|
||||
Stable tag: 5.6.3
|
||||
Requires PHP: 7.4
|
||||
License: GPLv3
|
||||
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
@ -218,15 +218,15 @@ Check our [Knowledge Base](https://kb.mailpoet.com) or contact us through our [s
|
||||
3. MailPoet email types
|
||||
4. Newsletter stats (Premium)
|
||||
5. Subscriber import (via a CSV file or directly from MailChimp)
|
||||
6. Automation editor (Premium)
|
||||
6. WooCommerce emails
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 5.7.0 - 2025-02-11 =
|
||||
* Improved: Rename Review Trigger in Automations;
|
||||
* Changed: minimum required WooCommerce is 9.5;
|
||||
* Fixed: Automation UI shows wrong saved status after failed activation;
|
||||
* Fixed: Email preview does not work with sent emails;
|
||||
* Fixed: email content patterns are mixed with page starter patterns.
|
||||
= 5.6.3 - 2025-01-27 =
|
||||
* Improved: Purchased-related automation templates;
|
||||
* Fixed: Removed some unwanted templates from the template selector;
|
||||
* Fixed: The new email editor is not working on some web hostings;
|
||||
* Fixed: Inserting personalization tags attributes;
|
||||
* Fixed: The swap template behaviour.
|
||||
|
||||
[See the changelog for all versions.](https://github.com/mailpoet/mailpoet/blob/trunk/mailpoet/changelog.txt)
|
||||
|
@ -3,7 +3,6 @@ parameters:
|
||||
tmpDir: ../../temp/phpstan
|
||||
bootstrapFiles:
|
||||
- ../../vendor/autoload.php
|
||||
- ../../../packages/php/email-editor/vendor/autoload.php
|
||||
- vendor/php-stubs/wordpress-stubs/wordpress-stubs.php
|
||||
- ../../../tests_env/vendor/codeception/codeception/autoload.php
|
||||
- ../../../tests_env/vendor/codeception/verify/src/Codeception/Verify/Verify.php
|
||||
|
@ -84,6 +84,11 @@ class ChangelogController {
|
||||
$headingPrefix = explode(self::HEADING_GLUE, $heading)[0];
|
||||
$headersDelimiter = "\n";
|
||||
|
||||
if (strpos($fileName, '.md') !== false) {
|
||||
$headersDelimiter .= "\n";
|
||||
$changesList = preg_replace("/^\*/m", "-", $changesList);
|
||||
}
|
||||
|
||||
$fileContents = file_get_contents($fileName);
|
||||
$changelog = "$heading$headersDelimiter$changesList";
|
||||
|
||||
|
@ -113,19 +113,17 @@ class CreateAndSendEmailUsingGutenbergCest {
|
||||
$i->waitForText('Email saved!');
|
||||
// there is weird issue in the acceptance env where preview popup goes beyond sidebar
|
||||
// this issue is not confirmed to be real issue but only on the acceptance test site
|
||||
$i->click('[aria-label="Close sidebar"]'); // close sidebar
|
||||
$i->click('div.interface-pinned-items > button'); // close sidebar
|
||||
$i->click('.mailpoet-preview-dropdown button[aria-label="Preview"]');
|
||||
$i->waitForElementVisible('//a[text()="Preview in new tab"]');
|
||||
$i->waitForElementClickable('//a[text()="Preview in new tab"]');
|
||||
$i->click('//a[text()="Preview in new tab"]');
|
||||
$i->waitForElementVisible('//button[text()="Preview in new tab"]');
|
||||
$i->waitForElementClickable('//button[text()="Preview in new tab"]');
|
||||
$i->click('//button[text()="Preview in new tab"]');
|
||||
$i->switchToNextTab();
|
||||
$i->canSeeInCurrentUrl('post_type=mailpoet_email');
|
||||
$i->canSeeInCurrentUrl('endpoint=view_in_browser');
|
||||
$i->canSee('Sample text');
|
||||
$i->closeTab();
|
||||
|
||||
$i->wantTo('Send preview email and verify it was delivered');
|
||||
$i->click('.mailpoet-preview-dropdown button[aria-label="Preview"]');
|
||||
$i->waitForElementVisible('//span[text()="Send a test email"]');
|
||||
$i->click('//span[text()="Send a test email"]'); // MenuItem component renders a button containing span
|
||||
$i->waitForElementClickable('//button[text()="Send test email"]');
|
||||
$i->click('//button[text()="Send test email"]');
|
||||
|
@ -189,12 +189,6 @@ class WooCheckoutBlocksCest {
|
||||
private function orderProduct(\AcceptanceTester $i, string $userEmail, bool $doRegister = true, bool $doSubscribe = true): void {
|
||||
$i->addProductToCart($this->product);
|
||||
$i->amOnPage("/?p={$this->checkoutPostId}");
|
||||
|
||||
// refresh the page to fix https://github.com/woocommerce/woocommerce/issues/54805
|
||||
// this can be removed after the above is released
|
||||
// should be done in Woo 9.7 (release date 24th Feb 2025)
|
||||
$i->reloadPage();
|
||||
|
||||
$this->fillBlocksCustomerInfo($i, $userEmail);
|
||||
if ($doSubscribe) {
|
||||
$settings = (ContainerWrapper::getInstance())->get(SettingsController::class);
|
||||
|
@ -8,7 +8,6 @@ use MailPoet\Automation\Engine\Data\NextStep;
|
||||
use MailPoet\Automation\Engine\Data\Step;
|
||||
use MailPoet\Automation\Engine\Data\Subject;
|
||||
use MailPoet\Automation\Engine\Storage\AutomationRunStorage;
|
||||
use MailPoet\Automation\Engine\Storage\AutomationStorage;
|
||||
use MailPoet\Automation\Integrations\WooCommerce\Subjects\AbandonedCartSubject;
|
||||
use MailPoet\Automation\Integrations\WooCommerce\Triggers\AbandonedCart\AbandonedCartTrigger;
|
||||
use MailPoet\Cron\Workers\Automations\AbandonedCartWorker;
|
||||
@ -30,9 +29,6 @@ class AbandonedCartTriggerTest extends \MailPoetTest {
|
||||
/** @var AutomationRunStorage */
|
||||
private $automationRunStorage;
|
||||
|
||||
/** @var AutomationStorage */
|
||||
private $automationStorage;
|
||||
|
||||
/** @var int */
|
||||
private $productId;
|
||||
|
||||
@ -48,7 +44,6 @@ class AbandonedCartTriggerTest extends \MailPoetTest {
|
||||
public function _before() {
|
||||
$this->testee = $this->diContainer->get(AbandonedCartTrigger::class);
|
||||
$this->automationRunStorage = $this->diContainer->get(AutomationRunStorage::class);
|
||||
$this->automationStorage = $this->diContainer->get(AutomationStorage::class);
|
||||
$this->tasksRepository = $this->diContainer->get(ScheduledTasksRepository::class);
|
||||
$this->abandonedCartWorker = $this->diContainer->get(AbandonedCartWorker::class);
|
||||
$this->productId = $this->createProduct('abandoned cart trigger test product');
|
||||
@ -118,66 +113,6 @@ class AbandonedCartTriggerTest extends \MailPoetTest {
|
||||
$this->assertSame([$this->productId], $abandonedCartSubject->getArgs()['product_ids']);
|
||||
}
|
||||
|
||||
public function testTasksDoNotGetStuckIfAutomationDisabled() {
|
||||
$wait = 1;
|
||||
$automation = $this->createAutomation($wait);
|
||||
$this->testee->registerHooks();
|
||||
$this->assertEmpty($this->tasksRepository->findFutureScheduledByType(AbandonedCartWorker::TASK_TYPE));
|
||||
|
||||
$expectedScheduledTime = new Carbon();
|
||||
$expectedScheduledTime->addMinutes($wait);
|
||||
|
||||
// Add something to the cart.
|
||||
$this->assertIsString(WC()->cart->add_to_cart($this->productId));
|
||||
$scheduledTasks = $this->tasksRepository->findFutureScheduledByType(AbandonedCartWorker::TASK_TYPE);
|
||||
$this->assertCount(1, $scheduledTasks);
|
||||
$task = current($scheduledTasks);
|
||||
$this->assertInstanceOf(ScheduledTaskEntity::class, $task);
|
||||
|
||||
// Disable automation
|
||||
$automation->setStatus(Automation::STATUS_DEACTIVATING);
|
||||
$this->automationStorage->updateAutomation($automation);
|
||||
|
||||
// When the task gets executed, a run is created.
|
||||
$this->assertTrue($this->abandonedCartWorker->processTaskStrategy($task, 1));
|
||||
|
||||
$oneMinutePassed = $expectedScheduledTime->subMinutes($wait);
|
||||
$task->setCreatedAt($oneMinutePassed->subSeconds(60));
|
||||
$this->assertTrue($this->abandonedCartWorker->processTaskStrategy($task, 1));
|
||||
$runs = $this->automationRunStorage->getAutomationRunsForAutomation($automation);
|
||||
$this->assertCount(0, $runs);
|
||||
}
|
||||
|
||||
public function testTasksDoNotGetStuckIfAutomationSubscribersDeleted() {
|
||||
$wait = 1;
|
||||
$automation = $this->createAutomation($wait);
|
||||
$this->testee->registerHooks();
|
||||
$this->assertEmpty($this->tasksRepository->findFutureScheduledByType(AbandonedCartWorker::TASK_TYPE));
|
||||
|
||||
$expectedScheduledTime = new Carbon();
|
||||
$expectedScheduledTime->addMinutes($wait);
|
||||
|
||||
// Add something to the cart.
|
||||
$this->assertIsString(WC()->cart->add_to_cart($this->productId));
|
||||
|
||||
// Delete subscriber directly from the DB
|
||||
$subscriberTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
|
||||
$this->entityManager->getConnection()->executeStatement("TRUNCATE TABLE $subscriberTable");
|
||||
$this->entityManager->clear();
|
||||
|
||||
// Get scheduled task for abandoned cart
|
||||
$scheduledTasks = $this->tasksRepository->findFutureScheduledByType(AbandonedCartWorker::TASK_TYPE);
|
||||
$this->assertCount(1, $scheduledTasks);
|
||||
$task = current($scheduledTasks);
|
||||
$this->assertInstanceOf(ScheduledTaskEntity::class, $task);
|
||||
|
||||
$oneMinutePassed = $expectedScheduledTime->subMinutes($wait);
|
||||
$task->setCreatedAt($oneMinutePassed->subSeconds(60));
|
||||
$this->assertTrue($this->abandonedCartWorker->processTaskStrategy($task, 1));
|
||||
$runs = $this->automationRunStorage->getAutomationRunsForAutomation($automation);
|
||||
$this->assertCount(0, $runs);
|
||||
}
|
||||
|
||||
public function testItCancelsTheRunCreationWhenCartIsEmptied() {
|
||||
$wait = 1;
|
||||
$automation = $this->createAutomation($wait);
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="mailpoet_widget_icon">
|
||||
<%= source('newsletter/templates/svg/block-icons/coupon.svg') %>
|
||||
<%= source('newsletter/templates/svg/block-icons/product.svg') %>
|
||||
</div>
|
||||
<div class="mailpoet_widget_title"><%= __('Coupon') %></div>
|
||||
|
@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="31" viewBox="0 0 32 31">
|
||||
<path d="M32 9.20566L30.384 13.7177V13.7017C29.552 13.3977 28.736 13.4457 27.936 13.8297C27.136 14.2137 26.576 14.8217 26.272 15.6537C25.968 16.4857 26.016 17.3017 26.384 18.1017C26.768 18.9017 27.376 19.4457 28.224 19.7497V19.7657L26.608 24.2777L2.512 15.6697L4.096 11.2057C4.928 11.5097 5.744 11.4617 6.544 11.0777C7.344 10.7097 7.888 10.1017 8.192 9.26966C8.496 8.43766 8.448 7.62166 8.064 6.82166C7.696 6.03766 7.088 5.49366 6.256 5.18966L7.888 0.597656L32 9.20566ZM24.048 18.3097L26.24 12.2937C26.432 11.7977 26.4 11.2537 26.176 10.7737C25.952 10.2937 25.552 9.92566 25.056 9.73366L13.024 5.36566C12 4.99766 10.832 5.55766 10.464 6.54966L8.272 12.5657C7.888 13.6057 8.432 14.7577 9.456 15.1257L21.488 19.5097C21.712 19.5897 21.936 19.6377 22.176 19.6377C23.008 19.6377 23.776 19.1097 24.048 18.3097V18.3097ZM12.752 6.11766L24.768 10.4857C25.072 10.5977 25.312 10.8217 25.456 11.1097C25.584 11.3977 25.6 11.7177 25.488 12.0217L23.296 18.0377C23.088 18.6457 22.368 18.9817 21.76 18.7577L9.744 14.3737C9.12 14.1497 8.8 13.4617 9.024 12.8377L11.216 6.82166C11.376 6.35766 11.84 6.03766 12.336 6.03766C12.48 6.03766 12.608 6.06966 12.752 6.11766V6.11766ZM23.664 25.5417C24.224 25.9737 24.864 26.1977 25.584 26.1977H25.6V30.9977H0V26.2617C0.88 26.2617 1.648 25.9417 2.256 25.3177C2.88 24.7097 3.2 23.9417 3.2 23.0617C3.2 22.1817 2.88 21.4297 2.256 20.8057C1.632 20.1817 0.88 19.8617 0 19.8617V14.9977H1.68L1.232 16.2777L5.824 17.9097C5.008 18.1657 4.4 18.9017 4.4 19.7977V26.1977C4.4 27.3017 5.296 28.1977 6.4 28.1977H19.2C20.304 28.1977 21.2 27.3017 21.2 26.1977V23.3977L22.528 23.8777C22.72 24.5657 23.104 25.1257 23.664 25.5417ZM5.2 26.1977V19.7977C5.2 19.1417 5.744 18.5977 6.4 18.5977H7.728L20.4 23.1257V26.1977C20.4 26.8537 19.856 27.3977 19.2 27.3977H6.4C5.744 27.3977 5.2 26.8537 5.2 26.1977Z" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.9 KiB |
@ -1,3 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="22" viewBox="0 0 30 22">
|
||||
<path d="M29.4055 0.402344V3.60234H0.605469V0.402344H29.4055ZM2.20547 5.20234H27.8055V21.2023H2.20547V5.20234ZM19.8055 10.0023V8.40234H10.2055V10.0023H19.8055Z"/>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg viewBox="320 237.161 844.61 844.73" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M712.312 495.688c1.823-3.419 2.506-6.838 2.278-10.94-.455-5.245-2.736-9.575-7.066-12.994-4.331-3.42-9.117-5.016-14.361-4.562-6.611.458-11.627 3.65-15.044 10.033-14.134 25.757-24.165 67.475-30.092 125.374-8.661-21.883-15.956-47.644-21.655-77.961-2.508-13.449-8.661-19.83-18.691-19.147-6.839.455-12.54 5.015-17.101 13.678l-49.921 95.058c-8.206-33.052-15.956-73.401-23.022-121.045-1.595-11.856-8.206-17.326-19.833-16.411-6.383.455-11.169 2.732-14.361 7.065-3.191 4.101-4.559 9.346-3.645 15.273 13.448 85.481 25.986 143.157 37.611 173.02 4.559 10.939 9.8 16.184 15.956 15.728 9.575-.684 20.972-13.904 34.421-39.665 7.067-14.589 18.009-36.473 32.827-65.65 12.309 43.084 29.178 75.453 50.378 97.111 5.927 6.154 12.081 8.889 18.009 8.433 5.244-.455 9.345-3.191 12.083-8.206 2.278-4.332 3.191-9.349 2.733-15.045-1.366-20.743.686-49.695 6.382-86.854 5.926-38.296 13.222-65.879 22.114-82.293zM984.263 564.531c.229-17.779-3.646-32.599-10.941-44.907-8.205-14.361-20.289-23.022-36.471-26.443-4.333-.912-8.436-1.367-12.312-1.367-21.885 0-39.663 11.396-53.569 34.193-11.855 19.376-17.78 40.804-17.78 64.284 0 17.552 3.646 32.596 10.94 45.135 8.207 14.36 20.289 23.022 36.473 26.442 4.331.913 8.434 1.369 12.311 1.369 22.111 0 39.892-11.397 53.57-34.194 11.852-19.605 17.779-41.032 17.779-64.512zM944.6 582.766v.002c-3.191 15.044-8.891 26.215-17.327 33.736-6.609 5.929-12.764 8.435-18.462 7.294-5.473-1.139-10.032-5.925-13.45-14.815-2.738-7.066-4.103-14.134-4.103-20.745 0-5.697.455-11.397 1.595-16.639 2.053-9.348 5.928-18.467 12.081-27.127 7.523-11.172 15.503-15.728 23.708-14.134 5.472 1.139 10.032 5.927 13.45 14.817 2.735 7.065 4.103 14.132 4.103 20.743 0 5.928-.455 11.625-1.595 16.868zM794.148 493.181c-4.33-.912-8.433-1.367-12.311-1.367-21.883 0-39.664 11.396-53.568 34.193-11.854 19.376-17.781 40.804-17.781 64.284 0 17.552 3.647 32.596 10.941 45.135 8.206 14.36 20.289 23.022 36.472 26.442 4.333.913 8.435 1.369 12.312 1.369 22.111 0 39.892-11.397 53.569-34.194 11.855-19.605 17.78-41.032 17.78-64.512 0-17.779-3.646-32.599-10.941-44.907-8.207-14.361-20.517-23.022-36.473-26.443zm7.522 89.585v.002c-3.192 15.044-8.893 26.215-17.325 33.736-6.61 5.929-12.766 8.435-18.465 7.294-5.471-1.139-10.029-5.925-13.451-14.815-2.733-7.066-4.1-14.134-4.1-20.745 0-5.697.454-11.397 1.594-16.639 2.053-9.348 5.928-18.467 12.083-27.127 7.522-11.172 15.501-15.728 23.706-14.134 5.472 1.139 10.032 5.927 13.45 14.817 2.737 7.065 4.103 14.132 4.103 20.743.229 5.928-.456 11.625-1.595 16.868z"/>
|
||||
<path d="M 320.95 238.875 L 320.95 1080.766 L 882.21 1080.766 L 1162.84 800.135 L 1162.84 238.875 L 320.95 238.875 Z M 828.787 784.994 L 729.974 729.973 L 513.781 729.973 C 484.895 729.973 461.511 706.59 461.511 677.702 L 461.511 503.464 C 461.281 474.805 484.662 451.192 513.553 451.192 L 970.01 451.192 C 998.899 451.192 1022.282 474.576 1022.282 503.464 L 1022.282 677.701 C 1022.282 706.59 998.898 729.972 970.01 729.972 L 806.32 729.972 L 828.787 784.994 Z M 882.21 1010.608 L 882.21 800.135 L 1092.684 800.135 L 882.21 1010.608 Z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 256 B After Width: | Height: | Size: 3.1 KiB |
@ -3,7 +3,7 @@
|
||||
var mailpoet_logo_url = '<%= cdn_url('welcome-wizard/mailpoet-logo.20200623.png') %>';
|
||||
var wizard_sender_illustration_url = '<%= cdn_url('welcome-wizard/sender.20200623.png') %>';
|
||||
var wizard_tracking_illustration_url = '<%= cdn_url('welcome-wizard/tracking.20200623.png') %>';
|
||||
var wizard_woocommerce_illustration_url = '<%= cdn_url('welcome-wizard/woocommerce.20241219.png') %>';
|
||||
var wizard_woocommerce_illustration_url = '<%= cdn_url('welcome-wizard/woocommerce.20200623.png') %>';
|
||||
var wizard_MSS_pitch_illustration_url = '<%= cdn_url('welcome-wizard/illu-pitch-mss.20190912.png') %>';
|
||||
var finish_wizard_url = '<%= finish_wizard_url %>';
|
||||
var admin_email = <%= json_encode(admin_email) %>;
|
||||
|
@ -3,7 +3,7 @@
|
||||
<% block content %>
|
||||
<script>
|
||||
var mailpoet_logo_url = '<%= cdn_url('welcome-wizard/mailpoet-logo.20200623.png') %>';
|
||||
var wizard_woocommerce_illustration_url = '<%= cdn_url('welcome-wizard/woocommerce.20241219.png') %>';
|
||||
var wizard_woocommerce_illustration_url = '<%= cdn_url('welcome-wizard/woocommerce.20200623.png') %>';
|
||||
var mailpoet_show_customers_import = <%= json_encode(show_customers_import) %>;
|
||||
var finish_wizard_url = '<%= finish_wizard_url %>';
|
||||
</script>
|
||||
|
@ -4,8 +4,6 @@ This folder contains the code for the MailPoet Email Editor JS Package.
|
||||
We aim to extract the package as an independent library, so it can be used in other projects.
|
||||
As we are still in an exploration phase, we keep it together with the MailPoet codebase.
|
||||
|
||||
You can try the email editor in [the WordPress Playground](https://playground.wordpress.net/?mode=seamless#%7B%22preferredVersions%22:%7B%22php%22:%228.2%22,%22wp%22:%22latest%22%7D,%22phpExtensionBundles%22:%5B%22kitchen-sink%22%5D,%22features%22:%7B%7D,%22landingPage%22:%22/wp-admin/admin.php?page=mailpoet-newsletters%22,%22steps%22:%5B%7B%22step%22:%22login%22,%22username%22:%22admin%22,%22password%22:%22password%22%7D,%7B%22step%22:%22installPlugin%22,%22pluginData%22:%7B%22resource%22:%22url%22,%22url%22:%22https://account.mailpoet.com/playground/plugin-proxy/branch:trunk%22%7D%7D,%7B%22step%22:%22mkdir%22,%22path%22:%22wordpress/wp-content/mu-plugins%22%7D,%7B%22step%22:%22writeFile%22,%22path%22:%22wordpress/wp-content/mu-plugins/addFilter-2.php%22,%22data%22:%22%3C?php%20%5Cnuse%20MailPoet%5C%5CDI%5C%5CContainerWrapper;%5Cnuse%20MailPoet%5C%5CFeatures%5C%5CFeatureFlagsRepository;%5Cnuse%20MailPoet%5C%5CFeatures%5C%5CFeaturesController;%5Cnadd_filter('mailpoet_skip_welcome_wizard',%20'__return_true');%22%7D%5D%7D).
|
||||
|
||||
You can locate the PHP package here `packages/php/email-editor`
|
||||
|
||||
## Workflow Commands
|
||||
@ -32,15 +30,6 @@ pnpm run format # runs prettier on files. This uses
|
||||
**Renderer** – responsible for converting saved HTML from Gutenberg editor to HTML for email clients.
|
||||
**Theme Controller** – The theme controller is used to generate settings and styles for the editor. We can define which features for working with content are available in settings. The styles are also used in the Render.
|
||||
|
||||
### Dependencies
|
||||
|
||||
#### Rich-text
|
||||
|
||||
The **Personalization tags** feature relies on the `@wordpress/rich-text` package, which is included in both the Gutenberg plugin and WordPress core.
|
||||
To ensure the correct functionality of the Email Editor and its features, you must use **at least version 7.14.0** of the `@wordpress/rich-text` package.
|
||||
The required minimum version of this package is stored in the assets directory.
|
||||
If your WordPress installation does not use the Gutenberg plugin or does not include the required version, replace the existing `@wordpress/rich-text` package with the one provided in the assets directory.
|
||||
|
||||
### Email Editor
|
||||
* Bootstrapped in the plugin in the [email editor controller](https://github.com/mailpoet/mailpoet/blob/13bf305aeb29bbadd0695ee02a3735e62cc4f21f/mailpoet/lib/EmailEditor/Integrations/MailPoet/EmailEditor.php)
|
||||
* **Components folder** - basically the whole UI of the editor. Most of Gutenberg’s blocks magic happens in block-editor folder.
|
||||
@ -82,27 +71,3 @@ If your WordPress installation does not use the Gutenberg plugin or does not inc
|
||||
| Orange.fr | iOS/Android | Latest | ? | -/0.07 | No | |
|
||||
| Thunderbird | Windows, macOS, Linux | Latest | Gecko | -/0.61 | Yes | It uses bundled rendering engine so it should be enough to test on one platform |
|
||||
| Windows Mail | Windows | 10, 11 | Word | -/- | Yes | Default Client in Windows. Market share should be over 6% in desktop clients |
|
||||
|
||||
## Actions and Filters
|
||||
|
||||
These actions and filters are currently **Work-in-progress**.
|
||||
We may add, update and delete any of them.
|
||||
|
||||
**Please use with caution**.
|
||||
|
||||
### Actions
|
||||
|
||||
| Name | Argument | Description |
|
||||
|--------------------------------|--------------------|---------------------|
|
||||
| `mailpoet_email_editor_events` | `EventData.detail` | Email editor events |
|
||||
|
||||
### Filters
|
||||
|
||||
| Name | Argument | Return | Description |
|
||||
|--------------------------------------------------|---------------------------|--------------------------------------|---------------------------------------------------------------------------------------------------------------------|
|
||||
| `mailpoet_email_editor_events_tracking_enabled` | `boolean` (false-default) | `boolean` | Used to enable the email editor events tracking and collection |
|
||||
| `mailpoet_email_editor_wrap_editor_component` | `JSX.Element` Editor | `JSX.Element` Editor | The main editor component. Custom component can wrap the editor and provide additional functionality |
|
||||
| `mailpoet_email_editor_send_button_label` | `string` 'Send' | `string` 'Send' (default) | Email editor send button label. The `Send` text can be updated using this filter |
|
||||
| `mailpoet_email_editor_send_action_callback` | `function` sendAction | `function` sendAction | Action to perform when the Send button is clicked |
|
||||
| `mailpoet_email_editor_content_validation_rules` | `array` rules | `EmailContentValidationRule[]` rules | Email editor content validation rules. The validation is done on `send btton` click and revalidated on `save draft` |
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
<?php return array('dependencies' => array('wp-a11y', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-escape-html', 'wp-i18n', 'wp-keycodes'), 'version' => '6249fa45d0f64d6a0eda');
|
@ -17,7 +17,7 @@ import {
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { storeName, editorCurrentPostType } from '../../store';
|
||||
import { storeName } from '../../store';
|
||||
import { recordEvent, recordEventOnce } from '../../events';
|
||||
|
||||
// @see https://github.com/WordPress/gutenberg/blob/5e0ffdbc36cb2e967dfa6a6b812a10a2e56a598f/packages/edit-post/src/components/header/document-actions/index.js
|
||||
@ -34,7 +34,7 @@ export function CampaignName() {
|
||||
|
||||
const [ emailTitle = '', setTitle ] = useEntityProp(
|
||||
'postType',
|
||||
editorCurrentPostType,
|
||||
'mailpoet_email',
|
||||
'title'
|
||||
);
|
||||
|
||||
|
@ -27,7 +27,7 @@ import {
|
||||
* Internal dependencies
|
||||
*/
|
||||
|
||||
import { storeName, editorCurrentPostType } from '../../store';
|
||||
import { storeName } from '../../store';
|
||||
import { MoreMenu } from './more-menu';
|
||||
import { PreviewDropdown } from '../preview';
|
||||
import { SaveEmailButton } from './save-email-button';
|
||||
@ -95,7 +95,7 @@ export function Header() {
|
||||
|
||||
const { dirtyEntityRecords } = useEntitiesSavedStatesIsDirty();
|
||||
const hasNonEmailEdits = dirtyEntityRecords.some(
|
||||
( entity ) => entity.name !== editorCurrentPostType
|
||||
( entity ) => entity.name !== 'mailpoet_email'
|
||||
);
|
||||
|
||||
const preventDefault = ( event ) => {
|
||||
|
@ -13,7 +13,7 @@ import { useSelect, useDispatch } from '@wordpress/data';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { storeName, editorCurrentPostType } from '../../store';
|
||||
import { storeName } from '../../store';
|
||||
import { TrashModal } from './trash-modal';
|
||||
import { recordEvent } from '../../events';
|
||||
|
||||
@ -32,7 +32,7 @@ export function MoreMenu(): JSX.Element {
|
||||
);
|
||||
const [ status, setStatus ] = useEntityProp(
|
||||
'postType',
|
||||
editorCurrentPostType,
|
||||
'mailpoet_email',
|
||||
'status'
|
||||
);
|
||||
const { saveEditedEmail, updateEmailMailPoetProperty } =
|
||||
|
@ -10,7 +10,6 @@ import { store as noticesStore } from '@wordpress/notices';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { editorCurrentPostType } from '../../store';
|
||||
import { recordEvent } from '../../events';
|
||||
|
||||
export function TrashModal( {
|
||||
@ -32,7 +31,7 @@ export function TrashModal( {
|
||||
recordEvent( 'trash_modal_move_to_trash_button_clicked' );
|
||||
const success = await deleteEntityRecord(
|
||||
'postType',
|
||||
editorCurrentPostType,
|
||||
'mailpoet_email',
|
||||
postId as unknown as string,
|
||||
{},
|
||||
{ throwOnError: false }
|
||||
@ -42,7 +41,7 @@ export function TrashModal( {
|
||||
} else {
|
||||
const lastError = getLastEntityDeleteError(
|
||||
'postType',
|
||||
editorCurrentPostType,
|
||||
'mailpoet_email',
|
||||
postId
|
||||
);
|
||||
// Already deleted.
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
getCursorPosition,
|
||||
replacePersonalizationTagsWithHTMLComments,
|
||||
} from './rich-text-utils';
|
||||
import { storeName, editorCurrentPostType } from '../../store';
|
||||
import { storeName } from '../../store';
|
||||
import { PersonalizationTagsPopover } from './personalization-tags-popover';
|
||||
import { recordEvent, recordEventOnce } from '../../events';
|
||||
|
||||
@ -30,7 +30,7 @@ export function RichTextWithButton( {
|
||||
} ) {
|
||||
const [ mailpoetEmailData ] = useEntityProp(
|
||||
'postType',
|
||||
editorCurrentPostType,
|
||||
'mailpoet_email',
|
||||
'mailpoet_data'
|
||||
);
|
||||
|
||||
|
@ -1,20 +1,31 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { MenuGroup, MenuItem, DropdownMenu } from '@wordpress/components';
|
||||
import {
|
||||
MenuGroup,
|
||||
MenuItem,
|
||||
Button,
|
||||
DropdownMenu,
|
||||
} from '@wordpress/components';
|
||||
import { useEntityProp } from '@wordpress/core-data';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Icon, external, check, mobile, desktop } from '@wordpress/icons';
|
||||
import { PostPreviewButton } from '@wordpress/editor';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { SendPreviewEmail } from './send-preview-email';
|
||||
import { storeName } from '../../store';
|
||||
import { recordEvent } from '../../events';
|
||||
import { SendPreviewEmail } from './send-preview-email';
|
||||
|
||||
export function PreviewDropdown() {
|
||||
const [ mailpoetEmailData ] = useEntityProp(
|
||||
'postType',
|
||||
'mailpoet_email',
|
||||
'mailpoet_data'
|
||||
);
|
||||
|
||||
const previewDeviceType = useSelect(
|
||||
( select ) => select( storeName ).getDeviceType(),
|
||||
[]
|
||||
@ -22,11 +33,16 @@ export function PreviewDropdown() {
|
||||
|
||||
const { changePreviewDeviceType, togglePreviewModal } =
|
||||
useDispatch( storeName );
|
||||
const newsletterPreviewUrl: string = mailpoetEmailData?.preview_url || '';
|
||||
|
||||
const changeDeviceType = ( newDeviceType: string ) => {
|
||||
void changePreviewDeviceType( newDeviceType );
|
||||
};
|
||||
|
||||
const openInNewTab = ( url: string ) => {
|
||||
window.open( url, '_blank', 'noreferrer' );
|
||||
};
|
||||
|
||||
const deviceIcons = {
|
||||
mobile,
|
||||
desktop,
|
||||
@ -88,33 +104,29 @@ export function PreviewDropdown() {
|
||||
{ __( 'Send a test email', 'mailpoet' ) }
|
||||
</MenuItem>
|
||||
</MenuGroup>
|
||||
<MenuGroup>
|
||||
<div className="edit-post-header-preview__grouping-external">
|
||||
<PostPreviewButton
|
||||
role="menuitem"
|
||||
forceIsAutosaveable={ true }
|
||||
aria-label={ __(
|
||||
'Preview in new tab',
|
||||
'mailpoet'
|
||||
) }
|
||||
textContent={
|
||||
<>
|
||||
{ __(
|
||||
'Preview in new tab',
|
||||
'mailpoet'
|
||||
) }
|
||||
<Icon icon={ external } />
|
||||
</>
|
||||
}
|
||||
onPreview={ () => {
|
||||
recordEvent(
|
||||
'header_preview_dropdown_preview_in_new_tab_selected'
|
||||
);
|
||||
onClose();
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
</MenuGroup>
|
||||
{ newsletterPreviewUrl ? (
|
||||
<MenuGroup>
|
||||
<div className="edit-post-header-preview__grouping-external">
|
||||
<Button
|
||||
className="edit-post-header-preview__button-external components-menu-item__button"
|
||||
onClick={ () => {
|
||||
recordEvent(
|
||||
'header_preview_dropdown_preview_in_new_tab_selected'
|
||||
);
|
||||
openInNewTab(
|
||||
newsletterPreviewUrl
|
||||
);
|
||||
} }
|
||||
>
|
||||
{ __(
|
||||
'Preview in new tab',
|
||||
'mailpoet'
|
||||
) }
|
||||
<Icon icon={ external } />
|
||||
</Button>
|
||||
</div>
|
||||
</MenuGroup>
|
||||
) : null }
|
||||
</>
|
||||
) }
|
||||
</DropdownMenu>
|
||||
|
@ -22,7 +22,6 @@ import {
|
||||
MailPoetEmailData,
|
||||
SendingPreviewStatus,
|
||||
storeName,
|
||||
editorCurrentPostType,
|
||||
} from '../../store';
|
||||
import { recordEvent, recordEventOnce } from '../../events';
|
||||
|
||||
@ -44,7 +43,7 @@ function RawSendPreviewEmail() {
|
||||
|
||||
const [ mailpoetEmailData ] = useEntityProp(
|
||||
'postType',
|
||||
editorCurrentPostType,
|
||||
'mailpoet_email',
|
||||
'mailpoet_data'
|
||||
) as [ MailPoetEmailData, unknown, unknown ];
|
||||
|
||||
|
@ -10,9 +10,8 @@ import classnames from 'classnames';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { editorCurrentPostType } from '../../store';
|
||||
import { recordEvent } from '../../events';
|
||||
import { RichTextWithButton } from '../personalization-tags/rich-text-with-button';
|
||||
import { recordEvent } from '../../events';
|
||||
|
||||
const previewTextMaxLength = 150;
|
||||
const previewTextRecommendedLength = 80;
|
||||
@ -20,7 +19,7 @@ const previewTextRecommendedLength = 80;
|
||||
export function DetailsPanel() {
|
||||
const [ mailpoetEmailData ] = useEntityProp(
|
||||
'postType',
|
||||
editorCurrentPostType,
|
||||
'mailpoet_email',
|
||||
'mailpoet_data'
|
||||
);
|
||||
|
||||
|
@ -16,7 +16,6 @@ import {
|
||||
storeName,
|
||||
TemplateCategory,
|
||||
TemplatePreview,
|
||||
editorCurrentPostType,
|
||||
} from '../../store';
|
||||
import { TemplateList } from './template-list';
|
||||
import { TemplateCategoriesListSidebar } from './template-categories-list-sidebar';
|
||||
@ -60,7 +59,7 @@ function SelectTemplateBody( {
|
||||
setSelectedCategory( TemplateCategories[ 0 ].name );
|
||||
}
|
||||
}, 1000 ); // using setTimeout to ensure the template styles are available before block preview
|
||||
}, [ hasEmailPosts, hideRecentCategory ] );
|
||||
}, [ hasEmailPosts ] );
|
||||
|
||||
return (
|
||||
<div className="block-editor-block-patterns-explorer">
|
||||
@ -95,7 +94,7 @@ export function SelectTemplateModal( {
|
||||
const hasTemplates = templates?.length > 0;
|
||||
|
||||
const handleTemplateSelection = ( template: TemplatePreview ) => {
|
||||
const templateIsPostContent = template.type === editorCurrentPostType;
|
||||
const templateIsPostContent = template.type === 'mailpoet_email';
|
||||
|
||||
const postContent = template.template as unknown as EmailEditorPostType;
|
||||
|
||||
|
@ -12,7 +12,7 @@ import '@wordpress/format-library'; // Enables text formatting capabilities
|
||||
import { initBlocks } from './blocks';
|
||||
import { initializeLayout } from './layouts/flex-email';
|
||||
import { InnerEditor } from './components/block-editor';
|
||||
import { createStore, storeName, editorCurrentPostType } from './store';
|
||||
import { createStore, storeName } from './store';
|
||||
import { initHooks } from './editor-hooks';
|
||||
import { KeyboardShortcuts } from './components/keybord-shortcuts';
|
||||
import { initEventCollector } from './events';
|
||||
@ -33,7 +33,7 @@ function Editor() {
|
||||
<InnerEditor
|
||||
initialEdits={ [] }
|
||||
postId={ postId }
|
||||
postType={ editorCurrentPostType }
|
||||
postType="mailpoet_email"
|
||||
settings={ settings }
|
||||
/>
|
||||
</StrictMode>
|
||||
|
10
packages/js/email-editor/src/global.d.ts
vendored
@ -1,14 +1,18 @@
|
||||
interface Window {
|
||||
MailPoetEmailEditor: {
|
||||
json_api_root: string;
|
||||
api_token: string;
|
||||
api_version: string;
|
||||
cdn_url: string;
|
||||
is_premium_plugin_active: boolean;
|
||||
current_wp_user_email: string;
|
||||
user_theme_post_id: number;
|
||||
urls: {
|
||||
listings: string;
|
||||
send: string;
|
||||
};
|
||||
editor_settings: unknown; // Can't import type in global.d.ts. Typed in getEditorSettings() in store/settings.ts
|
||||
email_styles: unknown; // Can't import type in global.d.ts. Typed in getEmailStyles() in store/settings.ts
|
||||
editor_layout: unknown; // Can't import type in global.d.ts. Typed in getEmailLayout() in store/settings.ts
|
||||
editor_theme: unknown; // Can't import type in global.d.ts. Typed in getEditorTheme() in store/settings.ts
|
||||
current_post_type: string;
|
||||
current_post_id: string;
|
||||
};
|
||||
}
|
||||
|
@ -1,51 +1,123 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useCallback } from '@wordpress/element';
|
||||
import { useSelect, subscribe } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useCallback, useMemo } from '@wordpress/element';
|
||||
import { dispatch, useSelect, subscribe } from '@wordpress/data';
|
||||
import { store as blockEditorStore } from '@wordpress/block-editor';
|
||||
import { createBlock } from '@wordpress/blocks';
|
||||
import { store as coreDataStore } from '@wordpress/core-data';
|
||||
import { applyFilters } from '@wordpress/hooks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
EmailContentValidationRule,
|
||||
storeName as emailEditorStore,
|
||||
} from '../store';
|
||||
import { storeName as emailEditorStore } from '../store';
|
||||
import { useShallowEqual } from './use-shallow-equal';
|
||||
import { useValidationNotices } from './use-validation-notices';
|
||||
|
||||
// Shared reference to an empty array for cases where it is important to avoid
|
||||
// returning a new array reference on every invocation
|
||||
const EMPTY_ARRAY = [];
|
||||
|
||||
export type ContentValidationData = {
|
||||
isInvalid: boolean;
|
||||
validateContent: () => boolean;
|
||||
};
|
||||
|
||||
export const useContentValidation = (): ContentValidationData => {
|
||||
const { contentBlockId, hasFooter } = useSelect( ( select ) => {
|
||||
const allBlocks = select( blockEditorStore ).getBlocks();
|
||||
const noBodyBlocks = allBlocks.filter(
|
||||
( block ) =>
|
||||
block.name !== 'mailpoet/powered-by-mailpoet' &&
|
||||
block.name !== 'core/post-content'
|
||||
);
|
||||
// @ts-expect-error getBlocksByName is not defined in types
|
||||
const blocks = select( blockEditorStore ).getBlocksByName(
|
||||
'core/post-content'
|
||||
) as string[] | undefined;
|
||||
return {
|
||||
contentBlockId: blocks?.[ 0 ],
|
||||
hasFooter: noBodyBlocks.length > 0,
|
||||
};
|
||||
} );
|
||||
|
||||
const { addValidationNotice, hasValidationNotice, removeValidationNotice } =
|
||||
useValidationNotices();
|
||||
|
||||
const { editedContent, editedTemplateContent } = useSelect(
|
||||
const { editedContent, editedTemplateContent, postTemplateId } = useSelect(
|
||||
( mapSelect ) => ( {
|
||||
editedContent:
|
||||
mapSelect( emailEditorStore ).getEditedEmailContent(),
|
||||
editedTemplateContent:
|
||||
mapSelect( emailEditorStore ).getCurrentTemplateContent(),
|
||||
postTemplateId:
|
||||
mapSelect( emailEditorStore ).getCurrentTemplate()?.id,
|
||||
} )
|
||||
);
|
||||
|
||||
const rules: EmailContentValidationRule[] = applyFilters(
|
||||
'mailpoet_email_editor_content_validation_rules',
|
||||
EMPTY_ARRAY
|
||||
) as EmailContentValidationRule[];
|
||||
|
||||
const content = useShallowEqual( editedContent );
|
||||
const templateContent = useShallowEqual( editedTemplateContent );
|
||||
|
||||
const contentLink = `<a data-link-href='[mailpoet/subscription-unsubscribe-url]' contenteditable='false' style='text-decoration: underline;' class='mailpoet-email-editor__personalization-tags-link'>${ __(
|
||||
'Unsubscribe',
|
||||
'mailpoet'
|
||||
) }</a> | <a data-link-href='[mailpoet/subscription-manage-url]' contenteditable='false' style='text-decoration: underline;' class='mailpoet-email-editor__personalization-tags-link'>${ __(
|
||||
'Manage subscription',
|
||||
'mailpoet'
|
||||
) }</a>`;
|
||||
|
||||
const rules = useMemo( () => {
|
||||
const linksParagraphBlock = createBlock( 'core/paragraph', {
|
||||
align: 'center',
|
||||
fontSize: 'small',
|
||||
content: contentLink,
|
||||
} );
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'missing-unsubscribe-link',
|
||||
test: ( emailContent ) =>
|
||||
! emailContent.includes(
|
||||
'[mailpoet/subscription-unsubscribe-url]'
|
||||
),
|
||||
message: __(
|
||||
'All emails must include an "Unsubscribe" link.',
|
||||
'mailpoet'
|
||||
),
|
||||
actions: [
|
||||
{
|
||||
label: __( 'Insert link', 'mailpoet' ),
|
||||
onClick: () => {
|
||||
if ( ! hasFooter ) {
|
||||
void dispatch( blockEditorStore ).insertBlock(
|
||||
linksParagraphBlock,
|
||||
undefined,
|
||||
contentBlockId
|
||||
);
|
||||
} else {
|
||||
void dispatch( coreDataStore ).editEntityRecord(
|
||||
'postType',
|
||||
'wp_template',
|
||||
postTemplateId,
|
||||
{
|
||||
content: `
|
||||
${ editedTemplateContent }
|
||||
<!-- wp:paragraph {"align":"center","fontSize":"small"} -->
|
||||
${ contentLink }
|
||||
<!-- /wp:paragraph -->
|
||||
`,
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}, [
|
||||
contentBlockId,
|
||||
hasFooter,
|
||||
contentLink,
|
||||
postTemplateId,
|
||||
editedTemplateContent,
|
||||
] );
|
||||
|
||||
const validateContent = useCallback( (): boolean => {
|
||||
let isValid = true;
|
||||
rules.forEach( ( { id, test, message, actions } ) => {
|
||||
@ -73,7 +145,7 @@ export const useContentValidation = (): ContentValidationData => {
|
||||
return;
|
||||
}
|
||||
validateContent();
|
||||
}, coreDataStore );
|
||||
}, emailEditorStore );
|
||||
|
||||
return {
|
||||
isInvalid: hasValidationNotice(),
|
||||
|
@ -24,11 +24,7 @@ import {
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
storeName,
|
||||
mainSidebarDocumentTab,
|
||||
editorCurrentPostType,
|
||||
} from './constants';
|
||||
import { storeName, mainSidebarDocumentTab } from './constants';
|
||||
import {
|
||||
SendingPreviewStatus,
|
||||
State,
|
||||
@ -103,7 +99,7 @@ export function* saveEditedEmail() {
|
||||
|
||||
const result = yield dispatch( coreDataStore ).saveEditedEntityRecord(
|
||||
'postType',
|
||||
editorCurrentPostType,
|
||||
'mailpoet_email',
|
||||
postId,
|
||||
{ throwOnError: true }
|
||||
);
|
||||
@ -139,14 +135,14 @@ export function* updateEmailMailPoetProperty( name: string, value: string ) {
|
||||
// There can be a better way how to get the edited post data
|
||||
const editedPost = select( coreDataStore ).getEditedEntityRecord(
|
||||
'postType',
|
||||
editorCurrentPostType,
|
||||
'mailpoet_email',
|
||||
postId
|
||||
);
|
||||
// @ts-expect-error Property 'mailpoet_data' does not exist on type 'Updatable<Attachment<any>>'.
|
||||
const mailpoetData = editedPost?.mailpoet_data || {};
|
||||
yield dispatch( coreDataStore ).editEntityRecord(
|
||||
'postType',
|
||||
editorCurrentPostType,
|
||||
'mailpoet_email',
|
||||
postId,
|
||||
{
|
||||
mailpoet_data: {
|
||||
@ -163,7 +159,7 @@ export const setTemplateToPost =
|
||||
const postId = registry.select( storeName ).getEmailPostId();
|
||||
registry
|
||||
.dispatch( coreDataStore )
|
||||
.editEntityRecord( 'postType', editorCurrentPostType, postId, {
|
||||
.editEntityRecord( 'postType', 'mailpoet_email', postId, {
|
||||
template: templateSlug,
|
||||
} );
|
||||
};
|
||||
|
@ -4,11 +4,3 @@ export const mainSidebarId = 'email-editor/editor/main';
|
||||
export const mainSidebarDocumentTab = 'document';
|
||||
export const mainSidebarBlockTab = 'block';
|
||||
export const stylesSidebarId = 'email-editor/editor/styles';
|
||||
|
||||
// these values are set once on a page load, so it's fine to keep them here.
|
||||
export const editorCurrentPostType =
|
||||
window.MailPoetEmailEditor.current_post_type;
|
||||
export const editorCurrentPostId = parseInt(
|
||||
window.MailPoetEmailEditor.current_post_id,
|
||||
10
|
||||
);
|
||||
|
@ -1,12 +1,19 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { mainSidebarDocumentTab, editorCurrentPostId } from './constants';
|
||||
import { mainSidebarDocumentTab } from './constants';
|
||||
import { State } from './types';
|
||||
import { getEditorSettings, getEditorTheme, getUrls } from './settings';
|
||||
import {
|
||||
getEditorSettings,
|
||||
getCdnUrl,
|
||||
isPremiumPluginActive,
|
||||
getEditorTheme,
|
||||
getUrls,
|
||||
} from './settings';
|
||||
|
||||
export function getInitialState(): State {
|
||||
const postId = editorCurrentPostId;
|
||||
const searchParams = new URLSearchParams( window.location.search );
|
||||
const postId = parseInt( searchParams.get( 'post' ), 10 );
|
||||
return {
|
||||
inserterSidebar: {
|
||||
isOpened: false,
|
||||
@ -24,6 +31,8 @@ export function getInitialState(): State {
|
||||
globalStylesPostId: window.MailPoetEmailEditor.user_theme_post_id,
|
||||
},
|
||||
autosaveInterval: 60,
|
||||
cdnUrl: getCdnUrl(),
|
||||
isPremiumPluginActive: isPremiumPluginActive(),
|
||||
urls: getUrls(),
|
||||
preview: {
|
||||
deviceType: 'Desktop',
|
||||
|
@ -13,7 +13,7 @@ import { Post } from '@wordpress/core-data/build-types/entity-types/post';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { storeName, editorCurrentPostType } from './constants';
|
||||
import { storeName } from './constants';
|
||||
import { State, Feature, EmailTemplate, EmailEditorPostType } from './types';
|
||||
|
||||
function getContentFromEntity( entity ): string {
|
||||
@ -59,7 +59,7 @@ export const hasEdits = createRegistrySelector( ( select ) => (): boolean => {
|
||||
const postId = select( storeName ).getEmailPostId();
|
||||
return !! select( coreDataStore ).hasEditsForEntityRecord(
|
||||
'postType',
|
||||
editorCurrentPostType,
|
||||
'mailpoet_email',
|
||||
postId
|
||||
);
|
||||
} );
|
||||
@ -69,7 +69,7 @@ export const isEmailLoaded = createRegistrySelector(
|
||||
const postId = select( storeName ).getEmailPostId();
|
||||
return !! select( coreDataStore ).getEntityRecord(
|
||||
'postType',
|
||||
editorCurrentPostType,
|
||||
'mailpoet_email',
|
||||
postId
|
||||
);
|
||||
}
|
||||
@ -79,7 +79,7 @@ export const isSaving = createRegistrySelector( ( select ) => (): boolean => {
|
||||
const postId = select( storeName ).getEmailPostId();
|
||||
return !! select( coreDataStore ).isSavingEntityRecord(
|
||||
'postType',
|
||||
editorCurrentPostType,
|
||||
'mailpoet_email',
|
||||
postId
|
||||
);
|
||||
} );
|
||||
@ -89,7 +89,7 @@ export const isEmpty = createRegistrySelector( ( select ) => (): boolean => {
|
||||
|
||||
const post: EmailEditorPostType = select( coreDataStore ).getEntityRecord(
|
||||
'postType',
|
||||
editorCurrentPostType,
|
||||
'mailpoet_email',
|
||||
postId
|
||||
);
|
||||
if ( ! post ) {
|
||||
@ -111,7 +111,7 @@ export const hasEmptyContent = createRegistrySelector(
|
||||
|
||||
const post = select( coreDataStore ).getEntityRecord(
|
||||
'postType',
|
||||
editorCurrentPostType,
|
||||
'mailpoet_email',
|
||||
postId
|
||||
);
|
||||
if ( ! post ) {
|
||||
@ -130,7 +130,7 @@ export const isEmailSent = createRegistrySelector(
|
||||
|
||||
const post = select( coreDataStore ).getEntityRecord(
|
||||
'postType',
|
||||
editorCurrentPostType,
|
||||
'mailpoet_email',
|
||||
postId
|
||||
);
|
||||
if ( ! post ) {
|
||||
@ -154,7 +154,7 @@ export const getEditedEmailContent = createRegistrySelector(
|
||||
const postId = select( storeName ).getEmailPostId();
|
||||
const record = select( coreDataStore ).getEditedEntityRecord(
|
||||
'postType',
|
||||
editorCurrentPostType,
|
||||
'mailpoet_email',
|
||||
postId
|
||||
) as unknown as
|
||||
| { content: string | unknown; blocks: BlockInstance[] }
|
||||
@ -170,7 +170,7 @@ export const getEditedEmailContent = createRegistrySelector(
|
||||
export const getSentEmailEditorPosts = createRegistrySelector(
|
||||
( select ) => () =>
|
||||
select( coreDataStore )
|
||||
.getEntityRecords( 'postType', editorCurrentPostType, {
|
||||
.getEntityRecords( 'postType', 'mailpoet_email', {
|
||||
per_page: 30, // show a maximum of 30 for now
|
||||
status: 'publish,sent', // show only sent emails
|
||||
} )
|
||||
@ -290,13 +290,13 @@ export const getEmailTemplates = createRegistrySelector(
|
||||
select( coreDataStore )
|
||||
.getEntityRecords( 'postType', 'wp_template', {
|
||||
per_page: -1,
|
||||
post_type: editorCurrentPostType,
|
||||
post_type: 'mailpoet_email',
|
||||
} )
|
||||
// We still need to filter the templates because, in some cases, the API also returns custom templates
|
||||
// ignoring the post_type filter in the query
|
||||
?.filter( ( template ) =>
|
||||
// @ts-expect-error Missing property in type
|
||||
template.post_types.includes( editorCurrentPostType )
|
||||
template.post_types.includes( 'mailpoet_email' )
|
||||
)
|
||||
);
|
||||
|
||||
@ -353,6 +353,14 @@ export function getAutosaveInterval(
|
||||
return state.autosaveInterval;
|
||||
}
|
||||
|
||||
export function getCdnUrl( state: State ): State[ 'cdnUrl' ] {
|
||||
return state.cdnUrl;
|
||||
}
|
||||
|
||||
export function isPremiumPluginActive( state: State ): boolean {
|
||||
return state.isPremiumPluginActive;
|
||||
}
|
||||
|
||||
export function getTheme( state: State ): State[ 'theme' ] {
|
||||
return state.theme;
|
||||
}
|
||||
|
@ -7,6 +7,14 @@ export function getEditorSettings(): EmailEditorSettings {
|
||||
return window.MailPoetEmailEditor.editor_settings as EmailEditorSettings;
|
||||
}
|
||||
|
||||
export function getCdnUrl(): string {
|
||||
return window.MailPoetEmailEditor.cdn_url;
|
||||
}
|
||||
|
||||
export function isPremiumPluginActive(): boolean {
|
||||
return window.MailPoetEmailEditor.is_premium_plugin_active;
|
||||
}
|
||||
|
||||
export function getEditorTheme(): EmailTheme {
|
||||
return window.MailPoetEmailEditor.editor_theme as EmailTheme;
|
||||
}
|
||||
|
@ -195,7 +195,9 @@ export type State = {
|
||||
globalStylesPostId: number | null;
|
||||
};
|
||||
autosaveInterval: number;
|
||||
cdnUrl: string;
|
||||
urls: EmailEditorUrls;
|
||||
isPremiumPluginActive: boolean;
|
||||
preview: {
|
||||
deviceType: string;
|
||||
toEmail: string;
|
||||
@ -266,18 +268,6 @@ export type MailPoetEmailPostContentExtended = {
|
||||
};
|
||||
|
||||
export type EmailEditorPostType = Omit< Post, 'type' > & {
|
||||
type: string;
|
||||
type: 'mailpoet_email';
|
||||
mailpoet_data?: MailPoetEmailPostContentExtended;
|
||||
};
|
||||
|
||||
export type EmailContentValidationAction = {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export type EmailContentValidationRule = {
|
||||
id: string;
|
||||
test: ( emailContent: string ) => boolean;
|
||||
message: string;
|
||||
actions: EmailContentValidationAction[];
|
||||
};
|
||||
|
@ -18,11 +18,9 @@ declare module '@wordpress/block-editor' {
|
||||
}
|
||||
|
||||
declare module '@wordpress/editor' {
|
||||
import { ComponentType } from 'react';
|
||||
import * as editorActions from '@wordpress/editor/store/actions';
|
||||
import * as editorSelectors from '@wordpress/editor/store/selectors';
|
||||
import { StoreDescriptor as GenericStoreDescriptor } from '@wordpress/data/build-types/types';
|
||||
import { PostPreviewButton as WPPostPreviewButton } from '@wordpress/editor/components';
|
||||
|
||||
export * from '@wordpress/editor/index';
|
||||
|
||||
@ -33,15 +31,6 @@ declare module '@wordpress/editor' {
|
||||
actions: typeof editorActions;
|
||||
selectors: typeof editorSelectors;
|
||||
} >;
|
||||
|
||||
export const PostPreviewButton: ComponentType<
|
||||
WPPostPreviewButton.Props & {
|
||||
className?: string;
|
||||
role?: string;
|
||||
textContent: JSX.Element;
|
||||
onPreview: () => void;
|
||||
}
|
||||
>;
|
||||
}
|
||||
|
||||
// there are no @types/wordpress__interface yet
|
||||
|
@ -64,16 +64,21 @@ We may add, update and delete any of them.
|
||||
|
||||
### Filters
|
||||
|
||||
| Name | Argument | Return | Description |
|
||||
|-----------------------------------------------|-------------------------------------------|--------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `mailpoet_email_editor_post_types` | `Array` $postTypes | `Array` EmailPostType | Applied to the list of post types used by the `getPostTypes` method |
|
||||
| `mailpoet_email_editor_theme_json` | `WP_Theme_JSON` $coreThemeData | `WP_Theme_JSON` $themeJson | Applied to the theme json data. This theme json data is created from the merging of the `WP_Theme_JSON_Resolver::get_core_data` and MailPoet owns `theme.json` file |
|
||||
| `mailpoet_email_renderer_styles` | `string` $templateStyles, `WP_Post` $post | `string` $templateStyles | Applied to the email editor template styles. |
|
||||
| `mailpoet_blocks_renderer_parsed_blocks` | `WP_Block_Parser_Block[]` $output | `WP_Block_Parser_Block[]` $output | Applied to the result of parsed blocks created by the BlocksParser. |
|
||||
| `mailpoet_email_content_renderer_styles` | `string` $contentStyles, `WP_Post` $post | `string` $contentStyles | Applied to the inline content styles prior to use by the CSS Inliner. |
|
||||
| `mailpoet_is_email_editor_page` | `boolean` $isEditorPage | `boolean` | Check current page is the email editor page |
|
||||
| `mailpoet_email_editor_send_preview_email` | `Array` $postData | `boolean` Result of processing. Was email sent successfully? | Allows override of the send preview mail function. Folks may choose to use custom implementation |
|
||||
| `mailpoet_email_editor_post_sent_status_args` | `Array` `sent` post status args | `Array` register_post_status args | Allows update of the argument for the sent post status |
|
||||
| Name | Argument | Return | Description |
|
||||
|--------------------------------------------|-------------------------------------------|--------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `mailpoet_email_editor_post_types` | `Array` $postTypes | `Array` EmailPostType | Applied to the list of post types used by the `getPostTypes` method |
|
||||
| `mailpoet_email_editor_theme_json` | `WP_Theme_JSON` $coreThemeData | `WP_Theme_JSON` $themeJson | Applied to the theme json data. This theme json data is created from the merging of the `WP_Theme_JSON_Resolver::get_core_data` and MailPoet owns `theme.json` file |
|
||||
| `mailpoet_email_renderer_styles` | `string` $templateStyles, `WP_Post` $post | `string` $templateStyles | Applied to the email editor template styles. |
|
||||
| `mailpoet_blocks_renderer_parsed_blocks` | `WP_Block_Parser_Block[]` $output | `WP_Block_Parser_Block[]` $output | Applied to the result of parsed blocks created by the BlocksParser. |
|
||||
| `mailpoet_email_content_renderer_styles` | `string` $contentStyles, `WP_Post` $post | `string` $contentStyles | Applied to the inline content styles prior to use by the CSS Inliner. |
|
||||
| `mailpoet_is_email_editor_page` | `boolean` $isEditorPage | `boolean` | Check current page is the email editor page |
|
||||
| `mailpoet_email_editor_send_preview_email` | `Array` $postData | `boolean` Result of processing. Was email sent successfully? | Allows override of the send preview mail function. Folks may choose to use custom implementation |
|
||||
|
||||
## TODO
|
||||
- The editor may not start if the URL starts with just `wp-admin/post-new.php?post_type=mailpoet_email` without the post id.
|
||||
- Most of the email editor logic heavily depends on the `mailpoet_email` post-type. This is a known issue and we are working to fix it.
|
||||
- We use `mailpoet_data` in some section of the codebase. This will be updated.
|
||||
- Native email editor implementation for the `preview_url`.
|
||||
- Fix the use of MailPoet vendor packages in the Email editor. We currently use `Emogrifier` and `Html2Text`.
|
||||
- We currently support post editing context (a post has to be created before we can use the editor). We need to add support for creation context.
|
||||
- The content validation in the editor looks for the unsubscribe link tag, which is registered in the MailPoet plugin. We need either introduce generic unsubscribe link tag or move the validation to the MailPoet plugin.
|
||||
|
@ -5,7 +5,10 @@
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
],
|
||||
"files": [
|
||||
"src/exceptions.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"classmap": [
|
||||
@ -13,8 +16,7 @@
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.4",
|
||||
"soundasleep/html2text": "^2.1"
|
||||
"php": ">=7.4"
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
@ -26,7 +28,7 @@
|
||||
"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",
|
||||
"phpstan": "php ./tasks/run-phpstan.php"
|
||||
"code-style-fix": "../../../mailpoet/tasks/code_sniffer/vendor/bin/phpcbf -p",
|
||||
"phpstan": "php ./tasks/run-phpstan.php"
|
||||
}
|
||||
}
|
||||
|
64
packages/php/email-editor/composer.lock
generated
@ -4,74 +4,18 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "371847deda16bb5e4bcb84b00459bce6",
|
||||
"packages": [
|
||||
{
|
||||
"name": "soundasleep/html2text",
|
||||
"version": "2.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/soundasleep/html2text.git",
|
||||
"reference": "83502b6f8f1aaef8e2e238897199d64f284b4af3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/soundasleep/html2text/zipball/83502b6f8f1aaef8e2e238897199d64f284b4af3",
|
||||
"reference": "83502b6f8f1aaef8e2e238897199d64f284b4af3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-libxml": "*",
|
||||
"php": "^7.3|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.9",
|
||||
"phpunit/phpunit": "^7.0|^8.0|^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Soundasleep\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jevon Wright",
|
||||
"homepage": "https://jevon.org",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "A PHP script to convert HTML into a plain text format",
|
||||
"homepage": "https://github.com/soundasleep/html2text",
|
||||
"keywords": [
|
||||
"email",
|
||||
"html",
|
||||
"php",
|
||||
"text"
|
||||
],
|
||||
"support": {
|
||||
"email": "support@jevon.org",
|
||||
"issues": "https://github.com/soundasleep/html2text/issues",
|
||||
"source": "https://github.com/soundasleep/html2text/tree/2.1.0"
|
||||
},
|
||||
"time": "2023-01-06T09:28:15+00:00"
|
||||
}
|
||||
],
|
||||
"content-hash": "6f2d8c8e0bf39d9c6f33dc9e7d7538b8",
|
||||
"packages": [],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {},
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=7.4"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"platform-dev": [],
|
||||
"platform-overrides": {
|
||||
"php": "7.4.0"
|
||||
},
|
||||
|
@ -8,9 +8,9 @@
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer\ContentRenderer;
|
||||
|
||||
use MailPoet\EmailEditor\Engine\Renderer\Css_Inliner;
|
||||
use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
use MailPoet\EmailEditor\Engine\Theme_Controller;
|
||||
use MailPoetVendor\Pelago\Emogrifier\CssInliner;
|
||||
use WP_Block_Template;
|
||||
use WP_Post;
|
||||
|
||||
@ -48,34 +48,24 @@ class Content_Renderer {
|
||||
|
||||
const CONTENT_STYLES_FILE = 'content.css';
|
||||
|
||||
/**
|
||||
* CSS inliner
|
||||
*
|
||||
* @var Css_Inliner
|
||||
*/
|
||||
private Css_Inliner $css_inliner;
|
||||
|
||||
/**
|
||||
* Content_Renderer constructor.
|
||||
*
|
||||
* @param Process_Manager $preprocess_manager Preprocess manager.
|
||||
* @param Blocks_Registry $blocks_registry Blocks registry.
|
||||
* @param Settings_Controller $settings_controller Settings controller.
|
||||
* @param Css_Inliner $css_inliner Css inliner.
|
||||
* @param Theme_Controller $theme_controller Theme controller.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -233,6 +223,6 @@ class Content_Renderer {
|
||||
|
||||
$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();
|
||||
return CssInliner::fromHtml( $styles . $html )->inlineCss()->render(); // TODO: Install CssInliner.
|
||||
}
|
||||
}
|
||||
|
@ -8,13 +8,13 @@
|
||||
declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer;
|
||||
|
||||
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 MailPoetVendor\Html2Text\Html2Text;
|
||||
use MailPoetVendor\Pelago\Emogrifier\CssInliner;
|
||||
use WP_Style_Engine;
|
||||
use WP_Theme_JSON;
|
||||
|
||||
/**
|
||||
* Class Renderer
|
||||
@ -41,35 +41,24 @@ class Renderer {
|
||||
*/
|
||||
private Templates $templates;
|
||||
|
||||
/**
|
||||
* Css inliner
|
||||
*
|
||||
* @var Css_Inliner
|
||||
*/
|
||||
private Css_Inliner $css_inliner;
|
||||
|
||||
const TEMPLATE_FILE = 'template-canvas.php';
|
||||
const TEMPLATE_STYLES_FILE = 'template-canvas.css';
|
||||
|
||||
|
||||
/**
|
||||
* Renderer constructor.
|
||||
*
|
||||
* @param Content_Renderer $content_renderer Content renderer.
|
||||
* @param Templates $templates Templates.
|
||||
* @param Css_Inliner $css_inliner CSS Inliner.
|
||||
* @param Theme_Controller $theme_controller Theme controller.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,7 +123,7 @@ class Renderer {
|
||||
* @return string
|
||||
*/
|
||||
private function inline_css_styles( $template ) {
|
||||
return $this->css_inliner->from_html( $template )->inline_css()->render();
|
||||
return CssInliner::fromHtml( $template )->inlineCss()->render(); // TODO: Install CssInliner.
|
||||
}
|
||||
|
||||
/**
|
||||
@ -145,8 +134,8 @@ class Renderer {
|
||||
*/
|
||||
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 ) {
|
||||
$result = Html2Text::convert( $template ); // TODO: Install Html2Text.
|
||||
if ( false === $result ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
@ -1,35 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the MailPoet Email Editor package.
|
||||
*
|
||||
* @package MailPoet\EmailEditor
|
||||
*/
|
||||
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer;
|
||||
|
||||
interface Css_Inliner {
|
||||
/**
|
||||
* Builds a new instance from the given HTML.
|
||||
*
|
||||
* @param string $unprocessed_html raw HTML, must be UTF-encoded, must not be empty.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function from_html( string $unprocessed_html ): self;
|
||||
|
||||
/**
|
||||
* Inlines the given CSS into the existing HTML.
|
||||
*
|
||||
* @param string $css the CSS to inline, must be UTF-8-encoded.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function inline_css( string $css = '' ): self;
|
||||
|
||||
/**
|
||||
* Renders the normalized and processed HTML.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render(): string;
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the MailPoet Email Editor package.
|
||||
*
|
||||
* @package MailPoet\EmailEditor
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\EmailEditor\Engine\Templates;
|
||||
|
||||
/**
|
||||
* The class represents a template
|
||||
*/
|
||||
class Template {
|
||||
/**
|
||||
* Plugin uri used in the template name.
|
||||
*
|
||||
* @var string $plugin_uri
|
||||
*/
|
||||
private string $plugin_uri;
|
||||
/**
|
||||
* The template slug used in the template name.
|
||||
*
|
||||
* @var string $slug
|
||||
*/
|
||||
private string $slug;
|
||||
/**
|
||||
* The template name used for block template registration.
|
||||
*
|
||||
* @var string $name
|
||||
*/
|
||||
private string $name;
|
||||
/**
|
||||
* The template title.
|
||||
*
|
||||
* @var string $title
|
||||
*/
|
||||
private string $title;
|
||||
/**
|
||||
* The template description.
|
||||
*
|
||||
* @var string $description
|
||||
*/
|
||||
private string $description;
|
||||
/**
|
||||
* The template content.
|
||||
*
|
||||
* @var string $content
|
||||
*/
|
||||
private string $content;
|
||||
/**
|
||||
* The list of supoorted post types.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private array $post_types;
|
||||
|
||||
/**
|
||||
* Constructor of the class.
|
||||
*
|
||||
* @param string $plugin_uri The plugin uri.
|
||||
* @param string $slug The template slug.
|
||||
* @param string $title The template title.
|
||||
* @param string $description The template description.
|
||||
* @param string $content The template content.
|
||||
* @param string[] $post_types The list of post types supported by the template.
|
||||
*/
|
||||
public function __construct(
|
||||
string $plugin_uri,
|
||||
string $slug,
|
||||
string $title,
|
||||
string $description,
|
||||
string $content,
|
||||
array $post_types = array()
|
||||
) {
|
||||
$this->plugin_uri = $plugin_uri;
|
||||
$this->slug = $slug;
|
||||
$this->name = "{$plugin_uri}//{$slug}"; // The template name is composed from the namespace and the slug.
|
||||
$this->title = $title;
|
||||
$this->description = $description;
|
||||
$this->content = $content;
|
||||
$this->post_types = $post_types;
|
||||
}
|
||||
/**
|
||||
* Get the plugin uri.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_pluginuri(): string {
|
||||
return $this->plugin_uri;
|
||||
}
|
||||
/**
|
||||
* Get the template slug.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_slug(): string {
|
||||
return $this->slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template name composed from the plugin_uri and the slug.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return $this->name;
|
||||
}
|
||||
/**
|
||||
* Get the template title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title(): string {
|
||||
return $this->title;
|
||||
}
|
||||
/**
|
||||
* Get the template description.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_description(): string {
|
||||
return $this->description;
|
||||
}
|
||||
/**
|
||||
* Get the template content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content(): string {
|
||||
return $this->content;
|
||||
}
|
||||
/**
|
||||
* Get the list of supported post types.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_post_types(): array {
|
||||
return $this->post_types;
|
||||
}
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the MailPoet Email Editor package.
|
||||
*
|
||||
* @package MailPoet\EmailEditor
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\EmailEditor\Engine\Templates;
|
||||
|
||||
/**
|
||||
* Registry for email templates.
|
||||
*/
|
||||
class Templates_Registry {
|
||||
/**
|
||||
* List of registered templates.
|
||||
*
|
||||
* @var Template[]
|
||||
*/
|
||||
private $templates = array();
|
||||
|
||||
/**
|
||||
* Initialize the template registry.
|
||||
* This method should be called only once.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize(): void {
|
||||
apply_filters( 'mailpoet_email_editor_register_templates', $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a template instance in the registry.
|
||||
*
|
||||
* @param Template $template The template to register.
|
||||
* @return void
|
||||
*/
|
||||
public function register( Template $template ): 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;
|
||||
}
|
||||
|
||||
if ( ! \WP_Block_Templates_Registry::get_instance()->is_registered( $template->get_name() ) ) {
|
||||
// skip registration if the template was already registered.
|
||||
register_block_template(
|
||||
$template->get_name(),
|
||||
array(
|
||||
'title' => $template->get_title(),
|
||||
'description' => $template->get_description(),
|
||||
'content' => $template->get_content(),
|
||||
'post_types' => $template->get_post_types(),
|
||||
)
|
||||
);
|
||||
$this->templates[ $template->get_name() ] = $template;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a template by its name.
|
||||
* Example: get_by_name( 'mailpoet//email-general' ) will return the instance of Template with identical name.
|
||||
*
|
||||
* @param string $name The name of the template.
|
||||
* @return Template|null The template object or null if not found.
|
||||
*/
|
||||
public function get_by_name( string $name ): ?Template {
|
||||
return $this->templates[ $name ] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a template by its slug.
|
||||
* Example: get_by_slug( 'email-general' ) will return the instance of Template with identical slug.
|
||||
*
|
||||
* @param string $slug The slug of the template.
|
||||
* @return Template|null The template object or null if not found.
|
||||
*/
|
||||
public function get_by_slug( string $slug ): ?Template {
|
||||
foreach ( $this->templates as $template ) {
|
||||
if ( $template->get_slug() === $slug ) {
|
||||
return $template;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all registered templates.
|
||||
*
|
||||
* @return array List of all registered templates.
|
||||
*/
|
||||
public function get_all() {
|
||||
return $this->templates;
|
||||
}
|
||||
}
|
@ -33,21 +33,6 @@ class Templates {
|
||||
* @var string $template_directory
|
||||
*/
|
||||
private string $template_directory = __DIR__ . DIRECTORY_SEPARATOR;
|
||||
/**
|
||||
* The templates registry.
|
||||
*
|
||||
* @var Templates_Registry $templates_registry
|
||||
*/
|
||||
private Templates_Registry $templates_registry;
|
||||
|
||||
/**
|
||||
* Constructor of the class.
|
||||
*
|
||||
* @param Templates_Registry $templates_registry The templates registry.
|
||||
*/
|
||||
public function __construct( Templates_Registry $templates_registry ) {
|
||||
$this->templates_registry = $templates_registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the class.
|
||||
@ -57,8 +42,7 @@ class Templates {
|
||||
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.
|
||||
add_filter( 'mailpoet_email_editor_register_templates', array( $this, 'register_templates' ) );
|
||||
$this->templates_registry->initialize();
|
||||
$this->register_templates();
|
||||
$this->register_post_types_to_api();
|
||||
}
|
||||
|
||||
@ -76,26 +60,36 @@ class Templates {
|
||||
|
||||
/**
|
||||
* Register the templates via register_block_template
|
||||
*
|
||||
* @param Templates_Registry $templates_registry The templates registry.
|
||||
*/
|
||||
public function register_templates( Templates_Registry $templates_registry ): Templates_Registry {
|
||||
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_slug = 'email-general';
|
||||
$template_filename = $general_email_slug . '.html';
|
||||
|
||||
$general_email = new Template(
|
||||
$this->template_prefix,
|
||||
$general_email_slug,
|
||||
__( 'General Email', 'mailpoet' ),
|
||||
__( 'A general template for emails.', 'mailpoet' ),
|
||||
(string) file_get_contents( $this->template_directory . $template_filename ),
|
||||
$this->post_types
|
||||
$general_email = array(
|
||||
'title' => __( 'General Email', 'mailpoet' ),
|
||||
'description' => __( 'A general template for emails.', 'mailpoet' ),
|
||||
'slug' => 'email-general',
|
||||
);
|
||||
$template_filename = $general_email['slug'] . '.html';
|
||||
|
||||
$templates_registry->register( $general_email );
|
||||
$template_name = $this->template_prefix . '//' . $general_email['slug'];
|
||||
|
||||
return $templates_registry;
|
||||
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' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,11 +123,9 @@ class Templates {
|
||||
* @return array
|
||||
*/
|
||||
public function get_post_types( $response_object ): array {
|
||||
$template = $this->templates_registry->get_by_slug( $response_object['slug'] ?? '' );
|
||||
if ( $template ) {
|
||||
return $template->get_post_types();
|
||||
if ( isset( $response_object['plugin'] ) && $response_object['plugin'] === $this->template_prefix ) {
|
||||
return $this->post_types;
|
||||
}
|
||||
|
||||
return $response_object['post_types'] ?? array();
|
||||
}
|
||||
|
||||
|
@ -1,13 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the MailPoet Email Editor package.
|
||||
* Template canvas file to render the emails custom post type.
|
||||
*
|
||||
* @package MailPoet\EmailEditor
|
||||
*/
|
||||
|
||||
// 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;
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the MailPoet Email Editor package.
|
||||
* This file is part of the MailPoet plugin.
|
||||
*
|
||||
* @package MailPoet\EmailEditor
|
||||
*/
|
||||
@ -41,6 +41,13 @@ class Email_Editor {
|
||||
* @var Patterns Patterns.
|
||||
*/
|
||||
private Patterns $patterns;
|
||||
/**
|
||||
* Property for the settings controller.
|
||||
*
|
||||
* @var Settings_Controller Settings controller.
|
||||
*/
|
||||
private Settings_Controller $settings_controller;
|
||||
|
||||
/**
|
||||
* Property for the send preview email controller.
|
||||
*
|
||||
@ -61,6 +68,7 @@ class Email_Editor {
|
||||
* @param Email_Api_Controller $email_api_controller Email API controller.
|
||||
* @param Templates $templates Templates.
|
||||
* @param Patterns $patterns Patterns.
|
||||
* @param Settings_Controller $settings_controller Settings controller.
|
||||
* @param Send_Preview_Email $send_preview_email Preview email controller.
|
||||
* @param Personalization_Tags_Registry $personalization_tags_controller Personalization tags registry that allows initializing personalization tags.
|
||||
*/
|
||||
@ -68,12 +76,14 @@ class Email_Editor {
|
||||
Email_Api_Controller $email_api_controller,
|
||||
Templates $templates,
|
||||
Patterns $patterns,
|
||||
Settings_Controller $settings_controller,
|
||||
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->settings_controller = $settings_controller;
|
||||
$this->send_preview_email = $send_preview_email;
|
||||
$this->personalization_tags_registry = $personalization_tags_controller;
|
||||
}
|
||||
@ -89,15 +99,15 @@ class Email_Editor {
|
||||
$this->register_block_patterns();
|
||||
$this->register_email_post_types();
|
||||
$this->register_block_templates();
|
||||
$this->register_email_post_sent_status();
|
||||
$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();
|
||||
$this->settings_controller->init();
|
||||
}
|
||||
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' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -174,7 +184,6 @@ class Email_Editor {
|
||||
'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.
|
||||
);
|
||||
}
|
||||
|
||||
@ -183,19 +192,16 @@ class Email_Editor {
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function register_email_post_sent_status(): void {
|
||||
$default_args = 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,
|
||||
'private' => true, // required by the preview in new tab feature for sent post (newsletter). Posts are only visible to site admins and editors.
|
||||
);
|
||||
$args = apply_filters( 'mailpoet_email_editor_post_sent_status_args', $default_args );
|
||||
private function register_email_post_send_status(): void {
|
||||
register_post_status(
|
||||
'sent',
|
||||
$args
|
||||
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,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -261,50 +267,4 @@ class Email_Editor {
|
||||
}
|
||||
return $theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current post object
|
||||
*
|
||||
* @return array|mixed|WP_Post|null
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a custom page template for the email editor frontend rendering.
|
||||
*
|
||||
* @param string $template post template.
|
||||
* @return string
|
||||
*/
|
||||
public function load_email_preview_template( $template ) {
|
||||
$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';
|
||||
}
|
||||
}
|
||||
|
@ -28,25 +28,15 @@ class Send_Preview_Email {
|
||||
*/
|
||||
private Renderer $renderer;
|
||||
|
||||
/**
|
||||
* Instance of the Personalizer class used for rendering personalization tags.
|
||||
*
|
||||
* @var Personalizer $personalizer
|
||||
*/
|
||||
private Personalizer $personalizer;
|
||||
|
||||
/**
|
||||
* Send_Preview_Email constructor.
|
||||
*
|
||||
* @param Renderer $renderer renderer instance.
|
||||
* @param Personalizer $personalizer personalizer instance.
|
||||
* @param Renderer $renderer renderer instance.
|
||||
*/
|
||||
public function __construct(
|
||||
Renderer $renderer,
|
||||
Personalizer $personalizer
|
||||
Renderer $renderer
|
||||
) {
|
||||
$this->renderer = $renderer;
|
||||
$this->personalizer = $personalizer;
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -68,21 +58,8 @@ class Send_Preview_Email {
|
||||
$email = $data['email'];
|
||||
$post_id = $data['postId'];
|
||||
|
||||
$post = $this->fetch_post( $post_id );
|
||||
$subject = $post->post_title;
|
||||
$post = $this->fetch_post( $post_id );
|
||||
|
||||
$email_html_content = $this->render_html( $post );
|
||||
|
||||
return $this->send_email( $email, $subject, $email_html_content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the HTML content of the post
|
||||
*
|
||||
* @param \WP_Post $post The WordPress post object.
|
||||
* @return string
|
||||
*/
|
||||
public function render_html( $post ): string {
|
||||
$subject = $post->post_title;
|
||||
$language = get_bloginfo( 'language' );
|
||||
|
||||
@ -93,26 +70,9 @@ class Send_Preview_Email {
|
||||
$language
|
||||
);
|
||||
|
||||
return $this->set_personalize_content( $rendered_data['html'] );
|
||||
}
|
||||
$email_html_content = $rendered_data['html'];
|
||||
|
||||
/**
|
||||
* Personalize the content.
|
||||
*
|
||||
* @param string $content HTML content.
|
||||
* @return string
|
||||
*/
|
||||
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 );
|
||||
return $this->send_email( $email, $subject, $email_html_content );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,14 +56,40 @@ class Settings_Controller {
|
||||
$this->theme_controller = $theme_controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to initialize the settings controller.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init(): void {
|
||||
/*
|
||||
* We need to initialize these assets early because they are read from global variables $wp_styles and $wp_scripts
|
||||
* and in later WordPress page load pages they contain stuff we don't want (e.g. html for admin login popup)
|
||||
* in the post editor this is called directly in post.php.
|
||||
*/
|
||||
$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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the settings for the email editor.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_settings(): array {
|
||||
$this->init_iframe_assets();
|
||||
|
||||
$core_default_settings = \get_default_block_editor_settings();
|
||||
$theme_settings = $this->theme_controller->get_settings();
|
||||
|
||||
@ -189,31 +215,4 @@ class Settings_Controller {
|
||||
public function translate_slug_to_color( string $color_slug ): string {
|
||||
return $this->theme_controller->translate_slug_to_color( $color_slug );
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to initialize iframe assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ declare(strict_types = 1);
|
||||
namespace MailPoet\EmailEditor\Engine\Renderer;
|
||||
|
||||
use MailPoet\EmailEditor\Engine\Email_Editor;
|
||||
use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
use MailPoet\EmailEditor\Engine\Templates\Templates;
|
||||
use MailPoet\EmailEditor\Engine\Templates\Utils;
|
||||
use MailPoet\EmailEditor\Engine\Theme_Controller;
|
||||
|
||||
@ -85,12 +87,11 @@ class Renderer_Test extends \MailPoetTest {
|
||||
'Subject',
|
||||
'Preheader content',
|
||||
'en',
|
||||
'<meta name="robots" content="noindex, nofollow" />'
|
||||
'noindex,nofollow'
|
||||
);
|
||||
|
||||
verify( $rendered['html'] )->stringContainsString( 'Subject' );
|
||||
verify( $rendered['html'] )->stringContainsString( 'Preheader content' );
|
||||
verify( $rendered['html'] )->stringContainsString( 'noindex, nofollow' );
|
||||
verify( $rendered['html'] )->stringContainsString( 'noindex,nofollow' );
|
||||
verify( $rendered['html'] )->stringContainsString( 'Hello!' );
|
||||
|
||||
verify( $rendered['text'] )->stringContainsString( 'Preheader content' );
|
||||
|
@ -60,11 +60,8 @@ class Send_Preview_Email_Test extends \MailPoetTest {
|
||||
$spe = $this->make(
|
||||
Send_Preview_Email::class,
|
||||
array(
|
||||
'renderer' => $this->renderer_mock,
|
||||
'send_email' => Expected::once( true ),
|
||||
'set_personalize_content' => function ( $param ) {
|
||||
return $param;
|
||||
},
|
||||
'renderer' => $this->renderer_mock,
|
||||
'send_email' => Expected::once( true ),
|
||||
)
|
||||
);
|
||||
|
||||
@ -94,11 +91,8 @@ class Send_Preview_Email_Test extends \MailPoetTest {
|
||||
$spe = $this->make(
|
||||
Send_Preview_Email::class,
|
||||
array(
|
||||
'renderer' => $this->renderer_mock,
|
||||
'send_email' => Expected::once( $mailing_status ),
|
||||
'set_personalize_content' => function ( $param ) {
|
||||
return $param;
|
||||
},
|
||||
'renderer' => $this->renderer_mock,
|
||||
'send_email' => Expected::once( $mailing_status ),
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -51,11 +51,10 @@ class Templates_Test extends \MailPoetTest {
|
||||
*/
|
||||
public function testItTriggersActionForRegisteringTemplates(): void {
|
||||
$trigger_check = false;
|
||||
add_filter(
|
||||
add_action(
|
||||
'mailpoet_email_editor_register_templates',
|
||||
function ( $registry ) use ( &$trigger_check ) {
|
||||
function () use ( &$trigger_check ) {
|
||||
$trigger_check = true;
|
||||
return $registry;
|
||||
}
|
||||
);
|
||||
$this->templates->initialize( array( 'mailpoet_email' ) );
|
||||
|
@ -71,13 +71,14 @@ class Renderer_Test extends \MailPoetTest {
|
||||
* Test it inlines heading font size
|
||||
*/
|
||||
public function testItInlinesHeadingFontSize() {
|
||||
$email_post = $this->tester->create_post(
|
||||
$email_post = $this->tester->create_post(
|
||||
array(
|
||||
'post_content' => '<!-- wp:heading {"level":1,"style":{"typography":{"fontSize":"large"}}} --><h1 class="wp-block-heading">Hello</h1><!-- /wp:heading -->',
|
||||
)
|
||||
);
|
||||
$rendered = $this->renderer->render( $email_post, 'Subject', '', 'en' );
|
||||
verify( $rendered['text'] )->stringContainsString( 'Hello' );
|
||||
$rendered = $this->renderer->render( $email_post, 'Subject', '', 'en' );
|
||||
$heading_html = $this->extractBlockHtml( $rendered['html'], 'wp-block-heading', 'h1' );
|
||||
verify( $heading_html )->stringContainsString( 'font-size: 42px' ); // large is 42px in theme.json.
|
||||
}
|
||||
|
||||
/**
|
||||
@ -150,23 +151,6 @@ class Renderer_Test extends \MailPoetTest {
|
||||
verify( $style )->stringContainsString( 'background-color: #000' ); // black is #000.
|
||||
}
|
||||
|
||||
/**
|
||||
* Test it renders text version
|
||||
*/
|
||||
public function testItRendersTextVersion() {
|
||||
$email_post = $this->tester->create_post(
|
||||
array(
|
||||
'post_content' => '<!-- wp:columns {"backgroundColor":"vivid-green-cyan", "textColor":"black"} -->
|
||||
<div class="wp-block-columns has-black-background-color has-luminous-vivid-orange-color"><!-- wp:column --><!-- /wp:column --></div>
|
||||
<!-- /wp:columns -->',
|
||||
)
|
||||
);
|
||||
$rendered = $this->renderer->render( $email_post, 'Subject', '', 'en' );
|
||||
$style = $this->extractBlockStyle( $rendered['html'], 'wp-block-columns', 'table' );
|
||||
verify( $style )->stringContainsString( 'color: #ff6900' ); // luminous-vivid-orange is #ff6900.
|
||||
verify( $style )->stringContainsString( 'background-color: #000' ); // black is #000.
|
||||
}
|
||||
|
||||
/**
|
||||
* Test it inlines column colors
|
||||
*/
|
||||
|
@ -14,7 +14,6 @@ use MailPoet\EmailEditor\Engine\Email_Api_Controller;
|
||||
use MailPoet\EmailEditor\Engine\Email_Editor;
|
||||
use MailPoet\EmailEditor\Engine\Patterns\Patterns;
|
||||
use MailPoet\EmailEditor\Engine\PersonalizationTags\Personalization_Tags_Registry;
|
||||
use MailPoet\EmailEditor\Engine\Personalizer;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Blocks_Registry;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Content_Renderer;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Postprocessors\Highlighting_Postprocessor;
|
||||
@ -25,14 +24,13 @@ use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors\Spacing_P
|
||||
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors\Typography_Preprocessor;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Process_Manager;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\Renderer;
|
||||
use MailPoet\EmailEditor\Engine\Send_Preview_Email;
|
||||
use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
use MailPoet\EmailEditor\Engine\Templates\Templates;
|
||||
use MailPoet\EmailEditor\Engine\Templates\Templates_Registry;
|
||||
use MailPoet\EmailEditor\Engine\Theme_Controller;
|
||||
use MailPoet\EmailEditor\Engine\User_Theme;
|
||||
use MailPoet\EmailEditor\Integrations\Core\Initializer;
|
||||
use MailPoet\EmailEditor\Integrations\MailPoet\Blocks\BlockTypesController;
|
||||
use MailPoet\EmailEditor\Engine\Send_Preview_Email;
|
||||
|
||||
if ( (bool) getenv( 'MULTISITE' ) === true ) {
|
||||
// REQUEST_URI needs to be set for WP to load the proper subsite where MailPoet is activated.
|
||||
@ -51,8 +49,6 @@ $console = new \Codeception\Lib\Console\Output( array() );
|
||||
$console->writeln( 'Loading WP core... (' . $wp_load_file . ')' );
|
||||
require_once $wp_load_file;
|
||||
|
||||
require_once __DIR__ . '/../../../../../mailpoet/lib/EmailEditor/Integrations/MailPoet/MailPoetCssInliner.php';
|
||||
|
||||
/**
|
||||
* Base class for MailPoet tests.
|
||||
*
|
||||
@ -188,16 +184,10 @@ abstract class MailPoetTest extends \Codeception\TestCase\Test { // phpcs:ignore
|
||||
return new Settings_Controller( $container->get( Theme_Controller::class ) );
|
||||
}
|
||||
);
|
||||
$container->set(
|
||||
Templates_Registry::class,
|
||||
function () {
|
||||
return new Templates_Registry();
|
||||
}
|
||||
);
|
||||
$container->set(
|
||||
Templates::class,
|
||||
function ( $container ) {
|
||||
return new Templates( $container->get( Templates_Registry::class ) );
|
||||
function () {
|
||||
return new Templates();
|
||||
}
|
||||
);
|
||||
$container->set(
|
||||
@ -268,7 +258,6 @@ abstract class MailPoetTest extends \Codeception\TestCase\Test { // phpcs:ignore
|
||||
$container->get( Process_Manager::class ),
|
||||
$container->get( Blocks_Registry::class ),
|
||||
$container->get( Settings_Controller::class ),
|
||||
new \MailPoet\EmailEditor\Integrations\MailPoet\MailPoetCssInliner(),
|
||||
$container->get( Theme_Controller::class ),
|
||||
);
|
||||
}
|
||||
@ -279,34 +268,24 @@ abstract class MailPoetTest extends \Codeception\TestCase\Test { // phpcs:ignore
|
||||
return new Renderer(
|
||||
$container->get( Content_Renderer::class ),
|
||||
$container->get( Templates::class ),
|
||||
new \MailPoet\EmailEditor\Integrations\MailPoet\MailPoetCssInliner(),
|
||||
$container->get( Theme_Controller::class ),
|
||||
);
|
||||
}
|
||||
);
|
||||
$container->set(
|
||||
Personalization_Tags_Registry::class,
|
||||
function () {
|
||||
return new Personalization_Tags_Registry();
|
||||
}
|
||||
);
|
||||
$container->set(
|
||||
Personalizer::class,
|
||||
function ( $container ) {
|
||||
return new Personalizer(
|
||||
$container->get( Personalization_Tags_Registry::class ),
|
||||
);
|
||||
}
|
||||
);
|
||||
$container->set(
|
||||
Send_Preview_Email::class,
|
||||
function ( $container ) {
|
||||
return new Send_Preview_Email(
|
||||
$container->get( Renderer::class ),
|
||||
$container->get( Personalizer::class ),
|
||||
);
|
||||
}
|
||||
);
|
||||
$container->set(
|
||||
Personalization_Tags_Registry::class,
|
||||
function () {
|
||||
return new Personalization_Tags_Registry();
|
||||
}
|
||||
);
|
||||
$container->set(
|
||||
Email_Api_Controller::class,
|
||||
function ( $container ) {
|
||||
@ -328,6 +307,7 @@ abstract class MailPoetTest extends \Codeception\TestCase\Test { // phpcs:ignore
|
||||
$container->get( Email_Api_Controller::class ),
|
||||
$container->get( Templates::class ),
|
||||
$container->get( Patterns::class ),
|
||||
$container->get( Settings_Controller::class ),
|
||||
$container->get( Send_Preview_Email::class ),
|
||||
$container->get( Personalization_Tags_Registry::class ),
|
||||
);
|
||||
|
@ -1,143 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the MailPoet Email Editor package.
|
||||
*
|
||||
* @package MailPoet\EmailEditor
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use MailPoet\EmailEditor\Engine\Templates\Templates_Registry;
|
||||
use MailPoet\EmailEditor\Engine\Templates\Template;
|
||||
|
||||
/**
|
||||
* Test cases for the Templates_Registry class.
|
||||
*/
|
||||
class TemplatesRegistryTest extends TestCase {
|
||||
/**
|
||||
* Property for the templates registry.
|
||||
*
|
||||
* @var Templates_Registry Templates registry instance.
|
||||
*/
|
||||
private Templates_Registry $registry;
|
||||
|
||||
/**
|
||||
* Set up the test case.
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
$this->registry = new Templates_Registry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a template and retrieve it by name.
|
||||
*/
|
||||
public function testRegisterAndGetTemplateByName(): void {
|
||||
$template = $this->createMock( Template::class );
|
||||
$template->method( 'get_name' )->willReturn( 'mailpoet//email-general' );
|
||||
$template->method( 'get_title' )->willReturn( 'Email General' );
|
||||
$template->method( 'get_description' )->willReturn( 'A general email template.' );
|
||||
$template->method( 'get_content' )->willReturn( '<!-- wp:paragraph --> <p>Hello World</p> <!-- /wp:paragraph -->' );
|
||||
$template->method( 'get_post_types' )->willReturn( array( 'mailpoet_email' ) );
|
||||
|
||||
// Register the template.
|
||||
$this->registry->register( $template );
|
||||
|
||||
// Retrieve the template by name.
|
||||
$retrieved_template = $this->registry->get_by_name( 'mailpoet//email-general' );
|
||||
|
||||
$this->assertNotNull( $retrieved_template );
|
||||
$this->assertSame( 'mailpoet//email-general', $retrieved_template->get_name() );
|
||||
$this->assertSame( 'Email General', $retrieved_template->get_title() );
|
||||
$this->assertSame( 'A general email template.', $retrieved_template->get_description() );
|
||||
$this->assertSame( '<!-- wp:paragraph --> <p>Hello World</p> <!-- /wp:paragraph -->', $retrieved_template->get_content() );
|
||||
$this->assertSame( array( 'mailpoet_email' ), $retrieved_template->get_post_types() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a template and retrieve it by slug.
|
||||
*/
|
||||
public function testRegisterAndGetTemplateBySlug(): void {
|
||||
$template = $this->createMock( Template::class );
|
||||
$template->method( 'get_name' )->willReturn( 'mailpoet//email-general' );
|
||||
$template->method( 'get_slug' )->willReturn( 'email-general' );
|
||||
|
||||
// Register the template.
|
||||
$this->registry->register( $template );
|
||||
|
||||
// Retrieve the template by slug.
|
||||
$retrieved_template = $this->registry->get_by_slug( 'email-general' );
|
||||
|
||||
$this->assertNotNull( $retrieved_template );
|
||||
$this->assertSame( 'email-general', $retrieved_template->get_slug() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to retrieve a template that hasn't been registered.
|
||||
*/
|
||||
public function testRetrieveNonexistentTemplate(): void {
|
||||
$this->assertNull( $this->registry->get_by_name( 'nonexistent-template' ) );
|
||||
$this->assertNull( $this->registry->get_by_slug( 'nonexistent-slug' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register multiple templates and retrieve them.
|
||||
*/
|
||||
public function testRegisterMultipleTemplates(): void {
|
||||
$template1 = $this->createMock( Template::class );
|
||||
$template1->method( 'get_name' )->willReturn( 'mailpoet//template-1' );
|
||||
$template1->method( 'get_slug' )->willReturn( 'template-1' );
|
||||
|
||||
$template2 = $this->createMock( Template::class );
|
||||
$template2->method( 'get_name' )->willReturn( 'mailpoet//template-2' );
|
||||
$template2->method( 'get_slug' )->willReturn( 'template-2' );
|
||||
|
||||
// Register both templates.
|
||||
$this->registry->register( $template1 );
|
||||
$this->registry->register( $template2 );
|
||||
|
||||
$all_templates = $this->registry->get_all();
|
||||
|
||||
$this->assertCount( 2, $all_templates );
|
||||
$this->assertArrayHasKey( 'mailpoet//template-1', $all_templates );
|
||||
$this->assertArrayHasKey( 'mailpoet//template-2', $all_templates );
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure duplicate templates are not registered.
|
||||
*/
|
||||
public function testRegisterDuplicateTemplate(): void {
|
||||
$template = $this->createMock( Template::class );
|
||||
$template->method( 'get_name' )->willReturn( 'mailpoet//email-general' );
|
||||
|
||||
// Register the same template twice.
|
||||
$this->registry->register( $template );
|
||||
$this->registry->register( $template );
|
||||
|
||||
// Registry should contain only one instance.
|
||||
$this->assertCount( 1, $this->registry->get_all() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the registry and apply a filter.
|
||||
*/
|
||||
public function testInitializeAppliesFilter(): void {
|
||||
// Mock WordPress's `apply_filters` function.
|
||||
global $wp_filter_applied;
|
||||
$wp_filter_applied = false;
|
||||
|
||||
add_filter(
|
||||
'mailpoet_email_editor_register_templates',
|
||||
function ( $registry ) use ( &$wp_filter_applied ) {
|
||||
$wp_filter_applied = true;
|
||||
return $registry;
|
||||
}
|
||||
);
|
||||
|
||||
// Initialize the registry.
|
||||
$this->registry->initialize();
|
||||
|
||||
// Assert that the filter was applied.
|
||||
$this->assertTrue( $wp_filter_applied );
|
||||
}
|
||||
}
|
@ -11,19 +11,6 @@ require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
$console = new \Codeception\Lib\Console\Output( array() );
|
||||
|
||||
if ( ! function_exists( 'register_block_template' ) ) {
|
||||
/**
|
||||
* Mock register_block_template function.
|
||||
*
|
||||
* @param string $name Template name.
|
||||
* @param array $attr Template attributes.
|
||||
* @return string
|
||||
*/
|
||||
function register_block_template( $name, $attr ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'esc_attr' ) ) {
|
||||
/**
|
||||
* Mock esc_attr function.
|
||||
|
@ -8,7 +8,6 @@
|
||||
declare(strict_types = 1);
|
||||
|
||||
// Dummy WP classes.
|
||||
// phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
|
||||
if ( ! class_exists( \WP_Theme_JSON::class ) ) {
|
||||
/**
|
||||
* Class WP_Theme_JSON
|
||||
@ -32,46 +31,3 @@ if ( ! class_exists( \WP_Theme_JSON::class ) ) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! class_exists( \WP_Block_Templates_Registry::class ) ) {
|
||||
/**
|
||||
* Dummy class to replace WP_Block_Templates_Registry in PHPUnit tests.
|
||||
*/
|
||||
class WP_Block_Templates_Registry {
|
||||
/**
|
||||
* List of registered templates.
|
||||
*
|
||||
* @var array Stores registered templates.
|
||||
*/
|
||||
private static array $registered_templates = array();
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*
|
||||
* @var self|null
|
||||
*/
|
||||
private static ?self $instance = null;
|
||||
|
||||
/**
|
||||
* Get singleton instance.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function get_instance(): self {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a template is registered.
|
||||
*
|
||||
* @param string $name Template name.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_registered( string $name ): bool {
|
||||
return isset( self::$registered_templates[ $name ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ services:
|
||||
- mailhog-data:/mailhog-data
|
||||
|
||||
wordpress:
|
||||
image: wordpress:${WORDPRESS_IMAGE_VERSION:-6.7.2-php8.3}
|
||||
image: wordpress:${WORDPRESS_IMAGE_VERSION:-6.7.1-php8.3}
|
||||
container_name: wordpress_${CIRCLE_NODE_INDEX:-default}
|
||||
depends_on:
|
||||
smtp:
|
||||
|