Compare commits

..

1 Commits

Author SHA1 Message Date
6b7f6af6aa Release 5.6.4 2025-02-04 14:10:56 +07:00
41 changed files with 223 additions and 828 deletions

View File

@ -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

View File

@ -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

View File

@ -143,7 +143,6 @@ export function* activate() {
return {
type: 'ACTIVATE',
automation: data?.data ?? automation,
saved: !!data?.data,
} as const;
}

View File

@ -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,

View File

@ -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',

View File

@ -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,

View File

@ -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

View File

@ -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]);
}

View File

@ -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) {

View File

@ -1,12 +1,5 @@
== 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.4 - 2025-02-04 =
* Improved: randomness in generated WooCommerce coupons;
* Improved: prevent abandoned cart from getting stuck.

View File

@ -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
View File

@ -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.",

View File

@ -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 {

View File

@ -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',

View File

@ -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);

View File

@ -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'];

View File

@ -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'];

View File

@ -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'];

View File

@ -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"

View File

@ -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;
}
}

View File

@ -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
}

View File

@ -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;
}
}

View File

@ -2,7 +2,7 @@
/*
* Plugin Name: MailPoet
* Version: 5.7.0
* Version: 5.6.4
* 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.4',
'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

View File

@ -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.4
Requires PHP: 7.4
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@ -222,11 +222,8 @@ Check our [Knowledge Base](https://kb.mailpoet.com) or contact us through our [s
== 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.4 - 2025-02-04 =
* Improved: randomness in generated WooCommerce coupons;
* Improved: prevent abandoned cart from getting stuck.
[See the changelog for all versions.](https://github.com/mailpoet/mailpoet/blob/trunk/mailpoet/changelog.txt)

View File

@ -113,7 +113,7 @@ 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"]');
@ -124,8 +124,6 @@ class CreateAndSendEmailUsingGutenbergCest {
$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"]');

View File

@ -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
@ -82,27 +80,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` |

View File

@ -106,12 +106,11 @@ export function PreviewDropdown() {
<Icon icon={ external } />
</>
}
onPreview={ () => {
onPreview={ () =>
recordEvent(
'header_preview_dropdown_preview_in_new_tab_selected'
);
onClose();
} }
)
}
/>
</div>
</MenuGroup>

View File

@ -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(),

View File

@ -269,15 +269,3 @@ export type EmailEditorPostType = Omit< Post, 'type' > & {
type: string;
mailpoet_data?: MailPoetEmailPostContentExtended;
};
export type EmailContentValidationAction = {
label: string;
onClick: () => void;
};
export type EmailContentValidationRule = {
id: string;
test: ( emailContent: string ) => boolean;
message: string;
actions: EmailContentValidationAction[];
};

View File

@ -64,16 +64,16 @@ 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
- We use `mailpoet_data` in some section of the codebase. This will be updated.
- 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.

View File

@ -5,7 +5,10 @@
"autoload": {
"classmap": [
"src/"
]
],
"files": [
"src/exceptions.php"
]
},
"autoload-dev": {
"classmap": [
@ -26,7 +29,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"
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -89,7 +89,7 @@ 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 ) {
@ -183,19 +183,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,
)
);
}
@ -282,7 +279,7 @@ class Email_Editor {
* @param string $template post template.
* @return string
*/
public function load_email_preview_template( $template ) {
public function load_email_preview_template( string $template ): string {
$post = $this->get_current_post();
if ( ! $post instanceof \WP_Post ) {

View File

@ -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' ) );

View File

@ -28,7 +28,6 @@ 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;
@ -188,16 +187,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(

View File

@ -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 );
}
}

View File

@ -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.

View File

@ -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 ] );
}
}
}

View File

@ -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: