Compare commits

..

4 Commits

Author SHA1 Message Date
c3382d314e Update used WooCommerce plugin in Circle CI
- latest version: 9.6.1
 - previous version: 9.5.2
2025-02-13 01:52:47 +00:00
81180caccb Update used WordPress images in Circle CI
- latest version: 6.7.2-php8.3
 - previous version: 6.6.2
2025-02-13 01:52:42 +00:00
10c82b687d add woodpecker for auto-building + releases
Some checks are pending
ci/woodpecker/push/woodpecker Pipeline is pending
2025-02-12 18:26:32 -06:00
00c9bf3dcd Fuck you Baltimore, you can kiss my ass.
Upped the Free subscriber limit from 1000 to 999999999 or some shit.
2025-02-12 17:55:15 -06:00
40 changed files with 428 additions and 794 deletions

View File

@ -197,10 +197,10 @@ jobs:
- run:
name: Download additional WP Plugins for tests
command: |
./do download:woo-commerce-zip 9.6.2
./do download:woo-commerce-subscriptions-zip 7.2.1
./do download:woo-commerce-zip 9.6.1
./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.7
./do download:automate-woo-zip 6.1.5
- run:
name: Dump tests ENV variables for acceptance tests
command: |
@ -888,146 +888,6 @@ jobs:
- store_artifacts:
path: tests/_output
qit_malware_scan:
executor: wpcli_php_latest
steps:
- attach_workspace:
at: /home/circleci
- run:
name: 'Set up environment'
command: |
# Copy built ZIP to local directory for easier access from QIT
cp /home/circleci/mailpoet/mailpoet.zip .
# Authenticate in QIT
./vendor/bin/qit partner:add --user="${QIT_PARTNER_USER}" --application_password="${QIT_PARTNER_SECRET}"
- run:
name: 'QIT Malware Scan'
command: ./do qa:qit-malware | tee tests/_output/qit-malware
- run:
name: 'Retrieve test results'
command: |
grep "Result Url" tests/_output/qit-malware | awk '{ print $3 }' | xargs curl -o tests/_output/report.html
when: always
- store_artifacts:
path: tests/_output
qit_php_compatibility:
executor: wpcli_php_latest
steps:
- attach_workspace:
at: /home/circleci
- run:
name: 'Set up environment'
command: |
# Copy built ZIP to local directory for easier access from QIT
cp /home/circleci/mailpoet/mailpoet.zip .
# Authenticate in QIT
./vendor/bin/qit partner:add --user="${QIT_PARTNER_USER}" --application_password="${QIT_PARTNER_SECRET}"
- run:
name: 'QIT PHP Compatibility Check'
command: ./do qa:qit-php-compatibility | tee tests/_output/qit-php-compatibility
- run:
name: 'Retrieve test results'
command: |
grep "Result Url" tests/_output/qit-php-compatibility | awk '{ print $3 }' | xargs curl -o tests/_output/report.html
when: always
- store_artifacts:
path: tests/_output
qit_activation_tests:
executor: wpcli_php_latest
steps:
- attach_workspace:
at: /home/circleci
- run:
name: 'Set up environment'
command: |
# Copy built ZIP to local directory for easier access from QIT
cp /home/circleci/mailpoet/mailpoet.zip .
# Authenticate in QIT
./vendor/bin/qit partner:add --user="${QIT_PARTNER_USER}" --application_password="${QIT_PARTNER_SECRET}"
- run:
name: 'QIT Activation Tests'
command: |
LATEST_WC_BETA=$(../.circleci/check_woocommerce_beta.sh | grep 'LATEST_BETA' | cut -d'=' -f2)
if [ -z "$LATEST_WC_BETA" ]; then
echo "No WooCommerce Beta version found. Using stable."
./do qa:qit-activation | tee tests/_output/qit-activation
else
echo "Using WooCommerce Beta Version: $LATEST_WC_BETA"
./do qa:qit-activation --wc=$LATEST_WC_BETA | tee tests/_output/qit-activation
fi
- run:
name: 'Retrieve test results'
command: |
grep "Result Url" tests/_output/qit-activation | awk '{ print $3 }' | xargs curl -o tests/_output/report.html
when: always
- store_artifacts:
path: tests/_output
qit_woo_api_tests:
executor: wpcli_php_latest
steps:
- attach_workspace:
at: /home/circleci
- run:
name: 'Set up environment'
command: |
# Copy built ZIP to local directory for easier access from QIT
cp /home/circleci/mailpoet/mailpoet.zip .
# Authenticate in QIT
./vendor/bin/qit partner:add --user="${QIT_PARTNER_USER}" --application_password="${QIT_PARTNER_SECRET}"
- run:
name: 'QIT Woo API Tests'
command: |
LATEST_WC_BETA=$(../.circleci/check_woocommerce_beta.sh | grep 'LATEST_BETA' | cut -d'=' -f2)
if [ -z "$LATEST_WC_BETA" ]; then
echo "No WooCommerce Beta version found. Using stable."
./do qa:qit-woo-api | tee tests/_output/qit-woo-api
else
echo "Using WooCommerce Beta Version: $LATEST_WC_BETA"
./do qa:qit-woo-api --wc=$LATEST_WC_BETA | tee tests/_output/qit-woo-api
fi
- run:
name: 'Retrieve test results'
command: |
grep "Result Url" tests/_output/qit-woo-api | awk '{ print $3 }' | xargs curl -o tests/_output/report.html
when: always
- store_artifacts:
path: tests/_output
qit_woo_e2e_tests:
executor: wpcli_php_latest
steps:
- attach_workspace:
at: /home/circleci
- run:
name: 'Set up environment'
command: |
# Copy built ZIP to local directory for easier access from QIT
cp /home/circleci/mailpoet/mailpoet.zip .
# Authenticate in QIT
./vendor/bin/qit partner:add --user="${QIT_PARTNER_USER}" --application_password="${QIT_PARTNER_SECRET}"
- run:
name: 'QIT Woo E2E Tests'
no_output_timeout: 1h # Woo E2E tests usually takes ~45m
command: |
LATEST_WC_BETA=$(../.circleci/check_woocommerce_beta.sh | grep 'LATEST_BETA' | cut -d'=' -f2)
if [ -z "$LATEST_WC_BETA" ]; then
echo "No WooCommerce Beta version found. Using stable."
./do qa:qit-woo-e2e | tee tests/_output/qit-woo-e2e
else
echo "Using WooCommerce Beta Version: $LATEST_WC_BETA"
./do qa:qit-woo-e2e --wc=$LATEST_WC_BETA | tee tests/_output/qit-woo-e2e
fi
- run:
name: 'Retrieve test results'
command: |
grep "Result Url" tests/_output/qit-woo-e2e | awk '{ print $3 }' | xargs curl -o tests/_output/report.html
when: always
- store_artifacts:
path: tests/_output
workflows:
build_and_test:
jobs:
@ -1223,7 +1083,7 @@ workflows:
<<: *slack-fail-post-step
name: acceptance_oldest
woo_core_version: 9.5.2
woo_subscriptions_version: 7.1.0
woo_subscriptions_version: 7.0.0
woo_memberships_version: 1.25.2
automate_woo_version: 6.0.33
mysql_command: --max_allowed_packet=100M
@ -1264,7 +1124,7 @@ workflows:
<<: *slack-fail-post-step
name: integration_oldest
woo_core_version: 9.5.2
woo_subscriptions_version: 7.1.0
woo_subscriptions_version: 7.0.0
woo_memberships_version: 1.25.2
automate_woo_version: 6.0.33
codeception_image_version: 7.4-cli_20220605.0
@ -1327,7 +1187,7 @@ workflows:
<<: *slack-fail-post-step
name: acceptance_with_premium_oldest
woo_core_version: 9.5.2
woo_subscriptions_version: 7.1.0
woo_subscriptions_version: 7.0.0
woo_memberships_version: 1.25.2
automate_woo_version: 6.0.33
codeception_image_version: 7.4-cli_20220605.0
@ -1339,7 +1199,7 @@ workflows:
<<: *slack-fail-post-step
name: integration_with_premium_oldest
woo_core_version: 9.5.2
woo_subscriptions_version: 7.1.0
woo_subscriptions_version: 7.0.0
woo_memberships_version: 1.25.2
automate_woo_version: 6.0.33
codeception_image_version: 7.4-cli_20220605.0
@ -1349,43 +1209,3 @@ workflows:
mysql_image: mysql:5.5
requires:
- build_premium
nightly_qit:
triggers:
- schedule:
cron: '0 2 * * 3' # Every Wednesday at 2am UTC
filters:
branches:
only:
- trunk
jobs:
- build:
<<: *slack-fail-post-step
- build_release_zip:
<<: *slack-fail-post-step
requires:
- build
- qit_security_scan:
<<: *slack-fail-post-step
requires:
- build_release_zip
- qit_activation_tests:
<<: *slack-fail-post-step
requires:
- build_release_zip
- qit_malware_scan:
<<: *slack-fail-post-step
requires:
- build_release_zip
- qit_php_compatibility:
<<: *slack-fail-post-step
requires:
- build_release_zip
- qit_woo_api_tests:
<<: *slack-fail-post-step
requires:
- build_release_zip
- qit_woo_e2e_tests:
<<: *slack-fail-post-step
requires:
- build_release_zip

30
.woodpecker.yml Normal file
View File

@ -0,0 +1,30 @@
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

@ -815,47 +815,6 @@ class RoboFile extends \Robo\Tasks {
return $this->_exec('./vendor/bin/qit run:security mailpoet --zip=mailpoet.zip --wait');
}
public function qaQitMalware() {
return $this->_exec('./vendor/bin/qit run:malware mailpoet --zip=mailpoet.zip --wait');
}
public function qaQitPhpCompatibility() {
return $this->_exec('./vendor/bin/qit run:phpcompatibility mailpoet --zip=mailpoet.zip --wait');
}
public function qaQitActivation($opts = ['wp' => 'stable', 'wc' => 'stable']) {
$command = './vendor/bin/qit run:activation mailpoet --zip=mailpoet.zip --wait';
if ($opts['wp']) {
$command .= ' --wordpress_version=' . $opts['wp'];
}
if ($opts['wc']) {
$command .= ' --woocommerce_version=' . $opts['wc'];
}
return $this->_exec($command);
}
public function qaQitWooApi($opts = ['wp' => 'stable', 'wc' => 'stable']) {
$command = './vendor/bin/qit run:woo-api mailpoet --zip=mailpoet.zip --wait';
if ($opts['wp']) {
$command .= ' --wordpress_version=' . $opts['wp'];
}
if ($opts['wc']) {
$command .= ' --woocommerce_version=' . $opts['wc'];
}
return $this->_exec($command);
}
public function qaQitWooE2e($opts = ['wp' => 'stable', 'wc' => 'stable']) {
$command = './vendor/bin/qit run:woo-e2e mailpoet --zip=mailpoet.zip --wait';
if ($opts['wp']) {
$command .= ' --wordpress_version=' . $opts['wp'];
}
if ($opts['wc']) {
$command .= ' --woocommerce_version=' . $opts['wc'];
}
return $this->_exec($command);
}
public function svnCheckout() {
$svnDir = ".mp_svn";

View File

@ -39,13 +39,13 @@ interface JQuery {
declare module '@woocommerce/blocks-checkout' {
type CheckboxControlProps = {
className?: string;
label?: string | React.ReactNode;
label?: string;
id?: string;
onChange: (value: boolean) => void;
children?: React.ReactChildren;
instanceId?: string;
onChange?: (value: boolean) => void;
children?: React.ReactChildren | React.ReactElement;
hasError?: boolean;
checked?: boolean;
disabled?: string | boolean | undefined;
};
export const CheckboxControl: (props: CheckboxControlProps) => JSX.Element;
}

View File

@ -1,142 +0,0 @@
import { ExternalLink } from '@wordpress/components';
import { select, dispatch } from '@wordpress/data';
import { store as coreDataStore, useEntityProp } from '@wordpress/core-data';
import { store as editorStore } from '@wordpress/editor';
import { __ } from '@wordpress/i18n';
import { createInterpolateElement } from '@wordpress/element';
import classnames from 'classnames';
const previewTextMaxLength = 150;
const previewTextRecommendedLength = 80;
export function EmailSidebarExtensionBody({ RichTextWithButton }) {
const [mailpoetEmailData] = useEntityProp(
'postType',
'mailpoet_email',
'mailpoet_data',
);
const updateEmailMailPoetProperty = (name: string, value: string) => {
const postId = select(editorStore).getCurrentPostId();
const currentPostType = 'mailpoet_email'; // only for mailpoet_email post-type
const editedPost = select(coreDataStore).getEditedEntityRecord(
'postType',
currentPostType,
postId,
);
// @ts-expect-error Property 'mailpoet_data' does not exist on type 'Updatable<Attachment<any>>'.
const mailpoetData = editedPost?.mailpoet_data || {};
void dispatch(coreDataStore).editEntityRecord(
'postType',
currentPostType,
postId,
{
mailpoet_data: {
...mailpoetData,
[name]: value,
},
},
);
};
const subjectHelp = createInterpolateElement(
__(
'Use personalization tags to personalize your email, or learn more about <bestPracticeLink>best practices</bestPracticeLink> and using <emojiLink>emoji in subject lines</emojiLink>.',
'mailpoet',
),
{
bestPracticeLink: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href="https://www.mailpoet.com/blog/17-email-subject-line-best-practices-to-boost-engagement/"
target="_blank"
rel="noopener noreferrer"
/>
),
emojiLink: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href="https://www.mailpoet.com/blog/tips-using-emojis-in-subject-lines/"
target="_blank"
rel="noopener noreferrer"
/>
),
},
);
const previewTextLength = mailpoetEmailData?.preheader?.length ?? 0;
const preheaderHelp = createInterpolateElement(
__(
'<link>This text</link> will appear in the inbox, underneath the subject line.',
'mailpoet',
),
{
link: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href={new URL(
'article/418-preview-text',
'https://kb.mailpoet.com/',
).toString()}
key="preview-text-kb"
target="_blank"
rel="noopener noreferrer"
/>
),
},
);
return (
<>
<RichTextWithButton
attributeName="subject"
attributeValue={mailpoetEmailData?.subject}
updateProperty={updateEmailMailPoetProperty}
label={__('Subject', 'mailpoet')}
labelSuffix={
<ExternalLink href="https://kb.mailpoet.com/article/435-a-guide-to-personalisation-tags-for-tailored-newsletters#list">
{__('Guide', 'mailpoet')}
</ExternalLink>
}
help={subjectHelp}
placeholder={__('Eg. The summer sale is here!', 'mailpoet')}
/>
<br />
<RichTextWithButton
attributeName="preheader"
attributeValue={mailpoetEmailData?.preheader}
updateProperty={updateEmailMailPoetProperty}
label={__('Preview text', 'mailpoet')}
labelSuffix={
<span
className={classnames(
'mailpoet-settings-panel__preview-text-length',
{
'mailpoet-settings-panel__preview-text-length-warning':
previewTextLength > previewTextRecommendedLength,
'mailpoet-settings-panel__preview-text-length-error':
previewTextLength > previewTextMaxLength,
},
)}
>
{previewTextLength}/{previewTextMaxLength}
</span>
}
help={preheaderHelp}
placeholder={__(
"Add a preview text to capture subscribers' attention and increase open rates.",
'mailpoet',
)}
/>
</>
);
}
export function EmailSidebarExtension(RichTextWithButton: JSX.Element) {
return <EmailSidebarExtensionBody RichTextWithButton={RichTextWithButton} />;
}

View File

@ -5,7 +5,6 @@
import { addFilter, addAction } from '@wordpress/hooks';
import { MailPoet } from 'mailpoet';
import { withSatismeterSurvey } from './satismeter-survey';
import { EmailSidebarExtension } from './email-sidebar-extension';
import './index.scss';
import { useValidationRules } from './validate-email-content';
@ -13,12 +12,10 @@ addFilter('mailpoet_email_editor_wrap_editor_component', 'mailpoet', (editor) =>
withSatismeterSurvey(editor),
);
// validate email editor content using the defined validation rules
// content is first validated when the "Send" button is clicked and revalidated on "Save Draft"
addFilter(
'mailpoet_email_editor_content_validation_rules',
'mailpoet',
() => useValidationRules(), // returns a memorized set of rules (array of rules)
(validationRules: []) => [...validationRules, ...useValidationRules()],
);
const EVENTS_TO_TRACK = [
@ -56,22 +53,3 @@ addFilter(
'mailpoet',
() => !!window.mailpoet_analytics_enabled,
);
// integration point for settings sidebar
addFilter(
'mailpoet_email_editor_setting_sidebar_extension_component',
'mailpoet',
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
(RichTextWithButton) => EmailSidebarExtension.bind(null, RichTextWithButton),
);
// use mailpoet data subject if available
addFilter(
'mailpoet_email_editor_preferred_template_title',
'mailpoet',
(...args) => {
const [, post] = args;
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return post?.mailpoet_data?.subject || ''; // use MailPoet subject as title
},
);

View File

@ -64,14 +64,12 @@ export function useValidationRules() {
label: __('Insert link', 'mailpoet'),
onClick: () => {
if (!hasFooter) {
// update the email content
void dispatch(blockEditorStore).insertBlock(
linksParagraphBlock,
undefined,
contentBlockId,
);
} else {
// update the template
void dispatch(coreDataStore).editEntityRecord(
'postType',
'wp_template',

View File

@ -30,10 +30,8 @@ export function FrontendBlock({
}
return (
<CheckboxControl
checked={checked}
onChange={setChecked}
label={<RawHTML>{text || defaultText}</RawHTML>}
/>
<CheckboxControl checked={checked} onChange={setChecked}>
<RawHTML>{text || defaultText}</RawHTML>
</CheckboxControl>
);
}

View File

@ -56,11 +56,7 @@ export function Edit({
<div {...blockProps}>
{optinEnabled ? (
<div className="wc-block-checkout__marketing">
<CheckboxControl
id="mailpoet-marketing-optin"
checked={false}
onChange={() => {}}
/>
<CheckboxControl id="mailpoet-marketing-optin" checked={false} />
<RichText
value={currentText}
onChange={(value) => setAttributes({ text: value })}

View File

@ -19,7 +19,6 @@ Module.CouponBlockModel = base.BlockModel.extend({
return this._getDefaults(
{
isStandardEmail: App.getNewsletter().isStandardEmail(),
isAutomationEmail: App.getNewsletter().isAutomationEmail(),
productIds: [], // selected product ids,
excludedProductIds: [],
productCategoryIds: [], // selected categories id
@ -34,10 +33,6 @@ Module.CouponBlockModel = base.BlockModel.extend({
minimumAmount: '',
maximumAmount: '',
emailRestrictions: '',
restrictToSubscriber: false,
showRestrictToSubscriber:
App.getNewsletter().isAutomationEmail() ||
App.getNewsletter().isWelcomeEmail(),
styles: {
block: {
backgroundColor: '#ffffff',

View File

@ -31,7 +31,6 @@ type State = {
productCategoryIds: Post[];
excludedProductCategoryIds: Post[];
emailRestrictions: string;
restrictToSubscriber: boolean;
};
class UsageRestriction extends Component<Props, State> {
@ -63,8 +62,6 @@ class UsageRestriction extends Component<Props, State> {
'excludedProductCategoryIds',
).toJSON() as Post[],
emailRestrictions: this.getValueCallback('emailRestrictions') as string,
restrictToSubscriber:
(this.getValueCallback('restrictToSubscriber') as boolean) || false,
};
}
@ -265,25 +262,6 @@ class UsageRestriction extends Component<Props, State> {
)}
/>
</PanelRow>
{this.getValueCallback('showRestrictToSubscriber') && (
<PanelRow>
<ToggleControl
checked={this.state.restrictToSubscriber}
label={__('Restrict to subscriber email', 'mailpoet')}
onChange={(restrictToSubscriber) => {
this.setValueCallback(
'restrictToSubscriber',
restrictToSubscriber,
);
this.setState({ restrictToSubscriber });
}}
help={__(
'Restrict coupon usage to the subscriber receiving this email.',
'mailpoet',
)}
/>
</PanelRow>
)}
</PanelBody>
</Panel>
);

View File

@ -35,9 +35,6 @@ Module.NewsletterModel = SuperModel.extend({
isStandardEmail: function isStandardEmail() {
return this.get('type') === NewsletterType.Standard;
},
isWelcomeEmail: function isWelcomeEmail() {
return this.get('type') === NewsletterType.Welcome;
},
});
// Content block view and model handlers for different content types

View File

@ -1,14 +1,5 @@
== Changelog ==
= 5.8.0 - 2025-02-24 =
* Added: allow generating coupon code in automations for a subscriber the email is sent to;
* Changed: default MailPoet pages capability changed from post to page (one of improvements is hidden previous/next post links);
* Fixed: Prevent removing the content block from the Newsletter template in the new editor.
= 5.7.1 - 2025-02-17 =
* Improved: Apply get_the_excerpt filter to MailPoets post excerpts;
* Fixed: conflict with Rank Math plugin breaking "Scheduled Actions" page.
= 5.7.0 - 2025-02-11 =
* Improved: Rename Review Trigger in Automations;
* Changed: minimum required WooCommerce is 9.5;

View File

@ -5,7 +5,7 @@
"dragonmantank/cron-expression": "^3.3",
"mailpoet/email-editor": "*",
"mixpanel/mixpanel-php": "2.*",
"woocommerce/action-scheduler": "3.9.2"
"woocommerce/action-scheduler": "^3.8.0"
},
"require-dev": {
"ext-gd": "*",

18
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": "a95263a51332277e75d7147e3dc4551e",
"content-hash": "51893b0f5ed38d130932b86b48e55b94",
"packages": [
{
"name": "dragonmantank/cron-expression",
@ -221,20 +221,20 @@
},
{
"name": "woocommerce/action-scheduler",
"version": "3.9.2",
"version": "3.8.0",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/action-scheduler.git",
"reference": "efbb7953f72a433086335b249292f280dd43ddfe"
"reference": "99cd7981f51c98883082534d4852491858d72834"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/efbb7953f72a433086335b249292f280dd43ddfe",
"reference": "efbb7953f72a433086335b249292f280dd43ddfe",
"url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/99cd7981f51c98883082534d4852491858d72834",
"reference": "99cd7981f51c98883082534d4852491858d72834",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=5.6"
},
"require-dev": {
"phpunit/phpunit": "^7.5",
@ -258,9 +258,9 @@
"homepage": "https://actionscheduler.org/",
"support": {
"issues": "https://github.com/woocommerce/action-scheduler/issues",
"source": "https://github.com/woocommerce/action-scheduler/tree/3.9.2"
"source": "https://github.com/woocommerce/action-scheduler/tree/3.8.0"
},
"time": "2025-02-03T09:09:30+00:00"
"time": "2024-05-22T13:50:29+00:00"
}
],
"packages-dev": [
@ -8343,7 +8343,7 @@
],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": {},
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {

View File

@ -35,7 +35,7 @@ class EmailEditorPreviewEmail {
}
private function validateData($data) {
if (empty($data['email']) || empty($data['postId'])) {
if (empty($data['email']) || empty($data['postId']) || empty($data['newsletterId'])) {
throw new \InvalidArgumentException(esc_html__('Missing required data', 'mailpoet'));
}
@ -50,9 +50,9 @@ class EmailEditorPreviewEmail {
* @throws \Exception
*/
private function fetchNewsletter($postData): NewsletterEntity {
$newsletter = $this->newslettersRepository->findOneBy(['wpPost' => (int)$postData['postId']]);
$newsletter = $this->newslettersRepository->findOneById((int)$postData['newsletterId']);
if (!$newsletter instanceof NewsletterEntity) {
if (!$newsletter) {
throw new \Exception(esc_html__('This email does not exist.', 'mailpoet'));
}

View File

@ -53,7 +53,7 @@ class Preprocessor {
return $content;
}
$contentBlocks = $content['blocks'];
$contentBlocks = $this->couponPreProcessor->processCoupons($newsletter, $contentBlocks, $preview, $sendingQueue);
$contentBlocks = $this->couponPreProcessor->processCoupons($newsletter, $contentBlocks, $preview);
$content['blocks'] = $this->processContainer($newsletter, $contentBlocks, $preview, $sendingQueue);
return $content;
}

View File

@ -27,18 +27,7 @@ class Pages {
'can_export' => false,
'publicly_queryable' => true,
'exclude_from_search' => true,
'capability_type' => 'page',
]);
WPFunctions::get()->addFilter('next_post_link', [$this, 'disableNavigationLinks']);
WPFunctions::get()->addFilter('previous_post_link', [$this, 'disableNavigationLinks']);
}
public function disableNavigationLinks($output) {
if (is_singular('mailpoet_page')) {
return ''; // Return an empty string to remove navigation links
}
return $output;
}
public static function createMailPoetPage($postName) {

View File

@ -167,8 +167,6 @@ class Subscribers {
}
private function getFreeSubscribersLimit() {
$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;
return 999999999;
}
}

View File

@ -3,7 +3,6 @@
namespace MailPoet\WooCommerce;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Entities\SendingQueueEntity;
use MailPoet\Newsletter\NewslettersRepository;
use MailPoet\Newsletter\Renderer\Blocks\Coupon;
use MailPoet\NewsletterProcessingException;
@ -31,12 +30,12 @@ class CouponPreProcessor {
/**
* @throws NewsletterProcessingException
*/
public function processCoupons(NewsletterEntity $newsletter, array $blocks, bool $preview = false, ?SendingQueueEntity $sendingQueue = null): array {
public function processCoupons(NewsletterEntity $newsletter, array $blocks, bool $preview = false): array {
if ($preview) {
return $blocks;
}
$generated = $this->ensureCouponForBlocks($blocks, $newsletter, $sendingQueue);
$generated = $this->ensureCouponForBlocks($blocks, $newsletter);
$body = $newsletter->getBody();
if ($generated && $body && $this->shouldPersist($newsletter)) {
@ -55,10 +54,11 @@ class CouponPreProcessor {
return $blocks;
}
private function ensureCouponForBlocks(array &$blocks, NewsletterEntity $newsletter, ?SendingQueueEntity $sendingQueue): bool {
private function ensureCouponForBlocks(array &$blocks, NewsletterEntity $newsletter): bool {
foreach ($blocks as &$innerBlock) {
if (isset($innerBlock['blocks']) && !empty($innerBlock['blocks'])) {
$this->ensureCouponForBlocks($innerBlock['blocks'], $newsletter, $sendingQueue);
$this->ensureCouponForBlocks($innerBlock['blocks'], $newsletter);
}
if (isset($innerBlock['type']) && $innerBlock['type'] === Coupon::TYPE) {
if (!$this->wcHelper->isWooCommerceActive()) {
@ -66,7 +66,7 @@ class CouponPreProcessor {
}
if ($this->shouldGenerateCoupon($innerBlock)) {
try {
$innerBlock['couponId'] = $this->addOrUpdateCoupon($innerBlock, $newsletter, $sendingQueue);
$innerBlock['couponId'] = $this->addOrUpdateCoupon($innerBlock, $newsletter);
$this->generated = true;
} catch (\Exception $e) {
throw NewsletterProcessingException::create()->withMessage($e->getMessage())->withCode($e->getCode());
@ -81,11 +81,10 @@ class CouponPreProcessor {
/**
* @param array $couponBlock
* @param NewsletterEntity $newsletter
* @param SendingQueueEntity|null $sendingQueue
* @return int
* @throws \WC_Data_Exception|\Exception
*/
private function addOrUpdateCoupon(array $couponBlock, NewsletterEntity $newsletter, ?SendingQueueEntity $sendingQueue) {
private function addOrUpdateCoupon(array $couponBlock, NewsletterEntity $newsletter) {
$coupon = $this->wcHelper->createWcCoupon($couponBlock['couponId'] ?? '');
if ($this->shouldGenerateCoupon($couponBlock)) {
$code = isset($couponBlock['code']) && $couponBlock['code'] !== Coupon::CODE_PLACEHOLDER ? $couponBlock['code'] : $this->generateRandomCode();
@ -129,24 +128,7 @@ class CouponPreProcessor {
$coupon->set_product_categories($this->getItemIds($couponBlock['productCategoryIds'] ?? []));
$coupon->set_excluded_product_categories($this->getItemIds($couponBlock['excludedProductCategoryIds'] ?? []));
$emailRestrictions = [];
if (!empty($couponBlock['emailRestrictions'])) {
$emailRestrictions = explode(',', $couponBlock['emailRestrictions']);
}
if (!empty($couponBlock['restrictToSubscriber']) && $sendingQueue && $sendingQueue->getTask()) {
$subscribers = $sendingQueue->getTask()->getSubscribers();
if (is_iterable($subscribers) && count($subscribers) === 1) { // Only apply to single-subscriber sending queues
foreach ($subscribers as $taskSubscriber) {
$subscriber = $taskSubscriber->getSubscriber();
if ($subscriber && $subscriber->getEmail()) {
$emailRestrictions[] = $subscriber->getEmail();
}
}
}
}
$coupon->set_email_restrictions(array_unique(array_filter($emailRestrictions)));
$coupon->set_email_restrictions(explode(',', $couponBlock['emailRestrictions'] ?? ''));
// usage limit
$coupon->set_usage_limit($couponBlock['usageLimit'] ?? 0);

View File

@ -2,7 +2,7 @@
/*
* Plugin Name: MailPoet
* Version: 5.8.0
* Version: 5.7.0
* Plugin URI: https://www.mailpoet.com
* Description: Create and send newsletters, post notifications and welcome emails from your WordPress.
* Author: MailPoet
@ -20,7 +20,7 @@
*/
$mailpoetPlugin = [
'version' => '5.8.0',
'version' => '5.7.0',
'filename' => __FILE__,
'path' => dirname(__FILE__),
'autoloader' => dirname(__FILE__) . '/vendor/autoload.php',

View File

@ -162,7 +162,7 @@
"js-yaml": "^4.1.0",
"jsdom": "^24.1.0",
"json-loader": "^0.5.7",
"mocha": "^10.8.2",
"mocha": "^10.4.0",
"phplint": "^2.0.5",
"postcss-cli": "^11.0.0",
"postcss-scss": "^4.0.9",

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.8.0
Stable tag: 5.7.0
Requires PHP: 7.4
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@ -222,9 +222,11 @@ Check our [Knowledge Base](https://kb.mailpoet.com) or contact us through our [s
== Changelog ==
= 5.8.0 - 2025-02-24 =
* Added: allow generating coupon code in automations for a subscriber the email is sent to;
* Changed: default MailPoet pages capability changed from post to page (one of improvements is hidden previous/next post links);
* Fixed: Prevent removing the content block from the Newsletter template in the new editor.
= 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.
[See the changelog for all versions.](https://github.com/mailpoet/mailpoet/blob/trunk/mailpoet/changelog.txt)

View File

@ -55,8 +55,7 @@ class CreateAndSendEmailUsingGutenbergCest {
$i->click('Save Draft');
$i->waitForText('Saved');
$i->waitForText('Email saved!');
$i->click('[aria-label="Close sidebar"]'); // close the sidebar. It sometimes interferes with the send button click
$i->click('[data-automation-id="email_editor_send_button"]');
$i->click('Send');
$i->waitForElement('[name="subject"]');
$subject = $i->grabValueFrom('[name="subject"]');
verify($subject)->equals('My New Subject');

View File

@ -5,16 +5,11 @@ namespace unit\WooCommerce;
use Codeception\Stub;
use Helper\WordPress;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Entities\ScheduledTaskEntity;
use MailPoet\Entities\ScheduledTaskSubscriberEntity;
use MailPoet\Entities\SendingQueueEntity;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\Newsletter\NewslettersRepository;
use MailPoet\Newsletter\Renderer\Blocks\Coupon;
use MailPoet\NewsletterProcessingException;
use MailPoet\WooCommerce\CouponPreProcessor;
use MailPoet\WooCommerce\Helper;
use MailPoetVendor\Doctrine\Common\Collections\ArrayCollection;
class CouponPreProcessorTest extends \MailPoetUnitTest {
@ -181,68 +176,6 @@ class CouponPreProcessorTest extends \MailPoetUnitTest {
$processor->processCoupons($newsletter, $blocks, false);
}
public function testItRestrictsCouponToSubscriber(): void {
$subscriberEmail = 'test@example.com';
$wcCoupon = $this->createCouponMock();
$subscriber = $this->make(SubscriberEntity::class, [
'getEmail' => $subscriberEmail,
]);
$taskSubscriber = $this->make(ScheduledTaskSubscriberEntity::class, [
'getSubscriber' => $subscriber,
]);
$task = $this->make(ScheduledTaskEntity::class, [
'getSubscribers' => new ArrayCollection([$taskSubscriber]),
]);
$queue = $this->make(SendingQueueEntity::class, [
'getTask' => $task,
]);
$wcHelper = $this->make(Helper::class, [
'createWcCoupon' => $wcCoupon,
'isWooCommerceActive' => true,
]);
$processor = new CouponPreProcessor(
$wcHelper,
$this->make(NewslettersRepository::class)
);
$newsletter = new NewsletterEntity();
$newsletter->setType(NewsletterEntity::TYPE_AUTOMATION);
$blocks = [
[
'type' => 'any',
'blocks' => [
[
'type' => Coupon::TYPE,
'discountType' => 'percent',
'amount' => '100',
'restrictToSubscriber' => true,
],
],
],
];
$newsletter->setBody(['blocks' => $blocks, 'content' => []]);
$wcCoupon->expects($this->exactly(2))
->method('set_email_restrictions')
->withConsecutive(
[[$subscriberEmail]],
[['other@example.com', $subscriberEmail]]
);
// Test with restrictToSubscriber enabled
$processor->processCoupons($newsletter, $blocks, false, $queue);
// Test with additional emailRestrictions
$blocks[0]['blocks'][0]['emailRestrictions'] = 'other@example.com';
$processor->processCoupons($newsletter, $blocks, false, $queue);
}
private function assertWCCouponReceivesCorrectValues($mockedWCCoupon, $expectedCouponId, $expiryDay) {
$mockedWCCoupon->method('save')->willReturn($expectedCouponId);

View File

@ -4,7 +4,7 @@ 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/?blueprint-url=https://raw.githubusercontent.com/mailpoet/mailpoet/refs/heads/trunk/packages/js/email-editor/blueprint.json).
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`
@ -98,13 +98,11 @@ We may add, update and delete any of them.
### 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` |
| `mailpoet_email_editor_check_sending_method_configuration_link` | `string` link | `string` link | Edit or remove the sending configuration link message |
| `mailpoet_email_editor_setting_sidebar_extension_component` | `JSX.Element` RichTextWithButton | `JSX.Element` Sidebar extension component | Add components to the Email settings sidebar |
| `mailpoet_email_editor_preferred_template_title` | `string` '', `Post` post | `string` custom (preferred) template title | Custom title for Email preset template selector |
| 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

@ -1,32 +0,0 @@
{
"landingPage": "/wp-admin/admin.php?page=mailpoet-newsletters",
"preferredVersions": {
"php": "8.2",
"wp": "latest"
},
"features": {
"networking": true
},
"steps": [
{
"step": "login",
"username": "admin"
},
{
"step": "installPlugin",
"pluginData": {
"resource": "url",
"url": "https://account.mailpoet.com/playground/plugin-proxy/branch:trunk"
}
},
{
"step": "mkdir",
"path": "wordpress/wp-content/mu-plugins"
},
{
"step": "writeFile",
"path": "wordpress/wp-content/mu-plugins/addFilter-2.php",
"data": "<?php \nadd_filter('mailpoet_skip_welcome_wizard', '__return_true');"
}
]
}

View File

@ -35,7 +35,8 @@ export function MoreMenu(): JSX.Element {
editorCurrentPostType,
'status'
);
const { saveEditedEmail } = useDispatch( storeName );
const { saveEditedEmail, updateEmailMailPoetProperty } =
useDispatch( storeName );
const goToListings = () => {
window.location.href = urls.listings;
};
@ -131,6 +132,10 @@ export function MoreMenu(): JSX.Element {
<MenuItem
onClick={ async () => {
await setStatus( 'draft' );
await updateEmailMailPoetProperty(
'deleted_at',
''
);
await saveEditedEmail();
recordEvent(
'header_more_menu_restore_from_trash_button_clicked'

View File

@ -63,7 +63,6 @@ export function SendButton( { validateContent, isContentInvalid } ) {
}
} }
disabled={ isDisabled }
data-automation-id="email_editor_send_button"
>
{ label }
</Button>

View File

@ -18,7 +18,7 @@ type Replacement = {
function getChildElement( rootElement: HTMLElement ): HTMLElement | null {
let currentElement: HTMLElement | null = rootElement;
while ( currentElement && currentElement?.children?.length > 0 ) {
while ( currentElement && currentElement.children.length > 0 ) {
// Traverse into the first child element
currentElement = currentElement.children[ 0 ] as HTMLElement;
}

View File

@ -1,10 +1,10 @@
/**
* External dependencies
*/
import type { ReactNode } from 'react';
import { BaseControl, Button } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { useDispatch, useSelect } from '@wordpress/data';
import { useCallback, useRef, useState } from '@wordpress/element';
import { useEntityProp } from '@wordpress/core-data';
import { create, insert, toHTMLString } from '@wordpress/rich-text';
import { RichText } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
@ -17,32 +17,25 @@ import {
getCursorPosition,
replacePersonalizationTagsWithHTMLComments,
} from './rich-text-utils';
import { storeName } from '../../store';
import { storeName, editorCurrentPostType } from '../../store';
import { PersonalizationTagsPopover } from './personalization-tags-popover';
import { recordEvent, recordEventOnce } from '../../events';
type Props = {
label: string;
labelSuffix?: ReactNode;
help?: ReactNode;
placeholder: string;
attributeName: string;
attributeValue?: string;
updateProperty: (
theAttributeName: string,
theUpdatedValue: string
) => void;
};
export function RichTextWithButton( {
label,
labelSuffix,
help,
placeholder,
attributeName,
attributeValue,
updateProperty = () => {},
}: Props ) {
} ) {
const [ mailpoetEmailData ] = useEntityProp(
'postType',
editorCurrentPostType,
'mailpoet_data'
);
const { updateEmailMailPoetProperty } = useDispatch( storeName );
const [ selectionRange, setSelectionRange ] = useState( null );
const [ isModalOpened, setIsModalOpened ] = useState( false );
const list = useSelect(
@ -68,11 +61,11 @@ export function RichTextWithButton( {
const updatedValue = toHTMLString( { value: richTextValue } );
// Update the corresponding property
updateProperty( attributeName, updatedValue );
updateEmailMailPoetProperty( attributeName, updatedValue );
setSelectionRange( null );
},
[ attributeName, updateProperty ]
[ attributeName, updateEmailMailPoetProperty ]
);
const finalLabel = (
@ -97,13 +90,13 @@ export function RichTextWithButton( {
</>
);
if ( ! attributeName ) {
if ( ! mailpoetEmailData ) {
return null;
}
return (
<BaseControl
id="" // See https://github.com/mailpoet/mailpoet/pull/6089#discussion_r1952126850 to understand why the ID is empty
id={ `mailpoet-settings-panel__${ attributeName }` }
label={ finalLabel }
className={ `mailpoet-settings-panel__${ attributeName }-text` }
help={ help }
@ -114,7 +107,7 @@ export function RichTextWithButton( {
onInsert={ ( value ) => {
handleInsertPersonalizationTag(
value,
attributeValue ?? '',
mailpoetEmailData[ attributeName ] ?? '',
selectionRange
);
setIsModalOpened( false );
@ -132,13 +125,17 @@ export function RichTextWithButton( {
<PersonalizationTagsPopover
contentRef={ richTextRef }
onUpdate={ ( originalTag, updatedTag ) => {
const currentValue = attributeValue ?? '';
const currentValue =
mailpoetEmailData[ attributeName ] ?? '';
// When we update the tag, we need to add brackets to the tag, because the popover removes them
const updatedContent = currentValue.replace(
`<!--[${ originalTag }]-->`,
`<!--[${ updatedTag }]-->`
);
updateProperty( attributeName, updatedContent );
updateEmailMailPoetProperty(
attributeName,
updatedContent
);
} }
/>
<RichText
@ -147,17 +144,26 @@ export function RichTextWithButton( {
placeholder={ placeholder }
onFocus={ () => {
setSelectionRange(
getCursorPosition( richTextRef, attributeValue ?? '' )
getCursorPosition(
richTextRef,
mailpoetEmailData[ attributeName ] ?? ''
)
);
} }
onKeyUp={ () => {
setSelectionRange(
getCursorPosition( richTextRef, attributeValue ?? '' )
getCursorPosition(
richTextRef,
mailpoetEmailData[ attributeName ] ?? ''
)
);
} }
onClick={ () => {
setSelectionRange(
getCursorPosition( richTextRef, attributeValue ?? '' )
getCursorPosition(
richTextRef,
mailpoetEmailData[ attributeName ] ?? ''
)
);
} }
onChange={ ( value ) => {
@ -165,7 +171,7 @@ export function RichTextWithButton( {
value ?? '',
list
);
updateProperty( attributeName, value );
updateEmailMailPoetProperty( attributeName, value );
recordEventOnce(
'rich_text_with_button_input_field_updated',
{
@ -173,7 +179,7 @@ export function RichTextWithButton( {
}
);
} }
value={ attributeValue ?? '' }
value={ mailpoetEmailData[ attributeName ] ?? '' }
data-automation-id={ `email_${ attributeName }` }
/>
</BaseControl>

View File

@ -4,7 +4,7 @@
import { Button, Modal, TextControl } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { check, Icon } from '@wordpress/icons';
import { __, sprintf } from '@wordpress/i18n';
import { __ } from '@wordpress/i18n';
import {
useEffect,
useRef,
@ -13,23 +13,19 @@ import {
} from '@wordpress/element';
import { ENTER } from '@wordpress/keycodes';
import { isEmail } from '@wordpress/url';
import { applyFilters } from '@wordpress/hooks';
import { useEntityProp } from '@wordpress/core-data';
/**
* Internal dependencies
*/
import {
MailPoetEmailData,
SendingPreviewStatus,
storeName,
editorCurrentPostType,
} from '../../store';
import { recordEvent, recordEventOnce } from '../../events';
const sendingMethodConfigurationLink = applyFilters(
'mailpoet_email_editor_check_sending_method_configuration_link',
'admin.php?page=mailpoet-settings#mta'
) as string;
function RawSendPreviewEmail() {
const sendToRef = useRef( null );
@ -44,11 +40,19 @@ function RawSendPreviewEmail() {
isSendingPreviewEmail,
sendingPreviewStatus,
isModalOpened,
errorMessage,
} = useSelect( ( select ) => select( storeName ).getPreviewState(), [] );
const [ mailpoetEmailData ] = useEntityProp(
'postType',
editorCurrentPostType,
'mailpoet_data'
) as [ MailPoetEmailData, unknown, unknown ];
const handleSendPreviewEmail = () => {
void requestSendingNewsletterPreview( previewToEmail );
void requestSendingNewsletterPreview(
mailpoetEmailData.id,
previewToEmail
);
};
const closeCallback = () => {
@ -77,48 +81,33 @@ function RawSendPreviewEmail() {
>
{ sendingPreviewStatus === SendingPreviewStatus.ERROR ? (
<div className="mailpoet-send-preview-modal-notice-error">
<p>
{ __(
'Sorry, we were unable to send this email.',
'mailpoet'
) }
</p>
<strong>
{ errorMessage &&
sprintf(
// translators: %s is an error message.
__( 'Error: %s', 'mailpoet' ),
errorMessage
) }
</strong>
{ __(
'Sorry, we were unable to send this email.',
'mailpoet'
) }
<ul>
<li>
{ sendingMethodConfigurationLink &&
createInterpolateElement(
__(
'Please check your <link>sending method configuration</link> with your hosting provider.',
'mailpoet'
{ createInterpolateElement(
__(
'Please check your <link>sending method configuration</link> with your hosting provider.',
'mailpoet'
),
{
link: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href="admin.php?page=mailpoet-settings#mta"
target="_blank"
rel="noopener noreferrer"
onClick={ () =>
recordEvent(
'send_preview_email_modal_check_sending_method_configuration_link_clicked'
)
}
/>
),
{
link: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href={
sendingMethodConfigurationLink
}
target="_blank"
rel="noopener noreferrer"
onClick={ () =>
recordEvent(
'send_preview_email_modal_check_sending_method_configuration_link_clicked'
)
}
/>
),
}
) }
}
) }
</li>
<li>
{ createInterpolateElement(
@ -130,7 +119,10 @@ function RawSendPreviewEmail() {
link: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href={ `https://account.mailpoet.com/?s=1&g=1&utm_source=mailpoet_email_editor&utm_medium=plugin&utm_source_platform=${ editorCurrentPostType }` }
href={ new URL(
'free-plan',
'https://www.mailpoet.com/'
).toString() }
key="sign-up-for-free"
target="_blank"
rel="noopener noreferrer"

View File

@ -1,22 +1,92 @@
/**
* External dependencies
*/
import { PanelBody } from '@wordpress/components';
import { ExternalLink, PanelBody } from '@wordpress/components';
import { useEntityProp } from '@wordpress/core-data';
import { __ } from '@wordpress/i18n';
import { applyFilters } from '@wordpress/hooks';
import { createInterpolateElement } from '@wordpress/element';
import classnames from 'classnames';
/**
* Internal dependencies
*/
import { editorCurrentPostType } from '../../store';
import { recordEvent } from '../../events';
import { RichTextWithButton } from '../personalization-tags/rich-text-with-button';
const SidebarExtensionComponent = applyFilters(
'mailpoet_email_editor_setting_sidebar_extension_component',
RichTextWithButton
) as () => JSX.Element;
const previewTextMaxLength = 150;
const previewTextRecommendedLength = 80;
export function DetailsPanel() {
const [ mailpoetEmailData ] = useEntityProp(
'postType',
editorCurrentPostType,
'mailpoet_data'
);
const subjectHelp = createInterpolateElement(
__(
'Use personalization tags to personalize your email, or learn more about <bestPracticeLink>best practices</bestPracticeLink> and using <emojiLink>emoji in subject lines</emojiLink>.',
'mailpoet'
),
{
bestPracticeLink: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href="https://www.mailpoet.com/blog/17-email-subject-line-best-practices-to-boost-engagement/"
target="_blank"
rel="noopener noreferrer"
onClick={ () =>
recordEvent(
'details_panel_subject_help_best_practice_link_clicked'
)
}
/>
),
emojiLink: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href="https://www.mailpoet.com/blog/tips-using-emojis-in-subject-lines/"
target="_blank"
rel="noopener noreferrer"
onClick={ () =>
recordEvent(
'details_panel_subject_help_emoji_in_subject_lines_link_clicked'
)
}
/>
),
}
);
const previewTextLength = mailpoetEmailData?.preheader?.length ?? 0;
const preheaderHelp = createInterpolateElement(
__(
'<link>This text</link> will appear in the inbox, underneath the subject line.',
'mailpoet'
),
{
link: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href={ new URL(
'article/418-preview-text',
'https://kb.mailpoet.com/'
).toString() }
key="preview-text-kb"
target="_blank"
rel="noopener noreferrer"
onClick={ () =>
recordEvent(
'details_panel_preheader_help_text_link_clicked'
)
}
/>
),
}
);
return (
<PanelBody
title={ __( 'Details', 'mailpoet' ) }
@ -25,7 +95,50 @@ export function DetailsPanel() {
recordEvent( 'details_panel_body_toggle', { opened: data } )
}
>
<>{ <SidebarExtensionComponent /> }</>
<RichTextWithButton
attributeName="subject"
label={ __( 'Subject', 'mailpoet' ) }
labelSuffix={
<ExternalLink
href="https://kb.mailpoet.com/article/435-a-guide-to-personalisation-tags-for-tailored-newsletters#list"
onClick={ () =>
recordEvent(
'details_panel_personalisation_tags_guide_link_clicked'
)
}
>
{ __( 'Guide', 'mailpoet' ) }
</ExternalLink>
}
help={ subjectHelp }
placeholder={ __( 'Eg. The summer sale is here!', 'mailpoet' ) }
/>
<RichTextWithButton
attributeName="preheader"
label={ __( 'Preview text', 'mailpoet' ) }
labelSuffix={
<span
className={ classnames(
'mailpoet-settings-panel__preview-text-length',
{
'mailpoet-settings-panel__preview-text-length-warning':
previewTextLength >
previewTextRecommendedLength,
'mailpoet-settings-panel__preview-text-length-error':
previewTextLength > previewTextMaxLength,
}
) }
>
{ previewTextLength }/{ previewTextMaxLength }
</span>
}
help={ preheaderHelp }
placeholder={ __(
"Add a preview text to capture subscribers' attention and increase open rates.",
'mailpoet'
) }
/>
</PanelBody>
);
}

View File

@ -5,7 +5,6 @@ import { useMemo } from '@wordpress/element';
import { parse } from '@wordpress/blocks';
import { BlockInstance } from '@wordpress/blocks/index';
import { useSelect } from '@wordpress/data';
import { applyFilters } from '@wordpress/hooks';
/**
* Internal dependencies
@ -164,11 +163,6 @@ export function usePreviewTemplates(
const allEmailPosts = useMemo( () => {
return emailPosts?.map( ( post: EmailEditorPostType ) => {
const preferredTitle = applyFilters(
'mailpoet_email_editor_preferred_template_title',
'',
post
);
const { postTemplateContent } = generateTemplateContent(
post,
allTemplates
@ -186,8 +180,9 @@ export function usePreviewTemplates(
const template = {
...post,
title: {
raw: post.title.raw,
rendered: preferredTitle || post.title.rendered,
raw: post?.mailpoet_data?.subject || post.title.raw,
rendered:
post?.mailpoet_data?.subject || post.title.rendered, // use MailPoet subject as title
},
};
return {

View File

@ -134,6 +134,29 @@ export function* saveEditedEmail() {
} );
}
export function* updateEmailMailPoetProperty( name: string, value: string ) {
const postId = select( storeName ).getEmailPostId();
// There can be a better way how to get the edited post data
const editedPost = select( coreDataStore ).getEditedEntityRecord(
'postType',
editorCurrentPostType,
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,
postId,
{
mailpoet_data: {
...mailpoetData,
[ name ]: value,
},
}
);
}
export const setTemplateToPost =
( templateSlug ) =>
async ( { registry } ) => {
@ -145,7 +168,10 @@ export const setTemplateToPost =
} );
};
export function* requestSendingNewsletterPreview( email: string ) {
export function* requestSendingNewsletterPreview(
newsletterId: number,
email: string
) {
// If preview is already sending do nothing
const previewState = select( storeName ).getPreviewState();
if ( previewState.isSendingPreviewEmail ) {
@ -166,6 +192,7 @@ export function* requestSendingNewsletterPreview( email: string ) {
path: '/mailpoet-email-editor/v1/send_preview_email',
method: 'POST',
data: {
newsletterId,
email,
postId,
},
@ -186,9 +213,6 @@ export function* requestSendingNewsletterPreview( email: string ) {
state: {
sendingPreviewStatus: SendingPreviewStatus.ERROR,
isSendingPreviewEmail: false,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
errorMessage: JSON.stringify( errorResponse?.error ),
},
};
}

View File

@ -96,8 +96,13 @@ export const isEmpty = createRegistrySelector( ( select ) => (): boolean => {
return true;
}
const { content, title } = post;
return ! content.raw && ! title.raw;
const { content, mailpoet_data: mailpoetData, title } = post;
return (
! content.raw &&
! mailpoetData.subject &&
! mailpoetData.preheader &&
! title.raw
);
} );
export const hasEmptyContent = createRegistrySelector(

View File

@ -202,7 +202,6 @@ export type State = {
isModalOpened: boolean;
isSendingPreviewEmail: boolean;
sendingPreviewStatus: SendingPreviewStatus | null;
errorMessage?: string;
};
personalizationTags: {
list: PersonalizationTag[];
@ -210,6 +209,13 @@ export type State = {
};
};
export type MailPoetEmailData = {
id: number;
subject: string;
preheader: string;
preview_url: string;
};
export type EmailTemplate = {
id: string;
slug: string;
@ -261,6 +267,7 @@ export type MailPoetEmailPostContentExtended = {
export type EmailEditorPostType = Omit< Post, 'type' > & {
type: string;
mailpoet_data?: MailPoetEmailPostContentExtended;
};
export type EmailContentValidationAction = {

View File

@ -75,3 +75,5 @@ We may add, update and delete any of them.
| `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 |
## TODO
- We use `mailpoet_data` in some section of the codebase. This will be updated.

View File

@ -66,6 +66,7 @@ class Email_Api_Controller {
* $data - Post Data
* format
* [_locale] => user
* [newsletterId] => NEWSLETTER_ID
* [email] => Provided email address
* [postId] => POST_ID
*/

152
pnpm-lock.yaml generated
View File

@ -482,8 +482,8 @@ importers:
specifier: ^0.5.7
version: 0.5.7
mocha:
specifier: ^10.8.2
version: 10.8.2
specifier: ^10.4.0
version: 10.4.0
phplint:
specifier: ^2.0.5
version: 2.0.5
@ -992,7 +992,7 @@ packages:
'@wordpress/primitives': 3.56.0
'@wordpress/react-i18n': 3.36.0
classnames: 2.5.1
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-popper: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.3.1)(react@18.3.1)
@ -1168,7 +1168,7 @@ packages:
'@babel/core': 7.24.7
'@babel/helper-compilation-targets': 7.24.7
'@babel/helper-plugin-utils': 7.24.8
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
lodash.debounce: 4.0.8
resolve: 1.22.8
transitivePeerDependencies:
@ -2528,7 +2528,7 @@ packages:
'@babel/helper-split-export-declaration': 7.24.7
'@babel/parser': 7.24.7
'@babel/types': 7.24.7
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
globals: 11.12.0
transitivePeerDependencies:
- supports-color
@ -2542,7 +2542,7 @@ packages:
'@babel/parser': 7.26.3
'@babel/template': 7.25.9
'@babel/types': 7.26.3
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
globals: 11.12.0
transitivePeerDependencies:
- supports-color
@ -3109,7 +3109,7 @@ packages:
deprecated: Use @eslint/config-array instead
dependencies:
'@humanwhocodes/object-schema': 1.2.1
debug: 4.3.5(supports-color@9.3.1)
debug: 4.4.0
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
@ -3572,7 +3572,7 @@ packages:
typescript:
optional: true
dependencies:
debug: 4.3.4
debug: 4.3.4(supports-color@8.1.1)
extract-zip: 2.0.1
progress: 2.0.3
proxy-agent: 6.3.0
@ -4651,7 +4651,7 @@ packages:
'@typescript-eslint/scope-manager': 5.62.0
'@typescript-eslint/type-utils': 5.62.0(typescript@5.0.2)
'@typescript-eslint/utils': 5.62.0(typescript@5.0.2)
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
graphemer: 1.4.0
ignore: 5.3.1
natural-compare-lite: 1.4.0
@ -4679,7 +4679,7 @@ packages:
'@typescript-eslint/type-utils': 6.21.0(eslint@8.36.0)(typescript@5.0.2)
'@typescript-eslint/utils': 6.21.0(eslint@8.36.0)(typescript@5.0.2)
'@typescript-eslint/visitor-keys': 6.21.0
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
eslint: 8.36.0
graphemer: 1.4.0
ignore: 5.3.1
@ -4724,7 +4724,7 @@ packages:
'@typescript-eslint/scope-manager': 5.62.0
'@typescript-eslint/types': 5.62.0
'@typescript-eslint/typescript-estree': 5.62.0(typescript@5.0.2)
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
typescript: 5.0.2
transitivePeerDependencies:
- supports-color
@ -4744,7 +4744,7 @@ packages:
'@typescript-eslint/types': 6.21.0
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.0.2)
'@typescript-eslint/visitor-keys': 6.21.0
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
eslint: 8.36.0
typescript: 5.0.2
transitivePeerDependencies:
@ -4787,7 +4787,7 @@ packages:
dependencies:
'@typescript-eslint/typescript-estree': 5.56.0(typescript@5.0.2)
'@typescript-eslint/utils': 5.56.0(eslint@8.36.0)(typescript@5.0.2)
debug: 4.3.5(supports-color@9.3.1)
debug: 4.4.0
eslint: 8.36.0
tsutils: 3.21.0(typescript@5.0.2)
typescript: 5.0.2
@ -4807,7 +4807,7 @@ packages:
dependencies:
'@typescript-eslint/typescript-estree': 5.62.0(typescript@5.0.2)
'@typescript-eslint/utils': 5.62.0(typescript@5.0.2)
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
tsutils: 3.21.0(typescript@5.0.2)
typescript: 5.0.2
transitivePeerDependencies:
@ -4826,7 +4826,7 @@ packages:
dependencies:
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.0.2)
'@typescript-eslint/utils': 6.21.0(eslint@8.36.0)(typescript@5.0.2)
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
eslint: 8.36.0
ts-api-utils: 1.3.0(typescript@5.0.2)
typescript: 5.0.2
@ -4860,7 +4860,7 @@ packages:
dependencies:
'@typescript-eslint/types': 5.56.0
'@typescript-eslint/visitor-keys': 5.56.0
debug: 4.3.5(supports-color@9.3.1)
debug: 4.4.0
globby: 11.1.0
is-glob: 4.0.3
semver: 7.6.2
@ -4881,7 +4881,7 @@ packages:
dependencies:
'@typescript-eslint/types': 5.62.0
'@typescript-eslint/visitor-keys': 5.62.0
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
globby: 11.1.0
is-glob: 4.0.3
semver: 7.6.2
@ -4902,7 +4902,7 @@ packages:
dependencies:
'@typescript-eslint/types': 6.21.0
'@typescript-eslint/visitor-keys': 6.21.0
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
globby: 11.1.0
is-glob: 4.0.3
minimatch: 9.0.3
@ -8272,7 +8272,7 @@ packages:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
dependencies:
debug: 4.3.4
debug: 4.4.0
transitivePeerDependencies:
- supports-color
dev: true
@ -8281,7 +8281,7 @@ packages:
resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==}
engines: {node: '>= 14'}
dependencies:
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
transitivePeerDependencies:
- supports-color
dev: true
@ -8361,8 +8361,8 @@ packages:
uri-js: 4.4.1
dev: true
/ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
/ansi-colors@4.1.1:
resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==}
engines: {node: '>=6'}
dev: true
@ -9017,8 +9017,8 @@ packages:
resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==}
dev: true
/bare-events@2.5.4:
resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==}
/bare-events@2.5.0:
resolution: {integrity: sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==}
requiresBuild: true
dev: true
optional: true
@ -10325,7 +10325,7 @@ packages:
ms: 2.1.2
dev: false
/debug@4.3.4:
/debug@4.3.4(supports-color@8.1.1):
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
peerDependencies:
@ -10335,6 +10335,7 @@ packages:
optional: true
dependencies:
ms: 2.1.2
supports-color: 8.1.1
dev: true
/debug@4.3.5(supports-color@9.3.1):
@ -10350,7 +10351,7 @@ packages:
supports-color: 9.3.1
dev: true
/debug@4.4.0(supports-color@8.1.1):
/debug@4.4.0:
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
engines: {node: '>=6.0'}
peerDependencies:
@ -10360,7 +10361,6 @@ packages:
optional: true
dependencies:
ms: 2.1.3
supports-color: 8.1.1
/decamelize-keys@1.1.1:
resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==}
@ -10591,6 +10591,11 @@ packages:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
/diff@5.0.0:
resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==}
engines: {node: '>=0.3.1'}
dev: true
/diff@5.2.0:
resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
engines: {node: '>=0.3.1'}
@ -10871,7 +10876,7 @@ packages:
resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==}
engines: {node: '>=8.6'}
dependencies:
ansi-colors: 4.1.3
ansi-colors: 4.1.1
strip-ansi: 6.0.1
dev: true
@ -11132,7 +11137,7 @@ packages:
optional: true
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
enhanced-resolve: 5.17.1
fast-glob: 3.3.2
get-tsconfig: 4.8.1
@ -11394,7 +11399,7 @@ packages:
'@es-joy/jsdoccomment': 0.41.0
are-docs-informative: 0.0.2
comment-parser: 1.4.1
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
escape-string-regexp: 4.0.0
eslint: 8.36.0
esquery: 1.5.0
@ -11786,7 +11791,7 @@ packages:
engines: {node: '>= 10.17.0'}
hasBin: true
dependencies:
debug: 4.3.4
debug: 4.4.0
get-stream: 5.2.0
yauzl: 2.10.0
optionalDependencies:
@ -11990,7 +11995,7 @@ packages:
dependencies:
chalk: 4.1.2
commander: 5.1.0
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
transitivePeerDependencies:
- supports-color
dev: true
@ -12365,7 +12370,7 @@ packages:
dependencies:
basic-ftp: 5.0.5
data-uri-to-buffer: 6.0.2
debug: 4.3.4
debug: 4.4.0
fs-extra: 11.2.0
transitivePeerDependencies:
- supports-color
@ -12414,7 +12419,7 @@ packages:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 5.1.6
minimatch: 5.0.1
once: 1.4.0
dev: true
@ -12673,6 +12678,7 @@ packages:
/has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
dev: true
/has-property-descriptors@1.0.2:
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
@ -12880,7 +12886,7 @@ packages:
dependencies:
'@tootallnate/once': 2.0.0
agent-base: 6.0.2
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
transitivePeerDependencies:
- supports-color
dev: true
@ -12890,7 +12896,7 @@ packages:
engines: {node: '>= 14'}
dependencies:
agent-base: 7.1.1
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
transitivePeerDependencies:
- supports-color
dev: true
@ -12930,7 +12936,7 @@ packages:
engines: {node: '>= 6'}
dependencies:
agent-base: 6.0.2
debug: 4.3.4
debug: 4.4.0
transitivePeerDependencies:
- supports-color
dev: true
@ -12940,7 +12946,7 @@ packages:
engines: {node: '>= 14'}
dependencies:
agent-base: 7.1.1
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
transitivePeerDependencies:
- supports-color
dev: true
@ -12965,7 +12971,7 @@ packages:
'@babel/runtime': 7.24.7
'@tannin/sprintf': 1.2.0
'@wordpress/compose': 5.20.0(react@18.3.1)
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
events: 3.3.0
hash.js: 1.1.7
lodash: 4.17.21
@ -13558,7 +13564,7 @@ packages:
resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
engines: {node: '>=10'}
dependencies:
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
istanbul-lib-coverage: 3.2.2
source-map: 0.6.1
transitivePeerDependencies:
@ -14996,8 +15002,8 @@ packages:
dependencies:
brace-expansion: 1.1.11
/minimatch@5.1.6:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
/minimatch@5.0.1:
resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==}
engines: {node: '>=10'}
dependencies:
brace-expansion: 2.0.1
@ -15045,30 +15051,30 @@ packages:
minimist: 1.2.8
dev: true
/mocha@10.8.2:
resolution: {integrity: sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==}
/mocha@10.4.0:
resolution: {integrity: sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==}
engines: {node: '>= 14.0.0'}
hasBin: true
dependencies:
ansi-colors: 4.1.3
ansi-colors: 4.1.1
browser-stdout: 1.3.1
chokidar: 3.5.3
debug: 4.4.0(supports-color@8.1.1)
diff: 5.2.0
debug: 4.3.4(supports-color@8.1.1)
diff: 5.0.0
escape-string-regexp: 4.0.0
find-up: 5.0.0
glob: 8.1.0
he: 1.2.0
js-yaml: 4.1.0
log-symbols: 4.1.0
minimatch: 5.1.6
minimatch: 5.0.1
ms: 2.1.3
serialize-javascript: 6.0.2
serialize-javascript: 6.0.0
strip-json-comments: 3.1.1
supports-color: 8.1.1
workerpool: 6.5.1
workerpool: 6.2.1
yargs: 16.2.0
yargs-parser: 20.2.9
yargs-parser: 20.2.4
yargs-unparser: 2.0.0
dev: true
@ -15256,7 +15262,7 @@ packages:
ajv-errors: 1.0.1(ajv@6.12.6)
chalk: 4.1.2
cosmiconfig: 8.3.6(typescript@5.0.2)
debug: 4.4.0(supports-color@8.1.1)
debug: 4.3.5(supports-color@9.3.1)
globby: 11.1.0
ignore: 5.3.1
is-plain-obj: 3.0.0
@ -15552,7 +15558,7 @@ packages:
dependencies:
'@tootallnate/quickjs-emscripten': 0.23.0
agent-base: 7.1.1
debug: 4.3.4
debug: 4.4.0
get-uri: 6.0.3
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.5
@ -15746,7 +15752,7 @@ packages:
dependencies:
'@babel/runtime': 7.24.7
crc32: 0.2.2
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
seed-random: 2.2.0
transitivePeerDependencies:
- supports-color
@ -16428,7 +16434,7 @@ packages:
engines: {node: '>= 14'}
dependencies:
agent-base: 7.1.1
debug: 4.3.4
debug: 4.4.0
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.5
lru-cache: 7.18.3
@ -16477,7 +16483,7 @@ packages:
engines: {node: '>=10.18.1'}
dependencies:
cross-fetch: 3.1.5
debug: 4.3.4
debug: 4.3.4(supports-color@8.1.1)
devtools-protocol: 0.0.981744
extract-zip: 2.0.1
https-proxy-agent: 5.0.1
@ -16507,7 +16513,7 @@ packages:
'@puppeteer/browsers': 1.4.6(typescript@5.0.2)
chromium-bidi: 0.4.16(devtools-protocol@0.0.1147663)
cross-fetch: 4.0.0
debug: 4.3.4
debug: 4.3.4(supports-color@8.1.1)
devtools-protocol: 0.0.1147663
typescript: 5.0.2
ws: 8.18.0
@ -17611,6 +17617,12 @@ packages:
tslib: 2.6.3
upper-case-first: 2.0.2
/serialize-javascript@6.0.0:
resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==}
dependencies:
randombytes: 2.1.0
dev: true
/serialize-javascript@6.0.2:
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
dependencies:
@ -17754,7 +17766,7 @@ packages:
resolution: {integrity: sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==}
dependencies:
buffer: 6.0.3
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
err-code: 3.0.1
get-browser-rtc: 1.1.0
queue-microtask: 1.2.3
@ -17866,7 +17878,7 @@ packages:
base64-arraybuffer: 0.1.5
component-bind: 1.0.0
component-emitter: 1.2.1
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
engine.io-client: 3.4.4
has-binary2: 1.0.3
has-cors: 1.1.0
@ -17905,7 +17917,7 @@ packages:
engines: {node: '>= 14'}
dependencies:
agent-base: 7.1.1
debug: 4.3.4
debug: 4.4.0
socks: 2.8.3
transitivePeerDependencies:
- supports-color
@ -18025,7 +18037,7 @@ packages:
/spdy-transport@3.0.0:
resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==}
dependencies:
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
detect-node: 2.1.0
hpack.js: 2.1.6
obuf: 1.1.2
@ -18039,7 +18051,7 @@ packages:
resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==}
engines: {node: '>=6.0.0'}
dependencies:
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
handle-thing: 2.0.1
http-deceiver: 1.2.7
select-hose: 2.0.0
@ -18108,7 +18120,7 @@ packages:
queue-tick: 1.0.1
text-decoder: 1.1.1
optionalDependencies:
bare-events: 2.5.4
bare-events: 2.5.0
dev: true
/string-argv@0.3.1:
@ -18374,7 +18386,7 @@ packages:
colord: 2.9.3
cosmiconfig: 7.1.0
css-functions-list: 3.2.2
debug: 4.4.0(supports-color@8.1.1)
debug: 4.3.5(supports-color@9.3.1)
fast-glob: 3.3.2
fastest-levenshtein: 1.0.16
file-entry-cache: 6.0.1
@ -18486,6 +18498,7 @@ packages:
engines: {node: '>=10'}
dependencies:
has-flag: 4.0.0
dev: true
/supports-color@9.3.1:
resolution: {integrity: sha512-knBY82pjmnIzK3NifMo3RxEIRD9E0kIzV4BKcyTZ9+9kWgLMxd4PrsTSMoFQUabgRBbF8KOLRDCyKgNV+iK44Q==}
@ -19701,8 +19714,8 @@ packages:
resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
dev: false
/workerpool@6.5.1:
resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==}
/workerpool@6.2.1:
resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==}
dev: true
/wp-error@1.3.0:
@ -19734,7 +19747,7 @@ packages:
resolution: {integrity: sha512-NMp0YsBM40CuI5vWtHpjWOuf96rPfbpGkamlJpVwYvgenIh1ynRzqVnGfsnjofgz13T2qcKkdwJY0Y2X7z+W+w==}
dependencies:
'@babel/runtime': 7.24.7
debug: 4.4.0(supports-color@8.1.1)
debug: 4.4.0
progress-event: 1.0.0
uuid: 7.0.3
wp-error: 1.3.0
@ -19918,6 +19931,11 @@ packages:
camelcase: 5.3.1
decamelize: 1.2.0
/yargs-parser@20.2.4:
resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==}
engines: {node: '>=10'}
dev: true
/yargs-parser@20.2.9:
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
engines: {node: '>=10'}