Compare commits
248 Commits
5.6.0
...
update-plu
Author | SHA1 | Date | |
---|---|---|---|
c3382d314e | |||
81180caccb | |||
10c82b687d | |||
00c9bf3dcd | |||
27b66e9dc9 | |||
0a166aa9e8 | |||
1727a87e1b | |||
05b7346fe8 | |||
3e68608859 | |||
97ea3f1364 | |||
699c9851d3 | |||
3b9db4f98b | |||
79eca8133a | |||
29fc396df6 | |||
4834863505 | |||
50e986715e | |||
9abf223005 | |||
30719a7840 | |||
0616e9db7b | |||
cb4815f417 | |||
044d203ade | |||
ee8daf137e | |||
b1ed43c333 | |||
076de3b8c6 | |||
e177f3bb36 | |||
c866950d54 | |||
5470b079a1 | |||
5190ac1ff2 | |||
e2286167d8 | |||
2deceb3360 | |||
e058fbd608 | |||
40ad420cfb | |||
dc8699cff4 | |||
da00efc1dc | |||
dade6add4e | |||
8a6ae87fd5 | |||
1464e9b9f6 | |||
bc90aab4dc | |||
145bca48e4 | |||
99dbdb124f | |||
32c7e06152 | |||
7ecb2b5f80 | |||
97be547fdb | |||
ad52971692 | |||
d26fb1b026 | |||
f1abe65e8e | |||
7dfdbef152 | |||
1cb5eda659 | |||
bf5cde8363 | |||
e7169304e0 | |||
00545b3e10 | |||
6a012a8dd6 | |||
a49b978050 | |||
9a2502826f | |||
350a90f872 | |||
1afc0d6260 | |||
0100372027 | |||
78f5791990 | |||
0aba89b89c | |||
849568ac2f | |||
a0ac8862ef | |||
ee6662571f | |||
6127dda6bb | |||
02fc9b2618 | |||
ddf6570f50 | |||
fbe79ae570 | |||
66741b9bc1 | |||
f20237ab1c | |||
bd266ed403 | |||
26ea3a0d04 | |||
a28ff7da50 | |||
ecb040c4a4 | |||
441541e3eb | |||
a41aa8ad54 | |||
94115d0ef2 | |||
c5f94e97b5 | |||
dadf76c519 | |||
14e5a82a49 | |||
edb5a0982c | |||
6ac171bdeb | |||
9b65494bf8 | |||
74f2281ff0 | |||
3d45ea92e2 | |||
6f8a1716c0 | |||
997de285c9 | |||
dc28138da1 | |||
f57fcab0e7 | |||
1b565bf430 | |||
0a02295c1e | |||
b7d6437b77 | |||
23be78a735 | |||
99547cff21 | |||
d7bf0de442 | |||
ffad0ea2e6 | |||
3a87e8a14e | |||
0dd6d64a26 | |||
5ec9e3890e | |||
2c2d6936ba | |||
d7a9062791 | |||
f688ab61a2 | |||
fabc3fd3a4 | |||
0c478691bb | |||
2c7387e781 | |||
019c1a6e44 | |||
58d1a48ab5 | |||
ef4a421eea | |||
9de0e0c137 | |||
17694b3c60 | |||
8b35b447f0 | |||
9120c7da2e | |||
6f396bd22c | |||
08bdf69b52 | |||
2a1e752f63 | |||
78ef34ba8c | |||
74d71a54b2 | |||
cbd8355806 | |||
072c1c0670 | |||
7c887075d6 | |||
adc91d5451 | |||
ce44e07c59 | |||
5680b5d97e | |||
49ccabb5d8 | |||
0d1e141e2f | |||
06532320b5 | |||
d168219ecd | |||
815ff9211b | |||
3300510dc2 | |||
27dbf2f29d | |||
2146323894 | |||
17cb0baa65 | |||
c052130a60 | |||
ce9cfb9a3f | |||
f8fc171c36 | |||
2070b502ad | |||
3693abf0d4 | |||
07629938cb | |||
4059e5cef0 | |||
99d7191a23 | |||
eb03fc9bac | |||
92e9642e1f | |||
3c7b5aba85 | |||
dbe2e6c7e2 | |||
233a4438a5 | |||
e88d0613a6 | |||
9aa900e071 | |||
b2a0f09a7f | |||
142b0f3d2c | |||
7bca42a32e | |||
b98342c295 | |||
8e6c81ae75 | |||
013dd6aad8 | |||
fc7ed37f0d | |||
3f44160ab4 | |||
b53f69cddb | |||
68a338fb79 | |||
1fc1342959 | |||
7e6f268fae | |||
63710534f2 | |||
29f1b0868c | |||
ef577ff865 | |||
19b8467fdc | |||
8c38cad8d4 | |||
bba68229ac | |||
05bf20ec20 | |||
c45345bcaf | |||
5f5fed418a | |||
84b1499932 | |||
e39c00d090 | |||
a8626874f8 | |||
fc69fce1ba | |||
c69096b259 | |||
49c8561baf | |||
a8a412b405 | |||
0ae5e8c7fe | |||
afb183bd71 | |||
79ec5a5ffd | |||
59b90d2836 | |||
6c005c96d7 | |||
c05a2edfba | |||
80f35169a2 | |||
97cc600e82 | |||
5f100aa872 | |||
edf09958ef | |||
d715d16cad | |||
67c3174a95 | |||
0c56b424e7 | |||
d2be185aac | |||
7717fc8d8c | |||
7d25909f17 | |||
94f7b67ccd | |||
78a632d1bc | |||
b032314cd0 | |||
3ba6aed303 | |||
9b2f6680b6 | |||
ad83043084 | |||
a16f20b93d | |||
cea59c8e38 | |||
81c8f88ab9 | |||
711f410f53 | |||
2e207efce1 | |||
d0326c4416 | |||
0dccc4a33d | |||
3ab2cd92bf | |||
c3cb92cd47 | |||
760c6e7506 | |||
993ac809df | |||
aab23e7adc | |||
da81fc89d8 | |||
80d698c8c7 | |||
150403f158 | |||
6f9211ca57 | |||
1e21223a73 | |||
e73ffc1a79 | |||
503e111722 | |||
8b9203fe09 | |||
9f65f04271 | |||
5de7df1433 | |||
1537cacbf4 | |||
34a8625318 | |||
69aa7b906e | |||
735baa2e73 | |||
0cb66ecc40 | |||
3f48d088f3 | |||
24355167cc | |||
37ceec2277 | |||
5abca8d264 | |||
ff3d0de5da | |||
dd2c88dfbe | |||
5decd55bf7 | |||
780c72d092 | |||
7b2d113330 | |||
498cfda3b1 | |||
03f4c8d9c3 | |||
ccd07ba46d | |||
fd6a20ebf2 | |||
4cc3fc3316 | |||
6ba1e19621 | |||
378ab3bc94 | |||
e429bd99b1 | |||
d23a495d07 | |||
17cad108d9 | |||
c102ef7003 | |||
70e6a9328b | |||
9fdcf2f4b3 | |||
2115425bd8 | |||
9247c72be8 | |||
798345c6e5 | |||
74c52597c5 |
@ -197,10 +197,10 @@ jobs:
|
||||
- run:
|
||||
name: Download additional WP Plugins for tests
|
||||
command: |
|
||||
./do download:woo-commerce-zip 9.5.1
|
||||
./do download:woo-commerce-subscriptions-zip 7.0.0
|
||||
./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.4
|
||||
./do download:automate-woo-zip 6.1.5
|
||||
- run:
|
||||
name: Dump tests ENV variables for acceptance tests
|
||||
command: |
|
||||
@ -1082,8 +1082,8 @@ workflows:
|
||||
- acceptance_tests:
|
||||
<<: *slack-fail-post-step
|
||||
name: acceptance_oldest
|
||||
woo_core_version: 9.4.3
|
||||
woo_subscriptions_version: 6.9.1
|
||||
woo_core_version: 9.5.2
|
||||
woo_subscriptions_version: 7.0.0
|
||||
woo_memberships_version: 1.25.2
|
||||
automate_woo_version: 6.0.33
|
||||
mysql_command: --max_allowed_packet=100M
|
||||
@ -1123,8 +1123,8 @@ workflows:
|
||||
- integration_tests:
|
||||
<<: *slack-fail-post-step
|
||||
name: integration_oldest
|
||||
woo_core_version: 9.4.3
|
||||
woo_subscriptions_version: 6.9.1
|
||||
woo_core_version: 9.5.2
|
||||
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
|
||||
@ -1186,8 +1186,8 @@ workflows:
|
||||
- acceptance_tests:
|
||||
<<: *slack-fail-post-step
|
||||
name: acceptance_with_premium_oldest
|
||||
woo_core_version: 9.4.3
|
||||
woo_subscriptions_version: 6.9.1
|
||||
woo_core_version: 9.5.2
|
||||
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
|
||||
@ -1198,8 +1198,8 @@ workflows:
|
||||
- integration_tests:
|
||||
<<: *slack-fail-post-step
|
||||
name: integration_with_premium_oldest
|
||||
woo_core_version: 9.4.3
|
||||
woo_subscriptions_version: 6.9.1
|
||||
woo_core_version: 9.5.2
|
||||
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
|
||||
|
@ -13,7 +13,7 @@ max_line_length = off
|
||||
[packages/php/email-editor/**]
|
||||
indent_style = tab
|
||||
|
||||
[packages/js/email-editor/**.{js,jsx,ts,tsx}]
|
||||
[packages/js/email-editor/**.{js,jsx,ts,tsx,scss}]
|
||||
indent_style = tab
|
||||
|
||||
[*.php]
|
||||
|
14
.github/workflows/email-editor-package.yml
vendored
14
.github/workflows/email-editor-package.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: mailpoet/vendor-prefixed
|
||||
key: ${{ runner.os }}-vendor-prefixed-${{ matrix.php-version }}-${{ hashFiles('mailpoet/prefixer/composer.lock') }}-${{ hashFiles('mailpoet/composer.json') }}
|
||||
key: ${{ runner.os }}-vendor-prefixed-${{ matrix.php-version }}-${{ hashFiles('mailpoet/prefixer/composer.lock') }}-${{ hashFiles('mailpoet/prefixer/composer.json') }}
|
||||
|
||||
- name: Cache Composer vendor for test environment
|
||||
id: composer-tests-env-cache
|
||||
@ -41,7 +41,7 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: packages/php/email-editor/vendor
|
||||
key: ${{ runner.os }}-composer-email-editor-${{ matrix.php-version }}-${{ hashFiles('packages/php/email-editor/composer.lock') }}-${{ hashFiles('mailpoet/composer.json') }}
|
||||
key: ${{ runner.os }}-composer-email-editor-${{ matrix.php-version }}-${{ hashFiles('packages/php/email-editor/composer.lock') }}-${{ hashFiles('packages/php/email-editor/composer.json') }}
|
||||
|
||||
- name: Set up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
@ -63,7 +63,8 @@ jobs:
|
||||
|
||||
# Install MailPoet dependencies only if the cache was not hit
|
||||
- name: Install mailpoet dependencies
|
||||
if: steps.composer-mailpoet-cache.outputs.cache-hit != 'true'
|
||||
if: |
|
||||
steps.composer-mailpoet-cache.outputs.cache-hit != 'true' || steps.vendor-prefixed-cache.outputs.cache-hit != 'true'
|
||||
run: ./tools/vendor/composer.phar install
|
||||
working-directory: mailpoet
|
||||
|
||||
@ -143,7 +144,7 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: mailpoet/vendor-prefixed
|
||||
key: ${{ runner.os }}-vendor-prefixed-${{ matrix.php-version }}-${{ hashFiles('mailpoet/prefixer/composer.lock') }}-${{ hashFiles('mailpoet/composer.json') }}
|
||||
key: ${{ runner.os }}-vendor-prefixed-${{ matrix.php-version }}-${{ hashFiles('mailpoet/prefixer/composer.lock') }}-${{ hashFiles('mailpoet/prefixer/composer.json') }}
|
||||
|
||||
- name: Cache Composer vendor for test environment
|
||||
id: composer-tests-env-cache
|
||||
@ -157,7 +158,7 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: packages/php/email-editor/vendor
|
||||
key: ${{ runner.os }}-composer-email-editor-${{ matrix.php-version }}-${{ hashFiles('packages/php/email-editor/composer.lock') }}-${{ hashFiles('mailpoet/composer.json') }}
|
||||
key: ${{ runner.os }}-composer-email-editor-${{ matrix.php-version }}-${{ hashFiles('packages/php/email-editor/composer.lock') }}-${{ hashFiles('packages/php/email-editor/composer.json') }}
|
||||
|
||||
- name: Set up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
@ -179,7 +180,8 @@ jobs:
|
||||
|
||||
# Install MailPoet dependencies only if the cache was not hit
|
||||
- name: Install mailpoet dependencies
|
||||
if: steps.composer-mailpoet-cache.outputs.cache-hit != 'true'
|
||||
if: |
|
||||
steps.composer-mailpoet-cache.outputs.cache-hit != 'true' || steps.vendor-prefixed-cache.outputs.cache-hit != 'true'
|
||||
run: ./tools/vendor/composer.phar install
|
||||
working-directory: mailpoet
|
||||
|
||||
|
30
.woodpecker.yml
Normal file
30
.woodpecker.yml
Normal 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
|
@ -46,7 +46,6 @@ services:
|
||||
NPM_CONFIG_CACHE: '/tmp/.npm'
|
||||
XDG_CACHE_HOME: '/tmp/.cache'
|
||||
MAILPOET_DEV_SITE: 1
|
||||
MP_ENV: development
|
||||
volumes:
|
||||
- './wordpress:/var/www/html'
|
||||
- './tsconfig.base.json:/var/www/html/wp-content/plugins/tsconfig.base.json:ro'
|
||||
@ -81,7 +80,6 @@ services:
|
||||
WORDPRESS_DB_USER: wordpress
|
||||
WORDPRESS_DB_PASSWORD: wordpress
|
||||
PHP_IDE_CONFIG: 'serverName=Mailpoet'
|
||||
MP_ENV: test
|
||||
volumes:
|
||||
- './mailpoet:/var/www/html/wp-content/plugins/mailpoet'
|
||||
- './mailpoet-premium:/var/www/html/wp-content/plugins/mailpoet-premium'
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -23,6 +23,7 @@ class RoboFile extends \Robo\Tasks {
|
||||
return $this->taskExecStack()
|
||||
->stopOnFail()
|
||||
->exec('./tools/vendor/composer.phar install')
|
||||
->exec('cd ../packages/php/email-editor && ../../../mailpoet/tools/vendor/composer.phar install && cd -')
|
||||
->exec('cd .. && pnpm install --frozen-lockfile --prefer-offline')
|
||||
->addCode([$this, 'cleanupCachedFiles'])
|
||||
->run();
|
||||
@ -32,6 +33,7 @@ class RoboFile extends \Robo\Tasks {
|
||||
return $this->taskExecStack()
|
||||
->stopOnFail()
|
||||
->exec('./tools/vendor/composer.phar install')
|
||||
->exec('cd ../packages/php/email-editor && ../../../mailpoet/tools/vendor/composer.phar install && cd -')
|
||||
->addCode([$this, 'cleanupCachedFiles'])
|
||||
->run();
|
||||
}
|
||||
|
@ -161,3 +161,12 @@ ul.sending-method-benefits {
|
||||
.mailpoet_install_premium_message {
|
||||
margin-bottom: $grid-gap-medium;
|
||||
}
|
||||
|
||||
.mailpoet-verify-key-button {
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.mailpoet-premium-key-toggle {
|
||||
height: 34px;
|
||||
padding: 0 10px !important;
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ function exportMixpanel() {
|
||||
|
||||
if (
|
||||
window.mailpoet_analytics_enabled &&
|
||||
window.MailPoet.libs3rdPartyEnabled
|
||||
window.mailpoet_3rd_party_libs_enabled
|
||||
) {
|
||||
window.MailPoet.trackEvent = track;
|
||||
} else {
|
||||
|
@ -16,7 +16,11 @@ export type ApiError = {
|
||||
|
||||
export const initializeApi = () => {
|
||||
apiFetch.use((options, next) => {
|
||||
if (options.path && options.path.startsWith('/wc-analytics/')) {
|
||||
if (
|
||||
options.path &&
|
||||
(options.path.startsWith('/wc-analytics/') ||
|
||||
options.path.startsWith('/wp/v2/'))
|
||||
) {
|
||||
return apiFetch.createRootURLMiddleware(`${api.root}/`)(options, next);
|
||||
}
|
||||
return apiFetch.createRootURLMiddleware(apiUrl)(options, next);
|
||||
|
@ -143,6 +143,7 @@ export function* activate() {
|
||||
return {
|
||||
type: 'ACTIVATE',
|
||||
automation: data?.data ?? automation,
|
||||
saved: !!data?.data,
|
||||
} as const;
|
||||
}
|
||||
|
||||
|
@ -41,11 +41,15 @@ export function reducer(state: State, action): State {
|
||||
savedState: 'saved',
|
||||
};
|
||||
case 'ACTIVATE':
|
||||
return {
|
||||
...state,
|
||||
automationData: action.automation,
|
||||
savedState: 'saved',
|
||||
};
|
||||
return action.saved
|
||||
? {
|
||||
...state,
|
||||
automationData: action.automation,
|
||||
savedState: 'saved',
|
||||
}
|
||||
: {
|
||||
...state,
|
||||
};
|
||||
case 'DEACTIVATE':
|
||||
return {
|
||||
...state,
|
||||
|
@ -3,6 +3,7 @@ import { Step } from '../../../../../editor/components/automation/types';
|
||||
import { storeName } from '../../../../../editor/store';
|
||||
|
||||
const transactionalTriggers = [
|
||||
'mailpoet:custom-trigger',
|
||||
'woocommerce:order-status-changed',
|
||||
'woocommerce:order-created',
|
||||
'woocommerce:order-completed',
|
||||
@ -15,6 +16,9 @@ const transactionalTriggers = [
|
||||
'woocommerce-subscriptions:subscription-status-changed',
|
||||
'woocommerce-subscriptions:trial-ended',
|
||||
'woocommerce-subscriptions:trial-started',
|
||||
'woocommerce:buys-from-a-tag',
|
||||
'woocommerce:buys-from-a-category',
|
||||
'woocommerce:buys-a-product',
|
||||
];
|
||||
|
||||
export function isTransactional(step: Step): boolean {
|
||||
|
@ -7,6 +7,7 @@ import { step as AbandonedCartTrigger } from './steps/abandoned-cart';
|
||||
import { MailPoet } from '../../../mailpoet';
|
||||
import { step as BuysAProductTrigger } from './steps/buys-a-product';
|
||||
import { step as BuysFromACategory } from './steps/buys-from-a-category';
|
||||
import { step as BuysFromATag } from './steps/buys-from-a-tag';
|
||||
import { step as MadeAReview } from './steps/made-a-review';
|
||||
// Insert new imports here
|
||||
|
||||
@ -21,6 +22,7 @@ export const initialize = (): void => {
|
||||
registerStepType(AbandonedCartTrigger);
|
||||
registerStepType(BuysAProductTrigger);
|
||||
registerStepType(BuysFromACategory);
|
||||
registerStepType(BuysFromATag);
|
||||
registerStepType(MadeAReview);
|
||||
// Insert new steps here
|
||||
};
|
||||
|
@ -0,0 +1,86 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Search } from '@woocommerce/components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { PanelBody } from '@wordpress/components';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
import { dispatch, useSelect } from '@wordpress/data';
|
||||
import { PlainBodyTitle } from '../../../../../editor/components';
|
||||
import { storeName } from '../../../../../editor/store';
|
||||
import { OrderStatusPanel } from '../../order-status-changed/edit/order-status-panel';
|
||||
import autocompleter from './tag-autocompleter';
|
||||
|
||||
type Tag = {
|
||||
key: string | number;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
async function fetchTags(include: number[], callback: (tags: Tag[]) => void) {
|
||||
const path = addQueryArgs('/wp/v2/product_tag/', { include });
|
||||
const data: { id: number; name: string }[] = await apiFetch({
|
||||
path,
|
||||
method: 'GET',
|
||||
});
|
||||
callback(data.map((item) => ({ key: item?.id, label: item?.name })));
|
||||
}
|
||||
|
||||
export function Edit(): JSX.Element {
|
||||
const [current, setCurrent] = useState<Tag[]>([]);
|
||||
const { selectedStep } = useSelect((select) => ({
|
||||
selectedStep: select(storeName).getSelectedStep(),
|
||||
}));
|
||||
const tagIds: number[] = useMemo(
|
||||
() => (selectedStep.args?.tag_ids as number[]) ?? [],
|
||||
[selectedStep],
|
||||
);
|
||||
const [isBusy, setIsBusy] = useState(tagIds.length > 0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isBusy) {
|
||||
return;
|
||||
}
|
||||
void fetchTags(tagIds, (tags: Tag[]) => {
|
||||
setCurrent(tags);
|
||||
setIsBusy(false);
|
||||
});
|
||||
}, [isBusy, tagIds]);
|
||||
return (
|
||||
<>
|
||||
<PanelBody opened>
|
||||
<PlainBodyTitle title={__('Tags', 'mailpoet')} />
|
||||
<Search
|
||||
disabled={isBusy}
|
||||
type="custom"
|
||||
autocompleter={autocompleter}
|
||||
className={`mailpoet-product-search ${isBusy ? 'is-busy' : ''}`}
|
||||
placeholder={__('Search for a tag', 'mailpoet')}
|
||||
selected={current}
|
||||
onChange={(items: Tag[]) => {
|
||||
setCurrent(items);
|
||||
void dispatch(storeName).updateStepArgs(
|
||||
selectedStep.id,
|
||||
'tag_ids',
|
||||
items.map((item) => item.key),
|
||||
);
|
||||
}}
|
||||
multiple
|
||||
inlineTags
|
||||
/>
|
||||
</PanelBody>
|
||||
|
||||
<OrderStatusPanel
|
||||
label={__('Order settings', 'mailpoet')}
|
||||
showFrom={false}
|
||||
showTo
|
||||
toLabel={__('Order status', 'mailpoet')}
|
||||
onChange={(status, property) => {
|
||||
void dispatch(storeName).updateStepArgs(
|
||||
selectedStep.id,
|
||||
property,
|
||||
status,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { AutoCompleter } from '@woocommerce/components/build-types/search/autocompleters';
|
||||
|
||||
const tagAutoCompleter: AutoCompleter = {
|
||||
name: 'tags',
|
||||
className: 'woocommerce-search__product-result',
|
||||
options(search) {
|
||||
const query = search
|
||||
? {
|
||||
search,
|
||||
per_page: 10,
|
||||
orderby: 'count',
|
||||
}
|
||||
: {};
|
||||
return apiFetch({
|
||||
path: addQueryArgs('/wp/v2/product_tag', query),
|
||||
});
|
||||
},
|
||||
isDebounced: true,
|
||||
getOptionIdentifier(tag) {
|
||||
return tag.id as number;
|
||||
},
|
||||
getOptionKeywords(tag) {
|
||||
return [tag.name] as string[];
|
||||
},
|
||||
getFreeTextOptions(query) {
|
||||
const label = (
|
||||
<span key="name" className="woocommerce-search__result-name">
|
||||
{__('Search results', 'mailpoet')}
|
||||
</span>
|
||||
);
|
||||
const titleOption = {
|
||||
key: 'title',
|
||||
label,
|
||||
value: { id: query, name: query },
|
||||
};
|
||||
|
||||
return [titleOption];
|
||||
},
|
||||
getOptionLabel(tag) {
|
||||
return (
|
||||
<span
|
||||
key="name"
|
||||
className="woocommerce-search__result-name"
|
||||
aria-label={tag.name}
|
||||
>
|
||||
{tag.name}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
// This is slightly different than gutenberg/Autocomplete, we don't support different methods
|
||||
// of replace/insertion, so we can just return the value.
|
||||
getOptionCompletion(tag) {
|
||||
const value = {
|
||||
key: tag.id,
|
||||
label: tag.name,
|
||||
};
|
||||
return value;
|
||||
},
|
||||
};
|
||||
|
||||
export default tagAutoCompleter;
|
@ -0,0 +1,26 @@
|
||||
export function Icon(): JSX.Element {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5.73173 4.00134C5.8081 3.03517 6.61579 2.27551 7.60166 2.27551C8.58753 2.27551 9.39522 3.03517 9.47159 4.00134H10.1904C10.7479 4.00134 11.2033 4.45681 11.2033 5.01426V10.1917C11.2033 10.7492 10.7479 11.2047 10.1904 11.2047H5.01292C4.45547 11.2047 4 10.7492 4 10.1917V5.01426C4 4.45681 4.45547 4.00134 5.01292 4.00134H5.73173ZM7.60166 3.43843C7.36389 3.43843 7.15161 3.55664 7.02145 3.73861C6.96586 3.81633 6.92553 3.90533 6.90474 4.00134H8.29858C8.27779 3.90533 8.23746 3.81633 8.18187 3.73861C8.05171 3.55664 7.83943 3.43843 7.60166 3.43843ZM5.16291 5.16426V9.65285C5.16291 9.86763 5.33703 10.0417 5.55181 10.0417H9.65151C9.86629 10.0417 10.0404 9.86763 10.0404 9.65285V5.16426H9.47749V5.87717C9.47749 6.19732 9.21618 6.45863 8.89603 6.45863C8.57589 6.45863 8.31458 6.19732 8.31458 5.87717V5.16426H6.88875V5.87717C6.88875 6.19732 6.62743 6.45863 6.30729 6.45863C5.98714 6.45863 5.72583 6.19732 5.72583 5.87717V5.16426H5.16291Z"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M13.5858 4.58722C13.9609 4.21215 14.4696 4.00143 15 4.00143H18C18.5304 4.00143 19.0391 4.21215 19.4142 4.58722C19.7893 4.96229 20 5.471 20 6.00143V9.00143C20 9.53187 19.7893 10.0406 19.4142 10.4156C19.0391 10.7907 18.5304 11.0014 18 11.0014H15C14.4696 11.0014 13.9609 10.7907 13.5858 10.4156C13.2107 10.0406 13 9.53187 13 9.00143V6.00143C13 5.471 13.2107 4.96229 13.5858 4.58722ZM15 5.50143H18C18.1326 5.50143 18.2598 5.55411 18.3536 5.64788C18.4473 5.74165 18.5 5.86883 18.5 6.00143V9.00143C18.5 9.13404 18.4473 9.26122 18.3536 9.35499C18.2598 9.44876 18.1326 9.50143 18 9.50143H15C14.8674 9.50143 14.7402 9.44876 14.6464 9.35499C14.5527 9.26122 14.5 9.13404 14.5 9.00143V6.00143C14.5 5.86883 14.5527 5.74165 14.6464 5.64788C14.7402 5.55411 14.8674 5.50143 15 5.50143Z"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M13.5858 13.5844C13.9609 13.2093 14.4696 12.9986 15 12.9986H18C18.5304 12.9986 19.0391 13.2093 19.4142 13.5844C19.7893 13.9594 20 14.4681 20 14.9986V17.9986C20 18.529 19.7893 19.0377 19.4142 19.4128C19.0391 19.7879 18.5304 19.9986 18 19.9986H15C14.4696 19.9986 13.9609 19.7879 13.5858 19.4128C13.2107 19.0377 13 18.529 13 17.9986V14.9986C13 14.4681 13.2107 13.9594 13.5858 13.5844ZM15 14.4986H18C18.1326 14.4986 18.2598 14.5513 18.3536 14.645C18.4473 14.7388 18.5 14.866 18.5 14.9986V17.9986C18.5 18.1312 18.4473 18.2584 18.3536 18.3521C18.2598 18.4459 18.1326 18.4986 18 18.4986H15C14.8674 18.4986 14.7402 18.4459 14.6464 18.3521C14.5527 18.2584 14.5 18.1312 14.5 17.9986V14.9986C14.5 14.866 14.5527 14.7388 14.6464 14.645C14.7402 14.5513 14.8674 14.4986 15 14.4986Z"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M4.58579 13.5844C4.21071 13.9594 4 14.4681 4 14.9986V17.9986C4 18.529 4.21071 19.0377 4.58579 19.4128C4.96086 19.7879 5.46957 19.9986 6 19.9986H9C9.53043 19.9986 10.0391 19.7879 10.4142 19.4128C10.7893 19.0377 11 18.529 11 17.9986V14.9986C11 14.4681 10.7893 13.9594 10.4142 13.5844C10.0391 13.2093 9.53043 12.9986 9 12.9986H6C5.46957 12.9986 4.96086 13.2093 4.58579 13.5844ZM9 14.4986H6C5.86739 14.4986 5.74021 14.5513 5.64645 14.645C5.55268 14.7388 5.5 14.866 5.5 14.9986V17.9986C5.5 18.1312 5.55268 18.2584 5.64645 18.3521C5.74021 18.4459 5.86739 18.4986 6 18.4986H9C9.13261 18.4986 9.25979 18.4459 9.35355 18.3521C9.44732 18.2584 9.5 18.1312 9.5 17.9986V14.9986C9.5 14.866 9.44732 14.7388 9.35355 14.645C9.25979 14.5513 9.13261 14.4986 9 14.4986Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { StepType } from '../../../../editor/store';
|
||||
import { Edit } from './edit';
|
||||
import { Icon } from './icon';
|
||||
|
||||
const keywords = [
|
||||
// translators: noun, used as a search keyword for "Customer buys from a tag" trigger
|
||||
__('tag', 'mailpoet'),
|
||||
// translators: verb, used as a search keyword for "Customer buys from a tag" trigger
|
||||
__('buy', 'mailpoet'),
|
||||
// translators: verb, used as a search keyword for "Customer buys from a tag" trigger
|
||||
__('purchase', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer buys from a tag" trigger
|
||||
__('ecommerce', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer buys from a tag" trigger
|
||||
__('woocommerce', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer buys from a tag" trigger
|
||||
__('product', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer buys from a tag" trigger
|
||||
__('order', 'mailpoet'),
|
||||
];
|
||||
export const step: StepType = {
|
||||
key: 'woocommerce:buys-from-a-tag',
|
||||
group: 'triggers',
|
||||
title: () => __('Customer buys from a tag', 'mailpoet'),
|
||||
description: () =>
|
||||
__(
|
||||
'Start the automation when a customer buys a product from a tag.',
|
||||
'mailpoet',
|
||||
),
|
||||
|
||||
subtitle: () => __('Trigger', 'mailpoet'),
|
||||
keywords,
|
||||
foreground: '#2271b1',
|
||||
background: '#f0f6fc',
|
||||
icon: () => <Icon />,
|
||||
edit: () => <Edit />,
|
||||
} as const;
|
@ -4,27 +4,27 @@ import { Icon } from './icon';
|
||||
import { PremiumModalForStepEdit } from '../../../../components/premium-modal-steps-edit';
|
||||
|
||||
const keywords = [
|
||||
// translators: noun, used as a search keyword for "Customer makes a review" trigger
|
||||
// translators: noun, used as a search keyword for "Customer posts a review" trigger
|
||||
__('review', 'mailpoet'),
|
||||
// translators: verb, used as a search keyword for "Customer makes a review" trigger
|
||||
// translators: verb, used as a search keyword for "Customer posts a review" trigger
|
||||
__('buy', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer makes a review" trigger
|
||||
// translators: noun, used as a search keyword for "Customer posts a review" trigger
|
||||
__('comment', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer makes a review" trigger
|
||||
// translators: noun, used as a search keyword for "Customer posts a review" trigger
|
||||
__('ecommerce', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer makes a review" trigger
|
||||
// translators: noun, used as a search keyword for "Customer posts a review" trigger
|
||||
__('woocommerce', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer makes a review" trigger
|
||||
// translators: noun, used as a search keyword for "Customer posts a review" trigger
|
||||
__('product', 'mailpoet'),
|
||||
// translators: noun, used as a search keyword for "Customer makes a review" trigger
|
||||
// translators: noun, used as a search keyword for "Customer posts a review" trigger
|
||||
__('order', 'mailpoet'),
|
||||
];
|
||||
export const step: StepType = {
|
||||
key: 'woocommerce:made-a-review',
|
||||
group: 'triggers',
|
||||
title: () => __('Customer makes a review', 'mailpoet'),
|
||||
title: () => __('Customer posts a review', 'mailpoet'),
|
||||
description: () =>
|
||||
__('Start the automation when a customer makes a review.', 'mailpoet'),
|
||||
__('Start the automation when a customer posts a review.', 'mailpoet'),
|
||||
|
||||
subtitle: () => __('Trigger', 'mailpoet'),
|
||||
keywords,
|
||||
|
@ -1,21 +1,40 @@
|
||||
import { Input } from 'common/index';
|
||||
import { _x } from '@wordpress/i18n';
|
||||
import { Button, Input } from 'common/index';
|
||||
import { useAction, useSelector } from 'settings/store/hooks';
|
||||
import { useState } from 'react';
|
||||
|
||||
type KeyInputPropType = {
|
||||
placeholder?: string;
|
||||
isFullWidth?: boolean;
|
||||
forceRevealed?: boolean;
|
||||
};
|
||||
|
||||
export function KeyInput({
|
||||
placeholder,
|
||||
isFullWidth = false,
|
||||
forceRevealed = false,
|
||||
}: KeyInputPropType) {
|
||||
const state = useSelector('getKeyActivationState')();
|
||||
const setState = useAction('updateKeyActivationState');
|
||||
const [isRevealed, setIsRevealed] = useState(false);
|
||||
const inputType = forceRevealed || isRevealed ? 'text' : 'password';
|
||||
const toggleButton = !forceRevealed && (
|
||||
<Button
|
||||
className="mailpoet-premium-key-toggle"
|
||||
variant="tertiary"
|
||||
onClick={() => setIsRevealed(!isRevealed)}
|
||||
>
|
||||
{isRevealed
|
||||
? // translators: Used as a button to show or hide the premium key
|
||||
_x('Hide', 'verb', 'mailpoet')
|
||||
: // translators: Used as a button to show or hide the premium key
|
||||
_x('Show', 'verb', 'mailpoet')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<Input
|
||||
type="text"
|
||||
type={inputType}
|
||||
id="mailpoet_premium_key"
|
||||
name="premium[premium_key]"
|
||||
placeholder={placeholder}
|
||||
@ -29,6 +48,7 @@ export function KeyInput({
|
||||
key: event.target.value.trim() || null,
|
||||
})
|
||||
}
|
||||
iconEnd={toggleButton}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
4
mailpoet/assets/js/src/global.d.ts
vendored
4
mailpoet/assets/js/src/global.d.ts
vendored
@ -134,6 +134,7 @@ interface Window {
|
||||
mailpoet_date_format: string;
|
||||
mailpoet_listing_per_page: string;
|
||||
mailpoet_3rd_party_libs_enabled: string;
|
||||
mailpoet_analytics_enabled: boolean;
|
||||
mailpoet_datetime_format: string;
|
||||
mailpoet_api_version: string;
|
||||
mailpoet_email_regex: RegExp;
|
||||
@ -294,4 +295,7 @@ interface Window {
|
||||
dataInconsistencies: {
|
||||
[key: string]: number;
|
||||
};
|
||||
mailpoet_block_email_editor_enabled: boolean;
|
||||
satismeter: (action: string, data: Record<string, unknown>) => void;
|
||||
mailpoet_display_nps_email_editor: boolean;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Tooltip } from 'help-tooltip.jsx';
|
||||
import { Tooltip } from './help-tooltip.jsx';
|
||||
import { createElement } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
|
@ -73,7 +73,7 @@ export function ProductDiscovery({ onHide }: Props): JSX.Element {
|
||||
title={MailPoet.I18n.t('brandWooEmails')}
|
||||
description={MailPoet.I18n.t('brandWooEmailsDesc')}
|
||||
link="admin.php?page=mailpoet-settings#/woocommerce"
|
||||
imgSrc={`${MailPoet.cdnUrl}homepage/woo-transactional-email-illustration.png`}
|
||||
imgSrc={`${MailPoet.cdnUrl}homepage/woo-transactional-email-illustration.20241219.png`}
|
||||
isDone={tasksStatus.brandWooEmails}
|
||||
doneMessage={MailPoet.I18n.t('brandWooEmailsDone')}
|
||||
/>,
|
||||
|
@ -13,7 +13,7 @@ export function Resources(): JSX.Element {
|
||||
link="https://kb.mailpoet.com/article/141-create-an-email-types-of-campaigns?utm_source=plugin&utm_medium=homepage&utm_campaign=resources"
|
||||
abstract={MailPoet.I18n.t('createAnEmailAbstract')}
|
||||
title={MailPoet.I18n.t('createAnEmailTitle')}
|
||||
imgSrc={`${MailPoet.cdnUrl}homepage/resources/add_email.png`}
|
||||
imgSrc={`${MailPoet.cdnUrl}homepage/resources/add_email.20241219.png`}
|
||||
/>,
|
||||
<ResourcePost
|
||||
key="createAForm"
|
||||
|
@ -12,7 +12,7 @@ const Images = {
|
||||
icon_4: `${MailPoet.cdnUrl}landingpage/feature_icon_4.png`,
|
||||
},
|
||||
wooCommerceFeatureImages: {
|
||||
feature_1: `${MailPoet.cdnUrl}landingpage/woo_feature_automate_your_marketing.png`,
|
||||
feature_1: `${MailPoet.cdnUrl}landingpage/woo_feature_automate_your_marketing.20241219.png`,
|
||||
feature_2: `${MailPoet.cdnUrl}landingpage/woo_feature_measure_revenue_per_email.png`,
|
||||
feature_3: `${MailPoet.cdnUrl}landingpage/woo_feature_let_your_brand_shine.png`,
|
||||
feature_4: `${MailPoet.cdnUrl}landingpage/woo_feature_rescue_abandoned_carts.png`,
|
||||
|
@ -14,7 +14,7 @@
|
||||
"inserter": false,
|
||||
"lock": false
|
||||
},
|
||||
"apiVersion": 2,
|
||||
"apiVersion": 3,
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"attributes": {
|
||||
"logo": {
|
||||
|
@ -2,8 +2,11 @@ import { registerBlockType } from '@wordpress/blocks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
|
||||
import { PanelBody, RadioControl, Icon } from '@wordpress/components';
|
||||
import { useState } from 'react';
|
||||
import metadata from './block.json';
|
||||
import MailPoetIcon from './mailpoet-icon';
|
||||
import { PremiumModal } from '../../common/premium-modal';
|
||||
import './style.scss';
|
||||
|
||||
const getCdnUrl = () => window.mailpoet_cdn_url;
|
||||
const getPremiumPluginStatus = () => window.mailpoet_premium_active;
|
||||
@ -15,8 +18,31 @@ function LogoImage({
|
||||
logoSrc: string;
|
||||
style?: React.CSSProperties;
|
||||
}): JSX.Element {
|
||||
const [isModalOpened, setIsModalOpened] = useState(false);
|
||||
|
||||
return (
|
||||
<img src={logoSrc} style={style} alt="Powered by MailPoet" width="100px" />
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="mailpoet-email-footer-credit"
|
||||
onClick={() => setIsModalOpened(true)}
|
||||
>
|
||||
<img
|
||||
src={logoSrc}
|
||||
style={style}
|
||||
alt="Powered by MailPoet"
|
||||
width="100px"
|
||||
/>
|
||||
</button>
|
||||
{!!isModalOpened && (
|
||||
<PremiumModal onRequestClose={() => setIsModalOpened(false)}>
|
||||
{__(
|
||||
'A MailPoet logo will appear in the footer of all emails sent with the free version of MailPoet.',
|
||||
'mailpoet',
|
||||
)}
|
||||
</PremiumModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -65,15 +91,6 @@ function Edit({
|
||||
) as unknown as string,
|
||||
value: 'light',
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<LogoImage
|
||||
logoSrc={`${cdnUrl}email-editor/logo-dark.png`}
|
||||
style={{ background: '#000000' }}
|
||||
/>
|
||||
) as unknown as string,
|
||||
value: 'dark',
|
||||
},
|
||||
]}
|
||||
onChange={(value) => {
|
||||
setAttributes({
|
||||
|
@ -0,0 +1,28 @@
|
||||
.mailpoet-email-footer-credit {
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// Those styles are copied from the file _commons.scss but because there is only a few of them
|
||||
// it's better to keep them here to avoid creating some logic to include another file.
|
||||
.mailpoet-premium-modal.components-modal__frame {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.mailpoet-premium-modal-footer {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.mailpoet-premium-modal-error {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 8px;
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
// Core MailPoet styles. We need this here because the email editor does not register
|
||||
// under the mailpoet namespace and does not have access to the MailPoet plugin styles
|
||||
// These styles are required by ReviewRequest (mailpoet/assets/js/src/review-request.tsx) feature
|
||||
|
||||
// Settings
|
||||
// Global variables, config switches. Not producing any CSS.
|
||||
@ -23,3 +24,6 @@
|
||||
// Actual UI components.
|
||||
@import '../../../css/src/components-plugin/legacy-modal';
|
||||
@import '../../../css/src/components-plugin/review-request.scss';
|
||||
|
||||
//Integration
|
||||
@import './integration';
|
||||
|
@ -2,10 +2,54 @@
|
||||
// We have something similar for the PHP package in `mailpoet/lib/EmailEditor/Integrations`
|
||||
// Here, we can expose MailPoet specific components for use in the Email editor.
|
||||
|
||||
import { addFilter } from '@wordpress/hooks';
|
||||
import { withNpsPoll } from '../nps-poll';
|
||||
import { addFilter, addAction } from '@wordpress/hooks';
|
||||
import { MailPoet } from 'mailpoet';
|
||||
import { withSatismeterSurvey } from './satismeter-survey';
|
||||
import './index.scss';
|
||||
import { useValidationRules } from './validate-email-content';
|
||||
|
||||
addFilter('mailpoet_email_editor_wrap_editor_component', 'mailpoet', (editor) =>
|
||||
withNpsPoll(editor),
|
||||
withSatismeterSurvey(editor),
|
||||
);
|
||||
|
||||
addFilter(
|
||||
'mailpoet_email_editor_content_validation_rules',
|
||||
'mailpoet',
|
||||
(validationRules: []) => [...validationRules, ...useValidationRules()],
|
||||
);
|
||||
|
||||
const EVENTS_TO_TRACK = [
|
||||
'email_editor_events_editor_layout_loaded', // email editor was opened
|
||||
'email_editor_events_template_select_modal_template_selected', // a template was selected from the template-select modal
|
||||
'email_editor_events_template_select_modal_start_from_scratch_clicked', // start from scratch
|
||||
'email_editor_events_header_campaign_name_title_updated', // campaign title was used
|
||||
'email_editor_events_header_preview_dropdown_mobile_selected', // preview option - mobile
|
||||
'email_editor_events_header_preview_dropdown_desktop_selected', // preview option - desktop
|
||||
'email_editor_events_header_preview_dropdown_send_test_email_selected', // preview option - send test email
|
||||
'email_editor_events_sent_preview_email', // preview email sent
|
||||
'email_editor_events_header_preview_dropdown_preview_in_new_tab_selected', // preview option - in new tab
|
||||
'email_editor_events_rich_text_with_button_personalization_tags_shortcode_icon_clicked', // personalization_tags modal opened
|
||||
'email_editor_events_personalization_tags_modal_tag_insert_button_clicked', // personalization_tags inserted
|
||||
'email_editor_events_rich_text_with_button_input_field_updated', // either subject or preheader updated
|
||||
'email_editor_events_styles_sidebar_screen_typography_opened', // styles sidebar-typography was seen
|
||||
'email_editor_events_styles_sidebar_screen_colors_opened', // styles sidebar-colors was seen
|
||||
'email_editor_events_styles_sidebar_screen_layout_opened', // styles sidebar-layout was seen
|
||||
'email_editor_events_header_send_button_clicked', // Send button clicked
|
||||
'email_editor_events_trash_modal_move_to_trash_button_clicked', // Move to trash button was clicked
|
||||
];
|
||||
|
||||
addAction('mailpoet_email_editor_events', 'mailpoet', (editorEvents) => {
|
||||
const { name, ...data } = editorEvents;
|
||||
// To prevent going over mixpanel quota, we will limit the number of email editor events we track with mixpanel
|
||||
// Tracks will log all events. This will be done in MAILPOET-5995
|
||||
if (EVENTS_TO_TRACK.includes(String(name))) {
|
||||
MailPoet.trackEvent(name, data);
|
||||
}
|
||||
});
|
||||
|
||||
// enable email editor event tracking
|
||||
addFilter(
|
||||
'mailpoet_email_editor_events_tracking_enabled',
|
||||
'mailpoet',
|
||||
() => !!window.mailpoet_analytics_enabled,
|
||||
);
|
||||
|
@ -0,0 +1,10 @@
|
||||
.mailpoet-editor-feedback-button {
|
||||
bottom: 5px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
}
|
||||
// To be able to scroll up a bit more in case the sidebar content is long button is displayed over it.
|
||||
.edit-post-sidebar {
|
||||
box-sizing: border-box;
|
||||
padding-bottom: 40px;
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
import { MailPoet } from 'mailpoet';
|
||||
import { Button } from '@wordpress/components';
|
||||
import { useLayoutEffect, useState } from '@wordpress/element';
|
||||
import { commentContent } from '@wordpress/icons';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { initializeSatismeterSurvey } from 'nps-poll';
|
||||
|
||||
const emailEditorSatismeterWriteId = '9qCj2SJBE1s5OhnX5NYfRXu82pEDUB9x';
|
||||
|
||||
export function withSatismeterSurvey(Component) {
|
||||
return function WrappedBySurvey(props) {
|
||||
const [surveyAvailable, setSurveyAvailable] = useState(false);
|
||||
|
||||
const triggerSurvey = () => {
|
||||
// The survey is configured to open when we track the 'Request feedback' event
|
||||
window.satismeter('track', { event: 'Request feedback' });
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
// Initialize Satismeter Survey for the email editor
|
||||
void initializeSatismeterSurvey(emailEditorSatismeterWriteId)
|
||||
.then(() => {
|
||||
if (!window.satismeter) {
|
||||
return;
|
||||
}
|
||||
setSurveyAvailable(true);
|
||||
|
||||
// We want to show the survey immediately when there has been enough usage
|
||||
if (window.mailpoet_display_nps_email_editor) {
|
||||
window.mailpoet_display_nps_email_editor = false;
|
||||
void MailPoet.Ajax.post({
|
||||
api_version: MailPoet.apiVersion,
|
||||
endpoint: 'user_flags',
|
||||
action: 'set',
|
||||
data: {
|
||||
email_editor_survey_seen: MailPoet.Date.toGmtDatetimeString(
|
||||
new Date(),
|
||||
),
|
||||
},
|
||||
}).then(triggerSurvey);
|
||||
}
|
||||
})
|
||||
// Survey may fail to initialize when 3rd party libs are not allowed. It is OK we don't need to react.
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Component {...props} />
|
||||
{surveyAvailable && (
|
||||
<Button
|
||||
icon={commentContent}
|
||||
variant="tertiary"
|
||||
className="mailpoet-editor-feedback-button"
|
||||
onClick={triggerSurvey}
|
||||
>
|
||||
{__('Share feedback', 'mailpoet')}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
import { useMemo } from '@wordpress/element';
|
||||
import { createBlock } from '@wordpress/blocks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { dispatch, useSelect } from '@wordpress/data';
|
||||
import { store as blockEditorStore } from '@wordpress/block-editor';
|
||||
import { store as coreDataStore } from '@wordpress/core-data';
|
||||
|
||||
const emailEditorStore = 'email-editor/editor';
|
||||
|
||||
const contentLink = `<a data-link-href='[mailpoet/subscription-unsubscribe-url]' contenteditable='false' style='text-decoration: underline;' class='mailpoet-email-editor__personalization-tags-link'>${__(
|
||||
'Unsubscribe',
|
||||
'mailpoet',
|
||||
)}</a> | <a data-link-href='[mailpoet/subscription-manage-url]' contenteditable='false' style='text-decoration: underline;' class='mailpoet-email-editor__personalization-tags-link'>${__(
|
||||
'Manage subscription',
|
||||
'mailpoet',
|
||||
)}</a>`;
|
||||
|
||||
export function useValidationRules() {
|
||||
const { contentBlockId, hasFooter } = useSelect((select) => {
|
||||
const allBlocks = select(blockEditorStore).getBlocks();
|
||||
const noBodyBlocks = allBlocks.filter(
|
||||
(block) =>
|
||||
block.name !== 'mailpoet/powered-by-mailpoet' &&
|
||||
block.name !== 'core/post-content',
|
||||
);
|
||||
// @ts-expect-error getBlocksByName is not defined in types
|
||||
const blocks = select(blockEditorStore).getBlocksByName(
|
||||
'core/post-content',
|
||||
) as string[] | undefined;
|
||||
return {
|
||||
contentBlockId: blocks?.[0],
|
||||
hasFooter: noBodyBlocks.length > 0,
|
||||
};
|
||||
});
|
||||
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
const { editedTemplateContent, postTemplateId } = useSelect((mapSelect) => ({
|
||||
editedTemplateContent:
|
||||
// @ts-ignore
|
||||
mapSelect(emailEditorStore).getCurrentTemplateContent() as string,
|
||||
postTemplateId:
|
||||
// @ts-ignore
|
||||
mapSelect(emailEditorStore).getCurrentTemplate()?.id as string,
|
||||
}));
|
||||
|
||||
return useMemo(() => {
|
||||
const linksParagraphBlock = createBlock('core/paragraph', {
|
||||
align: 'center',
|
||||
fontSize: 'small',
|
||||
content: contentLink,
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'missing-unsubscribe-link',
|
||||
test: (emailContent: string) =>
|
||||
!emailContent.includes('[mailpoet/subscription-unsubscribe-url]'),
|
||||
message: __(
|
||||
'All emails must include an "Unsubscribe" link.',
|
||||
'mailpoet',
|
||||
),
|
||||
actions: [
|
||||
{
|
||||
label: __('Insert link', 'mailpoet'),
|
||||
onClick: () => {
|
||||
if (!hasFooter) {
|
||||
void dispatch(blockEditorStore).insertBlock(
|
||||
linksParagraphBlock,
|
||||
undefined,
|
||||
contentBlockId,
|
||||
);
|
||||
} else {
|
||||
void dispatch(coreDataStore).editEntityRecord(
|
||||
'postType',
|
||||
'wp_template',
|
||||
postTemplateId,
|
||||
{
|
||||
content: `
|
||||
${editedTemplateContent}
|
||||
<!-- wp:paragraph {"align":"center","fontSize":"small"} -->
|
||||
${contentLink}
|
||||
<!-- /wp:paragraph -->
|
||||
`,
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}, [contentBlockId, postTemplateId, hasFooter, editedTemplateContent]);
|
||||
}
|
@ -73,10 +73,16 @@ BehaviorsLookup.TextEditorBehavior = Marionette.Behavior.extend({
|
||||
return url;
|
||||
}
|
||||
|
||||
return this.documentBaseURI.toAbsolute(
|
||||
const result = this.documentBaseURI.toAbsolute(
|
||||
url,
|
||||
this.options.get('remove_script_host'),
|
||||
);
|
||||
// Because TinyMCE contains an issue when inserted URLs ampersands are encoded twice
|
||||
// We remove one of them to store the URL correctly into the database.
|
||||
// related GH issues:
|
||||
// - https://github.com/tinymce/tinymce/issues/9774
|
||||
// - https://github.com/tinymce/tinymce/issues/9618
|
||||
return result.replace('&', '&');
|
||||
},
|
||||
|
||||
plugins: this.options.plugins,
|
||||
|
@ -34,10 +34,7 @@ const redirectToNewsletterHome = () => {
|
||||
|
||||
const getEditorLink = (newsletter: NewsletterType) => {
|
||||
let editorHref = `?page=mailpoet-newsletter-editor&id=${newsletter.id}`;
|
||||
if (
|
||||
MailPoet.FeaturesController.isSupported('gutenberg_email_editor') &&
|
||||
newsletter.wp_post_id
|
||||
) {
|
||||
if (newsletter.wp_post_id) {
|
||||
editorHref = MailPoet.getBlockEmailEditorUrl(newsletter.wp_post_id);
|
||||
}
|
||||
return editorHref;
|
||||
|
@ -45,10 +45,22 @@ export function EditorSelectModal({
|
||||
if (!isModalOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MailPoet.trackEvent(
|
||||
'New Email Editor > try new email editor modal opened',
|
||||
{},
|
||||
{ send_immediately: true },
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={__('Try the new email editor', 'mailpoet')}
|
||||
onRequestClose={onClose}
|
||||
onRequestClose={() => {
|
||||
MailPoet.trackEvent(
|
||||
'New Email Editor > try new email editor modal closed',
|
||||
);
|
||||
onClose();
|
||||
}}
|
||||
className="mailpoet-new-editor-modal"
|
||||
>
|
||||
<div className="mailpoet-new-editor-modal-image">
|
||||
@ -85,7 +97,12 @@ export function EditorSelectModal({
|
||||
type="button"
|
||||
variant="tertiary"
|
||||
onClick={() => {
|
||||
onClose();
|
||||
MailPoet.trackEvent(
|
||||
'New Email Editor > try new email editor modal cancel button clicked',
|
||||
{},
|
||||
{ send_immediately: true },
|
||||
onClose,
|
||||
);
|
||||
}}
|
||||
>
|
||||
{__('Cancel', 'mailpoet')}
|
||||
@ -94,7 +111,14 @@ export function EditorSelectModal({
|
||||
type="button"
|
||||
variant="primary"
|
||||
isBusy={isLoading}
|
||||
onClick={createNewsletterAndOpenEditor}
|
||||
onClick={() => {
|
||||
MailPoet.trackEvent(
|
||||
'New Email Editor > try new email editor modal create with new editor button clicked',
|
||||
{},
|
||||
{ send_immediately: true },
|
||||
createNewsletterAndOpenEditor,
|
||||
);
|
||||
}}
|
||||
>
|
||||
{__('Continue', 'mailpoet')}
|
||||
</Button>
|
||||
|
@ -76,12 +76,8 @@ const messages = {
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: MailPoet.FeaturesController.isSupported('gutenberg_email_editor')
|
||||
? 'name'
|
||||
: 'subject',
|
||||
label: MailPoet.FeaturesController.isSupported('gutenberg_email_editor')
|
||||
? __('Name', 'mailpoet')
|
||||
: __('Subject', 'mailpoet'),
|
||||
name: 'name',
|
||||
label: __('Name', 'mailpoet'),
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
@ -114,10 +110,7 @@ const bulkActions = [
|
||||
|
||||
const confirmEdit = (newsletter) => {
|
||||
let editorHref = `?page=mailpoet-newsletter-editor&id=${newsletter.id}`;
|
||||
if (
|
||||
MailPoet.FeaturesController.isSupported('gutenberg_email_editor') &&
|
||||
newsletter.wp_post_id
|
||||
) {
|
||||
if (newsletter.wp_post_id) {
|
||||
editorHref = MailPoet.getBlockEmailEditorUrl(newsletter.wp_post_id);
|
||||
}
|
||||
|
||||
|
@ -75,10 +75,7 @@ function validateNewsletter(newsletter: NewsLetter) {
|
||||
// Don't validate emails created in the new editor.
|
||||
// The editor uses a different data format and will have own validation and also own send panel.
|
||||
// We are using the send page for the new editor only temporarily.
|
||||
if (
|
||||
MailPoet.FeaturesController.isSupported('gutenberg_email_editor') &&
|
||||
newsletter.wp_post_id !== null
|
||||
) {
|
||||
if (newsletter.wp_post_id !== null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -322,6 +319,13 @@ class NewsletterSendComponent extends Component<
|
||||
thumbnailPromise,
|
||||
validationError: validateNewsletter(response.data),
|
||||
});
|
||||
|
||||
if (response.data?.wp_post_id) {
|
||||
MailPoet.trackEvent(
|
||||
'New Email Editor > Send page opened-Newsletter created by BlockEmailEditor',
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.fail(() => {
|
||||
@ -471,6 +475,9 @@ class NewsletterSendComponent extends Component<
|
||||
scheduled: wasScheduled,
|
||||
'Segment Applied': !!this.state.item.options.filterSegmentId,
|
||||
segments,
|
||||
editor: this.state.item.wp_post_id
|
||||
? 'BlockEmailEditor'
|
||||
: 'legacyEditor',
|
||||
});
|
||||
if (wasScheduled) {
|
||||
this.context.notices.success(
|
||||
@ -871,9 +878,7 @@ class NewsletterSendComponent extends Component<
|
||||
<a
|
||||
className="mailpoet-link"
|
||||
href={
|
||||
MailPoet.FeaturesController.isSupported(
|
||||
'gutenberg_email_editor',
|
||||
) && wpPostId
|
||||
wpPostId
|
||||
? MailPoet.getBlockEmailEditorUrl(Number(wpPostId))
|
||||
: `?page=mailpoet-newsletter-editor&id=${Number(
|
||||
this.props.params.id,
|
||||
|
@ -36,9 +36,7 @@ export function NewsletterTypes({
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
|
||||
const [isSelectEditorModalOpen, setIsSelectEditorModalOpen] = useState(false);
|
||||
const isNewEmailEditorEnabled = MailPoet.FeaturesController.isSupported(
|
||||
'gutenberg_email_editor',
|
||||
);
|
||||
const isNewEmailEditorEnabled = window.mailpoet_block_email_editor_enabled;
|
||||
|
||||
const setupNewsletter = (type): void => {
|
||||
if (type !== undefined) {
|
||||
@ -156,10 +154,19 @@ export function NewsletterTypes({
|
||||
<Icon icon={chevronDown} size={24} />
|
||||
</Button>
|
||||
)}
|
||||
onToggle={(isOpen) =>
|
||||
isOpen &&
|
||||
MailPoet.trackEvent('New Email Editor > create email icon clicked')
|
||||
}
|
||||
renderContent={() => (
|
||||
<MenuItem
|
||||
variant="tertiary"
|
||||
onClick={() => setIsSelectEditorModalOpen(true)}
|
||||
onClick={() => {
|
||||
setIsSelectEditorModalOpen(true);
|
||||
MailPoet.trackEvent(
|
||||
'New Email Editor > creating using new email editor button clicked',
|
||||
);
|
||||
}}
|
||||
>
|
||||
{__('Create using the new email editor (Alpha)', 'mailpoet')}
|
||||
</MenuItem>
|
||||
|
@ -5,78 +5,101 @@ import satismeter from 'satismeter-loader';
|
||||
import { ReviewRequest } from 'review-request';
|
||||
import { getTrackingData } from 'analytics.js';
|
||||
|
||||
const useNpsPoll = () => {
|
||||
useLayoutEffect(() => {
|
||||
const showReviewRequestModal = () => {
|
||||
MailPoet.Modal.popup({
|
||||
width: 800,
|
||||
template: ReactDOMServer.renderToString(
|
||||
ReviewRequest({
|
||||
username:
|
||||
window.mailpoet_current_wp_user_firstname ||
|
||||
window.mailpoet_current_wp_user.user_login,
|
||||
reviewRequestIllustrationUrl:
|
||||
window.mailpoet_review_request_illustration_url,
|
||||
installedDaysAgo: window.mailpoet_installed_days_ago,
|
||||
}),
|
||||
),
|
||||
onInit: () => {
|
||||
document
|
||||
.getElementById('mailpoet_review_request_not_now')
|
||||
.addEventListener('click', () => MailPoet.Modal.close());
|
||||
},
|
||||
});
|
||||
};
|
||||
export const initializeSatismeterSurvey = (writeId = null) => {
|
||||
const showReviewRequestModal = () => {
|
||||
MailPoet.Modal.popup({
|
||||
width: 800,
|
||||
template: ReactDOMServer.renderToString(
|
||||
ReviewRequest({
|
||||
username:
|
||||
window.mailpoet_current_wp_user_firstname ||
|
||||
window.mailpoet_current_wp_user.user_login,
|
||||
reviewRequestIllustrationUrl:
|
||||
window.mailpoet_review_request_illustration_url,
|
||||
installedDaysAgo: window.mailpoet_installed_days_ago,
|
||||
}),
|
||||
),
|
||||
onInit: () => {
|
||||
document
|
||||
.getElementById('mailpoet_review_request_not_now')
|
||||
.addEventListener('click', () => MailPoet.Modal.close());
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const callSatismeter = (trackingData) => {
|
||||
const newUsersPollId = '6L479eVPXk7pBn6S';
|
||||
const oldUsersPollId = 'k0aJAsQAWI2ERyGv';
|
||||
const formPollId = 'EqOgKsgZd832Sz9w';
|
||||
const emailEditorPollId = '9qCj2SJBE1s5OhnX5NYfRXu82pEDUB9x';
|
||||
let writeKey;
|
||||
if (window.mailpoet_display_nps_email_editor) {
|
||||
writeKey = emailEditorPollId;
|
||||
} else if (window.mailpoet_display_nps_form) {
|
||||
writeKey = formPollId;
|
||||
} else if (window.mailpoet_is_new_user) {
|
||||
writeKey = newUsersPollId;
|
||||
} else {
|
||||
writeKey = oldUsersPollId;
|
||||
}
|
||||
satismeter({
|
||||
writeKey,
|
||||
userId: window.mailpoet_current_wp_user.ID + window.mailpoet_site_url,
|
||||
traits: {
|
||||
name: window.mailpoet_current_wp_user.user_nicename,
|
||||
email: window.mailpoet_current_wp_user.user_email,
|
||||
mailpoetVersion: window.mailpoet_version,
|
||||
mailpoetPremiumIsActive: window.mailpoet_premium_active,
|
||||
createdAt: trackingData.installedAtIso,
|
||||
newslettersSent: trackingData.newslettersSent,
|
||||
welcomeEmails: trackingData.welcomeEmails,
|
||||
postnotificationEmails: trackingData.postnotificationEmails,
|
||||
woocommerceEmails: trackingData.woocommerceEmails,
|
||||
subscribers: trackingData.subscribers,
|
||||
lists: trackingData.lists,
|
||||
sendingMethod: trackingData.sendingMethod,
|
||||
woocommerceIsInstalled: trackingData.woocommerceIsInstalled,
|
||||
},
|
||||
events: {
|
||||
submit: (response) => {
|
||||
if (response.rating >= 9 && response.completed) {
|
||||
showReviewRequestModal();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
const callSatismeter = (trackingData, customWriteId) => {
|
||||
const newUsersPollId = '6L479eVPXk7pBn6S';
|
||||
const oldUsersPollId = 'k0aJAsQAWI2ERyGv';
|
||||
const formPollId = 'EqOgKsgZd832Sz9w';
|
||||
let writeKey;
|
||||
if (customWriteId) {
|
||||
writeKey = customWriteId;
|
||||
} else if (window.mailpoet_display_nps_form) {
|
||||
writeKey = formPollId;
|
||||
} else if (window.mailpoet_is_new_user) {
|
||||
writeKey = newUsersPollId;
|
||||
} else {
|
||||
writeKey = oldUsersPollId;
|
||||
}
|
||||
const traits = {
|
||||
name: window.mailpoet_current_wp_user.user_nicename,
|
||||
email: window.mailpoet_current_wp_user.user_email,
|
||||
mailpoetVersion: window.mailpoet_version,
|
||||
mailpoetPremiumIsActive: window.mailpoet_premium_active,
|
||||
createdAt: trackingData.installedAtIso,
|
||||
newslettersSent: trackingData.newslettersSent,
|
||||
welcomeEmails: trackingData.welcomeEmails,
|
||||
postnotificationEmails: trackingData.postnotificationEmails,
|
||||
woocommerceEmails: trackingData.woocommerceEmails,
|
||||
subscribers: trackingData.subscribers,
|
||||
lists: trackingData.lists,
|
||||
sendingMethod: trackingData.sendingMethod,
|
||||
woocommerceIsInstalled: trackingData.woocommerceIsInstalled,
|
||||
woocommerceVersion: trackingData.woocommerceVersion,
|
||||
WordPressVersion: trackingData.WordPressVersion,
|
||||
blockTheme: trackingData.blockTheme,
|
||||
themeVersion: trackingData.themeVersion,
|
||||
theme: trackingData.theme,
|
||||
};
|
||||
if (trackingData.gutenbergVersion) {
|
||||
traits.gutenbergVersion = trackingData.gutenbergVersion;
|
||||
}
|
||||
if (trackingData.wooCommerceVersion) {
|
||||
traits.wooCommerceVersion = trackingData.wooCommerceVersion;
|
||||
}
|
||||
satismeter({
|
||||
writeKey,
|
||||
userId: window.mailpoet_current_wp_user.ID + window.mailpoet_site_url,
|
||||
traits,
|
||||
events: {
|
||||
submit: (response) => {
|
||||
if (response.rating >= 9 && response.completed) {
|
||||
showReviewRequestModal();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (
|
||||
window.mailpoet_display_nps_poll &&
|
||||
window.mailpoet_3rd_party_libs_enabled
|
||||
) {
|
||||
getTrackingData().then(({ data }) => callSatismeter(data));
|
||||
getTrackingData().then(({ data }) => {
|
||||
callSatismeter(data, writeId);
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const useNpsPoll = () => {
|
||||
useLayoutEffect(() => {
|
||||
// Survey may fail to initialize when 3rd party libs are not allowed. It is OK. We don't need to react.
|
||||
initializeSatismeterSurvey().catch(() => {});
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
|
@ -12,6 +12,7 @@ import { Reinstall } from './reinstall';
|
||||
import { RecalculateSubscriberScore } from './recalculate-subscriber-score';
|
||||
import { Logging } from './logging';
|
||||
import { BounceAddress } from './bounce-address';
|
||||
import { CaptchaOnSignup } from './captcha-on-signup';
|
||||
|
||||
export function Advanced() {
|
||||
return (
|
||||
@ -27,6 +28,7 @@ export function Advanced() {
|
||||
<ShareData />
|
||||
<Libs3rdParty />
|
||||
<Captcha />
|
||||
<CaptchaOnSignup />
|
||||
<Reinstall />
|
||||
<Logging />
|
||||
<SaveButton />
|
||||
|
@ -0,0 +1,44 @@
|
||||
import { t } from 'common/functions';
|
||||
import { Radio } from 'common/form/radio/radio';
|
||||
import { useSelector, useSetting } from 'settings/store/hooks';
|
||||
import { Inputs, Label } from 'settings/components';
|
||||
|
||||
export function CaptchaOnSignup() {
|
||||
const [enabled, setEnabled] = useSetting(
|
||||
'captcha',
|
||||
'on_register_forms',
|
||||
'enabled',
|
||||
);
|
||||
const hasWooCommerce = useSelector('hasWooCommerce')();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Label
|
||||
title={t('captchaOnRegisterTitle')}
|
||||
description={t(
|
||||
hasWooCommerce
|
||||
? 'captchaOnRegisterWooActiveDescription'
|
||||
: 'captchaOnRegisterWooInactiveDescription',
|
||||
)}
|
||||
htmlFor=""
|
||||
/>
|
||||
<Inputs>
|
||||
<Radio
|
||||
id="captcha-on-register-enabled"
|
||||
value="1"
|
||||
checked={enabled === '1'}
|
||||
onCheck={setEnabled}
|
||||
/>
|
||||
<label htmlFor="captcha-on-register-enabled">{t('yes')}</label>
|
||||
<span className="mailpoet-gap" />
|
||||
<Radio
|
||||
id="captcha-on-register-disabled"
|
||||
value=""
|
||||
checked={enabled === ''}
|
||||
onCheck={setEnabled}
|
||||
/>
|
||||
<label htmlFor="captcha-on-register-disabled">{t('no')}</label>
|
||||
</Inputs>
|
||||
</>
|
||||
);
|
||||
}
|
@ -50,6 +50,7 @@ function asObject<T extends Schema>(schema: T) {
|
||||
function asIs<T>(value: T): T {
|
||||
return value;
|
||||
}
|
||||
|
||||
export function normalizeSettings(data: Record<string, unknown>): Settings {
|
||||
const text = asString('');
|
||||
const disabledCheckbox = asBoolean('1', '0', '0');
|
||||
@ -135,6 +136,9 @@ export function normalizeSettings(data: Record<string, unknown>): Settings {
|
||||
recaptcha_secret_token: text,
|
||||
recaptcha_invisible_site_token: text,
|
||||
recaptcha_invisible_secret_token: text,
|
||||
on_register_forms: asObject({
|
||||
enabled: disabledRadio,
|
||||
}),
|
||||
}),
|
||||
logging: asEnum(['everything', 'errors', 'nothing'], 'errors'),
|
||||
mta_group: asEnum(['mailpoet', 'website', 'smtp'], 'website'),
|
||||
|
@ -65,6 +65,9 @@ export type Settings = {
|
||||
recaptcha_secret_token: string;
|
||||
recaptcha_invisible_site_token: string;
|
||||
recaptcha_invisible_secret_token: string;
|
||||
on_register_forms: {
|
||||
enabled: '' | '1';
|
||||
};
|
||||
};
|
||||
logging: 'everything' | 'errors' | 'nothing';
|
||||
mta_group: 'mailpoet' | 'website' | 'smtp';
|
||||
|
@ -4,7 +4,7 @@ function WelcomeWizardStepLayoutBody(props) {
|
||||
return (
|
||||
<div className="mailpoet-wizard-step">
|
||||
<div className="mailpoet-wizard-step-illustration">
|
||||
<img src={props.illustrationUrl} width="500" alt="" />
|
||||
<img src={props.illustrationUrl} alt="" />
|
||||
</div>
|
||||
<div className="mailpoet-wizard-step-content">{props.children}</div>
|
||||
</div>
|
||||
|
@ -55,6 +55,7 @@ function MSSStepSecondPart(): JSX.Element {
|
||||
'welcomeWizardMSSSecondPartInputPlaceholder',
|
||||
)}
|
||||
isFullWidth
|
||||
forceRevealed
|
||||
/>
|
||||
</label>
|
||||
|
||||
|
@ -43,6 +43,9 @@ if [ -d 'vendor-prefixed' ]; then
|
||||
mv vendor-prefixed vendor-prefixed-backup
|
||||
fi
|
||||
|
||||
echo '[BUILD] Install email editor dependencies'
|
||||
cd ../packages/php/email-editor && ../../../mailpoet/tools/vendor/composer.phar install --no-dev --prefer-dist --optimize-autoloader --no-scripts && cd -
|
||||
|
||||
# Production libraries.
|
||||
echo '[BUILD] Fetching production libraries'
|
||||
mkdir vendor-prefixed
|
||||
|
2405
mailpoet/changelog.txt
Normal file
2405
mailpoet/changelog.txt
Normal file
File diff suppressed because it is too large
Load Diff
@ -61,6 +61,9 @@
|
||||
"MailPoet\\Test\\DataGenerator\\": "tests/DataGenerator"
|
||||
}
|
||||
},
|
||||
"replace": {
|
||||
"soundasleep/html2text": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"pre-install-cmd": [
|
||||
"@php tools/install.php",
|
||||
|
10
mailpoet/composer.lock
generated
10
mailpoet/composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "9957467448f215809ac9dc8fbcab2b00",
|
||||
"content-hash": "51893b0f5ed38d130932b86b48e55b94",
|
||||
"packages": [
|
||||
{
|
||||
"name": "dragonmantank/cron-expression",
|
||||
@ -73,10 +73,11 @@
|
||||
"dist": {
|
||||
"type": "path",
|
||||
"url": "../packages/php/email-editor",
|
||||
"reference": "311798cfd57b26bb5df1fc7f97b5732e45603419"
|
||||
"reference": "53577c5aa3a97e82c58284d48c3aa339cb2a15d4"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.4"
|
||||
"php": ">=7.4",
|
||||
"soundasleep/html2text": "^2.1"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@ -101,6 +102,9 @@
|
||||
],
|
||||
"code-style-fix": [
|
||||
"../../../mailpoet/tasks/code_sniffer/vendor/bin/phpcbf -p"
|
||||
],
|
||||
"phpstan": [
|
||||
"php ./tasks/run-phpstan.php"
|
||||
]
|
||||
},
|
||||
"description": "Email editor based on WordPress Gutenberg package.",
|
||||
|
@ -298,15 +298,17 @@ class Services extends APIEndpoint {
|
||||
}
|
||||
|
||||
public function pingBridge() {
|
||||
try {
|
||||
$bridgePingResponse = $this->bridge->pingBridge();
|
||||
} catch (\Exception $e) {
|
||||
$response = $this->bridge->pingBridge();
|
||||
if ($this->wp->isWpError($response)) {
|
||||
/** @var \WP_Error $response */
|
||||
$errorDesc = $this->getErrorDescriptionByCode(Bridge::CHECK_ERROR_UNKNOWN);
|
||||
return $this->errorResponse([
|
||||
APIError::UNKNOWN => $e->getMessage(),
|
||||
APIError::UNKNOWN => "{$errorDesc}: {$response->get_error_message()}",
|
||||
]);
|
||||
}
|
||||
if (!$this->bridge->validateBridgePingResponse($bridgePingResponse)) {
|
||||
$code = $bridgePingResponse ?: Bridge::CHECK_ERROR_UNKNOWN;
|
||||
|
||||
if (!$this->bridge->validateBridgePingResponse($response)) {
|
||||
$code = $this->wp->wpRemoteRetrieveResponseCode($response) ?: Bridge::CHECK_ERROR_UNKNOWN;
|
||||
return $this->errorResponse([
|
||||
APIError::UNKNOWN => $this->getErrorDescriptionByCode($code),
|
||||
]);
|
||||
|
@ -52,9 +52,10 @@ class Help {
|
||||
* @param array<string, string> $systemInfoData The system info data array.
|
||||
*/
|
||||
$systemInfoData = WPFunctions::get()->applyFilters('mailpoet_system_info_data', $this->systemReportCollector->getData(true));
|
||||
|
||||
try {
|
||||
$cronPingUrl = $this->cronHelper->getCronUrl(CronDaemon::ACTION_PING);
|
||||
$cronPingResponse = $this->cronHelper->pingDaemon();
|
||||
$cronPingResponse = $this->systemReportCollector->getCronPingResponse();
|
||||
} catch (\Exception $e) {
|
||||
$cronPingResponse = __('Can‘t generate cron URL.', 'mailpoet') . ' (' . $e->getMessage() . ')';
|
||||
$cronPingUrl = $cronPingResponse;
|
||||
@ -62,6 +63,7 @@ class Help {
|
||||
|
||||
$mailerLog = MailerLog::getMailerLog();
|
||||
$mailerLog['sent'] = MailerLog::sentSince();
|
||||
$bridgePingResponse = $this->systemReportCollector->getBridgePingResponse();
|
||||
$systemStatusData = [
|
||||
'cron' => [
|
||||
'url' => $cronPingUrl,
|
||||
@ -70,16 +72,18 @@ class Help {
|
||||
],
|
||||
'mss' => [
|
||||
'enabled' => $this->bridge->isMailpoetSendingServiceEnabled(),
|
||||
'isReachable' => $this->bridge->validateBridgePingResponse($this->bridge->pingBridge()),
|
||||
'isReachable' => $this->bridge->validateBridgePingResponse($bridgePingResponse),
|
||||
],
|
||||
'cronStatus' => $this->cronHelper->getDaemon(),
|
||||
'queueStatus' => $mailerLog,
|
||||
];
|
||||
|
||||
$systemStatusData['cronStatus']['accessible'] = $this->cronHelper->isDaemonAccessible();
|
||||
$systemStatusData['queueStatus']['tasksStatusCounts'] = $this->scheduledTasksRepository->getCountsPerStatus();
|
||||
$systemStatusData['queueStatus']['latestTasks'] = array_map(function ($task) {
|
||||
return $this->buildTaskData($task);
|
||||
}, $this->scheduledTasksRepository->getLatestTasks(SendingQueue::TASK_TYPE));
|
||||
|
||||
$scheduledTasks = $this->scheduledTasksRepository->getLatestTasks(SendingQueue::TASK_TYPE);
|
||||
$systemStatusData['queueStatus']['latestTasks'] = array_map(fn($task) => $this->buildTaskData($task), $scheduledTasks);
|
||||
|
||||
$this->pageRenderer->displayPage(
|
||||
'help.html',
|
||||
[
|
||||
@ -132,6 +136,7 @@ class Help {
|
||||
$subscriber = $subscribers->first() ? $subscribers->first()->getSubscriber() : null;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $task->getId(),
|
||||
'type' => $task->getType(),
|
||||
|
@ -6,6 +6,8 @@ use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\AutomaticEmails\AutomaticEmails;
|
||||
use MailPoet\Config\Env;
|
||||
use MailPoet\Config\Menu;
|
||||
use MailPoet\EmailEditor\Engine\Dependency_Check;
|
||||
use MailPoet\EmailEditor\Integrations\MailPoet\DependencyNotice;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Listing\PageLimit;
|
||||
@ -53,6 +55,10 @@ class Newsletters {
|
||||
|
||||
private WooCommerce $wooCommerceSegment;
|
||||
|
||||
private Dependency_Check $dependencyCheck;
|
||||
|
||||
private DependencyNotice $dependencyNotice;
|
||||
|
||||
private CapabilitiesManager $capabilitiesManager;
|
||||
|
||||
public function __construct(
|
||||
@ -70,6 +76,8 @@ class Newsletters {
|
||||
AuthorizedEmailsController $authorizedEmailsController,
|
||||
UserFlagsController $userFlagsController,
|
||||
WooCommerce $wooCommerceSegment,
|
||||
Dependency_Check $dependencyCheck,
|
||||
DependencyNotice $dependencyNotice,
|
||||
CapabilitiesManager $capabilitiesManager
|
||||
) {
|
||||
$this->pageRenderer = $pageRenderer;
|
||||
@ -86,6 +94,8 @@ class Newsletters {
|
||||
$this->authorizedEmailsController = $authorizedEmailsController;
|
||||
$this->userFlagsController = $userFlagsController;
|
||||
$this->wooCommerceSegment = $wooCommerceSegment;
|
||||
$this->dependencyCheck = $dependencyCheck;
|
||||
$this->dependencyNotice = $dependencyNotice;
|
||||
$this->capabilitiesManager = $capabilitiesManager;
|
||||
}
|
||||
|
||||
@ -160,6 +170,8 @@ class Newsletters {
|
||||
|
||||
$data['legacy_automatic_emails_notice_dismissed'] = (bool)$this->userFlagsController->get('legacy_automatic_emails_notice_dismissed');
|
||||
|
||||
$data['block_email_editor_enabled'] = $this->dependencyCheck->are_dependencies_met(); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$this->dependencyNotice->displayMessageIfNeeded();
|
||||
$this->pageRenderer->displayPage('newsletters.html', $data);
|
||||
}
|
||||
|
||||
|
@ -161,11 +161,10 @@ class Reporter {
|
||||
'WP_MAX_MEMORY_LIMIT' => WP_MAX_MEMORY_LIMIT,
|
||||
'PHP memory_limit' => ini_get('memory_limit'),
|
||||
'PHP max_execution_time' => ini_get('max_execution_time'),
|
||||
'users_can_register' => $this->wp->getOption('users_can_register') ? 'yes' : 'no',
|
||||
'MailPoet Free version' => MAILPOET_VERSION,
|
||||
'MailPoet Premium version' => (defined('MAILPOET_PREMIUM_VERSION')) ? MAILPOET_PREMIUM_VERSION : 'N/A',
|
||||
'Total number of subscribers' => $this->subscribersFeature->getSubscribersCount(),
|
||||
'Sending Method' => isset($mta['method']) ? $mta['method'] : null,
|
||||
'Sending Method' => $mta['method'] ?? null,
|
||||
"Send all site's emails with" => $this->settings->get('send_transactional_emails') ? 'current sending method' : 'default WordPress sending method',
|
||||
'Date of plugin installation' => $this->settings->get('installed_at'),
|
||||
'Subscribe in comments' => (boolean)$this->settings->get('subscribe.on_comment.enabled', false),
|
||||
@ -183,11 +182,17 @@ class Reporter {
|
||||
'Number of active post notifications' => $newsletters['notifications_count'],
|
||||
'Number of active welcome emails' => $newsletters['welcome_newsletters_count'],
|
||||
'Total number of standard newsletters sent' => $newsletters['sent_newsletters_count'],
|
||||
'Total number of block editor gutenberg newsletters' => $newsletters['total_gutenberg_newsletter_count'],
|
||||
'Number of block editor gutenberg newsletters sent' => $newsletters['sent_gutenberg_newsletter_count'],
|
||||
'Number of segments' => isset($segments['dynamic']) ? (int)$segments['dynamic'] : 0,
|
||||
'Number of lists' => isset($segments['default']) ? (int)$segments['default'] : 0,
|
||||
'Number of subscriber tags' => $this->tagRepository->countBy([]),
|
||||
'Site is using block theme' => $this->wp->wpIsBlockTheme(),
|
||||
'Stop sending to inactive subscribers' => $inactiveSubscribersStatus,
|
||||
'CAPTCHA setting' => $this->settings->get(CaptchaConstants::TYPE_SETTING_NAME, '') ?: 'disabled',
|
||||
'Is CAPTCHA on register forms enabled' => $this->settings->get(CaptchaConstants::ON_REGISTER_FORMS_SETTING_NAME, false) ? 'yes' : 'no',
|
||||
'users_can_register' => $this->wp->getOption('users_can_register') ? 'yes' : 'no',
|
||||
'Is WooCommerce account creation on "My account" enabled' => $this->wp->getOption('woocommerce_enable_myaccount_registration') ?? 'no',
|
||||
'Plugin > MailPoet Premium' => $this->wp->isPluginActive('mailpoet-premium/mailpoet-premium.php'),
|
||||
'Plugin > bounce add-on' => $this->wp->isPluginActive('mailpoet-bounce-handler/mailpoet-bounce-handler.php'),
|
||||
'Plugin > Bloom' => $this->wp->isPluginActive('bloom-for-publishers/bloom.php'),
|
||||
@ -412,11 +417,15 @@ class Reporter {
|
||||
}
|
||||
|
||||
public function getTrackingData() {
|
||||
global $wp_version, $woocommerce; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$newsletters = $this->newslettersRepository->getAnalytics();
|
||||
$segments = $this->segmentsRepository->getCountsPerType();
|
||||
$mta = $this->settings->get('mta', []);
|
||||
$installedAt = new Carbon($this->settings->get('installed_at'));
|
||||
return [
|
||||
$theme = $this->wp->wpGetTheme();
|
||||
$result = [
|
||||
'WordPressVersion' => $wp_version, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
'pluginGutenberg' => $this->wp->isPluginActive('gutenberg/gutenberg.php'),
|
||||
'installedAtIso' => $installedAt->format(Carbon::ISO8601),
|
||||
'newslettersSent' => $newsletters['sent_newsletters_count'],
|
||||
'welcomeEmails' => $newsletters['welcome_newsletters_count'],
|
||||
@ -424,9 +433,19 @@ class Reporter {
|
||||
'woocommerceEmails' => $newsletters['automatic_emails_count'],
|
||||
'subscribers' => $this->subscribersFeature->getSubscribersCount(),
|
||||
'lists' => isset($segments['default']) ? (int)$segments['default'] : 0,
|
||||
'sendingMethod' => isset($mta['method']) ? $mta['method'] : null,
|
||||
'sendingMethod' => $mta['method'] ?? null,
|
||||
'woocommerceIsInstalled' => $this->woocommerceHelper->isWooCommerceActive(),
|
||||
'blockTheme' => $this->wp->wpIsBlockTheme(),
|
||||
'theme' => $theme->Name, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
'themeVersion' => $theme->Version, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
];
|
||||
if (defined('GUTENBERG_VERSION')) {
|
||||
$result['gutenbergVersion'] = GUTENBERG_VERSION;
|
||||
}
|
||||
if ($this->woocommerceHelper->isWooCommerceActive()) {
|
||||
$result['wooCommerceVersion'] = $woocommerce->version;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function isFilterTypeActive(string $filterType, string $action): bool {
|
||||
|
@ -148,7 +148,7 @@ class StepHandler {
|
||||
}, $subjectEntries));
|
||||
|
||||
$step->validate($validationArgs);
|
||||
$step->run($args, $this->stepRunControllerFactory->createController($args));
|
||||
$step->run($args, $this->stepRunControllerFactory->createController($args, $logger));
|
||||
|
||||
// check if run is not completed by now (e.g., one of if/else branches is empty)
|
||||
$automationRun = $this->automationRunStorage->getAutomationRun($runId);
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace MailPoet\Automation\Engine\Control;
|
||||
|
||||
use MailPoet\Automation\Engine\Control\StepRunLogger;
|
||||
use MailPoet\Automation\Engine\Data\StepRunArgs;
|
||||
|
||||
class StepRunController {
|
||||
@ -11,12 +12,17 @@ class StepRunController {
|
||||
/** @var StepRunArgs */
|
||||
private $stepRunArgs;
|
||||
|
||||
/** @var StepRunLogger */
|
||||
private $stepRunLogger;
|
||||
|
||||
public function __construct(
|
||||
StepScheduler $stepScheduler,
|
||||
StepRunArgs $stepRunArgs
|
||||
StepRunArgs $stepRunArgs,
|
||||
StepRunLogger $stepRunLogger
|
||||
) {
|
||||
$this->stepScheduler = $stepScheduler;
|
||||
$this->stepRunArgs = $stepRunArgs;
|
||||
$this->stepRunLogger = $stepRunLogger;
|
||||
}
|
||||
|
||||
public function scheduleProgress(int $timestamp = null): int {
|
||||
@ -34,4 +40,8 @@ class StepRunController {
|
||||
public function hasScheduledNextStep(): bool {
|
||||
return $this->stepScheduler->hasScheduledNextStep($this->stepRunArgs);
|
||||
}
|
||||
|
||||
public function getRunLog(): StepRunLogger {
|
||||
return $this->stepRunLogger;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace MailPoet\Automation\Engine\Control;
|
||||
|
||||
use MailPoet\Automation\Engine\Control\StepRunLogger;
|
||||
use MailPoet\Automation\Engine\Data\StepRunArgs;
|
||||
|
||||
class StepRunControllerFactory {
|
||||
@ -14,7 +15,7 @@ class StepRunControllerFactory {
|
||||
$this->stepScheduler = $stepScheduler;
|
||||
}
|
||||
|
||||
public function createController(StepRunArgs $args): StepRunController {
|
||||
return new StepRunController($this->stepScheduler, $args);
|
||||
public function createController(StepRunArgs $args, StepRunLogger $logger): StepRunController {
|
||||
return new StepRunController($this->stepScheduler, $args, $logger);
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,15 @@ class StepRunLogger {
|
||||
$this->automationRunLogStorage->updateAutomationRunLog($log);
|
||||
}
|
||||
|
||||
private function getLog(): AutomationRunLog {
|
||||
public function saveLogData(array $data): void {
|
||||
$log = $this->getLog();
|
||||
foreach ($data as $key => $value) {
|
||||
$log->setData($key, $value);
|
||||
}
|
||||
$this->automationRunLogStorage->updateAutomationRunLog($log);
|
||||
}
|
||||
|
||||
public function getLog(): AutomationRunLog {
|
||||
if (!$this->log) {
|
||||
$this->log = $this->automationRunLogStorage->getAutomationRunLogByRunAndStepId($this->runId, $this->stepId);
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ class AutomationRunLog {
|
||||
}
|
||||
}
|
||||
|
||||
public function getId(): int {
|
||||
public function getId(): ?int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,21 @@ class SendEmailAction implements Action {
|
||||
25 * DAY_IN_SECONDS, // ~1 month
|
||||
];
|
||||
|
||||
// Retry intervals for sending. These are used when the email address
|
||||
// is not confirmed, and we need send non-transactional emails.
|
||||
private const OPTIN_RETRY_INTERVALS = [
|
||||
1 * MINUTE_IN_SECONDS, // ~1 minute
|
||||
5 * MINUTE_IN_SECONDS, // ~5 minutes
|
||||
20 * MINUTE_IN_SECONDS, // ~20 minutes
|
||||
1 * HOUR_IN_SECONDS, // ~1 hour
|
||||
12 * HOUR_IN_SECONDS, // ~12 hours
|
||||
1 * DAY_IN_SECONDS, // ~1 day
|
||||
];
|
||||
private const WAIT_OPTIN = 'wait_optin';
|
||||
private const OPTIN_RETRIES = 'optin_retries';
|
||||
|
||||
private const TRANSACTIONAL_TRIGGERS = [
|
||||
'mailpoet:custom-trigger',
|
||||
'woocommerce:order-status-changed',
|
||||
'woocommerce:order-created',
|
||||
'woocommerce:order-completed',
|
||||
@ -61,6 +75,9 @@ class SendEmailAction implements Action {
|
||||
'woocommerce-subscriptions:subscription-status-changed',
|
||||
'woocommerce-subscriptions:trial-ended',
|
||||
'woocommerce-subscriptions:trial-started',
|
||||
'woocommerce:buys-from-a-tag',
|
||||
'woocommerce:buys-from-a-category',
|
||||
'woocommerce:buys-a-product',
|
||||
];
|
||||
|
||||
private AutomationController $automationController;
|
||||
@ -169,42 +186,98 @@ class SendEmailAction implements Action {
|
||||
public function run(StepRunArgs $args, StepRunController $controller): void {
|
||||
$newsletter = $this->getEmailForStep($args->getStep());
|
||||
$subscriber = $this->getSubscriber($args);
|
||||
$state = null;
|
||||
|
||||
if ($args->isFirstRun()) {
|
||||
// run #1: schedule email sending
|
||||
$subscriberStatus = $subscriber->getStatus();
|
||||
if ($newsletter->getType() !== NewsletterEntity::TYPE_AUTOMATION_TRANSACTIONAL && $subscriberStatus !== SubscriberEntity::STATUS_SUBSCRIBED) {
|
||||
// translators: %s is the subscriber's status.
|
||||
throw InvalidStateException::create()->withMessage(sprintf(__("Cannot send the email because the subscriber's status is '%s'.", 'mailpoet'), $subscriberStatus));
|
||||
}
|
||||
|
||||
if ($subscriberStatus === SubscriberEntity::STATUS_BOUNCED) {
|
||||
// translators: %s is the subscriber's status.
|
||||
throw InvalidStateException::create()->withMessage(sprintf(__("Cannot send the email because the subscriber's status is '%s'.", 'mailpoet'), $subscriberStatus));
|
||||
}
|
||||
|
||||
$meta = $this->getNewsletterMeta($args);
|
||||
try {
|
||||
$this->automationEmailScheduler->createSendingTask($newsletter, $subscriber, $meta);
|
||||
} catch (Throwable $e) {
|
||||
throw InvalidStateException::create()->withMessage(__('Could not create sending task.', 'mailpoet'));
|
||||
if ($this->isOptInRequired($newsletter, $subscriber)) {
|
||||
$controller->getRunLog()->saveLogData([self::WAIT_OPTIN => 1]);
|
||||
$this->rerunLater($args->getRunNumber(), $controller, $newsletter, $subscriber);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->scheduleEmail($args, $newsletter, $subscriber);
|
||||
} else {
|
||||
// run #N: check/sync sending status with the automation step
|
||||
// Re-running for opt-in?
|
||||
$state = $this->getRunLogData($controller);
|
||||
|
||||
if (array_key_exists(self::WAIT_OPTIN, $state) && $state[self::WAIT_OPTIN] === 1) {
|
||||
if ($this->isOptInRequired($newsletter, $subscriber)) {
|
||||
$this->rerunLater($args->getRunNumber(), $controller, $newsletter, $subscriber);
|
||||
return;
|
||||
}
|
||||
|
||||
// Subscriber is now confirmed, so we can schedule an email.
|
||||
$controller->getRunLog()->saveLogData([
|
||||
self::WAIT_OPTIN => 0,
|
||||
self::OPTIN_RETRIES => $args->getRunNumber(),
|
||||
]);
|
||||
$this->scheduleEmail($args, $newsletter, $subscriber);
|
||||
}
|
||||
|
||||
// Check/sync sending status with the automation step
|
||||
$success = $this->checkSendingStatus($args, $newsletter, $subscriber);
|
||||
if ($success) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule a progress run to sync the email sending status to the automation step.
|
||||
// Normally, a progress run is executed immediately after sending; we're scheduling
|
||||
// these runs to poll for the status if sync fails or email never sends (timeout).
|
||||
$nextInterval = self::POLL_INTERVALS[$args->getRunNumber() - 1] ?? 0;
|
||||
// At this point, we're re-running to check sending status. We need
|
||||
// to offset opt-in reruns count from sending reruns.
|
||||
$runNumber = $args->getRunNumber();
|
||||
$state = $state ?? $this->getRunLogData($controller);
|
||||
$optinRetryCount = $state[self::OPTIN_RETRIES] ?? 0;
|
||||
$runNumber -= $optinRetryCount;
|
||||
$this->rerunLater($runNumber, $controller, $newsletter, $subscriber);
|
||||
}
|
||||
|
||||
private function scheduleEmail(StepRunArgs $args, NewsletterEntity $newsletter, SubscriberEntity $subscriber): void {
|
||||
$meta = $this->getNewsletterMeta($args);
|
||||
try {
|
||||
$this->automationEmailScheduler->createSendingTask($newsletter, $subscriber, $meta);
|
||||
} catch (Throwable $e) {
|
||||
throw InvalidStateException::create()->withMessage(__('Could not create sending task.', 'mailpoet'));
|
||||
}
|
||||
}
|
||||
|
||||
private function getRunLogData(StepRunController $controller): array {
|
||||
$runLog = $controller->getRunLog()->getLog();
|
||||
return $runLog->getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a progress run to sync the email sending status to the automation step.
|
||||
* Normally, a progress run is executed immediately after sending; we're scheduling
|
||||
* these runs to poll for the status if sync fails or email never sends (timeout),
|
||||
* or if we need to wait for subscriber opt-in.
|
||||
*/
|
||||
private function rerunLater(int $runNumber, StepRunController $controller, NewsletterEntity $newsletter, SubscriberEntity $subscriber): void {
|
||||
$nextInterval = self::POLL_INTERVALS[$runNumber - 1] ?? 0;
|
||||
|
||||
// Use different intervals when retrying for opt-in.
|
||||
if ($this->isOptInRequired($newsletter, $subscriber)) {
|
||||
if ($runNumber > count(self::OPTIN_RETRY_INTERVALS)) {
|
||||
$subscriberStatus = $subscriber->getStatus();
|
||||
// translators: %s is the subscriber's status.
|
||||
throw InvalidStateException::create()->withMessage(sprintf(__("Cannot send the email because the subscriber's status is '%s'.", 'mailpoet'), $subscriberStatus));
|
||||
}
|
||||
$nextInterval = self::OPTIN_RETRY_INTERVALS[$runNumber - 1];
|
||||
}
|
||||
|
||||
$controller->scheduleProgress(time() + $nextInterval);
|
||||
}
|
||||
|
||||
private function isOptInRequired(NewsletterEntity $newsletter, SubscriberEntity $subscriber): bool {
|
||||
$subscriberStatus = $subscriber->getStatus();
|
||||
if ($newsletter->getType() === NewsletterEntity::TYPE_AUTOMATION_TRANSACTIONAL) return false;
|
||||
return $subscriberStatus !== SubscriberEntity::STATUS_SUBSCRIBED;
|
||||
}
|
||||
|
||||
/** @param mixed $data */
|
||||
public function handleEmailSent($data): void {
|
||||
if (!is_array($data)) {
|
||||
|
@ -302,7 +302,18 @@ class TemplatesFactory {
|
||||
function (): Automation {
|
||||
return $this->builder->createFromSequence(
|
||||
__('Purchased a product', 'mailpoet'),
|
||||
$this->createPurchasedTemplateBody('woocommerce:order:products')
|
||||
[
|
||||
[
|
||||
'key' => 'woocommerce:buys-a-product',
|
||||
],
|
||||
[
|
||||
'key' => 'mailpoet:send-email',
|
||||
'args' => [
|
||||
'name' => __('Important information about your order', 'mailpoet'),
|
||||
'subject' => __('Important information about your order', 'mailpoet'),
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
},
|
||||
[
|
||||
@ -324,7 +335,18 @@ class TemplatesFactory {
|
||||
function (): Automation {
|
||||
return $this->builder->createFromSequence(
|
||||
__('Purchased a product with a tag', 'mailpoet'),
|
||||
$this->createPurchasedTemplateBody('woocommerce:order:tags')
|
||||
[
|
||||
[
|
||||
'key' => 'woocommerce:buys-from-a-tag',
|
||||
],
|
||||
[
|
||||
'key' => 'mailpoet:send-email',
|
||||
'args' => [
|
||||
'name' => __('Important information about your order', 'mailpoet'),
|
||||
'subject' => __('Important information about your order', 'mailpoet'),
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
},
|
||||
[
|
||||
@ -346,7 +368,18 @@ class TemplatesFactory {
|
||||
function (): Automation {
|
||||
return $this->builder->createFromSequence(
|
||||
__('Purchased in a category', 'mailpoet'),
|
||||
$this->createPurchasedTemplateBody('woocommerce:order:categories')
|
||||
[
|
||||
[
|
||||
'key' => 'woocommerce:buys-from-a-category',
|
||||
],
|
||||
[
|
||||
'key' => 'mailpoet:send-email',
|
||||
'args' => [
|
||||
'name' => __('Important information about your order', 'mailpoet'),
|
||||
'subject' => __('Important information about your order', 'mailpoet'),
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
},
|
||||
[
|
||||
@ -355,30 +388,4 @@ class TemplatesFactory {
|
||||
AutomationTemplate::TYPE_DEFAULT
|
||||
);
|
||||
}
|
||||
|
||||
private function createPurchasedTemplateBody(string $filterField): array {
|
||||
return [
|
||||
[
|
||||
'key' => 'woocommerce:order-completed',
|
||||
'filters' => [
|
||||
'operator' => 'and',
|
||||
'groups' => [
|
||||
[
|
||||
'operator' => 'and',
|
||||
'filters' => [
|
||||
['field' => $filterField, 'condition' => 'matches-any-of', 'value' => null],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'mailpoet:send-email',
|
||||
'args' => [
|
||||
'name' => __('Important information about your order', 'mailpoet'),
|
||||
'subject' => __('Important information about your order', 'mailpoet'),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,65 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\WooCommerce\Triggers;
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Field;
|
||||
use MailPoet\Automation\Engine\Data\Filter;
|
||||
use MailPoet\Automation\Engine\Data\StepRunArgs;
|
||||
use MailPoet\Automation\Integrations\Core\Filters\EnumArrayFilter;
|
||||
use MailPoet\Automation\Integrations\Core\Filters\EnumFilter;
|
||||
use MailPoet\Validator\Builder;
|
||||
use MailPoet\Validator\Schema\ObjectSchema;
|
||||
|
||||
class BuysFromATagTrigger extends BuysAProductTrigger {
|
||||
|
||||
|
||||
const KEY = 'woocommerce:buys-from-a-tag';
|
||||
|
||||
public function getKey(): string {
|
||||
return self::KEY;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
// translators: automation trigger title
|
||||
return __('Customer buys from a tag', 'mailpoet');
|
||||
}
|
||||
|
||||
public function getArgsSchema(): ObjectSchema {
|
||||
return Builder::object([
|
||||
'tag_ids' => Builder::array(
|
||||
Builder::integer()
|
||||
)->minItems(1)->required(),
|
||||
'to' => Builder::string()->required()->default('wc-completed'),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getFilters(StepRunArgs $args): array {
|
||||
$triggerArgs = $args->getStep()->getArgs();
|
||||
$filters = [
|
||||
Filter::fromArray([
|
||||
'id' => '',
|
||||
'field_type' => Field::TYPE_ENUM_ARRAY,
|
||||
'field_key' => 'woocommerce:order:tags',
|
||||
'condition' => EnumArrayFilter::CONDITION_MATCHES_ANY_OF,
|
||||
'args' => [
|
||||
'value' => $triggerArgs['tag_ids'] ?? [],
|
||||
],
|
||||
]),
|
||||
];
|
||||
$status = str_replace('wc-', '', $triggerArgs['to'] ?? 'completed');
|
||||
if ($status === 'any') {
|
||||
return $filters;
|
||||
}
|
||||
|
||||
$filters[] = Filter::fromArray([
|
||||
'id' => '',
|
||||
'field_type' => Field::TYPE_ENUM,
|
||||
'field_key' => 'woocommerce:order:status',
|
||||
'condition' => EnumFilter::IS_ANY_OF,
|
||||
'args' => [
|
||||
'value' => [$status],
|
||||
],
|
||||
]);
|
||||
return $filters;
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ use MailPoet\Automation\Integrations\WooCommerce\SubjectTransformers\WordPressUs
|
||||
use MailPoet\Automation\Integrations\WooCommerce\Triggers\AbandonedCart\AbandonedCartTrigger;
|
||||
use MailPoet\Automation\Integrations\WooCommerce\Triggers\BuysAProductTrigger;
|
||||
use MailPoet\Automation\Integrations\WooCommerce\Triggers\BuysFromACategoryTrigger;
|
||||
use MailPoet\Automation\Integrations\WooCommerce\Triggers\BuysFromATagTrigger;
|
||||
use MailPoet\Automation\Integrations\WooCommerce\Triggers\Orders\OrderCancelledTrigger;
|
||||
use MailPoet\Automation\Integrations\WooCommerce\Triggers\Orders\OrderCompletedTrigger;
|
||||
use MailPoet\Automation\Integrations\WooCommerce\Triggers\Orders\OrderCreatedTrigger;
|
||||
@ -35,6 +36,9 @@ class WooCommerceIntegration {
|
||||
/** @var BuysAProductTrigger */
|
||||
private $buysAProductTrigger;
|
||||
|
||||
/** @var BuysFromATagTrigger */
|
||||
private $buysFromATagTrigger;
|
||||
|
||||
/** @var BuysFromACategoryTrigger */
|
||||
private $buysFromACategoryTrigger;
|
||||
|
||||
@ -67,6 +71,7 @@ class WooCommerceIntegration {
|
||||
AbandonedCartTrigger $abandonedCartTrigger,
|
||||
BuysAProductTrigger $buysAProductTrigger,
|
||||
BuysFromACategoryTrigger $buysFromACategoryTrigger,
|
||||
BuysFromATagTrigger $buysFromATagTrigger,
|
||||
AbandonedCartSubject $abandonedCartSubject,
|
||||
OrderStatusChangeSubject $orderStatusChangeSubject,
|
||||
OrderSubject $orderSubject,
|
||||
@ -82,6 +87,7 @@ class WooCommerceIntegration {
|
||||
$this->abandonedCartTrigger = $abandonedCartTrigger;
|
||||
$this->buysAProductTrigger = $buysAProductTrigger;
|
||||
$this->buysFromACategoryTrigger = $buysFromACategoryTrigger;
|
||||
$this->buysFromATagTrigger = $buysFromATagTrigger;
|
||||
$this->abandonedCartSubject = $abandonedCartSubject;
|
||||
$this->orderStatusChangeSubject = $orderStatusChangeSubject;
|
||||
$this->orderSubject = $orderSubject;
|
||||
@ -111,6 +117,7 @@ class WooCommerceIntegration {
|
||||
$registry->addTrigger($this->abandonedCartTrigger);
|
||||
$registry->addTrigger($this->buysAProductTrigger);
|
||||
$registry->addTrigger($this->buysFromACategoryTrigger);
|
||||
$registry->addTrigger($this->buysFromATagTrigger);
|
||||
$registry->addSubjectTransformer($this->wordPressUserToWooCommerceCustomerTransformer);
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ class CaptchaConstants {
|
||||
const TYPE_RECAPTCHA_INVISIBLE = 'recaptcha-invisible';
|
||||
const TYPE_DISABLED = null;
|
||||
const TYPE_SETTING_NAME = 'captcha.type';
|
||||
const ON_REGISTER_FORMS_SETTING_NAME = 'captcha.on_register_forms.enabled';
|
||||
|
||||
public static function isReCaptcha(?string $captchaType) {
|
||||
return in_array($captchaType, [self::TYPE_RECAPTCHA, self::TYPE_RECAPTCHA_INVISIBLE]);
|
||||
@ -16,4 +17,8 @@ class CaptchaConstants {
|
||||
public static function isBuiltIn(?string $captchaType) {
|
||||
return $captchaType === self::TYPE_BUILTIN;
|
||||
}
|
||||
|
||||
public static function isDisabled(?string $captchaType) {
|
||||
return $captchaType === self::TYPE_DISABLED || $captchaType === '';
|
||||
}
|
||||
}
|
||||
|
@ -10,25 +10,26 @@ class CaptchaHooks {
|
||||
|
||||
private SettingsController $settings;
|
||||
private CaptchaValidator $captchaValidator;
|
||||
private CaptchaRenderer $captchaRenderer;
|
||||
|
||||
public function __construct(
|
||||
SettingsController $settings,
|
||||
CaptchaValidator $captchaValidator
|
||||
CaptchaValidator $captchaValidator,
|
||||
CaptchaRenderer $captchaRenderer
|
||||
) {
|
||||
$this->settings = $settings;
|
||||
$this->captchaValidator = $captchaValidator;
|
||||
$this->captchaRenderer = $captchaRenderer;
|
||||
}
|
||||
|
||||
public function isEnabled(): bool {
|
||||
// A transient code to enable incremental development of the feature.
|
||||
// Later when a setting is introduced, this function will be adjusted.
|
||||
if (!in_array(getenv('MP_ENV'), ['development', 'test'])) {
|
||||
if (!$this->settings->get(CaptchaConstants::ON_REGISTER_FORMS_SETTING_NAME, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return CaptchaConstants::isBuiltIn(
|
||||
$this->settings->get('captcha.type')
|
||||
);
|
||||
$type = $this->settings->get('captcha.type');
|
||||
return CaptchaConstants::isBuiltIn($type)
|
||||
|| (CaptchaConstants::isDisabled($type) && $this->captchaRenderer->isSupported());
|
||||
}
|
||||
|
||||
public function renderInWPRegisterForm() {
|
||||
|
@ -1,15 +1,13 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Config;
|
||||
namespace MailPoet\Captcha;
|
||||
|
||||
use MailPoet\Captcha\CaptchaConstants;
|
||||
use MailPoet\Captcha\ReCaptchaRenderer;
|
||||
use MailPoet\Captcha\ReCaptchaValidator;
|
||||
use MailPoet\Config\Env;
|
||||
use MailPoet\Config\Renderer as BasicRenderer;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class HooksReCaptcha {
|
||||
class ReCaptchaHooks {
|
||||
|
||||
const RECAPTCHA_LIB_URL = 'https://www.google.com/recaptcha/api.js';
|
||||
|
||||
@ -43,9 +41,7 @@ class HooksReCaptcha {
|
||||
}
|
||||
|
||||
public function isEnabled(): bool {
|
||||
// A transient code to enable incremental development of the feature.
|
||||
// Later when a setting is introduced, this function will be adjusted.
|
||||
if (!in_array(getenv('MP_ENV'), ['development', 'test'])) {
|
||||
if (!$this->settings->get(CaptchaConstants::ON_REGISTER_FORMS_SETTING_NAME, false)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace MailPoet\Config;
|
||||
|
||||
use MailPoet\Captcha\CaptchaHooks;
|
||||
use MailPoet\Captcha\ReCaptchaHooks;
|
||||
use MailPoet\Cron\CronTrigger;
|
||||
use MailPoet\Form\DisplayFormInWPContent;
|
||||
use MailPoet\Mailer\WordPress\WordpressMailerReplacer;
|
||||
@ -72,9 +73,6 @@ class Hooks {
|
||||
/** @var HooksWooCommerce */
|
||||
private $hooksWooCommerce;
|
||||
|
||||
/** @var HooksReCaptcha */
|
||||
private $reCaptcha;
|
||||
|
||||
/** @var SubscriberChangesNotifier */
|
||||
private $subscriberChangesNotifier;
|
||||
|
||||
@ -87,6 +85,9 @@ class Hooks {
|
||||
/** @var CaptchaHooks */
|
||||
private $captchaHooks;
|
||||
|
||||
/** @var ReCaptchaHooks */
|
||||
private $reCaptchaHooks;
|
||||
|
||||
/** @var WooSystemInfoController */
|
||||
private $wooSystemInfoController;
|
||||
|
||||
@ -108,7 +109,7 @@ class Hooks {
|
||||
DisplayFormInWPContent $displayFormInWPContent,
|
||||
HooksWooCommerce $hooksWooCommerce,
|
||||
CaptchaHooks $captchaHooks,
|
||||
HooksReCaptcha $reCaptcha,
|
||||
ReCaptchaHooks $reCaptchaHooks,
|
||||
SubscriberHandler $subscriberHandler,
|
||||
SubscriberChangesNotifier $subscriberChangesNotifier,
|
||||
WP $wpSegment,
|
||||
@ -131,7 +132,7 @@ class Hooks {
|
||||
$this->subscriberHandler = $subscriberHandler;
|
||||
$this->hooksWooCommerce = $hooksWooCommerce;
|
||||
$this->captchaHooks = $captchaHooks;
|
||||
$this->reCaptcha = $reCaptcha;
|
||||
$this->reCaptchaHooks = $reCaptchaHooks;
|
||||
$this->subscriberChangesNotifier = $subscriberChangesNotifier;
|
||||
$this->dotcomLicenseProvisioner = $dotcomLicenseProvisioner;
|
||||
$this->automateWooHooks = $automateWooHooks;
|
||||
@ -250,44 +251,6 @@ class Hooks {
|
||||
);
|
||||
}
|
||||
|
||||
// reCAPTCHA on WP registration form
|
||||
if ($this->reCaptcha->isEnabled()) {
|
||||
$this->wp->addAction(
|
||||
'login_enqueue_scripts',
|
||||
[$this->reCaptcha, 'enqueueScripts']
|
||||
);
|
||||
|
||||
$this->wp->addAction(
|
||||
'register_form',
|
||||
[$this->reCaptcha, 'render']
|
||||
);
|
||||
|
||||
$this->wp->addFilter(
|
||||
'registration_errors',
|
||||
[$this->reCaptcha, 'validate'],
|
||||
10,
|
||||
3
|
||||
);
|
||||
|
||||
// reCAPTCHA on WC registration form
|
||||
if ($this->wooHelper->isWooCommerceActive()) {
|
||||
$this->wp->addAction(
|
||||
'woocommerce_before_customer_login_form',
|
||||
[$this->reCaptcha, 'enqueueScripts']
|
||||
);
|
||||
|
||||
$this->wp->addAction(
|
||||
'woocommerce_register_form',
|
||||
[$this->reCaptcha, 'render']
|
||||
);
|
||||
|
||||
$this->wp->addAction(
|
||||
'woocommerce_process_registration_errors',
|
||||
[$this->reCaptcha, 'validate']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Manage subscription
|
||||
$this->wp->addAction(
|
||||
'admin_post_mailpoet_subscription_update',
|
||||
@ -635,6 +598,7 @@ class Hooks {
|
||||
);
|
||||
}
|
||||
|
||||
// CAPTCHA on WP & WC registration forms
|
||||
public function setupCaptchaOnRegisterForm(): void {
|
||||
if ($this->captchaHooks->isEnabled()) {
|
||||
$this->wp->addAction(
|
||||
@ -662,6 +626,40 @@ class Hooks {
|
||||
3
|
||||
);
|
||||
}
|
||||
} else if ($this->reCaptchaHooks->isEnabled()) {
|
||||
$this->wp->addAction(
|
||||
'login_enqueue_scripts',
|
||||
[$this->reCaptchaHooks, 'enqueueScripts']
|
||||
);
|
||||
|
||||
$this->wp->addAction(
|
||||
'register_form',
|
||||
[$this->reCaptchaHooks, 'render']
|
||||
);
|
||||
|
||||
$this->wp->addFilter(
|
||||
'registration_errors',
|
||||
[$this->reCaptchaHooks, 'validate'],
|
||||
10,
|
||||
3
|
||||
);
|
||||
|
||||
if ($this->wooHelper->isWooCommerceActive()) {
|
||||
$this->wp->addAction(
|
||||
'woocommerce_before_customer_login_form',
|
||||
[$this->reCaptchaHooks, 'enqueueScripts']
|
||||
);
|
||||
|
||||
$this->wp->addAction(
|
||||
'woocommerce_register_form',
|
||||
[$this->reCaptchaHooks, 'render']
|
||||
);
|
||||
|
||||
$this->wp->addAction(
|
||||
'woocommerce_process_registration_errors',
|
||||
[$this->reCaptchaHooks, 'validate']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,6 @@ use MailPoet\EmailEditor\Engine\Email_Editor;
|
||||
use MailPoet\EmailEditor\Integrations\Core\Initializer as CoreEmailEditorIntegration;
|
||||
use MailPoet\EmailEditor\Integrations\MailPoet\Blocks\BlockTypesController;
|
||||
use MailPoet\EmailEditor\Integrations\MailPoet\EmailEditor as MailpoetEmailEditorIntegration;
|
||||
use MailPoet\Features\FeaturesController;
|
||||
use MailPoet\InvalidStateException;
|
||||
use MailPoet\Migrator\Cli as MigratorCli;
|
||||
use MailPoet\PostEditorBlocks\PostEditorBlock;
|
||||
@ -132,9 +131,6 @@ class Initializer {
|
||||
/** @var BlockTypesController */
|
||||
private $blockTypesController;
|
||||
|
||||
/** @var FeaturesController */
|
||||
private $featureController;
|
||||
|
||||
/** @var Url */
|
||||
private $urlHelper;
|
||||
|
||||
@ -175,7 +171,6 @@ class Initializer {
|
||||
BlockTypesController $blockTypesController,
|
||||
MailpoetEmailEditorIntegration $mailpoetEmailEditorIntegration,
|
||||
CoreEmailEditorIntegration $coreEmailEditorIntegration,
|
||||
FeaturesController $featureController,
|
||||
Url $urlHelper
|
||||
) {
|
||||
$this->rendererFactory = $rendererFactory;
|
||||
@ -210,7 +205,6 @@ class Initializer {
|
||||
$this->mailpoetEmailEditorIntegration = $mailpoetEmailEditorIntegration;
|
||||
$this->coreEmailEditorIntegration = $coreEmailEditorIntegration;
|
||||
$this->blockTypesController = $blockTypesController;
|
||||
$this->featureController = $featureController;
|
||||
$this->urlHelper = $urlHelper;
|
||||
}
|
||||
|
||||
@ -366,10 +360,8 @@ class Initializer {
|
||||
$this->subscriberActivityTracker->trackActivity();
|
||||
$this->postEditorBlock->init();
|
||||
$this->automationEngine->initialize();
|
||||
if ($this->featureController->isSupported(FeaturesController::GUTENBERG_EMAIL_EDITOR)) {
|
||||
$this->blockTypesController->initialize();
|
||||
$this->emailEditor->initialize();
|
||||
}
|
||||
$this->blockTypesController->initialize();
|
||||
$this->emailEditor->initialize();
|
||||
$this->wpFunctions->doAction('mailpoet_initialized', MAILPOET_VERSION);
|
||||
} catch (InvalidStateException $e) {
|
||||
return $this->handleRunningMigration($e);
|
||||
|
@ -45,16 +45,16 @@ class AbandonedCartWorker extends SimpleWorker {
|
||||
|
||||
$subscribers = $task->getSubscribers();
|
||||
if ($subscribers->count() !== 1) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
$subscriber = isset($subscribers[0]) ? $subscribers[0]->getSubscriber() : null;
|
||||
if (!$subscriber) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
$automation = $this->automationStorage->getAutomation((int)$automationId, (int)$automationVersion);
|
||||
if (!$automation || $automation->getStatus() !== Automation::STATUS_ACTIVE) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->wp->doAction(
|
||||
|
@ -219,6 +219,7 @@ class ContainerConfigurator implements IContainerConfigurator {
|
||||
$container->autowire(\MailPoet\Automation\Integrations\WooCommerce\Triggers\Orders\OrderCancelledTrigger::class)->setPublic(true)->setShared(false);
|
||||
$container->autowire(\MailPoet\Automation\Integrations\WooCommerce\Triggers\BuysAProductTrigger::class)->setPublic(true)->setShared(false);
|
||||
$container->autowire(\MailPoet\Automation\Integrations\WooCommerce\Triggers\BuysFromACategoryTrigger::class)->setPublic(true)->setShared(false);
|
||||
$container->autowire(\MailPoet\Automation\Integrations\WooCommerce\Triggers\BuysFromATagTrigger::class)->setPublic(true)->setShared(false);
|
||||
$container->autowire(\MailPoet\Automation\Integrations\WooCommerce\Subjects\OrderSubject::class)->setPublic(true)->setShared(false);
|
||||
$container->autowire(\MailPoet\Automation\Integrations\WooCommerce\Subjects\OrderStatusChangeSubject::class)->setPublic(true)->setShared(false);
|
||||
$container->autowire(\MailPoet\Automation\Integrations\WooCommerce\Subjects\CustomerSubject::class)->setPublic(true)->setShared(false);
|
||||
@ -337,6 +338,7 @@ class ContainerConfigurator implements IContainerConfigurator {
|
||||
$container->autowire(\MailPoet\CustomFields\CustomFieldsRepository::class)->setPublic(true);
|
||||
// Email Editor
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Email_Editor::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Dependency_Check::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Email_Api_Controller::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Settings_Controller::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Theme_Controller::class)->setPublic(true);
|
||||
@ -350,6 +352,7 @@ class ContainerConfigurator implements IContainerConfigurator {
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors\Typography_Preprocessor::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Renderer\Renderer::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Templates\Templates::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Templates\Templates_Registry::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Patterns\Patterns::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Content_Renderer::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Blocks_Registry::class)->setPublic(true);
|
||||
@ -358,8 +361,13 @@ class ContainerConfigurator implements IContainerConfigurator {
|
||||
$container->autowire(\MailPoet\EmailEditor\Integrations\MailPoet\Cli::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Integrations\MailPoet\EmailEditor::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Integrations\MailPoet\EditorPageRenderer::class)->setPublic(true);
|
||||
$container->register(\MailPoet\EmailEditor\Engine\Renderer\Css_Inliner::class)
|
||||
->setPublic(true)
|
||||
->setFactory([\MailPoet\EmailEditor\Integrations\MailPoet\MailpoetCssInlinerFactory::class, 'create']);
|
||||
$container->autowire(\MailPoet\EmailEditor\Integrations\MailPoet\MailPoetCssInliner::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Integrations\MailPoet\EmailApiController::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Integrations\MailPoet\EmailEditorPreviewEmail::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Integrations\MailPoet\DependencyNotice::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Integrations\MailPoet\Blocks\BlockTypesController::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Integrations\MailPoet\Blocks\BlockTypes\PoweredByMailpoet::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\EmailEditor\Integrations\MailPoet\Patterns\PatternsController::class)->setPublic(true);
|
||||
@ -685,7 +693,7 @@ class ContainerConfigurator implements IContainerConfigurator {
|
||||
// Tags
|
||||
$container->autowire(\MailPoet\Tags\TagRepository::class)->setPublic(true);
|
||||
// CAPTCHA
|
||||
$container->autowire(\MailPoet\Config\HooksReCaptcha::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Captcha\ReCaptchaHooks::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Captcha\ReCaptchaValidator::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Captcha\ReCaptchaRenderer::class)->setPublic(true);
|
||||
return $container;
|
||||
|
@ -24,8 +24,7 @@ abstract class AbstractBlock {
|
||||
|
||||
protected function registerAssets() {
|
||||
if (null !== $this->getEditorScript()) {
|
||||
// @todo Would usually just register, but the editor_script are not being loaded in the custom editor.
|
||||
wp_enqueue_script(
|
||||
wp_register_script(
|
||||
$this->getEditorScript('handle'),
|
||||
$this->getEditorScript('path'),
|
||||
$this->getEditorScript('dependencies'),
|
||||
@ -35,8 +34,7 @@ abstract class AbstractBlock {
|
||||
}
|
||||
|
||||
if (null !== $this->getEditorStyle()) {
|
||||
// @todo Would usually just register, but the editor_script are not being loaded in the custom editor.
|
||||
wp_enqueue_style(
|
||||
wp_register_style(
|
||||
$this->getEditorStyle('handle'),
|
||||
$this->getEditorStyle('path'),
|
||||
[],
|
||||
@ -50,7 +48,7 @@ abstract class AbstractBlock {
|
||||
if (\WP_Block_Type_Registry::get_instance()->is_registered($this->getBlockType())) {
|
||||
return;
|
||||
}
|
||||
$metadata_path = Env::$assetsPath . '/js/src/mailpoet-custom-email-editor-blocks/' . $this->blockName . '/block.json';
|
||||
$metadata_path = Env::$assetsPath . '/dist/js/email-editor-blocks/' . $this->blockName . '/block.json';
|
||||
$block_settings = [
|
||||
'render_callback' => [$this, 'render'],
|
||||
'editor_script' => $this->getEditorScript('handle'),
|
||||
@ -79,8 +77,11 @@ abstract class AbstractBlock {
|
||||
return $key ? $script[$key] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loading styles expect that the file with styles has the name `style`. If we use the name `index` or something else the prefixing of the name is different.
|
||||
*/
|
||||
protected function getEditorStyle($key = null) {
|
||||
$path = Env::$assetsUrl . '/dist/js/email-editor-blocks/style-' . $this->blockName . '-block.css';
|
||||
$path = Env::$assetsPath . '/dist/js/email-editor-blocks/style-' . $this->blockName . '-block.css';
|
||||
|
||||
if (!file_exists($path)) {
|
||||
return null;
|
||||
|
@ -0,0 +1,53 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\EmailEditor\Integrations\MailPoet;
|
||||
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\EmailEditor\Engine\Dependency_Check;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class DependencyNotice {
|
||||
private const EMAIL_EDITOR_DEPENDENCY_NOTICE = 'email_editor_dependencies_not_met';
|
||||
private WPFunctions $wp;
|
||||
private Dependency_Check $dependencyCheck;
|
||||
|
||||
public function __construct(
|
||||
WPFunctions $wp,
|
||||
Dependency_Check $dependencyCheck
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->dependencyCheck = $dependencyCheck;
|
||||
}
|
||||
|
||||
public function checkDependenciesAndEventuallyShowNotice(): bool {
|
||||
if ($this->dependencyCheck->are_dependencies_met()) {
|
||||
$this->wp->deleteTransient(self::EMAIL_EDITOR_DEPENDENCY_NOTICE);
|
||||
return false;
|
||||
}
|
||||
// For admins, we redirect to newsletters page and show notice there, for other users we display a notice immediately
|
||||
if ($this->wp->currentUserCan(AccessControl::PERMISSION_MANAGE_EMAILS)) {
|
||||
$this->wp->setTransient(self::EMAIL_EDITOR_DEPENDENCY_NOTICE, true);
|
||||
$this->wp->wpSafeRedirect($this->wp->adminUrl('admin.php?page=mailpoet-newsletters'));
|
||||
return true;
|
||||
} else {
|
||||
$this->displayMessage();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function displayMessageIfNeeded(): void {
|
||||
if ($this->wp->getTransient(self::EMAIL_EDITOR_DEPENDENCY_NOTICE)) {
|
||||
$this->displayMessage();
|
||||
}
|
||||
$this->wp->deleteTransient(self::EMAIL_EDITOR_DEPENDENCY_NOTICE);
|
||||
}
|
||||
|
||||
private function displayMessage(): void {
|
||||
$dependencyErrorMessage = sprintf(
|
||||
// translators: %1$s: WordPress version e.g. 6.7
|
||||
__('This email was created using the new editor, which requires WordPress version %1$s or higher. Please update your setup to continue editing or previewing this email.', 'mailpoet'),
|
||||
Dependency_Check::MIN_WP_VERSION,
|
||||
);
|
||||
echo '<div class="notice notice-warning is-dismissible"><p>' . esc_html($dependencyErrorMessage) . '</p></div>';
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace MailPoet\EmailEditor\Integrations\MailPoet;
|
||||
|
||||
use MailPoet\API\JSON\API;
|
||||
use MailPoet\Analytics\Analytics;
|
||||
use MailPoet\Config\Env;
|
||||
use MailPoet\Config\Installer;
|
||||
use MailPoet\Config\ServicesChecker;
|
||||
@ -10,8 +10,10 @@ use MailPoet\EmailEditor\Engine\Settings_Controller;
|
||||
use MailPoet\EmailEditor\Engine\Theme_Controller;
|
||||
use MailPoet\EmailEditor\Engine\User_Theme;
|
||||
use MailPoet\EmailEditor\Integrations\MailPoet\EmailEditor as EditorInitController;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Settings\SettingsController as MailPoetSettings;
|
||||
use MailPoet\Settings\UserFlagsController;
|
||||
use MailPoet\Util\CdnAssetUrl;
|
||||
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
@ -25,6 +27,8 @@ class EditorPageRenderer {
|
||||
|
||||
private User_Theme $userTheme;
|
||||
|
||||
private DependencyNotice $dependencyNotice;
|
||||
|
||||
private CdnAssetUrl $cdnAssetUrl;
|
||||
|
||||
private ServicesChecker $servicesChecker;
|
||||
@ -35,6 +39,10 @@ class EditorPageRenderer {
|
||||
|
||||
private NewslettersRepository $newslettersRepository;
|
||||
|
||||
private UserFlagsController $userFlagsController;
|
||||
|
||||
private Analytics $analytics;
|
||||
|
||||
public function __construct(
|
||||
WPFunctions $wp,
|
||||
Settings_Controller $settingsController,
|
||||
@ -43,8 +51,11 @@ class EditorPageRenderer {
|
||||
SubscribersFeature $subscribersFeature,
|
||||
Theme_Controller $themeController,
|
||||
User_Theme $userTheme,
|
||||
DependencyNotice $dependencyNotice,
|
||||
MailPoetSettings $mailpoetSettings,
|
||||
NewslettersRepository $newslettersRepository
|
||||
NewslettersRepository $newslettersRepository,
|
||||
UserFlagsController $userFlagsController,
|
||||
Analytics $analytics
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->settingsController = $settingsController;
|
||||
@ -53,16 +64,31 @@ class EditorPageRenderer {
|
||||
$this->subscribersFeature = $subscribersFeature;
|
||||
$this->themeController = $themeController;
|
||||
$this->userTheme = $userTheme;
|
||||
$this->dependencyNotice = $dependencyNotice;
|
||||
$this->mailpoetSettings = $mailpoetSettings;
|
||||
$this->newslettersRepository = $newslettersRepository;
|
||||
$this->userFlagsController = $userFlagsController;
|
||||
$this->analytics = $analytics;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$postId = isset($_GET['post']) ? intval($_GET['post']) : 0;
|
||||
$post = $this->wp->getPost($postId);
|
||||
if (!$post instanceof \WP_Post || $post->post_type !== EditorInitController::MAILPOET_EMAIL_POST_TYPE) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$currentPostType = $post->post_type; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
|
||||
if (!$post instanceof \WP_Post || $currentPostType !== EditorInitController::MAILPOET_EMAIL_POST_TYPE) {
|
||||
return;
|
||||
}
|
||||
$newsletter = $this->newslettersRepository->findOneBy(['wpPost' => $postId]);
|
||||
if (!$newsletter instanceof NewsletterEntity) {
|
||||
return;
|
||||
}
|
||||
$this->dependencyNotice->checkDependenciesAndEventuallyShowNotice();
|
||||
|
||||
// load analytics (mixpanel) library
|
||||
if ($this->analytics->isEnabled()) {
|
||||
add_filter('admin_footer', [$this, 'loadAnalyticsModule'], 24);
|
||||
}
|
||||
|
||||
// load mailpoet email editor JS integrations
|
||||
$editorIntegrationAssetsParams = require Env::$assetsPath . '/dist/js/email_editor_integration/email_editor_integration.asset.php';
|
||||
@ -109,30 +135,27 @@ class EditorPageRenderer {
|
||||
$assetsParams['version']
|
||||
);
|
||||
|
||||
$jsonAPIRoot = rtrim($this->wp->escUrlRaw(admin_url('admin-ajax.php')), '/');
|
||||
$token = $this->wp->wpCreateNonce('mailpoet_token');
|
||||
$apiVersion = API::CURRENT_VERSION;
|
||||
$currentUserEmail = $this->wp->wpGetCurrentUser()->user_email;
|
||||
$this->wp->wpLocalizeScript(
|
||||
'mailpoet_email_editor',
|
||||
'MailPoetEmailEditor',
|
||||
[
|
||||
'json_api_root' => esc_js($jsonAPIRoot),
|
||||
'api_token' => esc_js($token),
|
||||
'api_version' => esc_js($apiVersion),
|
||||
'cdn_url' => esc_js($this->cdnAssetUrl->generateCdnUrl("")),
|
||||
'is_premium_plugin_active' => (bool)$this->servicesChecker->isPremiumPluginActive(),
|
||||
'current_post_type' => esc_js($currentPostType),
|
||||
'current_post_id' => $post->ID,
|
||||
'current_wp_user_email' => esc_js($currentUserEmail),
|
||||
'editor_settings' => $this->settingsController->get_settings(),
|
||||
'editor_theme' => $this->themeController->get_base_theme()->get_raw_data(),
|
||||
'user_theme_post_id' => $this->userTheme->get_user_theme_post()->ID,
|
||||
'urls' => [
|
||||
'listings' => admin_url('admin.php?page=mailpoet-newsletters'),
|
||||
'send' => admin_url('admin.php?page=mailpoet-newsletters#/send/' . $newsletter->getId()),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$installedAtDiff = (new \DateTime($this->mailpoetSettings->get('installed_at')))->diff(new \DateTime());
|
||||
// Survey should be displayed only if there are 2 and more emails and the user hasn't seen it yet
|
||||
$displaySurvey = ($this->newslettersRepository->getCountOfEmailsWithWPPost() > 1) && !$this->userFlagsController->get(UserFlagsController::EMAIL_EDITOR_SURVEY);
|
||||
|
||||
// Renders additional script data that some components require e.g. PremiumModal. This is done here instead of using
|
||||
// PageRenderer since that introduces other dependencies we want to avoid. Used by getUpgradeInfo.
|
||||
@ -140,7 +163,7 @@ class EditorPageRenderer {
|
||||
$installer = new Installer(Installer::PREMIUM_PLUGIN_SLUG);
|
||||
$inline_script_data = [
|
||||
'mailpoet_premium_plugin_installed' => Installer::isPluginInstalled(Installer::PREMIUM_PLUGIN_SLUG),
|
||||
'mailpoet_premium_plugin_active' => $this->servicesChecker->isPremiumPluginActive(),
|
||||
'mailpoet_premium_active' => $this->servicesChecker->isPremiumPluginActive(),
|
||||
'mailpoet_premium_plugin_download_url' => $this->subscribersFeature->hasValidPremiumKey() ? $installer->generatePluginDownloadUrl() : null,
|
||||
'mailpoet_premium_plugin_activation_url' => $installer->generatePluginActivationUrl(Installer::PREMIUM_PLUGIN_PATH),
|
||||
'mailpoet_has_valid_api_key' => $this->subscribersFeature->hasValidApiKey(),
|
||||
@ -152,7 +175,7 @@ class EditorPageRenderer {
|
||||
'mailpoet_subscribers_limit_reached' => $this->subscribersFeature->check(),
|
||||
// settings needed for Satismeter tracking
|
||||
'mailpoet_3rd_party_libs_enabled' => $this->mailpoetSettings->get('3rd_party_libs.enabled') === '1',
|
||||
'mailpoet_display_nps_email_editor' => $this->newslettersRepository->getCountOfEmailsWithWPPost() > 1, // Poll should be displayed only if there are 2 and more emails
|
||||
'mailpoet_display_nps_email_editor' => $displaySurvey,
|
||||
'mailpoet_display_nps_poll' => true,
|
||||
'mailpoet_current_wp_user' => $this->wp->wpGetCurrentUser()->to_array(),
|
||||
'mailpoet_current_wp_user_firstname' => $this->wp->wpGetCurrentUser()->user_firstname,
|
||||
@ -162,7 +185,7 @@ class EditorPageRenderer {
|
||||
'mailpoet_installed_days_ago' => (int)$installedAtDiff->format('%a'),
|
||||
];
|
||||
$this->wp->wpAddInlineScript('mailpoet_email_editor', implode('', array_map(function ($key) use ($inline_script_data) {
|
||||
return sprintf("var %s=%s;", $key, json_encode($inline_script_data[$key]));
|
||||
return sprintf("var %s=%s;", $key, wp_json_encode($inline_script_data[$key]));
|
||||
}, array_keys($inline_script_data))), 'before');
|
||||
|
||||
// Load CSS from Post Editor
|
||||
@ -192,9 +215,14 @@ class EditorPageRenderer {
|
||||
'/wp/v2/settings',
|
||||
'/wp/v2/types?context=view',
|
||||
'/wp/v2/taxonomies?context=view',
|
||||
'/wp/v2/templates/lookup?slug=' . $templateSlug,
|
||||
];
|
||||
|
||||
if ($templateSlug) {
|
||||
$routes[] = '/wp/v2/templates/lookup?slug=' . $templateSlug;
|
||||
} else {
|
||||
$routes[] = '/wp/v2/mailpoet_email?context=edit&per_page=30&status=publish,sent';
|
||||
}
|
||||
|
||||
// Preload the data for the specified routes
|
||||
$preloadData = array_reduce(
|
||||
$routes,
|
||||
@ -211,4 +239,25 @@ class EditorPageRenderer {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function loadAnalyticsModule() { // phpcs:ignore -- MissingReturnStatement not required
|
||||
$publicId = $this->analytics->getPublicId();
|
||||
$isPublicIdNew = $this->analytics->isPublicIdNew();
|
||||
// this is required here because of `analytics-event.js` and order of script load and use in `mailpoet-email-editor-integration/index.ts`
|
||||
$libs3rdPartyEnabled = $this->mailpoetSettings->get('3rd_party_libs.enabled') === '1';
|
||||
|
||||
// we need to set this values because they are used in the analytics.html file
|
||||
?>
|
||||
<script type="text/javascript"> <?php // phpcs:ignore ?>
|
||||
window.mailpoet_analytics_enabled = true;
|
||||
window.mailpoet_analytics_public_id = '<?php echo esc_js($publicId); ?>';
|
||||
window.mailpoet_analytics_new_public_id = <?php echo wp_json_encode($isPublicIdNew); ?>;
|
||||
window.mailpoet_3rd_party_libs_enabled = <?php echo wp_json_encode($libs3rdPartyEnabled); ?>;
|
||||
window.mailpoet_version = '<?php echo esc_js(MAILPOET_VERSION); ?>';
|
||||
window.mailpoet_premium_version = '<?php echo esc_js((defined('MAILPOET_PREMIUM_VERSION')) ? MAILPOET_PREMIUM_VERSION : ''); ?>';
|
||||
</script>
|
||||
<?php
|
||||
|
||||
include_once Env::$viewsPath . '/analytics.html';
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ namespace MailPoet\EmailEditor\Integrations\MailPoet;
|
||||
|
||||
use MailPoet\EmailEditor\Integrations\MailPoet\Patterns\PatternsController;
|
||||
use MailPoet\EmailEditor\Integrations\MailPoet\Templates\TemplatesController;
|
||||
use MailPoet\Features\FeaturesController;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class EmailEditor {
|
||||
@ -12,8 +11,6 @@ class EmailEditor {
|
||||
|
||||
private WPFunctions $wp;
|
||||
|
||||
private FeaturesController $featuresController;
|
||||
|
||||
private EmailApiController $emailApiController;
|
||||
|
||||
private EditorPageRenderer $editorPageRenderer;
|
||||
@ -30,7 +27,6 @@ class EmailEditor {
|
||||
|
||||
public function __construct(
|
||||
WPFunctions $wp,
|
||||
FeaturesController $featuresController,
|
||||
EmailApiController $emailApiController,
|
||||
EditorPageRenderer $editorPageRenderer,
|
||||
EmailEditorPreviewEmail $emailEditorPreviewEmail,
|
||||
@ -40,7 +36,6 @@ class EmailEditor {
|
||||
PersonalizationTagManager $personalizationTagManager
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->featuresController = $featuresController;
|
||||
$this->emailApiController = $emailApiController;
|
||||
$this->editorPageRenderer = $editorPageRenderer;
|
||||
$this->patternsController = $patternsController;
|
||||
@ -51,9 +46,6 @@ class EmailEditor {
|
||||
}
|
||||
|
||||
public function initialize(): void {
|
||||
if (!$this->featuresController->isSupported(FeaturesController::GUTENBERG_EMAIL_EDITOR)) {
|
||||
return;
|
||||
}
|
||||
$this->cli->initialize();
|
||||
$this->wp->addFilter('mailpoet_email_editor_post_types', [$this, 'addEmailPostType']);
|
||||
$this->wp->addAction('rest_delete_mailpoet_email', [$this->emailApiController, 'trashEmail'], 10, 1);
|
||||
|
@ -0,0 +1,31 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\EmailEditor\Integrations\MailPoet;
|
||||
|
||||
use MailPoet\EmailEditor\Engine\Renderer\Css_Inliner;
|
||||
use MailPoetVendor\Pelago\Emogrifier\CssInliner;
|
||||
|
||||
class MailPoetCssInliner implements Css_Inliner {
|
||||
private CssInliner $inliner;
|
||||
|
||||
public function from_html(string $unprocessed_html): self {// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps -- we need to match the interface
|
||||
$that = new self();
|
||||
$that->inliner = CssInliner::fromHtml($unprocessed_html);
|
||||
return $that;
|
||||
}
|
||||
|
||||
public function inline_css(string $css = ''): self {// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps -- we need to match the interface
|
||||
if (!isset($this->inliner)) {
|
||||
throw new \LogicException('You must call from_html before calling inline_css');
|
||||
}
|
||||
$this->inliner->inlineCss($css);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render(): string {
|
||||
if (!isset($this->inliner)) {
|
||||
throw new \LogicException('You must call from_html before calling inline_css');
|
||||
}
|
||||
return $this->inliner->render();
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\EmailEditor\Integrations\MailPoet;
|
||||
|
||||
class MailpoetCssInlinerFactory {
|
||||
public static function create(): MailPoetCssInliner {
|
||||
return new MailPoetCssInliner();
|
||||
}
|
||||
}
|
@ -6,15 +6,15 @@ use MailPoet\EmailEditor\Integrations\MailPoet\Patterns\Pattern;
|
||||
|
||||
class OneColumn extends Pattern {
|
||||
protected $name = '1-column-content';
|
||||
protected $block_types = ['core/post-content']; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
protected $block_types = []; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
protected $template_types = ['email-template']; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
protected $categories = ['email-contents'];
|
||||
|
||||
protected function get_content(): string { // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
return '
|
||||
<!-- wp:group {"style":{"spacing":{"padding":{"right":"var:preset|spacing|20","left":"var:preset|spacing|20"}}},"layout":{"type":"constrained"}} -->
|
||||
<div class="wp-block-group" style="padding-right:var(--wp--preset--spacing--20);padding-left:var(--wp--preset--spacing--20)"><!-- wp:heading {"fontSize":"large"} -->
|
||||
<h2 class="wp-block-heading has-large-font-size">' . __('1 column layout', 'mailpoet') . '</h2>
|
||||
<div class="wp-block-group" style="padding-right:var(--wp--preset--spacing--20);padding-left:var(--wp--preset--spacing--20)"><!-- wp:heading -->
|
||||
<h2 class="wp-block-heading">' . __('1 column layout', 'mailpoet') . '</h2>
|
||||
<!-- /wp:heading -->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
|
@ -6,20 +6,20 @@ use MailPoet\EmailEditor\Integrations\MailPoet\Patterns\Pattern;
|
||||
|
||||
class ThreeColumn extends Pattern {
|
||||
protected $name = '3-column-content';
|
||||
protected $block_types = ['core/post-content']; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
protected $block_types = []; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
protected $template_types = ['email-template']; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
protected $categories = ['email-contents'];
|
||||
|
||||
protected function get_content(): string { // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
return '<!-- wp:group {"style":{"spacing":{"padding":{"top":"var:preset|spacing|10","bottom":"var:preset|spacing|10","left":"var:preset|spacing|20","right":"var:preset|spacing|20"}}},"layout":{"type":"constrained"}} -->
|
||||
<div class="wp-block-group" style="padding-top:var(--wp--preset--spacing--10);padding-right:var(--wp--preset--spacing--20);padding-bottom:var(--wp--preset--spacing--10);padding-left:var(--wp--preset--spacing--20)"><!-- wp:heading {"fontSize":"large"} -->
|
||||
<h2 class="wp-block-heading has-large-font-size">' . __('3 column layout', 'mailpoet') . '</h2>
|
||||
<div class="wp-block-group" style="padding-top:var(--wp--preset--spacing--10);padding-right:var(--wp--preset--spacing--20);padding-bottom:var(--wp--preset--spacing--10);padding-left:var(--wp--preset--spacing--20)"><!-- wp:heading -->
|
||||
<h2 class="wp-block-heading">' . __('3 column layout', 'mailpoet') . '</h2>
|
||||
<!-- /wp:heading --></div>
|
||||
<!-- /wp:group -->
|
||||
|
||||
<!-- wp:columns {"style":{"spacing":{"padding":{"right":"var:preset|spacing|10","left":"var:preset|spacing|10"}}},"metadata":{"categories":["email-contents"],"patternName":"mailpoet/1-column-content"}} -->
|
||||
<div class="wp-block-columns" style="padding-right:var(--wp--preset--spacing--10);padding-left:var(--wp--preset--spacing--10)"><!-- wp:column {"backgroundColor":"white","style":{"spacing":{"padding":{"top":"0","bottom":"0","left":"var:preset|spacing|10","right":"var:preset|spacing|10"}}}} -->
|
||||
<div class="wp-block-column has-white-background-color has-background" style="padding-top:0;padding-right:var(--wp--preset--spacing--10);padding-bottom:0;padding-left:var(--wp--preset--spacing--10)"><!-- wp:image {"scale":"cover"} -->
|
||||
<div class="wp-block-columns" style="padding-right:var(--wp--preset--spacing--10);padding-left:var(--wp--preset--spacing--10)"><!-- wp:column {"style":{"spacing":{"padding":{"top":"0","bottom":"0","left":"var:preset|spacing|10","right":"var:preset|spacing|10"}}}} -->
|
||||
<div class="wp-block-column" style="padding-top:0;padding-right:var(--wp--preset--spacing--10);padding-bottom:0;padding-left:var(--wp--preset--spacing--10)"><!-- wp:image {"scale":"cover"} -->
|
||||
<figure class="wp-block-image"><img alt="" style="object-fit:cover"/></figure>
|
||||
<!-- /wp:image --></div>
|
||||
<!-- /wp:column -->
|
||||
@ -53,6 +53,6 @@ class ThreeColumn extends Pattern {
|
||||
|
||||
protected function get_title(): string { // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
/* translators: Name of a content pattern used as starting content of an email */
|
||||
return __('3 Column', 'mailpoet');
|
||||
return __('3 Columns', 'mailpoet');
|
||||
}
|
||||
}
|
||||
|
@ -6,14 +6,14 @@ use MailPoet\EmailEditor\Integrations\MailPoet\Patterns\Pattern;
|
||||
|
||||
class TwoColumn extends Pattern {
|
||||
protected $name = '2-column-content';
|
||||
protected $block_types = ['core/post-content']; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
protected $block_types = []; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
protected $template_types = ['email-template']; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
protected $categories = ['email-contents'];
|
||||
|
||||
protected function get_content(): string { // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
return '<!-- wp:group {"style":{"spacing":{"padding":{"top":"0","bottom":"0","left":"var:preset|spacing|20","right":"var:preset|spacing|20"}}},"layout":{"type":"constrained"}} -->
|
||||
<div class="wp-block-group" style="padding-top:0;padding-right:var(--wp--preset--spacing--20);padding-bottom:0;padding-left:var(--wp--preset--spacing--20)"><!-- wp:heading {"fontSize":"large"} -->
|
||||
<h2 class="wp-block-heading has-large-font-size">' . __('2 column layout', 'mailpoet') . '</h2>
|
||||
<div class="wp-block-group" style="padding-top:0;padding-right:var(--wp--preset--spacing--20);padding-bottom:0;padding-left:var(--wp--preset--spacing--20)"><!-- wp:heading -->
|
||||
<h2 class="wp-block-heading">' . __('2 column layout', 'mailpoet') . '</h2>
|
||||
<!-- /wp:heading -->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
@ -22,8 +22,8 @@ class TwoColumn extends Pattern {
|
||||
<!-- /wp:group -->
|
||||
|
||||
<!-- wp:columns {"style":{"spacing":{"padding":{"top":"0","bottom":"0","left":"var:preset|spacing|10","right":"var:preset|spacing|10"}}}} -->
|
||||
<div class="wp-block-columns" style="padding-top:0;padding-right:var(--wp--preset--spacing--10);padding-bottom:0;padding-left:var(--wp--preset--spacing--10)"><!-- wp:column {"width":"","backgroundColor":"white","style":{"spacing":{"padding":{"top":"0","bottom":"0","left":"var:preset|spacing|10","right":"var:preset|spacing|10"}}}} -->
|
||||
<div class="wp-block-column has-white-background-color has-background" style="padding-top:0;padding-right:var(--wp--preset--spacing--10);padding-bottom:0;padding-left:var(--wp--preset--spacing--10)"><!-- wp:image -->
|
||||
<div class="wp-block-columns" style="padding-top:0;padding-right:var(--wp--preset--spacing--10);padding-bottom:0;padding-left:var(--wp--preset--spacing--10)"><!-- wp:column {"width":"","style":{"spacing":{"padding":{"top":"0","bottom":"0","left":"var:preset|spacing|10","right":"var:preset|spacing|10"}}}} -->
|
||||
<div class="wp-block-column" style="padding-top:0;padding-right:var(--wp--preset--spacing--10);padding-bottom:0;padding-left:var(--wp--preset--spacing--10)"><!-- wp:image -->
|
||||
<figure class="wp-block-image"><img alt=""/></figure>
|
||||
<!-- /wp:image -->
|
||||
|
||||
@ -33,8 +33,8 @@ class TwoColumn extends Pattern {
|
||||
<!-- /wp:button --></div>
|
||||
<!-- /wp:buttons -->
|
||||
|
||||
<!-- wp:heading {"level":3,"fontSize":"large"} -->
|
||||
<h3 class="wp-block-heading has-large-font-size">' . __('Heading', 'mailpoet') . '</h3>
|
||||
<!-- wp:heading {"level":3} -->
|
||||
<h3 class="wp-block-heading">' . __('Heading', 'mailpoet') . '</h3>
|
||||
<!-- /wp:heading -->
|
||||
|
||||
<!-- wp:paragraph -->
|
||||
@ -48,8 +48,8 @@ class TwoColumn extends Pattern {
|
||||
<!-- /wp:buttons --></div>
|
||||
<!-- /wp:column -->
|
||||
|
||||
<!-- wp:column {"backgroundColor":"white","style":{"spacing":{"padding":{"right":"var:preset|spacing|10","left":"var:preset|spacing|10"}}}} -->
|
||||
<div class="wp-block-column has-white-background-color has-background" style="padding-right:var(--wp--preset--spacing--10);padding-left:var(--wp--preset--spacing--10)"><!-- wp:image -->
|
||||
<!-- wp:column {"style":{"spacing":{"padding":{"right":"var:preset|spacing|10","left":"var:preset|spacing|10"}}}} -->
|
||||
<div class="wp-block-column" style="padding-right:var(--wp--preset--spacing--10);padding-left:var(--wp--preset--spacing--10)"><!-- wp:image -->
|
||||
<figure class="wp-block-image"><img alt=""/></figure>
|
||||
<!-- /wp:image -->
|
||||
|
||||
@ -69,6 +69,6 @@ class TwoColumn extends Pattern {
|
||||
|
||||
protected function get_title(): string { // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
/* translators: Name of a content pattern used as starting content of an email */
|
||||
return __('2 Column', 'mailpoet');
|
||||
return __('2 Columns', 'mailpoet');
|
||||
}
|
||||
}
|
||||
|
@ -18,14 +18,14 @@ class Subscriber {
|
||||
$subscriberEmail = $context['recipient_email'] ?? null;
|
||||
$subscriber = $subscriberEmail ? $this->subscribersRepository->findOneBy(['email' => $subscriberEmail]) : null;
|
||||
|
||||
return $subscriber ? $subscriber->getFirstName() : $args['default'] ?? '';
|
||||
return ($subscriber && $subscriber->getFirstName()) ? $subscriber->getFirstName() : $args['default'] ?? '';
|
||||
}
|
||||
|
||||
public function getLastName(array $context, array $args = []): string {
|
||||
$subscriberEmail = $context['recipient_email'] ?? null;
|
||||
$subscriber = $subscriberEmail ? $this->subscribersRepository->findOneBy(['email' => $subscriberEmail]) : null;
|
||||
|
||||
return $subscriber ? $subscriber->getLastName() : $args['default'] ?? '';
|
||||
return ($subscriber && $subscriber->getLastName()) ? $subscriber->getLastName() : $args['default'] ?? '';
|
||||
}
|
||||
|
||||
public function getEmail(array $context, array $args = []): string {
|
||||
|
@ -28,7 +28,7 @@ class Newsletter {
|
||||
public function getContent(): string {
|
||||
// translators: This is a text used in a footer on an email <!--[mailpoet/site-title]--> will be replaced with the site title.
|
||||
$footerText = __('You received this email because you are subscribed to the <!--[mailpoet/site-title]-->', 'mailpoet');
|
||||
return '<!-- wp:group {"backgroundColor":"white","layout":{"type":"constrained"},"lock":{"move":false,"remove":false}} -->
|
||||
return '<!-- wp:group {"backgroundColor":"white","layout":{"type":"constrained"},"lock":{"move":false,"remove":true}} -->
|
||||
<div class="wp-block-group has-white-background-color has-background">
|
||||
<!-- wp:group {"style":{"spacing":{"padding":{"top":"var:preset|spacing|30","bottom":"var:preset|spacing|10","left":"var:preset|spacing|20","right":"var:preset|spacing|20"}}}} -->
|
||||
<div
|
||||
@ -51,7 +51,7 @@ class Newsletter {
|
||||
<!-- /wp:image -->
|
||||
</div>
|
||||
<!-- /wp:group -->
|
||||
<!-- wp:post-content {"lock":{"move":false,"remove":false},"layout":{"type":"default"}} /-->
|
||||
<!-- wp:post-content {"lock":{"move":false,"remove":true},"layout":{"type":"default"}} /-->
|
||||
<!-- wp:group {"style":{"spacing":{"padding":{"right":"var:preset|spacing|20","left":"var:preset|spacing|20","top":"var:preset|spacing|10","bottom":"var:preset|spacing|10"}}}} -->
|
||||
<div
|
||||
class="wp-block-group"
|
||||
@ -73,14 +73,12 @@ class Newsletter {
|
||||
padding-top: var(--wp--preset--spacing--20);
|
||||
padding-bottom: var(--wp--preset--spacing--20);
|
||||
"
|
||||
>
|
||||
' . $footerText . '
|
||||
<br /><a href="[link:subscription_unsubscribe_url]">' . __('Unsubscribe', 'mailpoet') . '</a> |
|
||||
<a href="[link:subscription_manage_url]">' . __('Manage subscription', 'mailpoet') . '</a>
|
||||
>' . $footerText . '<br /><a data-link-href="[mailpoet/subscription-unsubscribe-url]" contenteditable="false" style="text-decoration: underline;" class="mailpoet-email-editor__personalization-tags-link">' . __('Unsubscribe', 'mailpoet') . '</a> | <a data-link-href="[mailpoet/subscription-manage-url]" contenteditable="false" style="text-decoration: underline;" class="mailpoet-email-editor__personalization-tags-link">' . __('Manage subscription', 'mailpoet') . '</a>
|
||||
</p>
|
||||
<!-- /wp:paragraph -->
|
||||
</div>
|
||||
<!-- /wp:group -->
|
||||
<!-- wp:mailpoet/powered-by-mailpoet {"lock":{"move":true,"remove":true}} /-->
|
||||
</div>
|
||||
<!-- /wp:group -->';
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace MailPoet\EmailEditor\Integrations\MailPoet\Templates;
|
||||
|
||||
use MailPoet\EmailEditor\Engine\Templates\Template;
|
||||
use MailPoet\EmailEditor\Engine\Templates\Templates_Registry;
|
||||
use MailPoet\EmailEditor\Integrations\MailPoet\EmailEditor;
|
||||
use MailPoet\EmailEditor\Integrations\MailPoet\Templates\Library\Newsletter;
|
||||
use MailPoet\Util\CdnAssetUrl;
|
||||
@ -21,19 +23,22 @@ class TemplatesController {
|
||||
}
|
||||
|
||||
public function initialize() {
|
||||
$this->wp->addAction('mailpoet_email_editor_register_templates', [$this, 'registerTemplates'], 10, 0);
|
||||
$this->wp->addFilter('mailpoet_email_editor_register_templates', [$this, 'registerTemplates'], 10, 1);
|
||||
}
|
||||
|
||||
public function registerTemplates() {
|
||||
public function registerTemplates(Templates_Registry $templatesRegistry): Templates_Registry {
|
||||
$newsletter = new Newsletter($this->cdnAssetUrl);
|
||||
register_block_template(
|
||||
$this->templatePrefix . '//' . $newsletter->getSlug(),
|
||||
[
|
||||
'title' => $newsletter->getTitle(),
|
||||
'description' => $newsletter->getDescription(),
|
||||
'content' => $newsletter->getContent(),
|
||||
'post_types' => [EmailEditor::MAILPOET_EMAIL_POST_TYPE],
|
||||
]
|
||||
|
||||
$template = new Template(
|
||||
$this->templatePrefix,
|
||||
$newsletter->getSlug(),
|
||||
$newsletter->getTitle(),
|
||||
$newsletter->getDescription(),
|
||||
$newsletter->getContent(),
|
||||
[EmailEditor::MAILPOET_EMAIL_POST_TYPE]
|
||||
);
|
||||
$templatesRegistry->register($template);
|
||||
|
||||
return $templatesRegistry;
|
||||
}
|
||||
}
|
||||
|
@ -207,12 +207,18 @@ class NewsletterEntity {
|
||||
|
||||
/**
|
||||
* @deprecated This is here only for backward compatibility with custom shortcodes https://kb.mailpoet.com/article/160-create-a-custom-shortcode
|
||||
* This can be removed after 2021-08-01
|
||||
* This can be removed after 2026-01-01
|
||||
*/
|
||||
public function __get($key) {
|
||||
$getterName = 'get' . Helpers::underscoreToCamelCase($key, $capitaliseFirstChar = true);
|
||||
$callable = [$this, $getterName];
|
||||
if (is_callable($callable)) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- Intended for deprecation warnings
|
||||
trigger_error(
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- if the function is callable, it's safe to output
|
||||
"Direct access to \$newsletter->{$key} is deprecated and will be removed after 2026-01-01. Use \$newsletter->{$getterName}() instead.",
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
return call_user_func($callable);
|
||||
}
|
||||
}
|
||||
|
@ -86,12 +86,18 @@ class SendingQueueEntity {
|
||||
|
||||
/**
|
||||
* @deprecated This is here only for backward compatibility with custom shortcodes https://kb.mailpoet.com/article/160-create-a-custom-shortcode
|
||||
* This can be removed after 2021-08-01
|
||||
* This can be removed after 2026-01-01
|
||||
*/
|
||||
public function __get($key) {
|
||||
$getterName = 'get' . Helpers::underscoreToCamelCase($key, $capitaliseFirstChar = true);
|
||||
$callable = [$this, $getterName];
|
||||
if (is_callable($callable)) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- Intended for deprecation warnings
|
||||
trigger_error(
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- if the function is callable, it's safe to output
|
||||
"Direct access to \$sendingQueue->{$key} is deprecated and will be removed after 2026-01-01. Use \$sendingQueue->{$getterName}() instead.",
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
return call_user_func($callable);
|
||||
}
|
||||
}
|
||||
|
@ -232,12 +232,18 @@ class SubscriberEntity {
|
||||
|
||||
/**
|
||||
* @deprecated This is here only for backward compatibility with custom shortcodes https://kb.mailpoet.com/article/160-create-a-custom-shortcode
|
||||
* This can be removed after 2021-08-01
|
||||
* This can be removed after 2026-01-01
|
||||
*/
|
||||
public function __get($key) {
|
||||
$getterName = 'get' . Helpers::underscoreToCamelCase($key, $capitaliseFirstChar = true);
|
||||
$callable = [$this, $getterName];
|
||||
if (is_callable($callable)) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- Intended for deprecation warnings
|
||||
trigger_error(
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- if the function is callable, it's safe to output
|
||||
"Direct access to \$subscriber->{$key} is deprecated and will be removed after 2026-01-01. Use \$subscriber->{$getterName}() instead.",
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
return call_user_func($callable);
|
||||
}
|
||||
}
|
||||
|
@ -6,13 +6,11 @@ use MailPoetVendor\Doctrine\DBAL\Exception\TableNotFoundException;
|
||||
|
||||
class FeaturesController {
|
||||
const FEATURE_BRAND_TEMPLATES = 'brand_templates';
|
||||
const GUTENBERG_EMAIL_EDITOR = 'gutenberg_email_editor';
|
||||
|
||||
// Define feature defaults in the array below in the following form:
|
||||
// self::FEATURE_NAME_OF_FEATURE => true,
|
||||
private $defaults = [
|
||||
self::FEATURE_BRAND_TEMPLATES => false,
|
||||
self::GUTENBERG_EMAIL_EDITOR => false,
|
||||
];
|
||||
|
||||
/** @var array|null */
|
||||
|
@ -0,0 +1,43 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
use MailPoet\Entities\NewsletterLinkEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
|
||||
/**
|
||||
* The plugin from the version 5.5.2 to 5.6.1 contained a bug when we stored links containing & and in some cases also links with `&amp;` in the database.
|
||||
* This migration fixes the issue by replacing `&amp;` with `& and then & with &`.
|
||||
*
|
||||
* See https://mailpoet.atlassian.net/browse/MAILPOET-6433
|
||||
*/
|
||||
class Migration_20250120_094614_App extends AppMigration {
|
||||
public function run(): void {
|
||||
$sendingQueueId = $this->getSendingQueueId();
|
||||
if ($sendingQueueId) {
|
||||
$linksTable = $this->entityManager->getClassMetadata(NewsletterLinkEntity::class)->getTableName();
|
||||
$this->entityManager->getConnection()->executeQuery("
|
||||
UPDATE {$linksTable}
|
||||
SET url = REPLACE( REPLACE(url, '&amp;', '&'), '&', '&')
|
||||
WHERE queue_id >= :queue_id;
|
||||
", ['queue_id' => $sendingQueueId]);
|
||||
}
|
||||
}
|
||||
|
||||
private function getSendingQueueId(): ?int {
|
||||
$qb = $this->entityManager->createQueryBuilder();
|
||||
/** @var array{id: number}|null $result */
|
||||
$result = $qb->select('sq.id AS id')
|
||||
->from(SendingQueueEntity::class, 'sq')
|
||||
->where(
|
||||
$qb->expr()->gt('sq.createdAt', ':date')
|
||||
)
|
||||
->orderBy('sq.id', 'ASC')
|
||||
->setMaxResults(1)
|
||||
->setParameter('date', '2024-12-24:00:00:00')
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
return $result ? (int)$result['id'] : null;
|
||||
}
|
||||
}
|
@ -39,7 +39,13 @@ class PostContentManager {
|
||||
if ($this->wp->hasExcerpt($post)) {
|
||||
return self::stripShortCodes($this->wp->getTheExcerpt($post));
|
||||
}
|
||||
return $this->generateExcerpt($post->post_content); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
return self::stripShortCodes(
|
||||
$this->wp->applyFilters(
|
||||
'get_the_excerpt',
|
||||
$this->generateExcerpt($post->post_content), // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$post
|
||||
)
|
||||
);
|
||||
}
|
||||
return self::stripShortCodes($post->post_content); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace MailPoet\Newsletter;
|
||||
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Features\FeaturesController;
|
||||
use MailPoet\Services\Bridge;
|
||||
use MailPoet\Settings\TrackingConfig;
|
||||
use MailPoet\Validator\ValidationException;
|
||||
@ -16,23 +15,17 @@ class NewsletterValidator {
|
||||
/** @var TrackingConfig */
|
||||
private $trackingConfig;
|
||||
|
||||
/** @var FeaturesController */
|
||||
private $featuresController;
|
||||
|
||||
public function __construct(
|
||||
Bridge $bridge,
|
||||
TrackingConfig $trackingConfig,
|
||||
FeaturesController $featuresController
|
||||
TrackingConfig $trackingConfig
|
||||
) {
|
||||
$this->bridge = $bridge;
|
||||
$this->trackingConfig = $trackingConfig;
|
||||
$this->featuresController = $featuresController;
|
||||
}
|
||||
|
||||
public function validate(NewsletterEntity $newsletterEntity): ?string {
|
||||
if (
|
||||
$this->featuresController->isSupported(FeaturesController::GUTENBERG_EMAIL_EDITOR)
|
||||
&& $newsletterEntity->getWpPostId() !== null
|
||||
$newsletterEntity->getWpPostId() !== null
|
||||
) {
|
||||
// Temporarily skip validation for emails created via Gutenberg editor
|
||||
return null;
|
||||
|
@ -156,6 +156,28 @@ class NewslettersRepository extends Repository {
|
||||
->getSingleScalarResult() ?: 0;
|
||||
}
|
||||
|
||||
public function getGutenbergNewsletterSentCount(): int {
|
||||
return intval($this->entityManager->createQueryBuilder()
|
||||
->select('COUNT(n.id)')
|
||||
->from(NewsletterEntity::class, 'n')
|
||||
->where('n.deletedAt IS NULL')
|
||||
->andWhere('n.wpPost IS NOT NULL')
|
||||
->andWhere('n.status IN (:statuses)')
|
||||
->setParameter('statuses', [NewsletterEntity::STATUS_SENT])
|
||||
->getQuery()
|
||||
->getSingleScalarResult());
|
||||
}
|
||||
|
||||
public function getTotalGutenbergNewsletterCount() {
|
||||
return intval($this->entityManager->createQueryBuilder()
|
||||
->select('COUNT(n.id)')
|
||||
->from(NewsletterEntity::class, 'n')
|
||||
->where('n.deletedAt IS NULL')
|
||||
->andWhere('n.wpPost IS NOT NULL')
|
||||
->getQuery()
|
||||
->getSingleScalarResult());
|
||||
}
|
||||
|
||||
public function getAnalytics(): array {
|
||||
// for automatic emails join 'event' newsletter option to further group the counts
|
||||
$eventOptionId = (int)$this->entityManager->createQueryBuilder()
|
||||
@ -203,6 +225,8 @@ class NewslettersRepository extends Repository {
|
||||
'product_purchased_emails_count' => $analyticsMap[NewsletterEntity::TYPE_AUTOMATIC][PurchasedProduct::SLUG] ?? 0,
|
||||
'product_purchased_in_category_emails_count' => $analyticsMap[NewsletterEntity::TYPE_AUTOMATIC][PurchasedInCategory::SLUG] ?? 0,
|
||||
'abandoned_cart_emails_count' => $analyticsMap[NewsletterEntity::TYPE_AUTOMATIC][AbandonedCart::SLUG] ?? 0,
|
||||
'total_gutenberg_newsletter_count' => $this->getTotalGutenbergNewsletterCount() ?: 0,
|
||||
'sent_gutenberg_newsletter_count' => $this->getGutenbergNewsletterSentCount() ?: 0,
|
||||
];
|
||||
// Count all campaigns
|
||||
$analyticsMap[NewsletterEntity::TYPE_AUTOMATIC] = array_sum($analyticsMap[NewsletterEntity::TYPE_AUTOMATIC] ?? []);
|
||||
|
@ -36,8 +36,12 @@ class OpenTracking {
|
||||
if (!empty($template)) {
|
||||
$template = is_array($template) ? $template : [$template];
|
||||
array_map(
|
||||
fn($item) => $item->html($item->toString(true, true, 1) . $openTrackingImage),
|
||||
$template,
|
||||
function ($item) use ($openTrackingImage) {
|
||||
$itemHtml = $item->toString(true, true, 1);
|
||||
$item->html($itemHtml . $openTrackingImage);
|
||||
return $item;
|
||||
},
|
||||
$template
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ use MailPoet\Config\Env;
|
||||
use MailPoet\EmailEditor\Engine\Renderer\Renderer as GuntenbergRenderer;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Features\FeaturesController;
|
||||
use MailPoet\Logging\LoggerFactory;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Newsletter\Renderer\EscapeHelper as EHelper;
|
||||
@ -45,9 +44,6 @@ class Renderer {
|
||||
/*** @var SendingQueuesRepository */
|
||||
private $sendingQueuesRepository;
|
||||
|
||||
/** @var FeaturesController */
|
||||
private $featuresController;
|
||||
|
||||
private CapabilitiesManager $capabilitiesManager;
|
||||
|
||||
public function __construct(
|
||||
@ -59,7 +55,6 @@ class Renderer {
|
||||
LoggerFactory $loggerFactory,
|
||||
NewslettersRepository $newslettersRepository,
|
||||
SendingQueuesRepository $sendingQueuesRepository,
|
||||
FeaturesController $featuresController,
|
||||
CapabilitiesManager $capabilitiesManager
|
||||
) {
|
||||
$this->bodyRenderer = $bodyRenderer;
|
||||
@ -70,7 +65,6 @@ class Renderer {
|
||||
$this->loggerFactory = $loggerFactory;
|
||||
$this->newslettersRepository = $newslettersRepository;
|
||||
$this->sendingQueuesRepository = $sendingQueuesRepository;
|
||||
$this->featuresController = $featuresController;
|
||||
$this->capabilitiesManager = $capabilitiesManager;
|
||||
}
|
||||
|
||||
@ -88,7 +82,7 @@ class Renderer {
|
||||
$subject = $subject ?: $newsletter->getSubject();
|
||||
$wpPostEntity = $newsletter->getWpPost();
|
||||
$wpPost = $wpPostEntity ? $wpPostEntity->getWpPostInstance() : null;
|
||||
if ($this->featuresController->isSupported(FeaturesController::GUTENBERG_EMAIL_EDITOR) && $wpPost instanceof \WP_Post) {
|
||||
if ($wpPost instanceof \WP_Post) {
|
||||
$renderedNewsletter = $this->guntenbergRenderer->render($wpPost, $subject, $newsletter->getPreheader(), $language, $metaRobots);
|
||||
} else {
|
||||
$body = (is_array($newsletter->getBody()))
|
||||
@ -222,6 +216,15 @@ class Renderer {
|
||||
foreach ($templateDom->query('img') as $image) {
|
||||
$image->src = str_replace(' ', '%20', $image->src);
|
||||
}
|
||||
foreach ($templateDom->query('a') as $anchor) {
|
||||
// Fix for a TinyMCE bug in smart paste which encodes & as & which is then additionally encoded to &amp;
|
||||
// when saving the text block content in the editor
|
||||
$href = str_replace('&amp;', '&', $anchor->href);
|
||||
// Replace & with & in the href attributes of anchors. URLs are encoded when TinyMCE extracts Text block content via content.innerHTML.
|
||||
// Links containing & work when placed in an anchor tag in a browser, but they don't work when we redirect to them for example in tracking.
|
||||
$href = str_replace('&', '&', $href);
|
||||
$anchor->href = $href;
|
||||
}
|
||||
$template = $templateDom->__toString();
|
||||
$template = $this->wp->applyFilters(
|
||||
self::FILTER_POST_PROCESS,
|
||||
|
@ -70,11 +70,7 @@ class AutomationEmailScheduler {
|
||||
->andWhere('st.createdAt >= :runCreatedAt')
|
||||
->setParameter('newsletter', $email)
|
||||
->setParameter('subscriber', $subscriber)
|
||||
// Automation Run is fetched via WPDB and it ignores the gmt_offset. This query is processed via Doctrine.
|
||||
// Doctrine uses PDO connection and uses offset. So the run's created_at is expected to be provided with the offset.
|
||||
// By removing one day we make sure the offset is not a problem. It makes no harm as this is only performance optimization.
|
||||
// After we switch to WPDB we could remove this modification and use the exact created_at.
|
||||
->setParameter('runCreatedAt', $run->getCreatedAt()->modify('-1 day'))
|
||||
->setParameter('runCreatedAt', $run->getCreatedAt())
|
||||
->getQuery()
|
||||
->getResult();
|
||||
$result = null;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace MailPoet\Newsletter\ViewInBrowser;
|
||||
|
||||
use MailPoet\EmailEditor\Integrations\MailPoet\DependencyNotice;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Newsletter\Sending\SendingQueuesRepository;
|
||||
@ -28,12 +29,16 @@ class ViewInBrowserController {
|
||||
/** @var NewslettersRepository */
|
||||
private $newslettersRepository;
|
||||
|
||||
/** @var DependencyNotice */
|
||||
private $dependencyNotice;
|
||||
|
||||
public function __construct(
|
||||
LinkTokens $linkTokens,
|
||||
NewsletterUrl $newsletterUrl,
|
||||
NewslettersRepository $newslettersRepository,
|
||||
ViewInBrowserRenderer $viewInBrowserRenderer,
|
||||
SendingQueuesRepository $sendingQueuesRepository,
|
||||
DependencyNotice $dependencyNotice,
|
||||
SubscribersRepository $subscribersRepository
|
||||
) {
|
||||
$this->linkTokens = $linkTokens;
|
||||
@ -41,6 +46,7 @@ class ViewInBrowserController {
|
||||
$this->subscribersRepository = $subscribersRepository;
|
||||
$this->sendingQueuesRepository = $sendingQueuesRepository;
|
||||
$this->newsletterUrl = $newsletterUrl;
|
||||
$this->dependencyNotice = $dependencyNotice;
|
||||
$this->newslettersRepository = $newslettersRepository;
|
||||
}
|
||||
|
||||
@ -49,6 +55,9 @@ class ViewInBrowserController {
|
||||
$isPreview = !empty($data['preview']);
|
||||
$newsletter = $this->getNewsletter($data);
|
||||
$subscriber = $this->getSubscriber($data);
|
||||
if ($newsletter->getWpPostId() && $this->dependencyNotice->checkDependenciesAndEventuallyShowNotice()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// if queue and subscriber exist, subscriber must have received the newsletter
|
||||
$queue = isset($data['queue_id']) ? $this->sendingQueuesRepository->findOneById($data['queue_id']) : null;
|
||||
|
@ -34,9 +34,11 @@ class ViewInBrowser {
|
||||
}
|
||||
|
||||
private function displayNewsletter($result) {
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $result;
|
||||
if ($result) {
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $result;
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
|
@ -61,8 +61,8 @@ class Bridge {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use non-static function isMailpoetSendingServiceEnabled instead
|
||||
* @return bool
|
||||
* @deprecated Use non-static function isMailpoetSendingServiceEnabled instead
|
||||
*/
|
||||
public static function isMPSendingServiceEnabled() {
|
||||
try {
|
||||
@ -99,18 +99,24 @@ class Bridge {
|
||||
return !empty($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|\WP_Error
|
||||
*/
|
||||
public function pingBridge() {
|
||||
$params = [
|
||||
'blocking' => true,
|
||||
'timeout' => 10,
|
||||
];
|
||||
$wp = new WPFunctions();
|
||||
$result = $wp->wpRemoteGet(self::BRIDGE_URL, $params);
|
||||
return $wp->wpRemoteRetrieveResponseCode($result);
|
||||
return $wp->wpRemoteGet(self::BRIDGE_URL, $params);
|
||||
}
|
||||
|
||||
public function validateBridgePingResponse($responseCode) {
|
||||
return $responseCode === 200;
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function validateBridgePingResponse($response) {
|
||||
$wp = new WPFunctions();
|
||||
return $wp->wpRemoteRetrieveResponseCode($response) === 200;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -6,6 +6,7 @@ use MailPoet\Entities\UserFlagEntity;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class UserFlagsController {
|
||||
const EMAIL_EDITOR_SURVEY = 'email_editor_survey_seen';
|
||||
|
||||
/** @var array|null */
|
||||
private $data = null;
|
||||
@ -26,6 +27,7 @@ class UserFlagsController {
|
||||
'transactional_emails_opt_in_notice_dismissed' => false,
|
||||
'legacy_automations_notice_dismissed' => false,
|
||||
'legacy_automatic_emails_notice_dismissed' => false,
|
||||
self::EMAIL_EDITOR_SURVEY => null,
|
||||
];
|
||||
$this->userFlagsRepository = $userFlagsRepository;
|
||||
}
|
||||
|
@ -3,10 +3,11 @@
|
||||
namespace MailPoet\SystemReport;
|
||||
|
||||
use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\DI\ContainerWrapper;
|
||||
use MailPoet\Mailer\MailerLog;
|
||||
use MailPoet\Router\Endpoints\CronDaemon;
|
||||
use MailPoet\Services\Bridge;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Util\DataInconsistency\DataInconsistencyController;
|
||||
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
|
||||
use MailPoet\WooCommerce\Helper as WooCommerceHelper;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
@ -24,16 +25,37 @@ class SystemReportCollector {
|
||||
/** @var WooCommerceHelper */
|
||||
private $wooCommerceHelper;
|
||||
|
||||
/** @var DataInconsistencyController */
|
||||
private $dataInconsistencyController;
|
||||
|
||||
/** @var CronHelper */
|
||||
private $cronHelper;
|
||||
|
||||
/** @var string|null */
|
||||
private $cachedCronPingResponse = null;
|
||||
|
||||
/** @var array|\WP_Error|null */
|
||||
private $cachedBridgePingResponse = null;
|
||||
|
||||
/** @var Bridge */
|
||||
private $bridge;
|
||||
|
||||
public function __construct(
|
||||
SettingsController $settings,
|
||||
WPFunctions $wp,
|
||||
SubscribersFeature $subscribersFeature,
|
||||
WooCommerceHelper $wooCommerceHelper
|
||||
WooCommerceHelper $wooCommerceHelper,
|
||||
DataInconsistencyController $dataInconsistencyController,
|
||||
Bridge $bridge,
|
||||
CronHelper $cronHelper
|
||||
) {
|
||||
$this->settings = $settings;
|
||||
$this->wp = $wp;
|
||||
$this->subscribersFeature = $subscribersFeature;
|
||||
$this->wooCommerceHelper = $wooCommerceHelper;
|
||||
$this->dataInconsistencyController = $dataInconsistencyController;
|
||||
$this->bridge = $bridge;
|
||||
$this->cronHelper = $cronHelper;
|
||||
}
|
||||
|
||||
public function getData($maskApiKey = false) {
|
||||
@ -62,15 +84,29 @@ class SystemReportCollector {
|
||||
$premiumKey = $this->maskApiKey($premiumKey);
|
||||
}
|
||||
|
||||
$cronHelper = ContainerWrapper::getInstance()->get(CronHelper::class);
|
||||
$cronDaemonStatus = $this->cronHelper->getDaemon() ?? [];
|
||||
try {
|
||||
$cronPingUrl = $cronHelper->getCronUrl(
|
||||
CronDaemon::ACTION_PING
|
||||
);
|
||||
$cronPingUrl = $this->cronHelper->getCronUrl(CronDaemon::ACTION_PING);
|
||||
$cronPingResponse = $this->getCronPingResponse();
|
||||
} catch (\Exception $e) {
|
||||
$cronPingUrl = __('Can‘t generate cron URL.', 'mailpoet') . ' (' . $e->getMessage() . ')';
|
||||
$cronPingResponse = $cronPingUrl;
|
||||
}
|
||||
|
||||
$mailerLog = MailerLog::getMailerLog();
|
||||
$mailerLog['sent'] = MailerLog::sentSince();
|
||||
|
||||
$inconsistencyStatus = $this->dataInconsistencyController->getInconsistentDataStatus();
|
||||
unset($inconsistencyStatus['total']);
|
||||
|
||||
$pingBridgeResponse = $this->getBridgePingResponse();
|
||||
$pingResponse = $this->wp->isWpError($pingBridgeResponse)
|
||||
? $pingBridgeResponse->get_error_message() // @phpstan-ignore-line
|
||||
: $this->wp->wpRemoteRetrieveResponseCode($pingBridgeResponse) . ' HTTP status code';
|
||||
|
||||
$ApiKeyState = $this->settings->get(Bridge::API_KEY_STATE_SETTING_NAME . '.state');
|
||||
$premiumKeyState = $this->settings->get(Bridge::PREMIUM_KEY_STATE_SETTING_NAME . '.state');
|
||||
|
||||
// the HelpScout Beacon API has a limit of 20 attribute-value pairs (https://developer.helpscout.com/beacon-2/web/javascript-api/#beacon-session-data)
|
||||
return [
|
||||
'PHP version' => PHP_VERSION,
|
||||
@ -81,29 +117,112 @@ class SystemReportCollector {
|
||||
'Database version' => $dbVersion,
|
||||
'Web server' => (!empty($_SERVER["SERVER_SOFTWARE"])) ? sanitize_text_field(wp_unslash($_SERVER["SERVER_SOFTWARE"])) : 'N/A',
|
||||
'Server OS' => (function_exists('php_uname')) ? php_uname() : 'N/A',
|
||||
'WP info' => 'WP_MEMORY_LIMIT: ' . WP_MEMORY_LIMIT . ' - WP_MAX_MEMORY_LIMIT: ' . WP_MAX_MEMORY_LIMIT . ' - WP_DEBUG: ' . WP_DEBUG .
|
||||
' - WordPress language: ' . $this->wp->getLocale() . ' - WordPress timezone: ' . $this->wp->wpTimezoneString(),
|
||||
'PHP info' => 'PHP max_execution_time: ' . ini_get('max_execution_time') . ' - PHP memory_limit: ' . ini_get('memory_limit') .
|
||||
' - PHP upload_max_filesize: ' . ini_get('upload_max_filesize') . ' - PHP post_max_size: ' . ini_get('post_max_size'),
|
||||
'WP info' => $this->formatCompositeField([
|
||||
'WP_MEMORY_LIMIT' => WP_MEMORY_LIMIT,
|
||||
'WP_MAX_MEMORY_LIMIT' => WP_MAX_MEMORY_LIMIT,
|
||||
'WP_DEBUG' => WP_DEBUG,
|
||||
'WordPress language' => $this->wp->getLocale(),
|
||||
'WordPress timezone' => $this->wp->wpTimezoneString(),
|
||||
]),
|
||||
'PHP info' => $this->formatCompositeField([
|
||||
'PHP max_execution_time' => ini_get('max_execution_time'),
|
||||
'PHP memory_limit' => ini_get('memory_limit'),
|
||||
'PHP upload_max_filesize' => ini_get('upload_max_filesize'),
|
||||
'PHP post_max_size' => ini_get('post_max_size'),
|
||||
]),
|
||||
'Multisite environment?' => (is_multisite() ? 'Yes' : 'No'),
|
||||
'Current Theme' => $currentTheme->get('Name') .
|
||||
' (version ' . $currentTheme->get('Version') . ')',
|
||||
'Active Plugin names' => join(", ", $this->wp->getOption('active_plugins')),
|
||||
'Sending Method' => $mta['method'],
|
||||
'MailPoet Sending Service' => $this->formatCompositeField([
|
||||
'Is reachable' => $this->bridge->validateBridgePingResponse($pingBridgeResponse) ? 'Yes' : 'No',
|
||||
'Ping response' => $pingResponse,
|
||||
'API key state' => $ApiKeyState ?? 'Unset',
|
||||
'Premium key state' => $premiumKeyState ?? 'Unset',
|
||||
]),
|
||||
'Sending Frequency' => sprintf(
|
||||
'%d emails every %d minutes',
|
||||
$mta['frequency']['emails'],
|
||||
$mta['frequency']['interval']
|
||||
),
|
||||
'MailPoet sending info' => "Send all site's emails with: " . ($this->settings->get('send_transactional_emails') ? 'current sending method' : 'default WordPress sending method') .
|
||||
' - Task Scheduler method: ' . $this->settings->get('cron_trigger.method') . ' - Cron ping URL: ' . $cronPingUrl . ' - Default FROM address: ' . $this->settings->get('sender.address') .
|
||||
' - Default Reply-To address: ' . $this->settings->get('reply_to.address') . ' - Bounce Email Address: ' . $this->settings->get('bounce.address'),
|
||||
'MailPoet sending info' => $this->formatCompositeField([
|
||||
"Send all site's emails with" => ($this->settings->get('send_transactional_emails') ? 'current sending method' : 'default WordPress sending method'),
|
||||
'Task Scheduler method' => $this->settings->get('cron_trigger.method'),
|
||||
'Default FROM address' => $this->settings->get('sender.address'),
|
||||
'Default Reply-To address' => $this->settings->get('reply_to.address'),
|
||||
'Bounce Email Address' => $this->settings->get('bounce.address'),
|
||||
]),
|
||||
'MailPoet Cron / Action Scheduler' => $this->formatCompositeField([
|
||||
'Status' => $cronDaemonStatus['status'] ?? 'Unknown',
|
||||
'Is reachable' => $this->cronHelper->validatePingResponse($cronPingResponse) ? 'Yes' : 'No',
|
||||
'Ping URL' => $cronPingUrl,
|
||||
'Ping response' => $cronPingResponse,
|
||||
'Last run start' => isset($cronDaemonStatus['run_started_at']) ? date('Y-m-d H:i:s', $cronDaemonStatus['run_started_at']) : 'Unknown',
|
||||
'Last run end' => isset($cronDaemonStatus['run_completed_at']) ? date('Y-m-d H:i:s', $cronDaemonStatus['run_completed_at']) : 'Unknown',
|
||||
'Last seen error' => $cronDaemonStatus['last_error'] ?? 'None',
|
||||
]),
|
||||
'Total number of subscribers' => $this->subscribersFeature->getSubscribersCount(),
|
||||
'Plugin installed at' => $this->settings->get('installed_at'),
|
||||
'Installed via WooCommerce onboarding wizard' => $this->wooCommerceHelper->wasMailPoetInstalledViaWooCommerceOnboardingWizard(),
|
||||
'Sending queue status' => $this->formatCompositeField([
|
||||
'Status' => $mailerLog['status'] ?? 'Unknown',
|
||||
'Started at' => isset($mailerLog['started']) ? date('Y-m-d H:i:s', $mailerLog['started']) : 'Unknown',
|
||||
'Emails sent' => $mailerLog['sent'],
|
||||
'Retry attempts' => $mailerLog['retry_attempt'] ?? 0,
|
||||
'Last seen error' => isset($mailerLog['error'])
|
||||
? $mailerLog['error']['error_message'] . ' (' . $mailerLog['error']['operation'] . ')'
|
||||
: 'None',
|
||||
]),
|
||||
'Data inconsistency status' => $this->formatCompositeField($this->convertKeysToTitleCase($inconsistencyStatus)),
|
||||
];
|
||||
}
|
||||
|
||||
public function getCronPingResponse(): string {
|
||||
if ($this->cachedCronPingResponse !== null) {
|
||||
return $this->cachedCronPingResponse;
|
||||
}
|
||||
|
||||
$this->cachedCronPingResponse = $this->cronHelper->pingDaemon();
|
||||
return $this->cachedCronPingResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|\WP_Error
|
||||
*/
|
||||
public function getBridgePingResponse() {
|
||||
if ($this->cachedBridgePingResponse !== null) {
|
||||
return $this->cachedBridgePingResponse;
|
||||
}
|
||||
|
||||
$this->cachedBridgePingResponse = $this->bridge->pingBridge();
|
||||
return $this->cachedBridgePingResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $fields array of key-value pairs
|
||||
* @return string in the format "key1: value1 - key2: value2 - ..."
|
||||
*/
|
||||
private function formatCompositeField(array $fields) {
|
||||
if (empty($fields)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return implode(' - ', array_map(function ($key, $value) {
|
||||
return $key . ': ' . $value;
|
||||
}, array_keys($fields), array_values($fields)));
|
||||
}
|
||||
|
||||
private function convertKeysToTitleCase(array $array): array {
|
||||
$result = [];
|
||||
foreach ($array as $key => $value) {
|
||||
$titleCaseKey = ucfirst(str_replace('_', ' ', $key));
|
||||
$result[$titleCaseKey] = $value;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function maskApiKey($key) {
|
||||
// the length of this particular key is an even number.
|
||||
// for odd lengths this method will change the total number of characters (which shouldn't be a problem in this context).
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ class CouponPreProcessor {
|
||||
if ($preview) {
|
||||
return $blocks;
|
||||
}
|
||||
|
||||
|
||||
$generated = $this->ensureCouponForBlocks($blocks, $newsletter);
|
||||
$body = $newsletter->getBody();
|
||||
|
||||
@ -146,18 +146,27 @@ class CouponPreProcessor {
|
||||
}, $items);
|
||||
}
|
||||
|
||||
private function generateRandomSegment($length) {
|
||||
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
$segment = '';
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$randomIndex = rand(0, strlen($characters) - 1);
|
||||
$segment .= $characters[$randomIndex];
|
||||
}
|
||||
|
||||
return $segment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates Coupon code for XXXX-XXXXXX-XXXX pattern
|
||||
*/
|
||||
private function generateRandomCode(): string {
|
||||
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
$length = strlen($chars);
|
||||
return sprintf(
|
||||
"%s-%s-%s",
|
||||
substr($chars, rand(0, $length - 5), 4),
|
||||
substr($chars, rand(0, $length - 8), 7),
|
||||
substr($chars, rand(0, $length - 5), 4)
|
||||
);
|
||||
$part1 = $this->generateRandomSegment(4);
|
||||
$part2 = $this->generateRandomSegment(6);
|
||||
$part3 = $this->generateRandomSegment(4);
|
||||
|
||||
return $part1 . '-' . $part2 . '-' . $part3;
|
||||
}
|
||||
|
||||
private function shouldGenerateCoupon(array $block): bool {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user