Compare commits

...

32 Commits

Author SHA1 Message Date
243dcfd6cc Release 5.8.0
Some checks failed
Email Editor Package Tests / build (7.4) (push) Failing after 28m3s
Email Editor Package Tests / build (8.2) (push) Failing after 26m49s
Email Editor Package Tests / code-style (push) Successful in 3m55s
Email Editor Package Tests / phpstan-static-analysis (7.4) (push) Successful in 44m21s
Email Editor Package Tests / phpstan-static-analysis (8.2) (push) Successful in 45m39s
2025-02-24 20:55:43 +01:00
19abc8963a Update used Automate Woo plugin in Circle CI
- latest version: 6.1.7
 - previous version: 6.0.33
2025-02-24 20:46:53 +01:00
e23e4c4974 Add tests
[MAILPOET-6384]
2025-02-24 12:50:34 +01:00
a3ec922ca5 Add an option to restrict coupon to current subscriber
[MAILPOET-6384]
2025-02-24 12:50:34 +01:00
1d28e8d7c6 Run QIT workflow weekly (Wednesday at 2am) 2025-02-24 10:38:10 +01:00
0e680d50e1 Add QIT workflow to CircleCI 2025-02-24 10:38:10 +01:00
c05670a860 Add additional QIT test commands
[MAILPOET-6487]
2025-02-24 10:38:10 +01:00
e9baeadd6e Make sure the navigation links are hidden on all themes
[MAILPOET-6424]
2025-02-20 13:42:08 +01:00
838d896cec Update the page capability type
MailPoet default pages were created with the default post capability type.

This means post navigation links (previous and next) are often found on the default pages, such as the unsubscribe and the manage subscription page.

[MAILPOET-6424]
2025-02-20 13:42:08 +01:00
919a855f34 Update mocha [MAILPOET-6486] 2025-02-20 11:55:49 +01:00
336b63b43c Update used WooCommerce Subscriptions plugin in Circle CI
- latest version: 7.2.1
 - previous version: 7.1.0
2025-02-19 09:28:01 +03:00
fec3ae1239 Update used Automate Woo plugin in Circle CI
- latest version: 6.1.6
 - previous version: 6.0.33
2025-02-19 09:28:01 +03:00
227aab5c47 Update used WooCommerce plugin in Circle CI
- latest version: 9.6.2
 - previous version: 9.5.2
2025-02-19 09:28:01 +03:00
96eb805c5c Update used WordPress images in Circle CI
- latest version: 6.7.2-php8.3
 - previous version: 6.6.2
2025-02-19 09:28:01 +03:00
40f946f25c Release 5.7.1 2025-02-18 15:21:23 +03:00
cd44c42a1d Upgrade Action Scheduler to 3.9.2
[MAILPOET-6467]
2025-02-17 15:47:23 +01:00
a4d67a319a Properly set block opt-in checkbox label to render with correct classes
This fixes verical misalignment between the checkbox and the label

[MAILPOET-6436]
2025-02-17 13:08:29 +01:00
1372de1bba Update CheckboxControl types
6b8a403a03/plugins/woocommerce-blocks/packages/components/checkbox-control/index.tsx (L12-L21)

[MAILPOET-6436]
2025-02-17 13:08:29 +01:00
4de580b7df Update Readme and add missing text. 2025-02-14 09:06:09 +01:00
642733ee0c Update Readme with information about the email editor js filters 2025-02-14 09:06:09 +01:00
80f354b860 Simplify the WordPress Playground link and add networking support 2025-02-14 09:06:09 +01:00
f96e74cf96 Remove unnecessary early return in the RichTextWithButton component
MAILPOET-6431
2025-02-13 10:27:43 +01:00
ee513fd871 Add clarifying comments to sections of the codebase
MAILPOET-6431
2025-02-13 10:27:43 +01:00
2adc6d23ca Update acceptance test case to fix failing test
MAILPOET-6431
2025-02-13 10:27:43 +01:00
5023bbb6f2 Fix Incorrect use of <label for=FORM_ELEMENT notice
https://github.com/user-attachments/assets/9a996747-9ab1-453f-bd11-deffea36a468

MAILPOET-6431
2025-02-13 10:27:43 +01:00
b16071ee0e Remove unnecessary use of updateEmailMailPoetProperty
MAILPOET-6431
2025-02-13 10:27:43 +01:00
e5498b6baf Remove unnecessary use of mailpoet_data
MAILPOET-6431
2025-02-13 10:27:43 +01:00
9ad79e75b5 Update email editor mailpoet sidebar implementation
MAILPOET-6431
2025-02-13 10:27:43 +01:00
aa17e90365 Refactor RichTextWithButton component and ensure it is reusable
We also removed the usage of mailpoet_data

MAILPOET-6431
2025-02-13 10:27:43 +01:00
4b917dd1b3 Move current MailPoet specific information on the sidebar to an extension point
MAILPOET-6431
2025-02-13 10:27:43 +01:00
7c425cb04d Refactor send email preview component to remove hardcoded text and add point for extensibility
MAILPOET-6431
2025-02-13 10:27:43 +01:00
43990d9e8b Remove mailpoet_data use from send email preview feature
MAILPOET-6431
2025-02-13 10:27:43 +01:00
39 changed files with 792 additions and 398 deletions

View File

@ -197,10 +197,10 @@ jobs:
- run:
name: Download additional WP Plugins for tests
command: |
./do download:woo-commerce-zip 9.6.0
./do download:woo-commerce-subscriptions-zip 7.1.0
./do download:woo-commerce-zip 9.6.2
./do download:woo-commerce-subscriptions-zip 7.2.1
./do download:woo-commerce-memberships-zip 1.26.5
./do download:automate-woo-zip 6.1.5
./do download:automate-woo-zip 6.1.7
- run:
name: Dump tests ENV variables for acceptance tests
command: |
@ -888,6 +888,146 @@ 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:
@ -1083,7 +1223,7 @@ workflows:
<<: *slack-fail-post-step
name: acceptance_oldest
woo_core_version: 9.5.2
woo_subscriptions_version: 7.0.0
woo_subscriptions_version: 7.1.0
woo_memberships_version: 1.25.2
automate_woo_version: 6.0.33
mysql_command: --max_allowed_packet=100M
@ -1124,7 +1264,7 @@ workflows:
<<: *slack-fail-post-step
name: integration_oldest
woo_core_version: 9.5.2
woo_subscriptions_version: 7.0.0
woo_subscriptions_version: 7.1.0
woo_memberships_version: 1.25.2
automate_woo_version: 6.0.33
codeception_image_version: 7.4-cli_20220605.0
@ -1187,7 +1327,7 @@ workflows:
<<: *slack-fail-post-step
name: acceptance_with_premium_oldest
woo_core_version: 9.5.2
woo_subscriptions_version: 7.0.0
woo_subscriptions_version: 7.1.0
woo_memberships_version: 1.25.2
automate_woo_version: 6.0.33
codeception_image_version: 7.4-cli_20220605.0
@ -1199,7 +1339,7 @@ workflows:
<<: *slack-fail-post-step
name: integration_with_premium_oldest
woo_core_version: 9.5.2
woo_subscriptions_version: 7.0.0
woo_subscriptions_version: 7.1.0
woo_memberships_version: 1.25.2
automate_woo_version: 6.0.33
codeception_image_version: 7.4-cli_20220605.0
@ -1209,3 +1349,43 @@ 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

View File

@ -815,6 +815,47 @@ 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;
label?: string | React.ReactNode;
id?: string;
instanceId?: string;
onChange?: (value: boolean) => void;
children?: React.ReactChildren | React.ReactElement;
onChange: (value: boolean) => void;
children?: React.ReactChildren;
hasError?: boolean;
checked?: boolean;
disabled?: string | boolean | undefined;
};
export const CheckboxControl: (props: CheckboxControlProps) => JSX.Element;
}

View File

@ -0,0 +1,142 @@
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,6 +5,7 @@
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';
@ -12,10 +13,12 @@ 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',
(validationRules: []) => [...validationRules, ...useValidationRules()],
() => useValidationRules(), // returns a memorized set of rules (array of rules)
);
const EVENTS_TO_TRACK = [
@ -53,3 +56,22 @@ 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,12 +64,14 @@ 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,8 +30,10 @@ export function FrontendBlock({
}
return (
<CheckboxControl checked={checked} onChange={setChecked}>
<RawHTML>{text || defaultText}</RawHTML>
</CheckboxControl>
<CheckboxControl
checked={checked}
onChange={setChecked}
label={<RawHTML>{text || defaultText}</RawHTML>}
/>
);
}

View File

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

View File

@ -19,6 +19,7 @@ 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
@ -33,6 +34,10 @@ Module.CouponBlockModel = base.BlockModel.extend({
minimumAmount: '',
maximumAmount: '',
emailRestrictions: '',
restrictToSubscriber: false,
showRestrictToSubscriber:
App.getNewsletter().isAutomationEmail() ||
App.getNewsletter().isWelcomeEmail(),
styles: {
block: {
backgroundColor: '#ffffff',

View File

@ -31,6 +31,7 @@ type State = {
productCategoryIds: Post[];
excludedProductCategoryIds: Post[];
emailRestrictions: string;
restrictToSubscriber: boolean;
};
class UsageRestriction extends Component<Props, State> {
@ -62,6 +63,8 @@ class UsageRestriction extends Component<Props, State> {
'excludedProductCategoryIds',
).toJSON() as Post[],
emailRestrictions: this.getValueCallback('emailRestrictions') as string,
restrictToSubscriber:
(this.getValueCallback('restrictToSubscriber') as boolean) || false,
};
}
@ -262,6 +265,25 @@ 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,6 +35,9 @@ 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,5 +1,14 @@
== 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.8.0"
"woocommerce/action-scheduler": "3.9.2"
},
"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": "51893b0f5ed38d130932b86b48e55b94",
"content-hash": "a95263a51332277e75d7147e3dc4551e",
"packages": [
{
"name": "dragonmantank/cron-expression",
@ -221,20 +221,20 @@
},
{
"name": "woocommerce/action-scheduler",
"version": "3.8.0",
"version": "3.9.2",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/action-scheduler.git",
"reference": "99cd7981f51c98883082534d4852491858d72834"
"reference": "efbb7953f72a433086335b249292f280dd43ddfe"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/99cd7981f51c98883082534d4852491858d72834",
"reference": "99cd7981f51c98883082534d4852491858d72834",
"url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/efbb7953f72a433086335b249292f280dd43ddfe",
"reference": "efbb7953f72a433086335b249292f280dd43ddfe",
"shasum": ""
},
"require": {
"php": ">=5.6"
"php": ">=7.1"
},
"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.8.0"
"source": "https://github.com/woocommerce/action-scheduler/tree/3.9.2"
},
"time": "2024-05-22T13:50:29+00:00"
"time": "2025-02-03T09:09:30+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']) || empty($data['newsletterId'])) {
if (empty($data['email']) || empty($data['postId'])) {
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->findOneById((int)$postData['newsletterId']);
$newsletter = $this->newslettersRepository->findOneBy(['wpPost' => (int)$postData['postId']]);
if (!$newsletter) {
if (!$newsletter instanceof NewsletterEntity) {
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);
$contentBlocks = $this->couponPreProcessor->processCoupons($newsletter, $contentBlocks, $preview, $sendingQueue);
$content['blocks'] = $this->processContainer($newsletter, $contentBlocks, $preview, $sendingQueue);
return $content;
}

View File

@ -27,7 +27,18 @@ 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

@ -3,6 +3,7 @@
namespace MailPoet\WooCommerce;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Entities\SendingQueueEntity;
use MailPoet\Newsletter\NewslettersRepository;
use MailPoet\Newsletter\Renderer\Blocks\Coupon;
use MailPoet\NewsletterProcessingException;
@ -30,12 +31,12 @@ class CouponPreProcessor {
/**
* @throws NewsletterProcessingException
*/
public function processCoupons(NewsletterEntity $newsletter, array $blocks, bool $preview = false): array {
public function processCoupons(NewsletterEntity $newsletter, array $blocks, bool $preview = false, ?SendingQueueEntity $sendingQueue = null): array {
if ($preview) {
return $blocks;
}
$generated = $this->ensureCouponForBlocks($blocks, $newsletter);
$generated = $this->ensureCouponForBlocks($blocks, $newsletter, $sendingQueue);
$body = $newsletter->getBody();
if ($generated && $body && $this->shouldPersist($newsletter)) {
@ -54,11 +55,10 @@ class CouponPreProcessor {
return $blocks;
}
private function ensureCouponForBlocks(array &$blocks, NewsletterEntity $newsletter): bool {
private function ensureCouponForBlocks(array &$blocks, NewsletterEntity $newsletter, ?SendingQueueEntity $sendingQueue): bool {
foreach ($blocks as &$innerBlock) {
if (isset($innerBlock['blocks']) && !empty($innerBlock['blocks'])) {
$this->ensureCouponForBlocks($innerBlock['blocks'], $newsletter);
$this->ensureCouponForBlocks($innerBlock['blocks'], $newsletter, $sendingQueue);
}
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);
$innerBlock['couponId'] = $this->addOrUpdateCoupon($innerBlock, $newsletter, $sendingQueue);
$this->generated = true;
} catch (\Exception $e) {
throw NewsletterProcessingException::create()->withMessage($e->getMessage())->withCode($e->getCode());
@ -81,10 +81,11 @@ 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) {
private function addOrUpdateCoupon(array $couponBlock, NewsletterEntity $newsletter, ?SendingQueueEntity $sendingQueue) {
$coupon = $this->wcHelper->createWcCoupon($couponBlock['couponId'] ?? '');
if ($this->shouldGenerateCoupon($couponBlock)) {
$code = isset($couponBlock['code']) && $couponBlock['code'] !== Coupon::CODE_PLACEHOLDER ? $couponBlock['code'] : $this->generateRandomCode();
@ -128,7 +129,24 @@ class CouponPreProcessor {
$coupon->set_product_categories($this->getItemIds($couponBlock['productCategoryIds'] ?? []));
$coupon->set_excluded_product_categories($this->getItemIds($couponBlock['excludedProductCategoryIds'] ?? []));
$coupon->set_email_restrictions(explode(',', $couponBlock['emailRestrictions'] ?? ''));
$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)));
// usage limit
$coupon->set_usage_limit($couponBlock['usageLimit'] ?? 0);

View File

@ -2,7 +2,7 @@
/*
* Plugin Name: MailPoet
* Version: 5.7.0
* Version: 5.8.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.7.0',
'version' => '5.8.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.4.0",
"mocha": "^10.8.2",
"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.7.0
Stable tag: 5.8.0
Requires PHP: 7.4
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@ -222,11 +222,9 @@ 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.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.
[See the changelog for all versions.](https://github.com/mailpoet/mailpoet/blob/trunk/mailpoet/changelog.txt)

View File

@ -55,7 +55,8 @@ class CreateAndSendEmailUsingGutenbergCest {
$i->click('Save Draft');
$i->waitForText('Saved');
$i->waitForText('Email saved!');
$i->click('Send');
$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->waitForElement('[name="subject"]');
$subject = $i->grabValueFrom('[name="subject"]');
verify($subject)->equals('My New Subject');

View File

@ -5,11 +5,16 @@ 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 {
@ -176,6 +181,68 @@ 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/?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 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 locate the PHP package here `packages/php/email-editor`
@ -98,11 +98,13 @@ 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` |
| 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 |

View File

@ -0,0 +1,32 @@
{
"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,8 +35,7 @@ export function MoreMenu(): JSX.Element {
editorCurrentPostType,
'status'
);
const { saveEditedEmail, updateEmailMailPoetProperty } =
useDispatch( storeName );
const { saveEditedEmail } = useDispatch( storeName );
const goToListings = () => {
window.location.href = urls.listings;
};
@ -132,10 +131,6 @@ 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,6 +63,7 @@ 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 { useDispatch, useSelect } from '@wordpress/data';
import { 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,25 +17,32 @@ import {
getCursorPosition,
replacePersonalizationTagsWithHTMLComments,
} from './rich-text-utils';
import { storeName, editorCurrentPostType } from '../../store';
import { storeName } from '../../store';
import { PersonalizationTagsPopover } from './personalization-tags-popover';
import { recordEvent, recordEventOnce } from '../../events';
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,
} ) {
const [ mailpoetEmailData ] = useEntityProp(
'postType',
editorCurrentPostType,
'mailpoet_data'
);
const { updateEmailMailPoetProperty } = useDispatch( storeName );
attributeValue,
updateProperty = () => {},
}: Props ) {
const [ selectionRange, setSelectionRange ] = useState( null );
const [ isModalOpened, setIsModalOpened ] = useState( false );
const list = useSelect(
@ -61,11 +68,11 @@ export function RichTextWithButton( {
const updatedValue = toHTMLString( { value: richTextValue } );
// Update the corresponding property
updateEmailMailPoetProperty( attributeName, updatedValue );
updateProperty( attributeName, updatedValue );
setSelectionRange( null );
},
[ attributeName, updateEmailMailPoetProperty ]
[ attributeName, updateProperty ]
);
const finalLabel = (
@ -90,13 +97,13 @@ export function RichTextWithButton( {
</>
);
if ( ! mailpoetEmailData ) {
if ( ! attributeName ) {
return null;
}
return (
<BaseControl
id={ `mailpoet-settings-panel__${ attributeName }` }
id="" // See https://github.com/mailpoet/mailpoet/pull/6089#discussion_r1952126850 to understand why the ID is empty
label={ finalLabel }
className={ `mailpoet-settings-panel__${ attributeName }-text` }
help={ help }
@ -107,7 +114,7 @@ export function RichTextWithButton( {
onInsert={ ( value ) => {
handleInsertPersonalizationTag(
value,
mailpoetEmailData[ attributeName ] ?? '',
attributeValue ?? '',
selectionRange
);
setIsModalOpened( false );
@ -125,17 +132,13 @@ export function RichTextWithButton( {
<PersonalizationTagsPopover
contentRef={ richTextRef }
onUpdate={ ( originalTag, updatedTag ) => {
const currentValue =
mailpoetEmailData[ attributeName ] ?? '';
const currentValue = attributeValue ?? '';
// When we update the tag, we need to add brackets to the tag, because the popover removes them
const updatedContent = currentValue.replace(
`<!--[${ originalTag }]-->`,
`<!--[${ updatedTag }]-->`
);
updateEmailMailPoetProperty(
attributeName,
updatedContent
);
updateProperty( attributeName, updatedContent );
} }
/>
<RichText
@ -144,26 +147,17 @@ export function RichTextWithButton( {
placeholder={ placeholder }
onFocus={ () => {
setSelectionRange(
getCursorPosition(
richTextRef,
mailpoetEmailData[ attributeName ] ?? ''
)
getCursorPosition( richTextRef, attributeValue ?? '' )
);
} }
onKeyUp={ () => {
setSelectionRange(
getCursorPosition(
richTextRef,
mailpoetEmailData[ attributeName ] ?? ''
)
getCursorPosition( richTextRef, attributeValue ?? '' )
);
} }
onClick={ () => {
setSelectionRange(
getCursorPosition(
richTextRef,
mailpoetEmailData[ attributeName ] ?? ''
)
getCursorPosition( richTextRef, attributeValue ?? '' )
);
} }
onChange={ ( value ) => {
@ -171,7 +165,7 @@ export function RichTextWithButton( {
value ?? '',
list
);
updateEmailMailPoetProperty( attributeName, value );
updateProperty( attributeName, value );
recordEventOnce(
'rich_text_with_button_input_field_updated',
{
@ -179,7 +173,7 @@ export function RichTextWithButton( {
}
);
} }
value={ mailpoetEmailData[ attributeName ] ?? '' }
value={ attributeValue ?? '' }
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 { __ } from '@wordpress/i18n';
import { __, sprintf } from '@wordpress/i18n';
import {
useEffect,
useRef,
@ -13,19 +13,23 @@ import {
} from '@wordpress/element';
import { ENTER } from '@wordpress/keycodes';
import { isEmail } from '@wordpress/url';
import { useEntityProp } from '@wordpress/core-data';
import { applyFilters } from '@wordpress/hooks';
/**
* 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 );
@ -40,19 +44,11 @@ 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(
mailpoetEmailData.id,
previewToEmail
);
void requestSendingNewsletterPreview( previewToEmail );
};
const closeCallback = () => {
@ -81,33 +77,48 @@ function RawSendPreviewEmail() {
>
{ sendingPreviewStatus === SendingPreviewStatus.ERROR ? (
<div className="mailpoet-send-preview-modal-notice-error">
{ __(
'Sorry, we were unable to send this email.',
'mailpoet'
) }
<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>
<ul>
<li>
{ 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'
)
}
/>
{ sendingMethodConfigurationLink &&
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={
sendingMethodConfigurationLink
}
target="_blank"
rel="noopener noreferrer"
onClick={ () =>
recordEvent(
'send_preview_email_modal_check_sending_method_configuration_link_clicked'
)
}
/>
),
}
) }
</li>
<li>
{ createInterpolateElement(
@ -119,10 +130,7 @@ function RawSendPreviewEmail() {
link: (
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
<a
href={ new URL(
'free-plan',
'https://www.mailpoet.com/'
).toString() }
href={ `https://account.mailpoet.com/?s=1&g=1&utm_source=mailpoet_email_editor&utm_medium=plugin&utm_source_platform=${ editorCurrentPostType }` }
key="sign-up-for-free"
target="_blank"
rel="noopener noreferrer"

View File

@ -1,92 +1,22 @@
/**
* External dependencies
*/
import { ExternalLink, PanelBody } from '@wordpress/components';
import { useEntityProp } from '@wordpress/core-data';
import { PanelBody } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { createInterpolateElement } from '@wordpress/element';
import classnames from 'classnames';
import { applyFilters } from '@wordpress/hooks';
/**
* Internal dependencies
*/
import { editorCurrentPostType } from '../../store';
import { recordEvent } from '../../events';
import { RichTextWithButton } from '../personalization-tags/rich-text-with-button';
const previewTextMaxLength = 150;
const previewTextRecommendedLength = 80;
const SidebarExtensionComponent = applyFilters(
'mailpoet_email_editor_setting_sidebar_extension_component',
RichTextWithButton
) as () => JSX.Element;
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' ) }
@ -95,50 +25,7 @@ export function DetailsPanel() {
recordEvent( 'details_panel_body_toggle', { opened: data } )
}
>
<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'
) }
/>
<>{ <SidebarExtensionComponent /> }</>
</PanelBody>
);
}

View File

@ -5,6 +5,7 @@ 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
@ -163,6 +164,11 @@ 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
@ -180,9 +186,8 @@ export function usePreviewTemplates(
const template = {
...post,
title: {
raw: post?.mailpoet_data?.subject || post.title.raw,
rendered:
post?.mailpoet_data?.subject || post.title.rendered, // use MailPoet subject as title
raw: post.title.raw,
rendered: preferredTitle || post.title.rendered,
},
};
return {

View File

@ -134,29 +134,6 @@ 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 } ) => {
@ -168,10 +145,7 @@ export const setTemplateToPost =
} );
};
export function* requestSendingNewsletterPreview(
newsletterId: number,
email: string
) {
export function* requestSendingNewsletterPreview( email: string ) {
// If preview is already sending do nothing
const previewState = select( storeName ).getPreviewState();
if ( previewState.isSendingPreviewEmail ) {
@ -192,7 +166,6 @@ export function* requestSendingNewsletterPreview(
path: '/mailpoet-email-editor/v1/send_preview_email',
method: 'POST',
data: {
newsletterId,
email,
postId,
},
@ -213,6 +186,9 @@ export function* requestSendingNewsletterPreview(
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,13 +96,8 @@ export const isEmpty = createRegistrySelector( ( select ) => (): boolean => {
return true;
}
const { content, mailpoet_data: mailpoetData, title } = post;
return (
! content.raw &&
! mailpoetData.subject &&
! mailpoetData.preheader &&
! title.raw
);
const { content, title } = post;
return ! content.raw && ! title.raw;
} );
export const hasEmptyContent = createRegistrySelector(

View File

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

View File

@ -75,5 +75,3 @@ 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,7 +66,6 @@ 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.4.0
version: 10.4.0
specifier: ^10.8.2
version: 10.8.2
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
debug: 4.4.0(supports-color@8.1.1)
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
debug: 4.4.0(supports-color@8.1.1)
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
debug: 4.4.0(supports-color@8.1.1)
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
debug: 4.4.0(supports-color@8.1.1)
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.4.0
debug: 4.3.5(supports-color@9.3.1)
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
@ -3572,7 +3572,7 @@ packages:
typescript:
optional: true
dependencies:
debug: 4.3.4(supports-color@8.1.1)
debug: 4.3.4
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
debug: 4.4.0(supports-color@8.1.1)
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
debug: 4.4.0(supports-color@8.1.1)
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
debug: 4.4.0(supports-color@8.1.1)
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
debug: 4.4.0(supports-color@8.1.1)
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.4.0
debug: 4.3.5(supports-color@9.3.1)
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
debug: 4.4.0(supports-color@8.1.1)
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
debug: 4.4.0(supports-color@8.1.1)
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.4.0
debug: 4.3.5(supports-color@9.3.1)
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
debug: 4.4.0(supports-color@8.1.1)
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
debug: 4.4.0(supports-color@8.1.1)
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.4.0
debug: 4.3.4
transitivePeerDependencies:
- supports-color
dev: true
@ -8281,7 +8281,7 @@ packages:
resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==}
engines: {node: '>= 14'}
dependencies:
debug: 4.4.0
debug: 4.4.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
dev: true
@ -8361,8 +8361,8 @@ packages:
uri-js: 4.4.1
dev: true
/ansi-colors@4.1.1:
resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==}
/ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'}
dev: true
@ -9017,8 +9017,8 @@ packages:
resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==}
dev: true
/bare-events@2.5.0:
resolution: {integrity: sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==}
/bare-events@2.5.4:
resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==}
requiresBuild: true
dev: true
optional: true
@ -10325,7 +10325,7 @@ packages:
ms: 2.1.2
dev: false
/debug@4.3.4(supports-color@8.1.1):
/debug@4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
peerDependencies:
@ -10335,7 +10335,6 @@ packages:
optional: true
dependencies:
ms: 2.1.2
supports-color: 8.1.1
dev: true
/debug@4.3.5(supports-color@9.3.1):
@ -10351,7 +10350,7 @@ packages:
supports-color: 9.3.1
dev: true
/debug@4.4.0:
/debug@4.4.0(supports-color@8.1.1):
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
engines: {node: '>=6.0'}
peerDependencies:
@ -10361,6 +10360,7 @@ 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,11 +10591,6 @@ 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'}
@ -10876,7 +10871,7 @@ packages:
resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==}
engines: {node: '>=8.6'}
dependencies:
ansi-colors: 4.1.1
ansi-colors: 4.1.3
strip-ansi: 6.0.1
dev: true
@ -11137,7 +11132,7 @@ packages:
optional: true
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.0
debug: 4.4.0(supports-color@8.1.1)
enhanced-resolve: 5.17.1
fast-glob: 3.3.2
get-tsconfig: 4.8.1
@ -11399,7 +11394,7 @@ packages:
'@es-joy/jsdoccomment': 0.41.0
are-docs-informative: 0.0.2
comment-parser: 1.4.1
debug: 4.4.0
debug: 4.4.0(supports-color@8.1.1)
escape-string-regexp: 4.0.0
eslint: 8.36.0
esquery: 1.5.0
@ -11791,7 +11786,7 @@ packages:
engines: {node: '>= 10.17.0'}
hasBin: true
dependencies:
debug: 4.4.0
debug: 4.3.4
get-stream: 5.2.0
yauzl: 2.10.0
optionalDependencies:
@ -11995,7 +11990,7 @@ packages:
dependencies:
chalk: 4.1.2
commander: 5.1.0
debug: 4.4.0
debug: 4.4.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
dev: true
@ -12370,7 +12365,7 @@ packages:
dependencies:
basic-ftp: 5.0.5
data-uri-to-buffer: 6.0.2
debug: 4.4.0
debug: 4.3.4
fs-extra: 11.2.0
transitivePeerDependencies:
- supports-color
@ -12419,7 +12414,7 @@ packages:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 5.0.1
minimatch: 5.1.6
once: 1.4.0
dev: true
@ -12678,7 +12673,6 @@ 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==}
@ -12886,7 +12880,7 @@ packages:
dependencies:
'@tootallnate/once': 2.0.0
agent-base: 6.0.2
debug: 4.4.0
debug: 4.4.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
dev: true
@ -12896,7 +12890,7 @@ packages:
engines: {node: '>= 14'}
dependencies:
agent-base: 7.1.1
debug: 4.4.0
debug: 4.4.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
dev: true
@ -12936,7 +12930,7 @@ packages:
engines: {node: '>= 6'}
dependencies:
agent-base: 6.0.2
debug: 4.4.0
debug: 4.3.4
transitivePeerDependencies:
- supports-color
dev: true
@ -12946,7 +12940,7 @@ packages:
engines: {node: '>= 14'}
dependencies:
agent-base: 7.1.1
debug: 4.4.0
debug: 4.4.0(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
dev: true
@ -12971,7 +12965,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
debug: 4.4.0(supports-color@8.1.1)
events: 3.3.0
hash.js: 1.1.7
lodash: 4.17.21
@ -13564,7 +13558,7 @@ packages:
resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
engines: {node: '>=10'}
dependencies:
debug: 4.4.0
debug: 4.4.0(supports-color@8.1.1)
istanbul-lib-coverage: 3.2.2
source-map: 0.6.1
transitivePeerDependencies:
@ -15002,8 +14996,8 @@ packages:
dependencies:
brace-expansion: 1.1.11
/minimatch@5.0.1:
resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==}
/minimatch@5.1.6:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
engines: {node: '>=10'}
dependencies:
brace-expansion: 2.0.1
@ -15051,30 +15045,30 @@ packages:
minimist: 1.2.8
dev: true
/mocha@10.4.0:
resolution: {integrity: sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==}
/mocha@10.8.2:
resolution: {integrity: sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==}
engines: {node: '>= 14.0.0'}
hasBin: true
dependencies:
ansi-colors: 4.1.1
ansi-colors: 4.1.3
browser-stdout: 1.3.1
chokidar: 3.5.3
debug: 4.3.4(supports-color@8.1.1)
diff: 5.0.0
debug: 4.4.0(supports-color@8.1.1)
diff: 5.2.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.0.1
minimatch: 5.1.6
ms: 2.1.3
serialize-javascript: 6.0.0
serialize-javascript: 6.0.2
strip-json-comments: 3.1.1
supports-color: 8.1.1
workerpool: 6.2.1
workerpool: 6.5.1
yargs: 16.2.0
yargs-parser: 20.2.4
yargs-parser: 20.2.9
yargs-unparser: 2.0.0
dev: true
@ -15262,7 +15256,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.3.5(supports-color@9.3.1)
debug: 4.4.0(supports-color@8.1.1)
globby: 11.1.0
ignore: 5.3.1
is-plain-obj: 3.0.0
@ -15558,7 +15552,7 @@ packages:
dependencies:
'@tootallnate/quickjs-emscripten': 0.23.0
agent-base: 7.1.1
debug: 4.4.0
debug: 4.3.4
get-uri: 6.0.3
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.5
@ -15752,7 +15746,7 @@ packages:
dependencies:
'@babel/runtime': 7.24.7
crc32: 0.2.2
debug: 4.4.0
debug: 4.4.0(supports-color@8.1.1)
seed-random: 2.2.0
transitivePeerDependencies:
- supports-color
@ -16434,7 +16428,7 @@ packages:
engines: {node: '>= 14'}
dependencies:
agent-base: 7.1.1
debug: 4.4.0
debug: 4.3.4
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.5
lru-cache: 7.18.3
@ -16483,7 +16477,7 @@ packages:
engines: {node: '>=10.18.1'}
dependencies:
cross-fetch: 3.1.5
debug: 4.3.4(supports-color@8.1.1)
debug: 4.3.4
devtools-protocol: 0.0.981744
extract-zip: 2.0.1
https-proxy-agent: 5.0.1
@ -16513,7 +16507,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(supports-color@8.1.1)
debug: 4.3.4
devtools-protocol: 0.0.1147663
typescript: 5.0.2
ws: 8.18.0
@ -17617,12 +17611,6 @@ 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:
@ -17766,7 +17754,7 @@ packages:
resolution: {integrity: sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==}
dependencies:
buffer: 6.0.3
debug: 4.4.0
debug: 4.4.0(supports-color@8.1.1)
err-code: 3.0.1
get-browser-rtc: 1.1.0
queue-microtask: 1.2.3
@ -17878,7 +17866,7 @@ packages:
base64-arraybuffer: 0.1.5
component-bind: 1.0.0
component-emitter: 1.2.1
debug: 4.4.0
debug: 4.4.0(supports-color@8.1.1)
engine.io-client: 3.4.4
has-binary2: 1.0.3
has-cors: 1.1.0
@ -17917,7 +17905,7 @@ packages:
engines: {node: '>= 14'}
dependencies:
agent-base: 7.1.1
debug: 4.4.0
debug: 4.3.4
socks: 2.8.3
transitivePeerDependencies:
- supports-color
@ -18037,7 +18025,7 @@ packages:
/spdy-transport@3.0.0:
resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==}
dependencies:
debug: 4.4.0
debug: 4.4.0(supports-color@8.1.1)
detect-node: 2.1.0
hpack.js: 2.1.6
obuf: 1.1.2
@ -18051,7 +18039,7 @@ packages:
resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==}
engines: {node: '>=6.0.0'}
dependencies:
debug: 4.4.0
debug: 4.4.0(supports-color@8.1.1)
handle-thing: 2.0.1
http-deceiver: 1.2.7
select-hose: 2.0.0
@ -18120,7 +18108,7 @@ packages:
queue-tick: 1.0.1
text-decoder: 1.1.1
optionalDependencies:
bare-events: 2.5.0
bare-events: 2.5.4
dev: true
/string-argv@0.3.1:
@ -18386,7 +18374,7 @@ packages:
colord: 2.9.3
cosmiconfig: 7.1.0
css-functions-list: 3.2.2
debug: 4.3.5(supports-color@9.3.1)
debug: 4.4.0(supports-color@8.1.1)
fast-glob: 3.3.2
fastest-levenshtein: 1.0.16
file-entry-cache: 6.0.1
@ -18498,7 +18486,6 @@ packages:
engines: {node: '>=10'}
dependencies:
has-flag: 4.0.0
dev: true
/supports-color@9.3.1:
resolution: {integrity: sha512-knBY82pjmnIzK3NifMo3RxEIRD9E0kIzV4BKcyTZ9+9kWgLMxd4PrsTSMoFQUabgRBbF8KOLRDCyKgNV+iK44Q==}
@ -19714,8 +19701,8 @@ packages:
resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
dev: false
/workerpool@6.2.1:
resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==}
/workerpool@6.5.1:
resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==}
dev: true
/wp-error@1.3.0:
@ -19747,7 +19734,7 @@ packages:
resolution: {integrity: sha512-NMp0YsBM40CuI5vWtHpjWOuf96rPfbpGkamlJpVwYvgenIh1ynRzqVnGfsnjofgz13T2qcKkdwJY0Y2X7z+W+w==}
dependencies:
'@babel/runtime': 7.24.7
debug: 4.4.0
debug: 4.4.0(supports-color@8.1.1)
progress-event: 1.0.0
uuid: 7.0.3
wp-error: 1.3.0
@ -19931,11 +19918,6 @@ 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'}

View File

@ -75,7 +75,7 @@ services:
- mailhog-data:/mailhog-data
wordpress:
image: wordpress:${WORDPRESS_IMAGE_VERSION:-6.7.1-php8.3}
image: wordpress:${WORDPRESS_IMAGE_VERSION:-6.7.2-php8.3}
container_name: wordpress_${CIRCLE_NODE_INDEX:-default}
depends_on:
smtp: