Compare commits
104 Commits
Author | SHA1 | Date | |
---|---|---|---|
ad6753eb05 | |||
51a14a45d3 | |||
014b8249bb | |||
857ed69c61 | |||
f0434d74e7 | |||
8318246e1e | |||
6f98634b94 | |||
01cafdf719 | |||
374fbe6867 | |||
b63834b02b | |||
0229eb76ef | |||
f8d99b34bf | |||
9f24247285 | |||
db2e61e987 | |||
60f1234b71 | |||
9db41a5210 | |||
7814dda708 | |||
0d71d96cfa | |||
a5d44ba9b6 | |||
78732f8a5d | |||
2804125827 | |||
74f95b972f | |||
e3214e441b | |||
bbe471e653 | |||
ecd7614337 | |||
a78a31f110 | |||
fcc4be330c | |||
52397951d8 | |||
97f811390d | |||
7b64c47733 | |||
bc4014d4a3 | |||
ec6d26162b | |||
5d8d7adc08 | |||
15b351b3f1 | |||
d4d4db7cc5 | |||
c2c9429706 | |||
de9c03a3bc | |||
7b86de1346 | |||
749c4e5e43 | |||
6a86dfc7c0 | |||
ac6cc881d1 | |||
7bfcf4bd67 | |||
f24184fe16 | |||
6994afebb7 | |||
bf8e0f344a | |||
f4157947c4 | |||
2665ca65ae | |||
bf70c24511 | |||
e3a194a1dd | |||
c388c2ae9b | |||
646d8dbea4 | |||
4b3744717f | |||
5824905f18 | |||
de1fc6601c | |||
d65fdd7a6d | |||
928c100839 | |||
1e373ab7e0 | |||
01afa2ef31 | |||
3f8c0f2bc6 | |||
bd75f74cb5 | |||
41dff5f95a | |||
b3bd371a27 | |||
f09ddaff1e | |||
4a96f3dfea | |||
bc7fbd2469 | |||
8450e1bb1b | |||
2b5135474e | |||
877c736190 | |||
a947dd36cd | |||
d6da2af55b | |||
82d5621caf | |||
cb803d6f4b | |||
082ab78f90 | |||
d1f48b6772 | |||
f5e1ae9fba | |||
4454dd6203 | |||
ddfde2059e | |||
682527cd1b | |||
aa13c8956d | |||
18b227429f | |||
98d46f2fdb | |||
bcc96d27bb | |||
9aaffd9ceb | |||
115616620e | |||
bca9101921 | |||
1221a64d3d | |||
17168643c9 | |||
c492ff4d8d | |||
6f844e2bea | |||
5646464853 | |||
42b5efda1b | |||
a1017c4380 | |||
0409747216 | |||
4ab07621b3 | |||
881a971b8e | |||
5b913f7fe8 | |||
756160abc5 | |||
ee04736a88 | |||
6f5e22c54c | |||
7c2065bcbf | |||
19b33e15f5 | |||
62db03d95b | |||
8734d9762b | |||
4d021eeceb |
42
.husky/common.sh
Executable file
42
.husky/common.sh
Executable file
@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
. "$(dirname "$0")/../mailpoet/.env"
|
||||
|
||||
export MP_GIT_HOOKS_ENABLE="${MP_GIT_HOOKS_ENABLE:-true}"
|
||||
export MP_GIT_HOOKS_ESLINT="${MP_GIT_HOOKS_ESLINT:-true}"
|
||||
export MP_GIT_HOOKS_STYLELINT="${MP_GIT_HOOKS_STYLELINT:-true}"
|
||||
export MP_GIT_HOOKS_PHPLINT="${MP_GIT_HOOKS_PHPLINT:-true}"
|
||||
export MP_GIT_HOOKS_CODE_SNIFFER="${MP_GIT_HOOKS_CODE_SNIFFER:-true}"
|
||||
export MP_GIT_HOOKS_MINIMAL_PLUGIN_STANDARDS="${MP_GIT_HOOKS_MINIMAL_PLUGIN_STANDARDS:-true}"
|
||||
export MP_GIT_HOOKS_PHPSTAN="${MP_GIT_HOOKS_PHPSTAN:-true}"
|
||||
export MP_GIT_HOOKS_INSTALL_JS="${MP_GIT_HOOKS_INSTALL_JS:-false}"
|
||||
export MP_GIT_HOOKS_INSTALL_PHP="${MP_GIT_HOOKS_INSTALL_PHP:-false}"
|
||||
|
||||
fileChanged() {
|
||||
local filePattern="$1"
|
||||
local changedFiles="$2"
|
||||
if echo "$changedFiles" | grep -qE "$filePattern"; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
installIfUpdates() {
|
||||
local changedFiles="$(git diff-tree -r --name-only --no-commit-id HEAD@{1} HEAD)"
|
||||
|
||||
if [ "$MP_GIT_HOOKS_INSTALL_JS" = "true" ] && fileChanged "pnpm-lock.yaml" "$changedFiles"; then
|
||||
echo "Change detected in pnpm-lock.yaml, running do install:js"
|
||||
pushd mailpoet
|
||||
./do install:js
|
||||
popd
|
||||
fi
|
||||
|
||||
if [ "$MP_GIT_HOOKS_INSTALL_PHP" = "true" ] && fileChanged "mailpoet/composer.lock" "$changedFiles"; then
|
||||
echo "Change detected in mailpoet/composer.lock, running do install:php"
|
||||
pushd mailpoet
|
||||
./do install:php
|
||||
popd
|
||||
fi
|
||||
}
|
||||
|
6
.husky/post-checkout
Executable file
6
.husky/post-checkout
Executable file
@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
. "$(dirname "$0")/common.sh"
|
||||
[ "$MP_GIT_HOOKS_ENABLE" != "true" ] && exit 0
|
||||
|
||||
installIfUpdates
|
7
.husky/post-merge
Executable file
7
.husky/post-merge
Executable file
@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
. "$(dirname "$0")/common.sh"
|
||||
[ "$MP_GIT_HOOKS_ENABLE" != "true" ] && exit 0
|
||||
|
||||
|
||||
installIfUpdates
|
7
.husky/post-rewrite
Executable file
7
.husky/post-rewrite
Executable file
@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
. "$(dirname "$0")/common.sh"
|
||||
[ "$MP_GIT_HOOKS_ENABLE" != "true" ] && exit 0
|
||||
|
||||
|
||||
installIfUpdates
|
@ -1,5 +1,7 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
. "$(dirname "$0")/common.sh"
|
||||
[ "$MP_GIT_HOOKS_ENABLE" != "true" ] && exit 0
|
||||
|
||||
npx lint-staged -c mailpoet/package.json --cwd mailpoet
|
||||
npx lint-staged -c package.json
|
||||
|
@ -44,3 +44,14 @@ WP_TEST_PERFORMANCE_DATA_URL=
|
||||
WP_TEST_PERFORMANCE_PW=
|
||||
K6_CLOUD_TOKEN=
|
||||
K6_CLOUD_ID=
|
||||
|
||||
# git hooks configuration (must be 'true' to enable)
|
||||
MP_GIT_HOOKS_ENABLE=true
|
||||
MP_GIT_HOOKS_ESLINT=true
|
||||
MP_GIT_HOOKS_STYLELINT=true
|
||||
MP_GIT_HOOKS_PHPLINT=true
|
||||
MP_GIT_HOOKS_CODE_SNIFFER=true
|
||||
MP_GIT_HOOKS_MINIMAL_PLUGIN_STANDARDS=true
|
||||
MP_GIT_HOOKS_PHPSTAN=true
|
||||
MP_GIT_HOOKS_INSTALL_JS=false
|
||||
MP_GIT_HOOKS_INSTALL_PHP=false
|
||||
|
@ -28,6 +28,14 @@ class RoboFile extends \Robo\Tasks {
|
||||
->run();
|
||||
}
|
||||
|
||||
public function installPhp() {
|
||||
return $this->taskExecStack()
|
||||
->stopOnFail()
|
||||
->exec('./tools/vendor/composer.phar install')
|
||||
->addCode([$this, 'cleanupCachedFiles'])
|
||||
->run();
|
||||
}
|
||||
|
||||
public function installJs() {
|
||||
return $this->taskExecStack()
|
||||
->stopOnFail()
|
||||
|
@ -12,9 +12,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-automation-listing {
|
||||
.mailpoet-automation-listing.woocommerce-table {
|
||||
box-shadow: none;
|
||||
margin-bottom: 0;
|
||||
|
||||
.woocommerce-pagination {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-automation-listing-cell-name.woocommerce-table__item {
|
||||
|
@ -31,6 +31,7 @@ export function FormTokenField({
|
||||
suggestions={suggestions.map((item) => item.name)}
|
||||
__experimentalExpandOnFocus
|
||||
__experimentalAutoSelectFirstMatch
|
||||
__experimentalShowHowTo={false}
|
||||
placeholder={placeholder}
|
||||
onChange={(raw: string[]) => {
|
||||
const allSelected: FormTokenItem[] = raw
|
||||
|
@ -115,21 +115,25 @@ function Editor(): JSX.Element {
|
||||
if (!isBooting) {
|
||||
return;
|
||||
}
|
||||
if (automation.status === 'trash') {
|
||||
window.location.href = addQueryArgs(MailPoet.urls.automationListing, {
|
||||
notice: LISTING_NOTICES.automationHadBeenDeleted,
|
||||
'notice-args': [automation.name],
|
||||
});
|
||||
}
|
||||
updatingActiveAutomationNotPossible();
|
||||
setIsBooting(false);
|
||||
}, [isBooting]);
|
||||
}, [automation.name, automation.status, isBooting]);
|
||||
|
||||
if (automation.status === 'trash') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const className = classnames('interface-interface-skeleton', {
|
||||
'is-sidebar-opened': isSidebarOpened,
|
||||
'show-icon-labels': showIconLabels,
|
||||
});
|
||||
|
||||
if (automation.status === 'trash') {
|
||||
window.location.href = addQueryArgs(MailPoet.urls.automationListing, {
|
||||
notice: LISTING_NOTICES.automationHadBeenDeleted,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ShortcutProvider>
|
||||
<SlotFillProvider>
|
||||
|
@ -214,9 +214,10 @@ export function* trash(onTrashed: () => void = undefined) {
|
||||
|
||||
onTrashed?.();
|
||||
|
||||
if (data?.status === AutomationStatus.TRASH) {
|
||||
if (data?.data?.status === AutomationStatus.TRASH) {
|
||||
window.location.href = addQueryArgs(MailPoet.urls.automationListing, {
|
||||
notice: LISTING_NOTICES.automationDeleted,
|
||||
'notice-args': [automation.name],
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -3,13 +3,12 @@ import { __ } from '@wordpress/i18n';
|
||||
export function ShortcodeHelpText(): JSX.Element {
|
||||
return (
|
||||
<span className="mailpoet-shortcode-selector">
|
||||
You can use{' '}
|
||||
<a
|
||||
href="https://kb.mailpoet.com/article/215-personalize-newsletter-with-shortcodes"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{__('MailPoet shortcodes', 'mailpoet')}
|
||||
{__('You can use MailPoet shortcodes.', 'mailpoet')}
|
||||
</a>
|
||||
</span>
|
||||
);
|
||||
|
@ -45,6 +45,7 @@ export function ListPanel(): JSX.Element {
|
||||
values.map((item) => item.id),
|
||||
);
|
||||
}}
|
||||
__experimentalShowHowTo={false}
|
||||
/>
|
||||
</PanelBody>
|
||||
);
|
||||
|
@ -63,6 +63,7 @@ export function RolePanel(): JSX.Element {
|
||||
items.map((item) => item.id),
|
||||
);
|
||||
}}
|
||||
__experimentalShowHowTo={false}
|
||||
/>
|
||||
</PanelBody>
|
||||
);
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { useEffect } from 'react';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { store as noticesStore } from '@wordpress/notices';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { getQueryArg, removeQueryArgs } from '@wordpress/url';
|
||||
|
||||
export const LISTING_NOTICES = {
|
||||
automationActivated: 'activated',
|
||||
automationSaved: 'saved',
|
||||
automationDeleted: 'deleted',
|
||||
automationHadBeenDeleted: 'had-been-deleted',
|
||||
} as const;
|
||||
@ -16,29 +14,34 @@ export function useAutomationListingNotices(): void {
|
||||
|
||||
useEffect(() => {
|
||||
const notice = getQueryArg(window.location.href, 'notice');
|
||||
if (notice === LISTING_NOTICES.automationActivated) {
|
||||
createNotice('success', __('Your automation is now active.', 'mailpoet'));
|
||||
} else if (notice === LISTING_NOTICES.automationSaved) {
|
||||
const args = getQueryArg(window.location.href, 'notice-args') ?? [];
|
||||
const automationName = args[0] ?? 'Unknown';
|
||||
if (notice === LISTING_NOTICES.automationDeleted) {
|
||||
createNotice(
|
||||
'success',
|
||||
__('Your automation has been saved.', 'mailpoet'),
|
||||
);
|
||||
} else if (notice === LISTING_NOTICES.automationDeleted) {
|
||||
createNotice(
|
||||
'success',
|
||||
__('1 automation moved to the Trash.', 'mailpoet'),
|
||||
sprintf(
|
||||
__('Automation "%s" was moved to the trash.', 'mailpoet'),
|
||||
automationName,
|
||||
),
|
||||
);
|
||||
} else if (notice === LISTING_NOTICES.automationHadBeenDeleted) {
|
||||
createNotice(
|
||||
'error',
|
||||
__(
|
||||
'You cannot edit this automation because it is in the Trash.',
|
||||
'mailpoet',
|
||||
sprintf(
|
||||
__(
|
||||
'You cannot edit automation "%s" because it is in the trash.',
|
||||
'mailpoet',
|
||||
),
|
||||
automationName,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const urlWithoutNotices = removeQueryArgs(window.location.href, 'notice');
|
||||
const urlWithoutNotices = removeQueryArgs(
|
||||
window.location.href,
|
||||
'notice',
|
||||
'notice-args',
|
||||
);
|
||||
window.history.replaceState('', '', urlWithoutNotices);
|
||||
}, [createNotice]);
|
||||
}
|
||||
|
@ -14,32 +14,10 @@ import { plusIcon } from 'common/button/icon/plus';
|
||||
import { getRow } from './get-row';
|
||||
import { AutomationItem, storeName } from './store';
|
||||
import { Automation, AutomationStatus } from './automation';
|
||||
import { automationCount, legacyAutomationCount } from '../config';
|
||||
import { MailPoet } from '../../mailpoet';
|
||||
import { PageHeader } from '../../common/page-header';
|
||||
|
||||
const tabConfig = [
|
||||
{
|
||||
name: 'all',
|
||||
title: __('All', 'mailpoet'),
|
||||
className: 'mailpoet-tab-all',
|
||||
},
|
||||
{
|
||||
name: AutomationStatus.ACTIVE,
|
||||
title: __('Active', 'mailpoet'),
|
||||
className: 'mailpoet-tab-active',
|
||||
},
|
||||
{
|
||||
name: AutomationStatus.DRAFT,
|
||||
title: _x('Draft', 'noun', 'mailpoet'),
|
||||
className: 'mailpoet-tab-draft',
|
||||
},
|
||||
{
|
||||
name: AutomationStatus.TRASH,
|
||||
title: _x('Trash', 'noun', 'mailpoet'),
|
||||
className: 'mailpoet-tab-trash',
|
||||
},
|
||||
] as const;
|
||||
|
||||
const tableHeaders = [
|
||||
{
|
||||
key: 'name',
|
||||
@ -126,26 +104,48 @@ export function AutomationListing(): JSX.Element {
|
||||
return grouped;
|
||||
}, [automations]);
|
||||
|
||||
const tabs = useMemo(
|
||||
() =>
|
||||
tabConfig.map((tab) => {
|
||||
const count = (groupedAutomations[tab.name] ?? []).length;
|
||||
return {
|
||||
name: tab.name,
|
||||
title: (
|
||||
<>
|
||||
<span>{tab.title}</span>
|
||||
{count > 0 && <span className="count">{count}</span>}
|
||||
</>
|
||||
) as any, // eslint-disable-line @typescript-eslint/no-explicit-any -- typed as string but supports JSX
|
||||
className: tab.className,
|
||||
};
|
||||
}),
|
||||
[groupedAutomations],
|
||||
);
|
||||
const tabs = useMemo(() => {
|
||||
const tabConfig = [
|
||||
{
|
||||
name: 'all',
|
||||
title: __('All', 'mailpoet'),
|
||||
className: 'mailpoet-tab-all',
|
||||
},
|
||||
{
|
||||
name: AutomationStatus.ACTIVE,
|
||||
title: __('Active', 'mailpoet'),
|
||||
className: 'mailpoet-tab-active',
|
||||
},
|
||||
{
|
||||
name: AutomationStatus.DRAFT,
|
||||
title: _x('Draft', 'noun', 'mailpoet'),
|
||||
className: 'mailpoet-tab-draft',
|
||||
},
|
||||
{
|
||||
name: AutomationStatus.TRASH,
|
||||
title: _x('Trash', 'noun', 'mailpoet'),
|
||||
className: 'mailpoet-tab-trash',
|
||||
},
|
||||
] as const;
|
||||
|
||||
return tabConfig.map((tab) => {
|
||||
const count = (groupedAutomations[tab.name] ?? []).length;
|
||||
return {
|
||||
name: tab.name,
|
||||
title: (
|
||||
<>
|
||||
<span>{tab.title}</span>
|
||||
{count > 0 && <span className="count">{count}</span>}
|
||||
</>
|
||||
) as any, // eslint-disable-line @typescript-eslint/no-explicit-any -- typed as string but supports JSX
|
||||
className: tab.className,
|
||||
};
|
||||
});
|
||||
}, [groupedAutomations]);
|
||||
|
||||
const renderTabs = useCallback(
|
||||
(tab) => {
|
||||
const totalCount = automationCount + legacyAutomationCount;
|
||||
const filteredAutomations: AutomationItem[] =
|
||||
groupedAutomations[tab.name] ?? [];
|
||||
const rowsPerPage = parseInt(pageSearch.get('per_page') ?? '25', 10);
|
||||
@ -171,11 +171,11 @@ export function AutomationListing(): JSX.Element {
|
||||
filteredAutomations[i].id *
|
||||
(filteredAutomations[i].isLegacy ? -1 : 1)
|
||||
}
|
||||
rowsPerPage={rowsPerPage}
|
||||
rowsPerPage={Math.min(rowsPerPage, totalCount)}
|
||||
onQueryChange={(key) => (value) => {
|
||||
updateUrlSearchString({ [key]: value });
|
||||
}}
|
||||
totalRows={filteredAutomations.length}
|
||||
totalRows={automations ? filteredAutomations.length : totalCount}
|
||||
query={Object.fromEntries(pageSearch)}
|
||||
showMenu={false}
|
||||
/>
|
||||
|
@ -47,7 +47,10 @@ export function* trashAutomation(automation: Automation) {
|
||||
},
|
||||
});
|
||||
|
||||
const message = __('1 automation moved to the Trash.', 'mailpoet');
|
||||
const message = sprintf(
|
||||
__('Automation "%s" was moved to the trash.', 'mailpoet'),
|
||||
automation.name,
|
||||
);
|
||||
void createSuccessNotice(message, {
|
||||
id: `automation-trashed-${automation.id}`,
|
||||
__unstableHTML: (
|
||||
@ -81,7 +84,10 @@ export function* restoreAutomation(
|
||||
|
||||
void removeNotice(`automation-trashed-${automation.id}`);
|
||||
|
||||
const message = __('1 automation restored from the Trash.', 'mailpoet');
|
||||
const message = sprintf(
|
||||
__('Automation "%s" was restored from the trash.', 'mailpoet'),
|
||||
automation.name,
|
||||
);
|
||||
void createSuccessNotice(message, {
|
||||
__unstableHTML: (
|
||||
<p>
|
||||
@ -107,7 +113,13 @@ export function* deleteAutomation(automation: Automation) {
|
||||
});
|
||||
|
||||
void createSuccessNotice(
|
||||
__('1 automation and all associated data permanently deleted.', 'mailpoet'),
|
||||
sprintf(
|
||||
__(
|
||||
'Automation "%s" and all associated data were permanently deleted.',
|
||||
'mailpoet',
|
||||
),
|
||||
automation.name,
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { __unstableAwaitPromise as AwaitPromise } from '@wordpress/data-controls';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { store as noticesStore } from '@wordpress/notices';
|
||||
@ -56,7 +56,7 @@ export function* loadLegacyAutomations() {
|
||||
}
|
||||
|
||||
export function* trashLegacyAutomation(automation: Automation) {
|
||||
const data: { data: ListingItem } = yield AwaitPromise(
|
||||
yield AwaitPromise(
|
||||
legacyApiFetch({
|
||||
endpoint: 'newsletters',
|
||||
method: 'trash',
|
||||
@ -64,11 +64,17 @@ export function* trashLegacyAutomation(automation: Automation) {
|
||||
}),
|
||||
);
|
||||
|
||||
createSuccessNotice(__('1 automation moved to the Trash.', 'mailpoet'));
|
||||
createSuccessNotice(
|
||||
sprintf(
|
||||
__('Automation "%s" was moved to the trash.', 'mailpoet'),
|
||||
automation.name,
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
type: 'UPDATE_LEGACY_AUTOMATION',
|
||||
automation: mapToAutomation(data.data),
|
||||
type: 'UPDATE_LEGACY_AUTOMATION_STATUS',
|
||||
id: automation.id,
|
||||
status: AutomationStatus.TRASH,
|
||||
} as const;
|
||||
}
|
||||
|
||||
@ -83,16 +89,22 @@ export function* restoreLegacyAutomation(automation: Automation) {
|
||||
|
||||
void removeNotice(`automation-trashed-${automation.id}`);
|
||||
|
||||
createSuccessNotice(__('1 automation restored from the Trash.', 'mailpoet'));
|
||||
createSuccessNotice(
|
||||
sprintf(
|
||||
__('Automation "%s" was restored from the trash.', 'mailpoet'),
|
||||
automation.name,
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
type: 'UPDATE_LEGACY_AUTOMATION',
|
||||
automation: mapToAutomation(data.data),
|
||||
type: 'UPDATE_LEGACY_AUTOMATION_STATUS',
|
||||
id: automation.id,
|
||||
status: data.data.status,
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function* deleteLegacyAutomation(automation: Automation) {
|
||||
const data: { data: ListingItem } = yield AwaitPromise(
|
||||
yield AwaitPromise(
|
||||
legacyApiFetch({
|
||||
endpoint: 'newsletters',
|
||||
method: 'delete',
|
||||
@ -101,11 +113,17 @@ export function* deleteLegacyAutomation(automation: Automation) {
|
||||
);
|
||||
|
||||
void createSuccessNotice(
|
||||
__('1 automation and all associated data permanently deleted.', 'mailpoet'),
|
||||
sprintf(
|
||||
__(
|
||||
'Automation "%s" and all associated data were permanently deleted.',
|
||||
'mailpoet',
|
||||
),
|
||||
automation.name,
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
type: 'DELETE_LEGACY_AUTOMATION',
|
||||
automation: mapToAutomation(data.data),
|
||||
id: automation.id,
|
||||
} as const;
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ const getAutomaticInfo = (item: ListingItem): ReactNode => {
|
||||
}
|
||||
|
||||
const metaOptionValues =
|
||||
meta && meta.option
|
||||
meta && meta.option && Array.isArray(meta.option)
|
||||
? meta.option.map(({ name }: { name: string }) => name)
|
||||
: [];
|
||||
|
||||
|
@ -8,21 +8,21 @@ export function legacyReducer(state: State, action): State {
|
||||
...state,
|
||||
legacyAutomations: action.automations,
|
||||
};
|
||||
case 'UPDATE_LEGACY_AUTOMATION':
|
||||
case 'UPDATE_LEGACY_AUTOMATION_STATUS':
|
||||
return {
|
||||
...state,
|
||||
legacyAutomations: state.legacyAutomations.map(
|
||||
(automation: Automation) =>
|
||||
automation.id === action.automation.id
|
||||
? (action.automation as Automation)
|
||||
automation.id === action.id
|
||||
? { ...automation, status: action.status }
|
||||
: automation,
|
||||
),
|
||||
};
|
||||
case 'DELETE_LEGACY_AUTOMATION':
|
||||
return {
|
||||
...state,
|
||||
legacyAutomations: state.automations.filter(
|
||||
(automation: Automation) => automation.id !== action.automation.id,
|
||||
legacyAutomations: state.legacyAutomations.filter(
|
||||
(automation: Automation) => automation.id !== action.id,
|
||||
),
|
||||
};
|
||||
default:
|
||||
|
@ -57,6 +57,7 @@ export function TokenField({
|
||||
value={selectedValues}
|
||||
suggestions={suggestedValues}
|
||||
onChange={(tokens) => handleSave(tokens, onChange)}
|
||||
__experimentalShowHowTo={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ import { SenderActions } from './sender-domain-notice-actions';
|
||||
|
||||
export type SenderRestrictionsType = {
|
||||
lowerLimit: number;
|
||||
isNewUser: boolean;
|
||||
isEnforcementOfNewRestrictionsInEffect: boolean;
|
||||
isAuthorizedDomainRequiredForNewCampaigns?: boolean;
|
||||
campaignTypes?: string[];
|
||||
alwaysRewrite?: boolean;
|
||||
};
|
||||
|
||||
@ -56,20 +56,12 @@ function SenderDomainInlineNotice({
|
||||
|
||||
const LOWER_LIMIT = senderRestrictions?.lowerLimit || 500;
|
||||
|
||||
const isNewUser = senderRestrictions?.isNewUser ?? true;
|
||||
const isEnforcementOfNewRestrictionsInEffect =
|
||||
senderRestrictions?.isEnforcementOfNewRestrictionsInEffect ?? true;
|
||||
// TODO: Remove after the enforcement date has passed
|
||||
const onlyShowWarnings =
|
||||
!isNewUser && !isEnforcementOfNewRestrictionsInEffect;
|
||||
|
||||
const isSmallSender = subscribersCount <= LOWER_LIMIT;
|
||||
|
||||
if (
|
||||
isSmallSender ||
|
||||
isPartiallyVerifiedDomain ||
|
||||
senderRestrictions.alwaysRewrite ||
|
||||
onlyShowWarnings
|
||||
senderRestrictions.alwaysRewrite
|
||||
) {
|
||||
isAlert = false;
|
||||
}
|
||||
@ -102,8 +94,7 @@ function SenderDomainInlineNotice({
|
||||
emailAddressDomain={emailAddressDomain}
|
||||
isFreeDomain={isFreeDomain}
|
||||
isPartiallyVerifiedDomain={isPartiallyVerifiedDomain}
|
||||
isSmallSender={isSmallSender}
|
||||
onlyShowWarnings={onlyShowWarnings}
|
||||
isSmallSender={isSmallSender || senderRestrictions.alwaysRewrite}
|
||||
/>
|
||||
</InlineNotice>
|
||||
);
|
||||
|
@ -7,49 +7,45 @@ function SenderDomainNoticeBody({
|
||||
isFreeDomain,
|
||||
isPartiallyVerifiedDomain,
|
||||
isSmallSender,
|
||||
onlyShowWarnings = false,
|
||||
alwaysRewrite = false,
|
||||
}: {
|
||||
emailAddressDomain: string;
|
||||
isFreeDomain: boolean;
|
||||
isPartiallyVerifiedDomain: boolean;
|
||||
isSmallSender: boolean;
|
||||
onlyShowWarnings?: boolean;
|
||||
alwaysRewrite?: boolean;
|
||||
}) {
|
||||
const renderMessage = (messageKey: string) => {
|
||||
const messages: { [key: string]: string } = {
|
||||
freeSmall:
|
||||
freeSmall: __(
|
||||
"Shared 3rd-party domains like <emailDomain/> will send from MailPoet's shared domain. We recommend that you use your site's branded domain instead.",
|
||||
free: "MailPoet cannot send email campaigns from shared 3rd-party domains like <emailDomain/>. Please send from your site's branded domain instead.",
|
||||
// TODO: Remove freeWarning after the enforcement date has passed
|
||||
freeWarning:
|
||||
"Starting on February 1st, 2024, MailPoet will no longer be able to send from email addresses on shared 3rd party domains like <emailDomain/>. Please send from your site's branded domain instead.",
|
||||
partiallyVerified:
|
||||
'mailpoet',
|
||||
),
|
||||
free: __(
|
||||
"MailPoet cannot send email campaigns from shared 3rd-party domains like <emailDomain/>. Please send from your site's branded domain instead.",
|
||||
'mailpoet',
|
||||
),
|
||||
partiallyVerified: __(
|
||||
'Update your domain settings to improve email deliverability and meet new sending requirements.',
|
||||
smallSender:
|
||||
'mailpoet',
|
||||
),
|
||||
smallSender: __(
|
||||
'Authenticate to send as <emailDomain/> and improve email deliverability.',
|
||||
default: 'Authenticate domain to send new emails as <emailDomain/>.',
|
||||
'mailpoet',
|
||||
),
|
||||
default: __(
|
||||
'Authenticate domain to send new emails as <emailDomain/>.',
|
||||
'mailpoet',
|
||||
),
|
||||
};
|
||||
|
||||
const defaultMessage = messages[messageKey] || messages.default;
|
||||
|
||||
return createInterpolateElement(__(defaultMessage, 'mailpoet'), {
|
||||
return createInterpolateElement(defaultMessage, {
|
||||
emailDomain: <strong>{escapeHTML(emailAddressDomain)}</strong>,
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: Remove after the enforcement date has passed
|
||||
if (onlyShowWarnings) {
|
||||
if (isFreeDomain) {
|
||||
return renderMessage(isSmallSender ? 'freeSmall' : 'freeWarning');
|
||||
}
|
||||
if (isPartiallyVerifiedDomain) {
|
||||
return renderMessage('partiallyVerified');
|
||||
}
|
||||
return renderMessage('smallSender');
|
||||
}
|
||||
|
||||
if (isFreeDomain) {
|
||||
return renderMessage(isSmallSender || alwaysRewrite ? 'freeSmall' : 'free');
|
||||
}
|
||||
|
3
mailpoet/assets/js/src/global.d.ts
vendored
3
mailpoet/assets/js/src/global.d.ts
vendored
@ -155,9 +155,6 @@ interface Window {
|
||||
mailpoet_partially_verified_sender_domains?: string[];
|
||||
mailpoet_sender_restrictions?: {
|
||||
lowerLimit: number;
|
||||
upperLimit: number;
|
||||
isNewUser: boolean;
|
||||
isEnforcementOfNewRestrictionsInEffect: boolean;
|
||||
isAuthorizedDomainRequiredForNewCampaigns?: boolean;
|
||||
campaignTypes?: string[];
|
||||
};
|
||||
|
@ -1,11 +1,9 @@
|
||||
import { MailPoet } from 'mailpoet';
|
||||
import { Button } from 'common/button/button';
|
||||
import { AutomationsInfoNotice } from 'notices/automations-info-notice';
|
||||
|
||||
export function KnowledgeBase() {
|
||||
return (
|
||||
<>
|
||||
<AutomationsInfoNotice />
|
||||
<p>{MailPoet.I18n.t('knowledgeBaseIntro')}</p>
|
||||
<ul className="mailpoet-text-links">
|
||||
<li>
|
||||
|
@ -119,9 +119,8 @@ const stepsListingHeading = (
|
||||
{' '}
|
||||
</h1>
|
||||
<div className="mailpoet-flex-grow" />
|
||||
{!['automation', 'automation_transactional'].includes(emailType) && (
|
||||
<TutorialIcon />
|
||||
)}
|
||||
{!['automation', 'automation_transactional'].includes(emailType) &&
|
||||
step === 3 && <TutorialIcon />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -16,7 +16,6 @@ import { NewsletterSend } from 'newsletters/send';
|
||||
import { Congratulate } from 'newsletters/send/congratulate/congratulate.jsx';
|
||||
import { NewsletterTypeStandard } from 'newsletters/types/standard.jsx';
|
||||
import { NewsletterNotification } from 'newsletters/types/notification/notification.jsx';
|
||||
import { NewsletterWelcome } from 'newsletters/types/welcome/welcome.jsx';
|
||||
import { NewsletterTypeReEngagement } from 'newsletters/types/re-engagement/re-engagement';
|
||||
import { NewsletterListStandard } from 'newsletters/listings/standard.jsx';
|
||||
import { NewsletterListNotification } from 'newsletters/listings/notification.jsx';
|
||||
@ -131,7 +130,7 @@ const routes = [
|
||||
render: withBoundary(Tabs),
|
||||
},
|
||||
{
|
||||
path: '/(standard|welcome|notification|re_engagement)/(.*)?',
|
||||
path: '/(standard|notification|re_engagement)/(.*)?',
|
||||
render: withBoundary(Tabs),
|
||||
},
|
||||
/* New newsletter: types */
|
||||
@ -143,10 +142,6 @@ const routes = [
|
||||
path: '/new/notification',
|
||||
render: withBoundary(NewsletterNotification),
|
||||
},
|
||||
{
|
||||
path: '/new/welcome',
|
||||
render: withBoundary(NewsletterWelcome),
|
||||
},
|
||||
{
|
||||
path: '/new/re-engagement',
|
||||
render: withBoundary(NewsletterTypeReEngagement),
|
||||
|
@ -1,7 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import { ChangeEvent, Component, ContextType } from 'react';
|
||||
import jQuery from 'jquery';
|
||||
import { __, _x, sprintf } from '@wordpress/i18n';
|
||||
import { __, _x } from '@wordpress/i18n';
|
||||
import { History, Location } from 'history';
|
||||
import ReactStringReplace from 'react-string-replace';
|
||||
import slugify from 'slugify';
|
||||
@ -349,12 +349,13 @@ class NewsletterSendComponent extends Component<
|
||||
body: JSON.stringify(response.data.body),
|
||||
categories: '["recent"]',
|
||||
},
|
||||
}).fail((err) => {
|
||||
this.showError(err);
|
||||
this.setState({ loading: false });
|
||||
MailPoet.Modal.loading(false);
|
||||
});
|
||||
done();
|
||||
})
|
||||
.then(() => done())
|
||||
.fail((err) => {
|
||||
this.showError(err);
|
||||
this.setState({ loading: false });
|
||||
MailPoet.Modal.loading(false);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
this.showError({ errors: [err] });
|
||||
@ -512,22 +513,11 @@ class NewsletterSendComponent extends Component<
|
||||
this.state.item.type === 'automatic' &&
|
||||
automaticEmails[opts.group]
|
||||
) {
|
||||
this.context.notices.success(
|
||||
<p>
|
||||
{sprintf(
|
||||
__('Your %1s Automatic Email is now activated!', 'mailpoet'),
|
||||
automaticEmails[opts.group]?.title ?? '',
|
||||
)}
|
||||
</p>,
|
||||
);
|
||||
MailPoet.trackEvent('Emails > Automatic email activated', {
|
||||
Type: slugify(`${opts.group}-${opts.event}`),
|
||||
Delay: getTimingValueForTracking(opts),
|
||||
});
|
||||
} else if (response.data.type === 'welcome') {
|
||||
this.context.notices.success(
|
||||
<p>{__('Your Welcome Email is now activated!', 'mailpoet')}</p>,
|
||||
);
|
||||
MailPoet.trackEvent('Emails > Welcome email activated', {
|
||||
'List type': opts.event,
|
||||
Delay: getTimingValueForTracking(opts),
|
||||
|
@ -1,130 +0,0 @@
|
||||
import { Component } from 'react';
|
||||
import _ from 'underscore';
|
||||
import jQuery from 'jquery';
|
||||
import PropTypes from 'prop-types';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
import { Background } from 'common/background/background';
|
||||
import { Button } from 'common/button/button';
|
||||
import { Heading } from 'common/typography/heading/heading';
|
||||
import { Grid } from 'common/grid';
|
||||
import { ListingHeadingStepsRoute } from 'newsletters/listings/heading-steps-route';
|
||||
import { MailPoet } from 'mailpoet';
|
||||
import { WelcomeScheduling } from './scheduling.jsx';
|
||||
|
||||
const field = {
|
||||
name: 'options',
|
||||
label: 'Event',
|
||||
type: 'reactComponent',
|
||||
component: WelcomeScheduling,
|
||||
};
|
||||
|
||||
class NewsletterWelcome extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
let availableSegments = window.mailpoet_segments || [];
|
||||
let defaultSegment = 1;
|
||||
availableSegments = availableSegments.filter(
|
||||
(segment) => segment.type === 'default',
|
||||
);
|
||||
|
||||
if (_.size(availableSegments) > 0) {
|
||||
defaultSegment = _.first(availableSegments).id;
|
||||
}
|
||||
|
||||
this.state = {
|
||||
options: {
|
||||
event: 'segment',
|
||||
segment: defaultSegment,
|
||||
role: 'subscriber',
|
||||
afterTimeNumber: 1,
|
||||
afterTimeType: 'immediate',
|
||||
},
|
||||
};
|
||||
|
||||
this.handleValueChange = this.handleValueChange.bind(this);
|
||||
this.handleNext = this.handleNext.bind(this);
|
||||
}
|
||||
|
||||
handleValueChange(event) {
|
||||
const { state } = this;
|
||||
state[event.target.name] = event.target.value;
|
||||
this.setState(state);
|
||||
}
|
||||
|
||||
handleNext(event) {
|
||||
event.preventDefault();
|
||||
if (!this.isValid()) {
|
||||
this.validate();
|
||||
return;
|
||||
}
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletters',
|
||||
action: 'create',
|
||||
data: _.extend({}, this.state, {
|
||||
type: 'welcome',
|
||||
subject: __('Subject', 'mailpoet'),
|
||||
}),
|
||||
})
|
||||
.done((response) => {
|
||||
this.showTemplateSelection(response.data.id);
|
||||
})
|
||||
.fail((response) => {
|
||||
if (response.errors.length > 0) {
|
||||
MailPoet.Notice.error(
|
||||
response.errors.map((error) => error.message),
|
||||
{ scroll: true },
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
isValid = () => jQuery('#welcome_scheduling').parsley().isValid();
|
||||
|
||||
validate = () => jQuery('#welcome_scheduling').parsley().validate();
|
||||
|
||||
showTemplateSelection(newsletterId) {
|
||||
this.props.history.push(`/template/${newsletterId}`);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Background color="#fff" />
|
||||
|
||||
<ListingHeadingStepsRoute
|
||||
emailType="welcome"
|
||||
automationId="welcome_email_creation_heading"
|
||||
/>
|
||||
|
||||
<Grid.Column align="center" className="mailpoet-schedule-email">
|
||||
<Heading level={4}>
|
||||
{__('When to send this welcome email?', 'mailpoet')}
|
||||
</Heading>
|
||||
<form id="welcome_scheduling">
|
||||
<WelcomeScheduling
|
||||
item={this.state}
|
||||
field={field}
|
||||
onValueChange={this.handleValueChange}
|
||||
/>
|
||||
|
||||
<Button isFullWidth type="submit" onClick={this.handleNext}>
|
||||
{__('Next', 'mailpoet')}
|
||||
</Button>
|
||||
</form>
|
||||
</Grid.Column>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NewsletterWelcome.propTypes = {
|
||||
history: PropTypes.shape({
|
||||
push: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
NewsletterWelcome.displayName = 'NewsletterWelcome';
|
||||
|
||||
export { NewsletterWelcome };
|
@ -1,34 +0,0 @@
|
||||
import { createInterpolateElement } from '@wordpress/element';
|
||||
import { MailPoet } from 'mailpoet';
|
||||
import { Notice } from 'notices/notice';
|
||||
|
||||
function AutomationsInfoNotice() {
|
||||
const automationsInfo = createInterpolateElement(
|
||||
MailPoet.I18n.t('automationsInfoNotice'),
|
||||
{
|
||||
link1: (
|
||||
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
|
||||
<a
|
||||
rel="noreferrer"
|
||||
href="https://kb.mailpoet.com/article/397-how-to-set-up-an-automation"
|
||||
target="_blank"
|
||||
/>
|
||||
),
|
||||
link2: (
|
||||
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
|
||||
<a
|
||||
rel="noreferrer"
|
||||
href="https://kb.mailpoet.com/article/408-integration-with-automatewoo"
|
||||
target="_blank"
|
||||
/>
|
||||
),
|
||||
},
|
||||
);
|
||||
return (
|
||||
<Notice type="warning" scroll renderInPlace timeout={false}>
|
||||
<p>{automationsInfo}</p>
|
||||
</Notice>
|
||||
);
|
||||
}
|
||||
|
||||
export { AutomationsInfoNotice };
|
@ -1,19 +1,15 @@
|
||||
import { SaveButton } from 'settings/components';
|
||||
import { AutomationsInfoNotice } from 'notices/automations-info-notice';
|
||||
import { EmailCustomizer } from './email-customizer';
|
||||
import { CheckoutOptin } from './checkout-optin';
|
||||
import { SubscribeOldCustomers } from './subscribe-old-customers';
|
||||
|
||||
export function WooCommerce() {
|
||||
return (
|
||||
<>
|
||||
<AutomationsInfoNotice />
|
||||
<div className="mailpoet-settings-grid">
|
||||
<EmailCustomizer />
|
||||
<CheckoutOptin />
|
||||
<SubscribeOldCustomers />
|
||||
<SaveButton />
|
||||
</div>
|
||||
</>
|
||||
<div className="mailpoet-settings-grid">
|
||||
<EmailCustomizer />
|
||||
<CheckoutOptin />
|
||||
<SubscribeOldCustomers />
|
||||
<SaveButton />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -243,8 +243,6 @@ class NewslettersResponseBuilder {
|
||||
if ($task === null) {
|
||||
return null;
|
||||
}
|
||||
// the following crazy mix of '$queue' and '$task' comes from 'array_merge($task, $queue)'
|
||||
// (MailPoet\Tasks\Sending) which means all equal-named fields will be taken from '$queue'
|
||||
return [
|
||||
'id' => (string)$queue->getId(), // (string) for BC
|
||||
'type' => $task->getType(),
|
||||
|
@ -15,6 +15,7 @@ use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\InvalidStateException;
|
||||
use MailPoet\Listing;
|
||||
use MailPoet\Newsletter\Listing\NewsletterListingRepository;
|
||||
use MailPoet\Newsletter\NewsletterDeleteController;
|
||||
use MailPoet\Newsletter\NewsletterSaveController;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Newsletter\NewsletterValidator;
|
||||
@ -73,6 +74,8 @@ class Newsletters extends APIEndpoint {
|
||||
/** @var NewsletterSaveController */
|
||||
private $newsletterSaveController;
|
||||
|
||||
private NewsletterDeleteController $newsletterDeleteController;
|
||||
|
||||
/** @var NewsletterUrl */
|
||||
private $newsletterUrl;
|
||||
|
||||
@ -98,6 +101,7 @@ class Newsletters extends APIEndpoint {
|
||||
Emoji $emoji,
|
||||
SendPreviewController $sendPreviewController,
|
||||
NewsletterSaveController $newsletterSaveController,
|
||||
NewsletterDeleteController $newsletterDeleteController,
|
||||
NewsletterUrl $newsletterUrl,
|
||||
Scheduler $scheduler,
|
||||
NewsletterValidator $newsletterValidator,
|
||||
@ -115,6 +119,7 @@ class Newsletters extends APIEndpoint {
|
||||
$this->emoji = $emoji;
|
||||
$this->sendPreviewController = $sendPreviewController;
|
||||
$this->newsletterSaveController = $newsletterSaveController;
|
||||
$this->newsletterDeleteController = $newsletterDeleteController;
|
||||
$this->newsletterUrl = $newsletterUrl;
|
||||
$this->scheduler = $scheduler;
|
||||
$this->newsletterValidator = $newsletterValidator;
|
||||
@ -163,8 +168,6 @@ class Newsletters extends APIEndpoint {
|
||||
$newsletter = $this->newsletterSaveController->save($data);
|
||||
$response = $this->newslettersResponseBuilder->build($newsletter, [
|
||||
NewslettersResponseBuilder::RELATION_SEGMENTS,
|
||||
NewslettersResponseBuilder::RELATION_OPTIONS,
|
||||
NewslettersResponseBuilder::RELATION_QUEUE,
|
||||
]);
|
||||
$previewUrl = $this->getViewInBrowserUrl($newsletter);
|
||||
$response = $this->wp->applyFilters('mailpoet_api_newsletters_save_after', $response);
|
||||
@ -239,11 +242,7 @@ class Newsletters extends APIEndpoint {
|
||||
$this->newslettersRepository->flush();
|
||||
|
||||
return $this->successResponse(
|
||||
$this->newslettersResponseBuilder->build($newsletter, [
|
||||
NewslettersResponseBuilder::RELATION_SEGMENTS,
|
||||
NewslettersResponseBuilder::RELATION_OPTIONS,
|
||||
NewslettersResponseBuilder::RELATION_QUEUE,
|
||||
])
|
||||
$this->newslettersResponseBuilder->build($newsletter)
|
||||
);
|
||||
}
|
||||
|
||||
@ -253,11 +252,7 @@ class Newsletters extends APIEndpoint {
|
||||
$this->newslettersRepository->bulkRestore([$newsletter->getId()]);
|
||||
$this->newslettersRepository->refresh($newsletter);
|
||||
return $this->successResponse(
|
||||
$this->newslettersResponseBuilder->build($newsletter, [
|
||||
NewslettersResponseBuilder::RELATION_SEGMENTS,
|
||||
NewslettersResponseBuilder::RELATION_OPTIONS,
|
||||
NewslettersResponseBuilder::RELATION_QUEUE,
|
||||
]),
|
||||
$this->newslettersResponseBuilder->build($newsletter),
|
||||
['count' => 1]
|
||||
);
|
||||
} else {
|
||||
@ -273,11 +268,7 @@ class Newsletters extends APIEndpoint {
|
||||
$this->newslettersRepository->bulkTrash([$newsletter->getId()]);
|
||||
$this->newslettersRepository->refresh($newsletter);
|
||||
return $this->successResponse(
|
||||
$this->newslettersResponseBuilder->build($newsletter, [
|
||||
NewslettersResponseBuilder::RELATION_SEGMENTS,
|
||||
NewslettersResponseBuilder::RELATION_OPTIONS,
|
||||
NewslettersResponseBuilder::RELATION_QUEUE,
|
||||
]),
|
||||
$this->newslettersResponseBuilder->build($newsletter),
|
||||
['count' => 1]
|
||||
);
|
||||
} else {
|
||||
@ -291,7 +282,7 @@ class Newsletters extends APIEndpoint {
|
||||
$newsletter = $this->getNewsletter($data);
|
||||
if ($newsletter instanceof NewsletterEntity) {
|
||||
$this->wp->doAction('mailpoet_api_newsletters_delete_before', [$newsletter->getId()]);
|
||||
$this->newslettersRepository->bulkDelete([$newsletter->getId()]);
|
||||
$this->newsletterDeleteController->bulkDelete([(int)$newsletter->getId()]);
|
||||
$this->wp->doAction('mailpoet_api_newsletters_delete_after', [$newsletter->getId()]);
|
||||
return $this->successResponse(null, ['count' => 1]);
|
||||
} else {
|
||||
@ -308,11 +299,7 @@ class Newsletters extends APIEndpoint {
|
||||
$duplicate = $this->newsletterSaveController->duplicate($newsletter);
|
||||
$this->wp->doAction('mailpoet_api_newsletters_duplicate_after', $newsletter, $duplicate);
|
||||
return $this->successResponse(
|
||||
$this->newslettersResponseBuilder->build($duplicate, [
|
||||
NewslettersResponseBuilder::RELATION_SEGMENTS,
|
||||
NewslettersResponseBuilder::RELATION_OPTIONS,
|
||||
NewslettersResponseBuilder::RELATION_QUEUE,
|
||||
]),
|
||||
$this->newslettersResponseBuilder->build($duplicate),
|
||||
['count' => 1]
|
||||
);
|
||||
} else {
|
||||
@ -401,7 +388,7 @@ class Newsletters extends APIEndpoint {
|
||||
$this->newslettersRepository->bulkRestore($ids);
|
||||
} elseif ($data['action'] === 'delete') {
|
||||
$this->wp->doAction('mailpoet_api_newsletters_delete_before', $ids);
|
||||
$this->newslettersRepository->bulkDelete($ids);
|
||||
$this->newsletterDeleteController->bulkDelete($ids);
|
||||
$this->wp->doAction('mailpoet_api_newsletters_delete_after', $ids);
|
||||
} else {
|
||||
throw UnexpectedValueException::create()
|
||||
|
@ -10,19 +10,17 @@ use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Cron\ActionScheduler\Actions\DaemonTrigger;
|
||||
use MailPoet\Cron\CronTrigger;
|
||||
use MailPoet\Cron\Triggers\WordPress;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Mailer\MailerFactory;
|
||||
use MailPoet\Models\SendingQueue as SendingQueueModel;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Newsletter\NewsletterValidator;
|
||||
use MailPoet\Newsletter\Scheduler\Scheduler;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
|
||||
use MailPoet\Newsletter\Sending\SendingQueuesRepository;
|
||||
use MailPoet\Segments\SubscribersFinder;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Tasks\Sending as SendingTask;
|
||||
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
|
||||
@ -52,9 +50,6 @@ class SendingQueue extends APIEndpoint {
|
||||
/** @var NewsletterValidator */
|
||||
private $newsletterValidator;
|
||||
|
||||
/** @var Scheduler */
|
||||
private $scheduler;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
@ -71,7 +66,6 @@ class SendingQueue extends APIEndpoint {
|
||||
SubscribersFinder $subscribersFinder,
|
||||
ScheduledTasksRepository $scheduledTasksRepository,
|
||||
MailerFactory $mailerFactory,
|
||||
Scheduler $scheduler,
|
||||
SettingsController $settings,
|
||||
DaemonTrigger $actionSchedulerDaemonTriggerAction,
|
||||
NewsletterValidator $newsletterValidator,
|
||||
@ -83,7 +77,6 @@ class SendingQueue extends APIEndpoint {
|
||||
$this->sendingQueuesRepository = $sendingQueuesRepository;
|
||||
$this->scheduledTasksRepository = $scheduledTasksRepository;
|
||||
$this->mailerFactory = $mailerFactory;
|
||||
$this->scheduler = $scheduler;
|
||||
$this->settings = $settings;
|
||||
$this->actionSchedulerDaemonTriggerAction = $actionSchedulerDaemonTriggerAction;
|
||||
$this->newsletterValidator = $newsletterValidator;
|
||||
@ -118,98 +111,81 @@ class SendingQueue extends APIEndpoint {
|
||||
]);
|
||||
}
|
||||
|
||||
// check that the sending method has been configured properly by verifying that default mailer can be build
|
||||
try {
|
||||
// check that the sending method has been configured properly by verifying that default mailer can be build
|
||||
$this->mailerFactory->getDefaultMailer();
|
||||
} catch (\Exception $e) {
|
||||
return $this->errorResponse([
|
||||
$e->getCode() => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
// add newsletter to the sending queue
|
||||
$queue = SendingQueueModel::joinWithTasks()
|
||||
->where('queues.newsletter_id', $newsletter->getId())
|
||||
->whereNull('tasks.status')
|
||||
->findOne();
|
||||
$sendingQueue = $this->sendingQueuesRepository->findOneByNewsletterAndTaskStatus($newsletter, null);
|
||||
|
||||
if (!empty($queue)) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('This newsletter is already being sent.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$scheduledQueue = SendingQueueModel::joinWithTasks()
|
||||
->where('queues.newsletter_id', $newsletter->getId())
|
||||
->where('tasks.status', SendingQueueModel::STATUS_SCHEDULED)
|
||||
->findOne();
|
||||
if ($scheduledQueue instanceof SendingQueueModel) {
|
||||
$queue = SendingTask::createFromQueue($scheduledQueue);
|
||||
} else {
|
||||
$queue = SendingTask::create();
|
||||
$queue->newsletterId = $newsletter->getId();
|
||||
}
|
||||
|
||||
$taskModel = $queue->task();
|
||||
$taskEntity = $this->scheduledTasksRepository->findOneById($taskModel->id);
|
||||
|
||||
if (!$taskEntity instanceof ScheduledTaskEntity) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('Unable to find scheduled task associated with this newsletter.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
WordPress::resetRunInterval();
|
||||
if ((bool)$newsletter->getOptionValue('isScheduled')) {
|
||||
// set newsletter status
|
||||
$newsletter->setStatus(NewsletterEntity::STATUS_SCHEDULED);
|
||||
|
||||
// set queue status
|
||||
$scheduledAt = $this->scheduler->formatDatetimeString($newsletter->getOptionValue('scheduledAt'));
|
||||
$queue->status = SendingQueueModel::STATUS_SCHEDULED;
|
||||
$queue->scheduledAt = $scheduledAt;
|
||||
|
||||
// we need to refresh the entity here for now while this method still uses Paris
|
||||
$taskEntity->setStatus(SendingQueueModel::STATUS_SCHEDULED);
|
||||
$taskEntity->setScheduledAt(new Carbon($scheduledAt));
|
||||
} else {
|
||||
$segments = $newsletter->getSegmentIds();
|
||||
|
||||
$subscribersCount = $this->subscribersFinder->addSubscribersToTaskFromSegments($taskEntity, $segments, $newsletter->getFilterSegmentId());
|
||||
|
||||
if (!$subscribersCount) {
|
||||
if ($sendingQueue instanceof SendingQueueEntity) {
|
||||
return $this->errorResponse([
|
||||
APIError::UNKNOWN => __('There are no subscribers in that list!', 'mailpoet'),
|
||||
APIError::NOT_FOUND => __('This newsletter is already being sent.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
$queue->updateCount();
|
||||
$queue->status = null;
|
||||
$queue->scheduledAt = null;
|
||||
|
||||
// we need to refresh the entity here for now while this method still uses Paris
|
||||
$taskEntity->setStatus(null);
|
||||
$taskEntity->setScheduledAt(null);
|
||||
$sendingQueue = $this->sendingQueuesRepository->findOneByNewsletterAndTaskStatus($newsletter, ScheduledTaskEntity::STATUS_SCHEDULED);
|
||||
|
||||
// set newsletter status
|
||||
$newsletter->setStatus(NewsletterEntity::STATUS_SENDING);
|
||||
}
|
||||
$queue->save();
|
||||
$this->newsletterRepository->flush();
|
||||
// refreshing is needed while this method still uses Paris
|
||||
$this->newsletterRepository->refresh($newsletter);
|
||||
$latestQueue = $newsletter->getLatestQueue();
|
||||
if ($latestQueue instanceof SendingQueueEntity) {
|
||||
$this->sendingQueuesRepository->refresh($latestQueue);
|
||||
}
|
||||
if (is_null($sendingQueue)) {
|
||||
$scheduledTask = new ScheduledTaskEntity();
|
||||
$scheduledTask->setType(SendingQueueWorker::TASK_TYPE);
|
||||
$sendingQueue = new SendingQueueEntity();
|
||||
$sendingQueue->setNewsletter($newsletter);
|
||||
$sendingQueue->setTask($scheduledTask);
|
||||
|
||||
$this->sendingQueuesRepository->persist($sendingQueue);
|
||||
$this->newsletterRepository->refresh($newsletter);
|
||||
} else {
|
||||
$scheduledTask = $sendingQueue->getTask();
|
||||
}
|
||||
|
||||
if (!$scheduledTask instanceof ScheduledTaskEntity) {
|
||||
return $this->errorResponse([
|
||||
APIError::NOT_FOUND => __('Unable to find scheduled task associated with this newsletter.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$scheduledTask->setPriority(ScheduledTaskEntity::PRIORITY_MEDIUM);
|
||||
$this->scheduledTasksRepository->persist($scheduledTask);
|
||||
$this->scheduledTasksRepository->flush();
|
||||
|
||||
WordPress::resetRunInterval();
|
||||
if ((bool)$newsletter->getOptionValue('isScheduled')) {
|
||||
// set newsletter status
|
||||
$newsletter->setStatus(NewsletterEntity::STATUS_SCHEDULED);
|
||||
|
||||
// set scheduled task status
|
||||
$scheduledTask->setStatus(ScheduledTaskEntity::STATUS_SCHEDULED);
|
||||
$scheduledTask->setScheduledAt(new Carbon($newsletter->getOptionValue('scheduledAt')));
|
||||
} else {
|
||||
$segments = $newsletter->getSegmentIds();
|
||||
|
||||
$this->scheduledTasksRepository->refresh($scheduledTask);
|
||||
$subscribersCount = $this->subscribersFinder->addSubscribersToTaskFromSegments($scheduledTask, $segments, $newsletter->getFilterSegmentId());
|
||||
|
||||
if (!$subscribersCount) {
|
||||
return $this->errorResponse([
|
||||
APIError::UNKNOWN => __('There are no subscribers in that list!', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->sendingQueuesRepository->updateCounts($sendingQueue);
|
||||
$scheduledTask->setStatus(null);
|
||||
$scheduledTask->setScheduledAt(null);
|
||||
|
||||
// set newsletter status
|
||||
$newsletter->setStatus(NewsletterEntity::STATUS_SENDING);
|
||||
}
|
||||
$this->scheduledTasksRepository->persist($scheduledTask);
|
||||
$this->newsletterRepository->flush();
|
||||
|
||||
$errors = $queue->getErrors();
|
||||
if (!empty($errors)) {
|
||||
return $this->errorResponse($errors);
|
||||
} else {
|
||||
$this->triggerSending($newsletter);
|
||||
return $this->successResponse(
|
||||
($newsletter->getLatestQueue() instanceof SendingQueueEntity) ? $this->sendingQueuesResponseBuilder->build($newsletter->getLatestQueue()) : null
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return $this->errorResponse([
|
||||
$e->getCode() => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,9 +152,6 @@ class Newsletters {
|
||||
$data['all_sender_domains'] = $this->senderDomainController->getAllSenderDomains();
|
||||
$data['sender_restrictions'] = [
|
||||
'lowerLimit' => AuthorizedSenderDomainController::LOWER_LIMIT,
|
||||
'upperLimit' => AuthorizedSenderDomainController::UPPER_LIMIT,
|
||||
'isNewUser' => $this->senderDomainController->isNewUser(),
|
||||
'isEnforcementOfNewRestrictionsInEffect' => $this->senderDomainController->isEnforcementOfNewRestrictionsInEffect(),
|
||||
'isAuthorizedDomainRequiredForNewCampaigns' => $this->senderDomainController->isAuthorizedDomainRequiredForNewCampaigns(),
|
||||
'campaignTypes' => NewsletterEntity::CAMPAIGN_TYPES,
|
||||
];
|
||||
|
@ -106,9 +106,6 @@ class Settings {
|
||||
$data['all_sender_domains'] = $this->senderDomainController->getAllSenderDomains();
|
||||
$data['sender_restrictions'] = [
|
||||
'lowerLimit' => AuthorizedSenderDomainController::LOWER_LIMIT,
|
||||
'upperLimit' => AuthorizedSenderDomainController::UPPER_LIMIT,
|
||||
'isNewUser' => $this->senderDomainController->isNewUser(),
|
||||
'isEnforcementOfNewRestrictionsInEffect' => $this->senderDomainController->isEnforcementOfNewRestrictionsInEffect(),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ use MailPoet\Automation\Engine\Data\Step;
|
||||
use MailPoet\Automation\Engine\Hooks;
|
||||
use MailPoet\Automation\Engine\Storage\AutomationStorage;
|
||||
use MailPoet\Automation\Engine\WordPress;
|
||||
use MailPoet\Newsletter\NewsletterDeleteController;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
|
||||
class AutomationEditorLoadingHooks {
|
||||
@ -20,14 +21,18 @@ class AutomationEditorLoadingHooks {
|
||||
/** @var NewslettersRepository */
|
||||
private $newslettersRepository;
|
||||
|
||||
private NewsletterDeleteController $newsletterDeleteController;
|
||||
|
||||
public function __construct(
|
||||
WordPress $wp,
|
||||
AutomationStorage $automationStorage,
|
||||
NewslettersRepository $newslettersRepository
|
||||
NewslettersRepository $newslettersRepository,
|
||||
NewsletterDeleteController $newsletterDeleteController
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->automationStorage = $automationStorage;
|
||||
$this->newslettersRepository = $newslettersRepository;
|
||||
$this->newsletterDeleteController = $newsletterDeleteController;
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
@ -59,7 +64,7 @@ class AutomationEditorLoadingHooks {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->newslettersRepository->bulkDelete([$emailId]);
|
||||
$this->newsletterDeleteController->bulkDelete([$emailId]);
|
||||
$args = $step->getArgs();
|
||||
unset($args['email_id']);
|
||||
$updatedStep = new Step(
|
||||
|
@ -4,6 +4,7 @@ namespace MailPoet\Cron;
|
||||
|
||||
use MailPoet\Cron\Workers\WorkersFactory;
|
||||
use MailPoet\Logging\LoggerFactory;
|
||||
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
||||
|
||||
class Daemon {
|
||||
public $timer;
|
||||
@ -14,6 +15,9 @@ class Daemon {
|
||||
/** @var CronWorkerRunner */
|
||||
private $cronWorkerRunner;
|
||||
|
||||
/** @var EntityManager */
|
||||
private $entityManager;
|
||||
|
||||
/** @var WorkersFactory */
|
||||
private $workersFactory;
|
||||
|
||||
@ -23,12 +27,14 @@ class Daemon {
|
||||
public function __construct(
|
||||
CronHelper $cronHelper,
|
||||
CronWorkerRunner $cronWorkerRunner,
|
||||
EntityManager $entityManager,
|
||||
WorkersFactory $workersFactory,
|
||||
LoggerFactory $loggerFactory
|
||||
) {
|
||||
$this->timer = microtime(true);
|
||||
$this->workersFactory = $workersFactory;
|
||||
$this->cronWorkerRunner = $cronWorkerRunner;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->cronHelper = $cronHelper;
|
||||
$this->loggerFactory = $loggerFactory;
|
||||
}
|
||||
@ -40,6 +46,10 @@ class Daemon {
|
||||
$errors = [];
|
||||
foreach ($this->getWorkers() as $worker) {
|
||||
try {
|
||||
// Clear the entity manager memory for every cron run.
|
||||
// This avoids using stale data and prevents memory leaks.
|
||||
$this->entityManager->clear();
|
||||
|
||||
if ($worker instanceof CronWorkerInterface) {
|
||||
$this->cronWorkerRunner->run($worker);
|
||||
} else {
|
||||
|
@ -181,8 +181,9 @@ class SendingQueue {
|
||||
// pre-process newsletter (render, replace shortcodes/links, etc.)
|
||||
$newsletter = $this->newsletterTask->preProcessNewsletter($newsletter, $task);
|
||||
|
||||
// During pre-processing we may find that the newsletter can't be sent and we delete it including all associated entities
|
||||
// E.g. post notification history newsletter when there are no posts to send
|
||||
if (!$newsletter) {
|
||||
$this->deleteTask($task);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -575,6 +576,11 @@ class SendingQueue {
|
||||
}
|
||||
|
||||
private function stopProgress(ScheduledTaskEntity $task): void {
|
||||
// if task is not managed by entity manager, it's already deleted and detached
|
||||
// it can be deleted in self::processSending method
|
||||
if (!$this->entityManager->contains($task)) {
|
||||
return;
|
||||
}
|
||||
$task->setInProgress(false);
|
||||
$this->scheduledTasksRepository->flush();
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ use MailPoet\Mailer\Mailer as MailerInstance;
|
||||
use MailPoet\Mailer\MailerFactory;
|
||||
use MailPoet\Mailer\MailerLog;
|
||||
use MailPoet\Mailer\Methods\MailPoet;
|
||||
use MailPoet\Models\Subscriber;
|
||||
|
||||
class Mailer {
|
||||
/** @var MailerFactory */
|
||||
@ -62,9 +61,7 @@ class Mailer {
|
||||
}
|
||||
|
||||
public function prepareSubscriberForSending(SubscriberEntity $subscriber) {
|
||||
$subscriberModel = Subscriber::findOne($subscriber->getId());
|
||||
|
||||
return $this->mailer->formatSubscriberNameAndEmailAddress($subscriberModel);
|
||||
return $this->mailer->formatSubscriberNameAndEmailAddress($subscriber);
|
||||
}
|
||||
|
||||
public function sendBulk($preparedNewsletters, $preparedSubscribers, $extraParams = []) {
|
||||
|
@ -14,11 +14,13 @@ use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Logging\LoggerFactory;
|
||||
use MailPoet\Mailer\MailerLog;
|
||||
use MailPoet\Newsletter\Links\Links as NewsletterLinks;
|
||||
use MailPoet\Newsletter\NewsletterDeleteController;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Newsletter\Renderer\PostProcess\OpenTracking;
|
||||
use MailPoet\Newsletter\Renderer\Renderer;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
|
||||
use MailPoet\Newsletter\Sending\SendingQueuesRepository;
|
||||
use MailPoet\RuntimeException;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Settings\TrackingConfig;
|
||||
use MailPoet\Statistics\GATracking;
|
||||
@ -51,6 +53,9 @@ class Newsletter {
|
||||
/** @var NewslettersRepository */
|
||||
private $newslettersRepository;
|
||||
|
||||
/** @var NewsletterDeleteController */
|
||||
private $newsletterDeleteController;
|
||||
|
||||
/** @var Emoji */
|
||||
private $emoji;
|
||||
|
||||
@ -96,6 +101,7 @@ class Newsletter {
|
||||
$this->emoji = $emoji;
|
||||
$this->renderer = ContainerWrapper::getInstance()->get(Renderer::class);
|
||||
$this->newslettersRepository = ContainerWrapper::getInstance()->get(NewslettersRepository::class);
|
||||
$this->newsletterDeleteController = ContainerWrapper::getInstance()->get(NewsletterDeleteController::class);
|
||||
$this->linksTask = ContainerWrapper::getInstance()->get(LinksTask::class);
|
||||
$this->newsletterLinks = ContainerWrapper::getInstance()->get(NewsletterLinks::class);
|
||||
$this->sendingQueuesRepository = ContainerWrapper::getInstance()->get(SendingQueuesRepository::class);
|
||||
@ -134,11 +140,22 @@ class Newsletter {
|
||||
return $newsletter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-processes the newsletter before sending.
|
||||
* - Renders the newsletter
|
||||
* - Adds tracking
|
||||
* - Extracts links
|
||||
* - Checks if the newsletter is a post notification and if it contains at least 1 ALC post.
|
||||
* If not it deletes the notification history record and all associate entities.
|
||||
*
|
||||
* @return NewsletterEntity|false - Returns false only if the newsletter is a post notification history and was deleted.
|
||||
*
|
||||
*/
|
||||
public function preProcessNewsletter(NewsletterEntity $newsletter, ScheduledTaskEntity $task) {
|
||||
// return the newsletter if it was previously rendered
|
||||
$queue = $task->getSendingQueue();
|
||||
if (!$queue) {
|
||||
return false;
|
||||
throw new RuntimeException('Can‘t pre-process newsletter without queue.');
|
||||
}
|
||||
if ($queue->getNewsletterRenderedBody() !== null) {
|
||||
return $newsletter;
|
||||
@ -191,7 +208,7 @@ class Newsletter {
|
||||
'no posts in post notification, deleting it',
|
||||
['newsletter_id' => $newsletter->getId(), 'task_id' => $task->getId()]
|
||||
);
|
||||
$this->newslettersRepository->bulkDelete([(int)$newsletter->getId()]);
|
||||
$this->newsletterDeleteController->bulkDelete([(int)$newsletter->getId()]);
|
||||
return false;
|
||||
}
|
||||
// extract and save newsletter posts
|
||||
|
@ -40,4 +40,20 @@ class NewsletterLinkRepository extends Repository {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @param int[] $ids */
|
||||
public function deleteByNewsletterIds(array $ids): void {
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->delete(NewsletterLinkEntity::class, 'l')
|
||||
->where('l.newsletter IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// delete was done via DQL, make sure the entities are also detached from the entity manager
|
||||
$this->detachAll(function (NewsletterLinkEntity $entity) use ($ids) {
|
||||
$newsletter = $entity->getNewsletter();
|
||||
return $newsletter && in_array($newsletter->getId(), $ids, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -65,4 +65,20 @@ class StatsNotificationsRepository extends Repository {
|
||||
WHERE sn.id IS NULL AND st.type = :taskType;
|
||||
", ['taskType' => Worker::TASK_TYPE]);
|
||||
}
|
||||
|
||||
/** @param int[] $ids */
|
||||
public function deleteByNewsletterIds(array $ids): void {
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->delete(StatsNotificationEntity::class, 'n')
|
||||
->where('n.newsletter IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// delete was done via DQL, make sure the entities are also detached from the entity manager
|
||||
$this->detachAll(function (StatsNotificationEntity $entity) use ($ids) {
|
||||
$newsletter = $entity->getNewsletter();
|
||||
return $newsletter && in_array($newsletter->getId(), $ids, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -543,6 +543,7 @@ class ContainerConfigurator implements IContainerConfigurator {
|
||||
$container->autowire(\MailPoet\Newsletter\ApiDataSanitizer::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Newsletter\AutomatedLatestContent::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Newsletter\NewsletterSaveController::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Newsletter\NewsletterDeleteController::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Newsletter\NewsletterPostsRepository::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Newsletter\NewslettersRepository::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Newsletter\AutomaticEmailsRepository::class)->setPublic(true);
|
||||
|
@ -111,6 +111,19 @@ abstract class Repository {
|
||||
$this->entityManager->refresh($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(T): bool|null $filter
|
||||
*/
|
||||
public function refreshAll(callable $filter = null): void {
|
||||
$entities = $this->getAllFromIdentityMap();
|
||||
foreach ($entities as $entity) {
|
||||
if ($filter && !$filter($entity)) {
|
||||
continue;
|
||||
}
|
||||
$this->entityManager->refresh($entity);
|
||||
}
|
||||
}
|
||||
|
||||
public function flush() {
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
@ -130,13 +143,8 @@ abstract class Repository {
|
||||
* @param callable(T): bool|null $filter
|
||||
*/
|
||||
public function detachAll(callable $filter = null): void {
|
||||
$className = $this->getEntityClassName();
|
||||
$rootClassName = $this->entityManager->getClassMetadata($className)->rootEntityName;
|
||||
$entities = $this->entityManager->getUnitOfWork()->getIdentityMap()[$rootClassName] ?? [];
|
||||
$entities = $this->getAllFromIdentityMap();
|
||||
foreach ($entities as $entity) {
|
||||
if (!($entity instanceof $className)) {
|
||||
continue;
|
||||
}
|
||||
if ($filter && !$filter($entity)) {
|
||||
continue;
|
||||
}
|
||||
@ -144,22 +152,19 @@ abstract class Repository {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<object> $className
|
||||
* @param callable|null $filter
|
||||
*/
|
||||
public function detachEntitiesOfType($className, callable $filter = null): void {
|
||||
/** @return T[] */
|
||||
public function getAllFromIdentityMap(): array {
|
||||
$className = $this->getEntityClassName();
|
||||
$rootClassName = $this->entityManager->getClassMetadata($className)->rootEntityName;
|
||||
$entities = $this->entityManager->getUnitOfWork()->getIdentityMap()[$rootClassName] ?? [];
|
||||
|
||||
$result = [];
|
||||
foreach ($entities as $entity) {
|
||||
if (!($entity instanceof $className)) {
|
||||
continue;
|
||||
if ($entity instanceof $className) {
|
||||
$result[] = $entity;
|
||||
}
|
||||
if ($filter && !$filter($entity)) {
|
||||
continue;
|
||||
}
|
||||
$this->entityManager->detach($entity);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -36,19 +36,21 @@ class StatisticsNewsletterEntity {
|
||||
private $subscriber;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetimetz", nullable=true)
|
||||
* @var \DateTimeInterface|null
|
||||
* @ORM\Column(type="datetimetz", nullable=false)
|
||||
* @var \DateTimeInterface
|
||||
*/
|
||||
private $sentAt;
|
||||
|
||||
public function __construct(
|
||||
NewsletterEntity $newsletter,
|
||||
SendingQueueEntity $queue,
|
||||
SubscriberEntity $subscriber
|
||||
SubscriberEntity $subscriber,
|
||||
\DateTimeInterface $sentAt = null
|
||||
) {
|
||||
$this->newsletter = $newsletter;
|
||||
$this->queue = $queue;
|
||||
$this->subscriber = $subscriber;
|
||||
$this->sentAt = $sentAt ?: new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,7 +20,7 @@ class StatisticsWooCommercePurchaseEntity {
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\NewsletterEntity")
|
||||
* @ORM\JoinColumn(name="newsletter_id", referencedColumnName="id")
|
||||
* @ORM\JoinColumn(name="newsletter_id", referencedColumnName="id", nullable=true)
|
||||
* @var NewsletterEntity|null
|
||||
*/
|
||||
private $newsletter;
|
||||
|
@ -69,12 +69,19 @@ class FormsRepository extends Repository {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $this->entityManager->createQueryBuilder()
|
||||
$result = $this->entityManager->createQueryBuilder()
|
||||
->update(FormEntity::class, 'f')
|
||||
->set('f.deletedAt', 'CURRENT_TIMESTAMP()')
|
||||
->where('f.id IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()->execute();
|
||||
|
||||
// update was done via DQL, make sure the entities are also refreshed in the entity manager
|
||||
$this->refreshAll(function (FormEntity $entity) use ($ids) {
|
||||
return in_array($entity->getId(), $ids, true);
|
||||
});
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function bulkRestore(array $ids): int {
|
||||
@ -82,13 +89,20 @@ class FormsRepository extends Repository {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $this->entityManager->createQueryBuilder()
|
||||
$result = $this->entityManager->createQueryBuilder()
|
||||
->update(FormEntity::class, 'f')
|
||||
->set('f.deletedAt', ':deletedAt')
|
||||
->where('f.id IN (:ids)')
|
||||
->setParameter('deletedAt', null)
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()->execute();
|
||||
|
||||
// update was done via DQL, make sure the entities are also refreshed in the entity manager
|
||||
$this->refreshAll(function (FormEntity $entity) use ($ids) {
|
||||
return in_array($entity->getId(), $ids, true);
|
||||
});
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function bulkDelete(array $ids): int {
|
||||
|
@ -4,7 +4,6 @@ namespace MailPoet\Mailer;
|
||||
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Mailer\Methods\MailerMethod;
|
||||
use MailPoet\Models\Subscriber;
|
||||
|
||||
class Mailer {
|
||||
/** @var MailerMethod */
|
||||
@ -25,20 +24,23 @@ class Mailer {
|
||||
}
|
||||
|
||||
public function send($newsletter, $subscriber, $extraParams = []) {
|
||||
// This if adds support for code that calls this method to use SubscriberEntity while the Mailer class is still using the old model.
|
||||
// Once we add support for SubscriberEntity in the Mailer class, this if can be removed.
|
||||
if ($subscriber instanceof SubscriberEntity) {
|
||||
$subscriber = Subscriber::findOne($subscriber->getId());
|
||||
}
|
||||
$subscriber = $this->formatSubscriberNameAndEmailAddress($subscriber);
|
||||
return $this->mailerMethod->send($newsletter, $subscriber, $extraParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \MailPoet\Models\Subscriber|array|string $subscriber
|
||||
* @param SubscriberEntity|array|string $subscriber
|
||||
*/
|
||||
public function formatSubscriberNameAndEmailAddress($subscriber) {
|
||||
$subscriber = (is_object($subscriber)) ? $subscriber->asArray() : $subscriber;
|
||||
if ($subscriber instanceof SubscriberEntity) {
|
||||
$prepareSubscriber = [];
|
||||
$prepareSubscriber['email'] = $subscriber->getEmail();
|
||||
$prepareSubscriber['first_name'] = $subscriber->getFirstName();
|
||||
$prepareSubscriber['last_name'] = $subscriber->getLastName();
|
||||
|
||||
$subscriber = $prepareSubscriber;
|
||||
}
|
||||
|
||||
if (!is_array($subscriber)) return $subscriber;
|
||||
if (isset($subscriber['address'])) $subscriber['email'] = $subscriber['address'];
|
||||
$firstName = (isset($subscriber['first_name'])) ? $subscriber['first_name'] : '';
|
||||
|
@ -0,0 +1,39 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
|
||||
/**
|
||||
* Due to a bug https://mailpoet.atlassian.net/browse/MAILPOET-5886
|
||||
* The status of newsletters was not updated to sent when the task was completed
|
||||
* In this migration we find newsletters with status sending and their tasks are completed and update their status to sent
|
||||
*/
|
||||
class Migration_20240202_130053_App extends AppMigration {
|
||||
public function run(): void {
|
||||
$affectedNewsletterIds = $this->entityManager->createQueryBuilder()
|
||||
->select('n.id')
|
||||
->from(NewsletterEntity::class, 'n')
|
||||
->join('n.queues', 'q')
|
||||
->join('q.task', 't')
|
||||
->where('n.status = :status_sending')
|
||||
->andWhere('t.status = :status_completed')
|
||||
->setParameter('status_sending', NewsletterEntity::STATUS_SENDING)
|
||||
->setParameter('status_completed', ScheduledTaskEntity::STATUS_COMPLETED)
|
||||
->getQuery()
|
||||
->getArrayResult();
|
||||
|
||||
$affectedNewsletterIds = array_column($affectedNewsletterIds, 'id');
|
||||
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->update(NewsletterEntity::class, 'n')
|
||||
->set('n.status', ':status_sent')
|
||||
->where('n.id IN (:ids)')
|
||||
->setParameter('status_sent', NewsletterEntity::STATUS_SENT)
|
||||
->setParameter('ids', $affectedNewsletterIds)
|
||||
->getQuery()
|
||||
->execute();
|
||||
}
|
||||
}
|
189
mailpoet/lib/Migrations/App/Migration_20240207_105912_App.php
Normal file
189
mailpoet/lib/Migrations/App/Migration_20240207_105912_App.php
Normal file
@ -0,0 +1,189 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Entities\ScheduledTaskSubscriberEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Entities\StatisticsNewsletterEntity;
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
use MailPoetVendor\Doctrine\DBAL\Connection;
|
||||
|
||||
/**
|
||||
* We've had a set of bugs where campaign type newsletters (see NewsletterEntity::CAMPAIGN_TYPES),
|
||||
* such as post notifications, were getting stuck in the following state:
|
||||
* - The newsletter was in the "sending" state.
|
||||
* - The task failed to complete and ended up in the "invalid" state.
|
||||
*
|
||||
* This migration completes tasks that sent out all emails
|
||||
* and pauses those that have unprocessed subscribers.
|
||||
*/
|
||||
class Migration_20240207_105912_App extends AppMigration {
|
||||
public function run(): void {
|
||||
$this->pauseInvalidTasksWithUnprocessedSubscribers();
|
||||
$this->completeInvalidTasksWithAllSubscribersProcessed();
|
||||
$this->backfillMissingDataForMigratedNewsletters();
|
||||
}
|
||||
|
||||
private function pauseInvalidTasksWithUnprocessedSubscribers(): void {
|
||||
$ids = $this->entityManager->createQueryBuilder()
|
||||
->select('DISTINCT t.id')
|
||||
->from(ScheduledTaskEntity::class, 't')
|
||||
->join('t.subscribers', 's', 'WITH', 's.processed = :unprocessed')
|
||||
->join('t.sendingQueue', 'q')
|
||||
->join('q.newsletter', 'n')
|
||||
->where('t.deletedAt IS NULL')
|
||||
->andWhere('t.status = :invalid')
|
||||
->andWhere('n.deletedAt IS NULL')
|
||||
->andWhere('n.status = :sending')
|
||||
->andWhere('n.type IN (:campaignTypes)')
|
||||
->setParameter('unprocessed', ScheduledTaskSubscriberEntity::STATUS_UNPROCESSED)
|
||||
->setParameter('invalid', ScheduledTaskEntity::STATUS_INVALID)
|
||||
->setParameter('sending', NewsletterEntity::STATUS_SENDING)
|
||||
->setParameter('campaignTypes', NewsletterEntity::CAMPAIGN_TYPES)
|
||||
->getQuery()
|
||||
->getSingleColumnResult();
|
||||
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->update(ScheduledTaskEntity::class, 't')
|
||||
->set('t.status', ':paused')
|
||||
->where('t.id IN (:ids)')
|
||||
->setParameter('paused', ScheduledTaskEntity::STATUS_PAUSED)
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
}
|
||||
|
||||
private function completeInvalidTasksWithAllSubscribersProcessed(): void {
|
||||
$ids = $this->entityManager->createQueryBuilder()
|
||||
->select('DISTINCT t.id, n.id AS nid, t.updatedAt')
|
||||
->from(ScheduledTaskEntity::class, 't')
|
||||
->leftJoin('t.subscribers', 's', 'WITH', 's.processed = :unprocessed')
|
||||
->join('t.sendingQueue', 'q')
|
||||
->join('q.newsletter', 'n')
|
||||
->where('t.deletedAt IS NULL')
|
||||
->andWhere('t.status = :invalid')
|
||||
->andWhere('s.task IS NULL')
|
||||
->andWhere('n.deletedAt IS NULL')
|
||||
->andWhere('n.status = :sending')
|
||||
->andWhere('n.type IN (:campaignTypes)')
|
||||
->setParameter('unprocessed', ScheduledTaskSubscriberEntity::STATUS_UNPROCESSED)
|
||||
->setParameter('invalid', ScheduledTaskEntity::STATUS_INVALID)
|
||||
->setParameter('sending', NewsletterEntity::STATUS_SENDING)
|
||||
->setParameter('campaignTypes', NewsletterEntity::CAMPAIGN_TYPES)
|
||||
->getQuery()
|
||||
->getSingleColumnResult();
|
||||
|
||||
// update sending queue counts
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->update(SendingQueueEntity::class, 'q')
|
||||
->set('q.countProcessed', 'q.countTotal')
|
||||
->set('q.countToProcess', 0)
|
||||
->where('q.task IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// complete the invalid tasks
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->update(ScheduledTaskEntity::class, 't')
|
||||
->set('t.status', ':completed')
|
||||
->where('t.id IN (:ids)')
|
||||
->setParameter('completed', ScheduledTaskEntity::STATUS_COMPLETED)
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// mark newsletters as sent, update "sentAt" (DBAL needed to be able to use JOIN)
|
||||
$newslettersTable = $this->entityManager->getClassMetadata(NewsletterEntity::class)->getTableName();
|
||||
$scheduledTasksTable = $this->entityManager->getClassMetadata(ScheduledTaskEntity::class)->getTableName();
|
||||
$scheduledTaskSubscribersTable = $this->entityManager->getClassMetadata(ScheduledTaskSubscriberEntity::class)->getTableName();
|
||||
$sendingQueuesTable = $this->entityManager->getClassMetadata(SendingQueueEntity::class)->getTableName();
|
||||
$this->entityManager->getConnection()->executeStatement(
|
||||
"
|
||||
UPDATE $newslettersTable n
|
||||
JOIN $sendingQueuesTable q ON n.id = q.newsletter_id
|
||||
JOIN $scheduledTasksTable t ON q.task_id = t.id
|
||||
SET
|
||||
n.status = :sent,
|
||||
n.sent_at = COALESCE(
|
||||
(
|
||||
-- use 'updated_at' of processed subscriber with the highest ID ('MAX(subscriber_id)' can use index)
|
||||
SELECT updated_at FROM $scheduledTaskSubscribersTable WHERE task_id = t.id AND subscriber_id = (
|
||||
SELECT MAX(subscriber_id) FROM $scheduledTaskSubscribersTable WHERE task_id = t.id
|
||||
)
|
||||
),
|
||||
t.updated_at
|
||||
)
|
||||
WHERE t.id IN (:ids)
|
||||
",
|
||||
['sent' => NewsletterEntity::STATUS_SENT, 'ids' => $ids],
|
||||
['ids' => Connection::PARAM_INT_ARRAY]
|
||||
);
|
||||
}
|
||||
|
||||
private function backfillMissingDataForMigratedNewsletters(): void {
|
||||
// In https://mailpoet.atlassian.net/browse/MAILPOET-5886 we fixed missing "sent" status
|
||||
// by https://github.com/mailpoet/mailpoet/pull/5416, but didn't backfill missing data.
|
||||
|
||||
// get affected newsletter IDs
|
||||
$ids = $this->entityManager->createQueryBuilder()
|
||||
->select('n.id')
|
||||
->from(NewsletterEntity::class, 'n')
|
||||
->where('n.status = :sent')
|
||||
->andWhere('n.sentAt IS NULL')
|
||||
->setParameter('sent', NewsletterEntity::STATUS_SENT)
|
||||
->getQuery()
|
||||
->getSingleColumnResult();
|
||||
|
||||
// get missing newsletter statistics IDs
|
||||
$data = $this->entityManager->createQueryBuilder()
|
||||
->select('IDENTITY(q.newsletter) AS nid, q.id AS qid, IDENTITY(s.subscriber) AS sid, s.updatedAt AS sentAt')
|
||||
->from(SendingQueueEntity::class, 'q')
|
||||
->join('q.task', 't')
|
||||
->join('t.subscribers', 's')
|
||||
->leftJoin(StatisticsNewsletterEntity::class, 'ns', 'WITH', 'ns.queue = q AND ns.subscriber = s.subscriber')
|
||||
->where('q.newsletter IN (:ids)')
|
||||
->andWhere('ns.id IS NULL')
|
||||
->andWhere('s.processed = :processed')
|
||||
->setParameter('ids', $ids)
|
||||
->setParameter('processed', ScheduledTaskSubscriberEntity::STATUS_PROCESSED)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
// insert missing newsletter statistics
|
||||
$newsletterStatisticsTable = $this->entityManager->getClassMetadata(StatisticsNewsletterEntity::class)->getTableName();
|
||||
foreach ($data as $row) {
|
||||
$this->entityManager->getConnection()->executeStatement("
|
||||
INSERT IGNORE INTO $newsletterStatisticsTable (newsletter_id, queue_id, subscriber_id, sent_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
", [$row['nid'], $row['qid'], $row['sid'], $row['sentAt']->format('Y-m-d H:i:s')]);
|
||||
}
|
||||
|
||||
// add missing "sentAt" (DBAL needed to be able to use JOIN)
|
||||
$newslettersTable = $this->entityManager->getClassMetadata(NewsletterEntity::class)->getTableName();
|
||||
$scheduledTasksTable = $this->entityManager->getClassMetadata(ScheduledTaskEntity::class)->getTableName();
|
||||
$scheduledTaskSubscribersTable = $this->entityManager->getClassMetadata(ScheduledTaskSubscriberEntity::class)->getTableName();
|
||||
$sendingQueuesTable = $this->entityManager->getClassMetadata(SendingQueueEntity::class)->getTableName();
|
||||
$this->entityManager->getConnection()->executeStatement(
|
||||
"
|
||||
UPDATE $newslettersTable n
|
||||
JOIN $sendingQueuesTable q ON n.id = q.newsletter_id
|
||||
JOIN $scheduledTasksTable t ON q.task_id = t.id
|
||||
SET n.sent_at = COALESCE(
|
||||
(
|
||||
-- use 'updated_at' of processed subscriber with the highest ID ('MAX(subscriber_id)' can use index)
|
||||
SELECT updated_at FROM $scheduledTaskSubscribersTable WHERE task_id = t.id AND subscriber_id = (
|
||||
SELECT MAX(subscriber_id) FROM $scheduledTaskSubscribersTable WHERE task_id = t.id
|
||||
)
|
||||
),
|
||||
t.updated_at
|
||||
)
|
||||
WHERE q.newsletter_id IN (:ids)
|
||||
",
|
||||
['ids' => $ids],
|
||||
['ids' => Connection::PARAM_INT_ARRAY]
|
||||
);
|
||||
}
|
||||
}
|
22
mailpoet/lib/Migrations/Db/Migration_20240119_113943_Db.php
Normal file
22
mailpoet/lib/Migrations/Db/Migration_20240119_113943_Db.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\Db;
|
||||
|
||||
use MailPoet\Entities\StatisticsWooCommercePurchaseEntity;
|
||||
use MailPoet\Migrator\DbMigration;
|
||||
|
||||
class Migration_20240119_113943_Db extends DbMigration {
|
||||
public function run(): void {
|
||||
$table = $this->getTableName(StatisticsWooCommercePurchaseEntity::class);
|
||||
|
||||
if (!$this->tableExists($table)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make "newsletter_id" nullable
|
||||
$this->connection->executeStatement("ALTER TABLE $table CHANGE newsletter_id newsletter_id int(11) unsigned NULL");
|
||||
|
||||
// update data
|
||||
$this->connection->executeStatement("UPDATE $table SET newsletter_id = NULL WHERE newsletter_id = 0");
|
||||
}
|
||||
}
|
@ -4,10 +4,10 @@ namespace MailPoet\Models;
|
||||
|
||||
use MailPoet\DI\ContainerWrapper;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Newsletter\NewsletterDeleteController;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Newsletter\Options\NewsletterOptionFieldsRepository;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Tasks\Sending as SendingTask;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\Util\Security;
|
||||
|
||||
@ -142,7 +142,7 @@ class Newsletter extends Model {
|
||||
|
||||
public function delete() {
|
||||
trigger_error('Calling Newsletter::delete() is deprecated and will be removed. Use \MailPoet\Newsletter\NewslettersRepository instead.', E_USER_DEPRECATED);
|
||||
ContainerWrapper::getInstance()->get(NewslettersRepository::class)->bulkDelete([$this->id]);
|
||||
ContainerWrapper::getInstance()->get(NewsletterDeleteController::class)->bulkDelete([$this->id]);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -218,8 +218,11 @@ class Newsletter extends Model {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method is deprecated. \MailPoet\Entities\NewsletterEntity::getLatestQueue() instead. This method can be removed after 2024-05-30.
|
||||
*/
|
||||
public function getQueue($columns = '*') {
|
||||
return SendingTask::getByNewsletterId($this->id);
|
||||
self::deprecationError(__METHOD__);
|
||||
}
|
||||
|
||||
public function getBodyString(): string {
|
||||
@ -232,7 +235,11 @@ class Newsletter extends Model {
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method is deprecated. It method can be removed after 2024-05-30.
|
||||
*/
|
||||
public function withSendingQueue() {
|
||||
self::deprecationError(__METHOD__);
|
||||
$queue = $this->getQueue();
|
||||
if ($queue === false) {
|
||||
$this->queue = false;
|
||||
|
156
mailpoet/lib/Newsletter/NewsletterDeleteController.php
Normal file
156
mailpoet/lib/Newsletter/NewsletterDeleteController.php
Normal file
@ -0,0 +1,156 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Newsletter;
|
||||
|
||||
use MailPoet\Cron\Workers\StatsNotifications\NewsletterLinkRepository;
|
||||
use MailPoet\Cron\Workers\StatsNotifications\StatsNotificationsRepository;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Entities\StatsNotificationEntity;
|
||||
use MailPoet\Newsletter\Options\NewsletterOptionsRepository;
|
||||
use MailPoet\Newsletter\Segment\NewsletterSegmentRepository;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTaskSubscribersRepository;
|
||||
use MailPoet\Newsletter\Sending\SendingQueuesRepository;
|
||||
use MailPoet\Statistics\StatisticsClicksRepository;
|
||||
use MailPoet\Statistics\StatisticsNewslettersRepository;
|
||||
use MailPoet\Statistics\StatisticsOpensRepository;
|
||||
use MailPoet\Statistics\StatisticsWooCommercePurchasesRepository;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
||||
use Throwable;
|
||||
|
||||
class NewsletterDeleteController {
|
||||
private EntityManager $entityManager;
|
||||
private NewslettersRepository $newslettersRepository;
|
||||
private NewsletterLinkRepository $newsletterLinkRepository;
|
||||
private NewsletterOptionsRepository $newsletterOptionsRepository;
|
||||
private NewsletterPostsRepository $newsletterPostsRepository;
|
||||
private NewsletterSegmentRepository $newsletterSegmentRepository;
|
||||
private ScheduledTasksRepository $scheduledTasksRepository;
|
||||
private ScheduledTaskSubscribersRepository $scheduledTaskSubscribersRepository;
|
||||
private SendingQueuesRepository $sendingQueuesRepository;
|
||||
private StatisticsClicksRepository $statisticsClicksRepository;
|
||||
private StatisticsNewslettersRepository $statisticsNewslettersRepository;
|
||||
private StatisticsOpensRepository $statisticsOpensRepository;
|
||||
private StatisticsWooCommercePurchasesRepository $statisticsWooCommercePurchasesRepository;
|
||||
private StatsNotificationsRepository $statsNotificationsRepository;
|
||||
private WPFunctions $wp;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
NewslettersRepository $newslettersRepository,
|
||||
NewsletterLinkRepository $newsletterLinkRepository,
|
||||
NewsletterOptionsRepository $newsletterOptionsRepository,
|
||||
NewsletterPostsRepository $newsletterPostsRepository,
|
||||
NewsletterSegmentRepository $newsletterSegmentRepository,
|
||||
ScheduledTasksRepository $scheduledTasksRepository,
|
||||
ScheduledTaskSubscribersRepository $scheduledTaskSubscribersRepository,
|
||||
SendingQueuesRepository $sendingQueuesRepository,
|
||||
StatisticsClicksRepository $statisticsClicksRepository,
|
||||
StatisticsNewslettersRepository $statisticsNewslettersRepository,
|
||||
StatisticsOpensRepository $statisticsOpensRepository,
|
||||
StatisticsWooCommercePurchasesRepository $statisticsWooCommercePurchasesRepository,
|
||||
StatsNotificationsRepository $statsNotificationsRepository,
|
||||
WPFunctions $wp
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->newslettersRepository = $newslettersRepository;
|
||||
$this->newsletterLinkRepository = $newsletterLinkRepository;
|
||||
$this->newsletterOptionsRepository = $newsletterOptionsRepository;
|
||||
$this->newsletterPostsRepository = $newsletterPostsRepository;
|
||||
$this->newsletterSegmentRepository = $newsletterSegmentRepository;
|
||||
$this->scheduledTasksRepository = $scheduledTasksRepository;
|
||||
$this->scheduledTaskSubscribersRepository = $scheduledTaskSubscribersRepository;
|
||||
$this->sendingQueuesRepository = $sendingQueuesRepository;
|
||||
$this->statisticsClicksRepository = $statisticsClicksRepository;
|
||||
$this->statisticsNewslettersRepository = $statisticsNewslettersRepository;
|
||||
$this->statisticsOpensRepository = $statisticsOpensRepository;
|
||||
$this->statisticsWooCommercePurchasesRepository = $statisticsWooCommercePurchasesRepository;
|
||||
$this->statsNotificationsRepository = $statsNotificationsRepository;
|
||||
$this->wp = $wp;
|
||||
}
|
||||
|
||||
/** @param int[] $ids */
|
||||
public function bulkDelete(array $ids): int {
|
||||
if (!$ids) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Fetch children ids for deleting
|
||||
$childrenIds = $this->newslettersRepository->fetchChildrenIds($ids);
|
||||
$ids = array_merge($ids, $childrenIds);
|
||||
|
||||
$this->entityManager->beginTransaction();
|
||||
try {
|
||||
// Delete statistics data
|
||||
$this->statisticsNewslettersRepository->deleteByNewsletterIds($ids);
|
||||
$this->statisticsOpensRepository->deleteByNewsletterIds($ids);
|
||||
$this->statisticsClicksRepository->deleteByNewsletterIds($ids);
|
||||
|
||||
// Update WooCommerce statistics and remove newsletter and click id
|
||||
$this->statisticsWooCommercePurchasesRepository->removeNewsletterDataByNewsletterIds($ids);
|
||||
|
||||
// Delete newsletter posts, options, links, and segments
|
||||
$this->newsletterPostsRepository->deleteByNewsletterIds($ids);
|
||||
$this->newsletterOptionsRepository->deleteByNewsletterIds($ids);
|
||||
$this->newsletterLinkRepository->deleteByNewsletterIds($ids);
|
||||
$this->newsletterSegmentRepository->deleteByNewsletterIds($ids);
|
||||
|
||||
// Delete stats notifications and related tasks
|
||||
/** @var string[] $taskIds */
|
||||
$taskIds = $this->entityManager->createQueryBuilder()
|
||||
->select('IDENTITY(sn.task)')
|
||||
->from(StatsNotificationEntity::class, 'sn')
|
||||
->where('sn.newsletter IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->getSingleColumnResult();
|
||||
$taskIds = array_map('intval', $taskIds);
|
||||
|
||||
$this->scheduledTasksRepository->deleteByIds($taskIds);
|
||||
$this->statsNotificationsRepository->deleteByNewsletterIds($ids);
|
||||
|
||||
// Delete scheduled task subscribers, scheduled tasks, and sending queues
|
||||
/** @var string[] $taskIds */
|
||||
$taskIds = $this->entityManager->createQueryBuilder()
|
||||
->select('IDENTITY(q.task)')
|
||||
->from(SendingQueueEntity::class, 'q')
|
||||
->where('q.newsletter IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->getSingleColumnResult();
|
||||
$taskIds = array_map('intval', $taskIds);
|
||||
|
||||
$this->scheduledTaskSubscribersRepository->deleteByTaskIds($taskIds);
|
||||
$this->scheduledTasksRepository->deleteByIds($taskIds);
|
||||
$this->sendingQueuesRepository->deleteByNewsletterIds($ids);
|
||||
|
||||
// Fetch WP Posts IDs and delete them
|
||||
/** @var string[] $wpPostIds */
|
||||
$wpPostIds = $this->entityManager->createQueryBuilder()
|
||||
->select('IDENTITY(n.wpPost) AS id')
|
||||
->from(NewsletterEntity::class, 'n')
|
||||
->where('n.id IN (:ids)')
|
||||
->andWhere('n.wpPost IS NOT NULL')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->getSingleColumnResult();
|
||||
$wpPostIds = array_map('intval', $wpPostIds);
|
||||
|
||||
foreach ($wpPostIds as $wpPostId) {
|
||||
$this->wp->wpDeletePost($wpPostId, true);
|
||||
}
|
||||
|
||||
// Delete newsletter entities
|
||||
$this->newslettersRepository->deleteByIds($ids);
|
||||
|
||||
$this->entityManager->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->entityManager->rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return count($ids);
|
||||
}
|
||||
}
|
@ -12,4 +12,20 @@ class NewsletterPostsRepository extends Repository {
|
||||
protected function getEntityClassName() {
|
||||
return NewsletterPostEntity::class;
|
||||
}
|
||||
|
||||
/** @param int[] $ids */
|
||||
public function deleteByNewsletterIds(array $ids): void {
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->delete(NewsletterPostEntity::class, 'p')
|
||||
->where('p.newsletter IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// delete was done via DQL, make sure the entities are also detached from the entity manager
|
||||
$this->detachAll(function (NewsletterPostEntity $entity) use ($ids) {
|
||||
$newsletter = $entity->getNewsletter();
|
||||
return $newsletter && in_array($newsletter->getId(), $ids, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -9,22 +9,12 @@ use MailPoet\AutomaticEmails\WooCommerce\Events\PurchasedInCategory;
|
||||
use MailPoet\AutomaticEmails\WooCommerce\Events\PurchasedProduct;
|
||||
use MailPoet\Doctrine\Repository;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\NewsletterLinkEntity;
|
||||
use MailPoet\Entities\NewsletterOptionEntity;
|
||||
use MailPoet\Entities\NewsletterOptionFieldEntity;
|
||||
use MailPoet\Entities\NewsletterPostEntity;
|
||||
use MailPoet\Entities\NewsletterSegmentEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Entities\ScheduledTaskSubscriberEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Entities\StatisticsClickEntity;
|
||||
use MailPoet\Entities\StatisticsNewsletterEntity;
|
||||
use MailPoet\Entities\StatisticsOpenEntity;
|
||||
use MailPoet\Entities\StatisticsWooCommercePurchaseEntity;
|
||||
use MailPoet\Entities\StatsNotificationEntity;
|
||||
use MailPoet\Logging\LoggerFactory;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
use MailPoetVendor\Doctrine\DBAL\Connection;
|
||||
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
||||
@ -34,19 +24,13 @@ use MailPoetVendor\Doctrine\ORM\Query\Expr\Join;
|
||||
* @extends Repository<NewsletterEntity>
|
||||
*/
|
||||
class NewslettersRepository extends Repository {
|
||||
/** @var LoggerFactory */
|
||||
private $loggerFactory;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
private LoggerFactory $loggerFactory;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
WPFunctions $wp
|
||||
EntityManager $entityManager
|
||||
) {
|
||||
parent::__construct($entityManager);
|
||||
$this->loggerFactory = LoggerFactory::getInstance();
|
||||
$this->wp = $wp;
|
||||
}
|
||||
|
||||
protected function getEntityClassName() {
|
||||
@ -360,161 +344,19 @@ class NewslettersRepository extends Repository {
|
||||
return count($ids);
|
||||
}
|
||||
|
||||
public function bulkDelete(array $ids) {
|
||||
if (empty($ids)) {
|
||||
return 0;
|
||||
}
|
||||
// Fetch children ids for deleting
|
||||
$childrenIds = $this->fetchChildrenIds($ids);
|
||||
$ids = array_merge($ids, $childrenIds);
|
||||
/** @param int[] $ids */
|
||||
public function deleteByIds(array $ids): void {
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->delete(NewsletterEntity::class, 'n')
|
||||
->where('n.id IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
$isRelatedNewsletterToBeDeleted = function($entity) use ($ids): bool {
|
||||
if (is_string($entity) || !method_exists($entity, 'getNewsletter')) {
|
||||
return false;
|
||||
}
|
||||
$newsletter = $entity->getNewsletter();
|
||||
$newsletterId = $newsletter ? $newsletter->getId() : null;
|
||||
return in_array($newsletterId, $ids, true);
|
||||
};
|
||||
$this->entityManager->transactional(function (EntityManager $entityManager) use ($ids, $isRelatedNewsletterToBeDeleted) {
|
||||
// Delete statistics data
|
||||
$newsletterStatisticsTable = $entityManager->getClassMetadata(StatisticsNewsletterEntity::class)->getTableName();
|
||||
$entityManager->getConnection()->executeStatement("
|
||||
DELETE s FROM $newsletterStatisticsTable s
|
||||
WHERE s.`newsletter_id` IN (:ids)
|
||||
", ['ids' => $ids], ['ids' => Connection::PARAM_INT_ARRAY]);
|
||||
|
||||
$statisticsOpensTable = $entityManager->getClassMetadata(StatisticsOpenEntity::class)->getTableName();
|
||||
$entityManager->getConnection()->executeStatement("
|
||||
DELETE s FROM $statisticsOpensTable s
|
||||
WHERE s.`newsletter_id` IN (:ids)
|
||||
", ['ids' => $ids], ['ids' => Connection::PARAM_INT_ARRAY]);
|
||||
|
||||
$statisticsClicksTable = $entityManager->getClassMetadata(StatisticsClickEntity::class)->getTableName();
|
||||
$entityManager->getConnection()->executeStatement("
|
||||
DELETE s FROM $statisticsClicksTable s
|
||||
WHERE s.`newsletter_id` IN (:ids)
|
||||
", ['ids' => $ids], ['ids' => Connection::PARAM_INT_ARRAY]);
|
||||
|
||||
// Update WooCommerce statistics and remove newsletter and click id
|
||||
$statisticsPurchasesTable = $entityManager->getClassMetadata(StatisticsWooCommercePurchaseEntity::class)->getTableName();
|
||||
$entityManager->getConnection()->executeStatement("
|
||||
UPDATE $statisticsPurchasesTable s
|
||||
SET s.`newsletter_id` = 0 WHERE s.`newsletter_id` IN (:ids)
|
||||
", ['ids' => $ids], ['ids' => Connection::PARAM_INT_ARRAY]);
|
||||
|
||||
// Delete newsletter posts
|
||||
$postsTable = $entityManager->getClassMetadata(NewsletterPostEntity::class)->getTableName();
|
||||
$entityManager->getConnection()->executeStatement("
|
||||
DELETE np FROM $postsTable np
|
||||
WHERE np.`newsletter_id` IN (:ids)
|
||||
", ['ids' => $ids], ['ids' => Connection::PARAM_INT_ARRAY]);
|
||||
|
||||
// Delete newsletter options
|
||||
$optionsTable = $entityManager->getClassMetadata(NewsletterOptionEntity::class)->getTableName();
|
||||
$entityManager->getConnection()->executeStatement("
|
||||
DELETE no FROM $optionsTable no
|
||||
WHERE no.`newsletter_id` IN (:ids)
|
||||
", ['ids' => $ids], ['ids' => Connection::PARAM_INT_ARRAY]);
|
||||
|
||||
// Delete newsletter links
|
||||
$linksTable = $entityManager->getClassMetadata(NewsletterLinkEntity::class)->getTableName();
|
||||
$entityManager->getConnection()->executeStatement("
|
||||
DELETE nl FROM $linksTable nl
|
||||
WHERE nl.`newsletter_id` IN (:ids)
|
||||
", ['ids' => $ids], ['ids' => Connection::PARAM_INT_ARRAY]);
|
||||
|
||||
// Delete stats notifications tasks
|
||||
$scheduledTasksTable = $entityManager->getClassMetadata(ScheduledTaskEntity::class)->getTableName();
|
||||
$statsNotificationsTable = $entityManager->getClassMetadata(StatsNotificationEntity::class)->getTableName();
|
||||
$taskIds = $entityManager->getConnection()->executeQuery("
|
||||
SELECT task_id FROM $statsNotificationsTable sn
|
||||
WHERE sn.`newsletter_id` IN (:ids)
|
||||
", ['ids' => $ids], ['ids' => Connection::PARAM_INT_ARRAY])->fetchAllAssociative();
|
||||
$taskIds = array_column($taskIds, 'task_id');
|
||||
$entityManager->getConnection()->executeStatement("
|
||||
DELETE st FROM $scheduledTasksTable st
|
||||
WHERE st.`id` IN (:ids)
|
||||
", ['ids' => $taskIds], ['ids' => Connection::PARAM_INT_ARRAY]);
|
||||
|
||||
// Delete stats notifications
|
||||
$entityManager->getConnection()->executeStatement("
|
||||
DELETE sn FROM $statsNotificationsTable sn
|
||||
WHERE sn.`newsletter_id` IN (:ids)
|
||||
", ['ids' => $ids], ['ids' => Connection::PARAM_INT_ARRAY]);
|
||||
|
||||
// Delete scheduled tasks and scheduled task subscribers
|
||||
$sendingQueueTable = $entityManager->getClassMetadata(SendingQueueEntity::class)->getTableName();
|
||||
$scheduledTaskSubscribersTable = $entityManager->getClassMetadata(ScheduledTaskSubscriberEntity::class)->getTableName();
|
||||
|
||||
// Delete scheduled tasks subscribers
|
||||
$entityManager->getConnection()->executeStatement("
|
||||
DELETE ts FROM $scheduledTaskSubscribersTable ts
|
||||
JOIN $scheduledTasksTable t ON t.`id` = ts.`task_id`
|
||||
JOIN $sendingQueueTable q ON q.`task_id` = t.`id`
|
||||
WHERE q.`newsletter_id` IN (:ids)
|
||||
", ['ids' => $ids], ['ids' => Connection::PARAM_INT_ARRAY]);
|
||||
|
||||
$entityManager->getConnection()->executeStatement("
|
||||
DELETE t FROM $scheduledTasksTable t
|
||||
JOIN $sendingQueueTable q ON t.`id` = q.`task_id`
|
||||
WHERE q.`newsletter_id` IN (:ids)
|
||||
", ['ids' => $ids], ['ids' => Connection::PARAM_INT_ARRAY]);
|
||||
|
||||
// Delete sending queues
|
||||
$entityManager->getConnection()->executeStatement("
|
||||
DELETE q FROM $sendingQueueTable q
|
||||
WHERE q.`newsletter_id` IN (:ids)
|
||||
", ['ids' => $ids], ['ids' => Connection::PARAM_INT_ARRAY]);
|
||||
|
||||
// Delete newsletter segments
|
||||
$newsletterSegmentsTable = $entityManager->getClassMetadata(NewsletterSegmentEntity::class)->getTableName();
|
||||
$entityManager->getConnection()->executeStatement("
|
||||
DELETE ns FROM $newsletterSegmentsTable ns
|
||||
WHERE ns.`newsletter_id` IN (:ids)
|
||||
", ['ids' => $ids], ['ids' => Connection::PARAM_INT_ARRAY]);
|
||||
|
||||
// Fetch WP Posts IDs and delete them
|
||||
/** @var int[] $wpPostsIds */
|
||||
$wpPostsIds = $entityManager->createQueryBuilder()->select('wpp.id')
|
||||
->from(NewsletterEntity::class, 'n')
|
||||
->join('n.wpPost', 'wpp')
|
||||
->where('n.id IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()->getSingleColumnResult();
|
||||
foreach ($wpPostsIds as $wpPostId) {
|
||||
$this->wp->wpDeletePost(intval($wpPostId), true);
|
||||
}
|
||||
|
||||
// Delete newsletter entities
|
||||
$queryBuilder = $entityManager->createQueryBuilder();
|
||||
$queryBuilder->delete(NewsletterEntity::class, 'n')
|
||||
->where('n.id IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()->execute();
|
||||
|
||||
|
||||
$entityTypesToBeDetached = [
|
||||
StatisticsNewsletterEntity::class,
|
||||
StatisticsOpenEntity::class,
|
||||
StatisticsClickEntity::class,
|
||||
NewsletterPostEntity::class,
|
||||
NewsletterOptionEntity::class,
|
||||
NewsletterLinkEntity::class,
|
||||
StatsNotificationEntity::class,
|
||||
SendingQueueEntity::class,
|
||||
ScheduledTaskSubscriberEntity::class,
|
||||
NewsletterSegmentEntity::class,
|
||||
];
|
||||
foreach ($entityTypesToBeDetached as $entityType) {
|
||||
$this->detachEntitiesOfType($entityType, $isRelatedNewsletterToBeDeleted);
|
||||
}
|
||||
|
||||
$this->detachEntitiesOfType(ScheduledTaskEntity::class, function($entity) use ($taskIds): bool {
|
||||
return !is_string($entity) && method_exists($entity, 'getId') && in_array($entity->getId(), $taskIds, true);
|
||||
});
|
||||
// delete was done via DQL, make sure the entities are also detached from the entity manager
|
||||
$this->detachAll(function (NewsletterEntity $entity) use ($ids) {
|
||||
return in_array($entity->getId(), $ids, true);
|
||||
});
|
||||
return count($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -688,12 +530,19 @@ class NewslettersRepository extends Repository {
|
||||
$this->flush();
|
||||
}
|
||||
|
||||
private function fetchChildrenIds(array $parentIds) {
|
||||
$ids = $this->entityManager->createQueryBuilder()->select('n.id')
|
||||
/**
|
||||
* @param int[] $parentIds
|
||||
* @return int[]
|
||||
*/
|
||||
public function fetchChildrenIds(array $parentIds): array {
|
||||
/** @var string[] $ids */
|
||||
$ids = $this->entityManager->createQueryBuilder()
|
||||
->select('n.id')
|
||||
->from(NewsletterEntity::class, 'n')
|
||||
->where('n.parent IN (:ids)')
|
||||
->setParameter('ids', $parentIds)
|
||||
->getQuery()->getScalarResult();
|
||||
return array_column($ids, 'id');
|
||||
->getQuery()
|
||||
->getSingleColumnResult();
|
||||
return array_map('intval', $ids);
|
||||
}
|
||||
}
|
||||
|
@ -52,4 +52,20 @@ class NewsletterOptionsRepository extends Repository {
|
||||
->setParameter('segmentIds', $segmentIds)
|
||||
->getQuery()->getResult();
|
||||
}
|
||||
|
||||
/** @param int[] $ids */
|
||||
public function deleteByNewsletterIds(array $ids): void {
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->delete(NewsletterOptionEntity::class, 'o')
|
||||
->where('o.newsletter IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// delete was done via DQL, make sure the entities are also detached from the entity manager
|
||||
$this->detachAll(function (NewsletterOptionEntity $entity) use ($ids) {
|
||||
$newsletter = $entity->getNewsletter();
|
||||
return $newsletter && in_array($newsletter->getId(), $ids, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -65,4 +65,20 @@ class NewsletterSegmentRepository extends Repository {
|
||||
}
|
||||
return $nameMap;
|
||||
}
|
||||
|
||||
/** @param int[] $ids */
|
||||
public function deleteByNewsletterIds(array $ids): void {
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->delete(NewsletterSegmentEntity::class, 's')
|
||||
->where('s.newsletter IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// delete was done via DQL, make sure the entities are also detached from the entity manager
|
||||
$this->detachAll(function (NewsletterSegmentEntity $entity) use ($ids) {
|
||||
$newsletter = $entity->getNewsletter();
|
||||
return $newsletter && in_array($newsletter->getId(), $ids, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -94,6 +94,11 @@ class ScheduledTaskSubscribersRepository extends Repository {
|
||||
->setParameter('task', $task)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// update was done via DQL, make sure the entities are also refreshed in the entity manager
|
||||
$this->refreshAll(function (ScheduledTaskSubscriberEntity $entity) use ($task, $subscriberIds) {
|
||||
return $entity->getTask() === $task && in_array($entity->getSubscriberId(), $subscriberIds, true);
|
||||
});
|
||||
}
|
||||
|
||||
$this->checkCompleted($task);
|
||||
@ -118,6 +123,22 @@ class ScheduledTaskSubscribersRepository extends Repository {
|
||||
$stmt->executeQuery();
|
||||
}
|
||||
|
||||
/** @param int[] $ids */
|
||||
public function deleteByTaskIds(array $ids): void {
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->delete(ScheduledTaskSubscriberEntity::class, 'sts')
|
||||
->where('sts.task IN (:taskIds)')
|
||||
->setParameter('taskIds', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// delete was done via DQL, make sure the entities are also detached from the entity manager
|
||||
$this->detachAll(function (ScheduledTaskSubscriberEntity $entity) use ($ids) {
|
||||
$task = $entity->getTask();
|
||||
return $task && in_array($task->getId(), $ids, true);
|
||||
});
|
||||
}
|
||||
|
||||
public function deleteByScheduledTask(ScheduledTaskEntity $scheduledTask): void {
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->delete(ScheduledTaskSubscriberEntity::class, 'sts')
|
||||
|
@ -308,6 +308,11 @@ class ScheduledTasksRepository extends Repository {
|
||||
->setParameter('ids', $ids, Connection::PARAM_INT_ARRAY)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// update was done via DQL, make sure the entities are also refreshed in the entity manager
|
||||
$this->refreshAll(function (ScheduledTaskEntity $entity) use ($ids) {
|
||||
return in_array($entity->getId(), $ids, true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -337,6 +342,21 @@ class ScheduledTasksRepository extends Repository {
|
||||
$this->flush();
|
||||
}
|
||||
|
||||
/** @param int[] $ids */
|
||||
public function deleteByIds(array $ids): void {
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->delete(ScheduledTaskEntity::class, 't')
|
||||
->where('t.id IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// delete was done via DQL, make sure the entities are also detached from the entity manager
|
||||
$this->detachAll(function (ScheduledTaskEntity $entity) use ($ids) {
|
||||
return in_array($entity->getId(), $ids, true);
|
||||
});
|
||||
}
|
||||
|
||||
protected function findByTypeAndStatus($type, $status, $limit = null, $future = false) {
|
||||
$queryBuilder = $this->doctrineRepository->createQueryBuilder('st')
|
||||
->select('st')
|
||||
|
@ -49,17 +49,28 @@ class SendingQueuesRepository extends Repository {
|
||||
return SendingQueueEntity::class;
|
||||
}
|
||||
|
||||
public function findOneByNewsletterAndTaskStatus(NewsletterEntity $newsletter, string $status): ?SendingQueueEntity {
|
||||
return $this->entityManager->createQueryBuilder()
|
||||
/**
|
||||
* @param NewsletterEntity $newsletter
|
||||
* @param string|null $status
|
||||
* @return SendingQueueEntity|null
|
||||
* @throws \MailPoetVendor\Doctrine\ORM\NonUniqueResultException
|
||||
*/
|
||||
public function findOneByNewsletterAndTaskStatus(NewsletterEntity $newsletter, $status): ?SendingQueueEntity {
|
||||
$queryBuilder = $this->entityManager->createQueryBuilder()
|
||||
->select('s')
|
||||
->from(SendingQueueEntity::class, 's')
|
||||
->join('s.task', 't')
|
||||
->where('t.status = :status')
|
||||
->andWhere('s.newsletter = :newsletter')
|
||||
->setParameter('status', $status)
|
||||
->setParameter('newsletter', $newsletter)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
->setParameter('newsletter', $newsletter);
|
||||
|
||||
if (is_null($status)) {
|
||||
$queryBuilder->andWhere('t.status IS NULL');
|
||||
} else {
|
||||
$queryBuilder->andWhere('t.status = :status')
|
||||
->setParameter('status', $status);
|
||||
}
|
||||
|
||||
return $queryBuilder->getQuery()->getOneOrNullResult();
|
||||
}
|
||||
|
||||
public function countAllByNewsletterAndTaskStatus(NewsletterEntity $newsletter, string $status): int {
|
||||
@ -227,4 +238,20 @@ class SendingQueuesRepository extends Repository {
|
||||
}
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
/** @param int[] $ids */
|
||||
public function deleteByNewsletterIds(array $ids): void {
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->delete(SendingQueueEntity::class, 'q')
|
||||
->where('q.newsletter IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// delete was done via DQL, make sure the entities are also detached from the entity manager
|
||||
$this->detachAll(function (SendingQueueEntity $entity) use ($ids) {
|
||||
$newsletter = $entity->getNewsletter();
|
||||
return $newsletter && in_array($newsletter->getId(), $ids, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +93,11 @@ class NewsletterTemplatesRepository extends Repository {
|
||||
->setParameter('recentIds', array_column($recentIds, 'id'))
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// delete was done via DQL, make sure the entities are also detached from the entity manager
|
||||
$this->detachAll(function (NewsletterTemplateEntity $entity) use ($recentIds) {
|
||||
return $entity->getCategories() === self::RECENTLY_SENT_CATEGORIES && !in_array($entity->getId(), $recentIds, true);
|
||||
});
|
||||
}
|
||||
|
||||
public function getRecentlySentCount(): int {
|
||||
|
@ -69,7 +69,7 @@ class SegmentsRepository extends Repository {
|
||||
->getResult();
|
||||
}
|
||||
|
||||
public function getWPUsersSegment(): ?SegmentEntity {
|
||||
public function getWPUsersSegment(): SegmentEntity {
|
||||
$segment = $this->findOneBy(['type' => SegmentEntity::TYPE_WP_USERS]);
|
||||
|
||||
if (!$segment) {
|
||||
|
@ -6,9 +6,7 @@ use MailPoet\Config\SubscriberChangesNotifier;
|
||||
use MailPoet\DI\ContainerWrapper;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Models\Segment;
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Models\SubscriberSegment;
|
||||
use MailPoet\Entities\SubscriberSegmentEntity;
|
||||
use MailPoet\Newsletter\Scheduler\WelcomeScheduler;
|
||||
use MailPoet\Services\Validator;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
@ -19,7 +17,7 @@ use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\WooCommerce\Helper as WooCommerceHelper;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
use MailPoetVendor\Idiorm\ORM;
|
||||
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
||||
|
||||
class WP {
|
||||
|
||||
@ -43,6 +41,18 @@ class WP {
|
||||
/** @var Validator */
|
||||
private $validator;
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentsRepository;
|
||||
|
||||
/** @var EntityManager */
|
||||
private $entityManager;
|
||||
|
||||
/** @var string */
|
||||
private $subscribersTable;
|
||||
|
||||
/** @var \MailPoetVendor\Doctrine\DBAL\Connection */
|
||||
private $databaseConnection;
|
||||
|
||||
public function __construct(
|
||||
WPFunctions $wp,
|
||||
WelcomeScheduler $welcomeScheduler,
|
||||
@ -50,7 +60,9 @@ class WP {
|
||||
SubscribersRepository $subscribersRepository,
|
||||
SubscriberSegmentRepository $subscriberSegmentRepository,
|
||||
SubscriberChangesNotifier $subscriberChangesNotifier,
|
||||
Validator $validator
|
||||
Validator $validator,
|
||||
SegmentsRepository $segmentsRepository,
|
||||
EntityManager $entityManager
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->welcomeScheduler = $welcomeScheduler;
|
||||
@ -59,6 +71,10 @@ class WP {
|
||||
$this->subscriberSegmentRepository = $subscriberSegmentRepository;
|
||||
$this->subscriberChangesNotifier = $subscriberChangesNotifier;
|
||||
$this->validator = $validator;
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->databaseConnection = $this->entityManager->getConnection();
|
||||
$this->subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,46 +85,39 @@ class WP {
|
||||
$wpUser = \get_userdata($wpUserId);
|
||||
if ($wpUser === false) return;
|
||||
|
||||
$subscriber = Subscriber::where('wp_user_id', $wpUser->ID)
|
||||
->findOne();
|
||||
$subscriber = $this->subscribersRepository->findOneBy(['wpUserId' => $wpUserId]);
|
||||
|
||||
$currentFilter = $this->wp->currentFilter();
|
||||
// Delete
|
||||
if (in_array($currentFilter, ['delete_user', 'deleted_user', 'remove_user_from_blog'])) {
|
||||
$this->deleteSubscriber($subscriber);
|
||||
if ($subscriber instanceof SubscriberEntity) {
|
||||
$this->deleteSubscriber($subscriber);
|
||||
}
|
||||
return;
|
||||
}
|
||||
$this->createOrUpdateSubscriber($currentFilter, $wpUser, $subscriber, $oldWpUserData);
|
||||
$this->handleCreatingOrUpdatingSubscriber($currentFilter, $wpUser, $subscriber, $oldWpUserData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param false|Subscriber $subscriber
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function deleteSubscriber($subscriber) {
|
||||
if ($subscriber !== false) {
|
||||
// unlink subscriber from wp user and delete
|
||||
$subscriber->set('wp_user_id', null);
|
||||
$subscriber->delete();
|
||||
}
|
||||
private function deleteSubscriber(SubscriberEntity $subscriber): void {
|
||||
$this->subscribersRepository->remove($subscriber);
|
||||
$this->subscribersRepository->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $currentFilter
|
||||
* @param \WP_User $wpUser
|
||||
* @param Subscriber|false $subscriber
|
||||
* @param ?SubscriberEntity $subscriber
|
||||
* @param array|false $oldWpUserData
|
||||
*/
|
||||
private function createOrUpdateSubscriber(string $currentFilter, \WP_User $wpUser, $subscriber = false, $oldWpUserData = false): void {
|
||||
private function handleCreatingOrUpdatingSubscriber(string $currentFilter, \WP_User $wpUser, ?SubscriberEntity $subscriber = null, $oldWpUserData = false): void {
|
||||
// Add or update
|
||||
$wpSegment = Segment::getWPSegment();
|
||||
if (!$wpSegment) return;
|
||||
$wpSegment = $this->segmentsRepository->getWPUsersSegment();
|
||||
|
||||
// find subscriber by email when is false
|
||||
if (!$subscriber) {
|
||||
$subscriber = Subscriber::where('email', $wpUser->user_email)->findOne(); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
// find subscriber by email when is null
|
||||
if (is_null($subscriber)) {
|
||||
$subscriber = $this->subscribersRepository->findOneBy(['email' => $wpUser->user_email]); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
}
|
||||
|
||||
// get first name & last name
|
||||
$firstName = html_entity_decode($wpUser->first_name); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$lastName = html_entity_decode($wpUser->last_name); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
@ -116,7 +125,7 @@ class WP {
|
||||
$firstName = html_entity_decode($wpUser->display_name); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
}
|
||||
$signupConfirmationEnabled = SettingsController::getInstance()->get('signup_confirmation.enabled');
|
||||
$status = $signupConfirmationEnabled ? Subscriber::STATUS_UNCONFIRMED : Subscriber::STATUS_SUBSCRIBED;
|
||||
$status = $signupConfirmationEnabled ? SubscriberEntity::STATUS_UNCONFIRMED : SubscriberEntity::STATUS_SUBSCRIBED;
|
||||
// we want to mark a new subscriber as unsubscribe when the checkbox from registration is unchecked
|
||||
if (isset($_POST['mailpoet']['subscribe_on_register_active']) && (bool)$_POST['mailpoet']['subscribe_on_register_active'] === true) {
|
||||
$status = SubscriberEntity::STATUS_UNSUBSCRIBED;
|
||||
@ -132,19 +141,18 @@ class WP {
|
||||
'source' => Source::WORDPRESS_USER,
|
||||
];
|
||||
|
||||
if ($subscriber !== false) {
|
||||
$data['id'] = $subscriber->id();
|
||||
if (!is_null($subscriber)) {
|
||||
$data['id'] = $subscriber->getId();
|
||||
unset($data['status']); // don't override status for existing users
|
||||
unset($data['source']); // don't override status for existing users
|
||||
}
|
||||
|
||||
$addingNewUserToDisabledWPSegment = $wpSegment->deletedAt !== null && $currentFilter === 'user_register';
|
||||
$addingNewUserToDisabledWPSegment = $wpSegment->getDeletedAt() !== null && $currentFilter === 'user_register';
|
||||
|
||||
$otherActiveSegments = [];
|
||||
if ($subscriber) {
|
||||
$subscriber = $subscriber->withSegments();
|
||||
$otherActiveSegments = array_filter($subscriber->segments ?? [], function ($segment) {
|
||||
return $segment['type'] !== SegmentEntity::TYPE_WP_USERS && $segment['deleted_at'] === null;
|
||||
$otherActiveSegments = array_filter($subscriber->getSegments()->toArray() ?? [], function (SegmentEntity $segment) {
|
||||
return $segment->getType() !== SegmentEntity::TYPE_WP_USERS && $segment->getDeletedAt() === null;
|
||||
});
|
||||
}
|
||||
$isWooCustomer = $this->wooHelper->isWooCommerceActive() && in_array('customer', $wpUser->roles, true);
|
||||
@ -155,58 +163,86 @@ class WP {
|
||||
$data['status'] = SubscriberEntity::STATUS_UNCONFIRMED;
|
||||
}
|
||||
|
||||
$subscriber = Subscriber::createOrUpdate($data);
|
||||
if ($subscriber->getErrors() === false && $subscriber->id > 0) {
|
||||
// add subscriber to the WP Users segment
|
||||
SubscriberSegment::subscribeToSegments(
|
||||
$subscriber,
|
||||
[$wpSegment->id]
|
||||
);
|
||||
try {
|
||||
$subscriber = $this->createOrUpdateSubscriber($data, $subscriber);
|
||||
} catch (\Exception $e) {
|
||||
return; // fails silently as this was the behavior of this methods before the Doctrine refactor.
|
||||
}
|
||||
|
||||
if (!$signupConfirmationEnabled && $subscriber->status === Subscriber::STATUS_SUBSCRIBED && $currentFilter === 'user_register') {
|
||||
$subscriberSegment = $this->subscriberSegmentRepository->findOneBy([
|
||||
'subscriber' => $subscriber->id(),
|
||||
'segment' => $wpSegment->id(),
|
||||
]);
|
||||
// add subscriber to the WP Users segment
|
||||
$this->subscriberSegmentRepository->subscribeToSegments(
|
||||
$subscriber,
|
||||
[$wpSegment]
|
||||
);
|
||||
|
||||
if (!is_null($subscriberSegment)) {
|
||||
$this->wp->doAction('mailpoet_segment_subscribed', $subscriberSegment);
|
||||
}
|
||||
}
|
||||
if (!$signupConfirmationEnabled && $subscriber->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED && $currentFilter === 'user_register') {
|
||||
$subscriberSegment = $this->subscriberSegmentRepository->findOneBy([
|
||||
'subscriber' => $subscriber->getId(),
|
||||
'segment' => $wpSegment->getId(),
|
||||
]);
|
||||
|
||||
$subscribeOnRegisterEnabled = SettingsController::getInstance()->get('subscribe.on_register.enabled');
|
||||
$sendConfirmationEmail =
|
||||
$signupConfirmationEnabled
|
||||
&& $subscribeOnRegisterEnabled
|
||||
&& $currentFilter !== 'profile_update'
|
||||
&& !$addingNewUserToDisabledWPSegment;
|
||||
|
||||
if ($sendConfirmationEmail && ($subscriber->status === Subscriber::STATUS_UNCONFIRMED)) {
|
||||
/** @var ConfirmationEmailMailer $confirmationEmailMailer */
|
||||
$confirmationEmailMailer = ContainerWrapper::getInstance()->get(ConfirmationEmailMailer::class);
|
||||
$subscriberEntity = $this->subscribersRepository->findOneById($subscriber->id);
|
||||
if ($subscriberEntity instanceof SubscriberEntity) {
|
||||
try {
|
||||
$confirmationEmailMailer->sendConfirmationEmailOnce($subscriberEntity);
|
||||
} catch (\Exception $e) {
|
||||
// ignore errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// welcome email
|
||||
$scheduleWelcomeNewsletter = false;
|
||||
if (in_array($currentFilter, ['profile_update', 'user_register', 'add_user_role', 'set_user_role'])) {
|
||||
$scheduleWelcomeNewsletter = true;
|
||||
}
|
||||
if ($scheduleWelcomeNewsletter === true) {
|
||||
$this->welcomeScheduler->scheduleWPUserWelcomeNotification(
|
||||
$subscriber->id,
|
||||
(array)$wpUser,
|
||||
(array)$oldWpUserData
|
||||
);
|
||||
if (!is_null($subscriberSegment)) {
|
||||
$this->wp->doAction('mailpoet_segment_subscribed', $subscriberSegment);
|
||||
}
|
||||
}
|
||||
|
||||
$subscribeOnRegisterEnabled = SettingsController::getInstance()->get('subscribe.on_register.enabled');
|
||||
$sendConfirmationEmail =
|
||||
$signupConfirmationEnabled
|
||||
&& $subscribeOnRegisterEnabled
|
||||
&& $currentFilter !== 'profile_update'
|
||||
&& !$addingNewUserToDisabledWPSegment;
|
||||
|
||||
if ($sendConfirmationEmail && ($subscriber->getStatus() === SubscriberEntity::STATUS_UNCONFIRMED)) {
|
||||
/** @var ConfirmationEmailMailer $confirmationEmailMailer */
|
||||
$confirmationEmailMailer = ContainerWrapper::getInstance()->get(ConfirmationEmailMailer::class);
|
||||
try {
|
||||
$confirmationEmailMailer->sendConfirmationEmailOnce($subscriber);
|
||||
} catch (\Exception $e) {
|
||||
// ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
// welcome email
|
||||
$scheduleWelcomeNewsletter = false;
|
||||
if (in_array($currentFilter, ['profile_update', 'user_register', 'add_user_role', 'set_user_role'])) {
|
||||
$scheduleWelcomeNewsletter = true;
|
||||
}
|
||||
if ($scheduleWelcomeNewsletter === true) {
|
||||
$this->welcomeScheduler->scheduleWPUserWelcomeNotification(
|
||||
$subscriber->getId(),
|
||||
(array)$wpUser,
|
||||
(array)$oldWpUserData
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function createOrUpdateSubscriber(array $data, ?SubscriberEntity $subscriber = null): SubscriberEntity {
|
||||
if (is_null($subscriber)) {
|
||||
$subscriber = new SubscriberEntity();
|
||||
}
|
||||
|
||||
$subscriber->setWpUserId($data['wp_user_id']);
|
||||
$subscriber->setEmail($data['email']);
|
||||
$subscriber->setFirstName($data['first_name']);
|
||||
$subscriber->setLastName($data['last_name']);
|
||||
|
||||
if (isset($data['status'])) {
|
||||
$subscriber->setStatus($data['status']);
|
||||
}
|
||||
|
||||
if (isset($data['source'])) {
|
||||
$subscriber->setSource($data['source']);
|
||||
}
|
||||
|
||||
if (isset($data['deleted_at'])) {
|
||||
$subscriber->setDeletedAt($data['deleted_at']);
|
||||
}
|
||||
|
||||
$this->subscribersRepository->persist($subscriber);
|
||||
$this->subscribersRepository->flush();
|
||||
|
||||
return $subscriber;
|
||||
}
|
||||
|
||||
public function synchronizeUsers(): bool {
|
||||
@ -227,6 +263,7 @@ class WP {
|
||||
$this->insertUsersToSegment();
|
||||
$this->removeOrphanedSubscribers();
|
||||
$this->subscribersRepository->invalidateTotalSubscribersCache();
|
||||
$this->subscribersRepository->refreshAll();
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -241,32 +278,39 @@ class WP {
|
||||
if (!$invalidWpUserIds) {
|
||||
return;
|
||||
}
|
||||
ORM::for_table(Subscriber::$_table)->whereIn('wp_user_id', $invalidWpUserIds)->delete_many();
|
||||
|
||||
$this->subscribersRepository->removeByWpUserIds($invalidWpUserIds);
|
||||
}
|
||||
|
||||
private function updateSubscribersEmails(): array {
|
||||
global $wpdb;
|
||||
Subscriber::rawExecute('SELECT NOW();');
|
||||
$startTime = Subscriber::getLastStatement()->fetch(\PDO::FETCH_COLUMN);
|
||||
|
||||
$subscribersTable = Subscriber::$_table;
|
||||
Subscriber::rawExecute(sprintf('
|
||||
UPDATE IGNORE %1$s
|
||||
INNER JOIN %2$s as wu ON %1$s.wp_user_id = wu.id
|
||||
SET %1$s.email = wu.user_email;
|
||||
', $subscribersTable, $wpdb->users));
|
||||
$stmt = $this->databaseConnection->executeQuery('SELECT NOW();');
|
||||
$startTime = $stmt->fetchOne();
|
||||
|
||||
return ORM::for_table(Subscriber::$_table)->raw_query(sprintf(
|
||||
'SELECT wp_user_id as id, email FROM %s
|
||||
WHERE updated_at >= \'%s\';
|
||||
', $subscribersTable, $startTime))->findArray();
|
||||
if (!is_string($startTime)) {
|
||||
throw new \RuntimeException("Failed to fetch the current time.");
|
||||
}
|
||||
|
||||
$updateSql =
|
||||
"UPDATE IGNORE {$this->subscribersTable} s
|
||||
INNER JOIN {$wpdb->users} as wu ON s.wp_user_id = wu.id
|
||||
SET s.email = wu.user_email";
|
||||
$this->databaseConnection->executeStatement($updateSql);
|
||||
|
||||
$selectSql =
|
||||
"SELECT wp_user_id as id, email FROM {$this->subscribersTable}
|
||||
WHERE updated_at >= '{$startTime}'";
|
||||
$updatedEmails = $this->databaseConnection->fetchAllAssociative($selectSql);
|
||||
|
||||
return $updatedEmails;
|
||||
}
|
||||
|
||||
private function insertSubscribers(): array {
|
||||
global $wpdb;
|
||||
$wpSegment = Segment::getWPSegment();
|
||||
if (!$wpSegment) return [];
|
||||
if ($wpSegment->deletedAt !== null) {
|
||||
$wpSegment = $this->segmentsRepository->getWPUsersSegment();
|
||||
|
||||
if ($wpSegment->getDeletedAt() !== null) {
|
||||
$subscriberStatus = SubscriberEntity::STATUS_UNCONFIRMED;
|
||||
$deletedAt = 'CURRENT_TIMESTAMP()';
|
||||
} else {
|
||||
@ -274,92 +318,85 @@ class WP {
|
||||
$subscriberStatus = $signupConfirmationEnabled ? SubscriberEntity::STATUS_UNCONFIRMED : SubscriberEntity::STATUS_SUBSCRIBED;
|
||||
$deletedAt = 'null';
|
||||
}
|
||||
$subscribersTable = Subscriber::$_table;
|
||||
$insertedUserIds = ORM::for_table($wpdb->users)->raw_query(sprintf(
|
||||
'SELECT %2$s.id, %2$s.user_email as email FROM %2$s
|
||||
LEFT JOIN %1$s AS mps ON mps.wp_user_id = %2$s.id
|
||||
WHERE mps.wp_user_id IS NULL AND %2$s.user_email != ""
|
||||
', $subscribersTable, $wpdb->users))->findArray();
|
||||
|
||||
Subscriber::rawExecute(sprintf(
|
||||
'
|
||||
INSERT IGNORE INTO %1$s(wp_user_id, email, status, created_at, `source`, deleted_at)
|
||||
SELECT wu.id, wu.user_email, "%4$s", CURRENT_TIMESTAMP(), "%3$s", %5$s FROM %2$s wu
|
||||
LEFT JOIN %1$s mps ON wu.id = mps.wp_user_id
|
||||
WHERE mps.wp_user_id IS NULL AND wu.user_email != ""
|
||||
ON DUPLICATE KEY UPDATE wp_user_id = wu.id
|
||||
',
|
||||
$subscribersTable,
|
||||
$wpdb->users,
|
||||
Source::WORDPRESS_USER,
|
||||
$subscriberStatus,
|
||||
$deletedAt
|
||||
));
|
||||
// Fetch users that are not in the subscribers table
|
||||
$selectSql =
|
||||
"SELECT u.id, u.user_email as email
|
||||
FROM {$wpdb->users} u
|
||||
LEFT JOIN {$this->subscribersTable} AS s ON s.wp_user_id = u.id
|
||||
WHERE s.wp_user_id IS NULL AND u.user_email != ''";
|
||||
$insertedUserIds = $this->databaseConnection->fetchAllAssociative($selectSql);
|
||||
|
||||
// Insert new users into the subscribers table
|
||||
$insertSql =
|
||||
"INSERT IGNORE INTO {$this->subscribersTable} (wp_user_id, email, status, created_at, `source`, deleted_at)
|
||||
SELECT wu.id, wu.user_email, :subscriberStatus, CURRENT_TIMESTAMP(), :source, {$deletedAt}
|
||||
FROM {$wpdb->users} wu
|
||||
LEFT JOIN {$this->subscribersTable} s ON wu.id = s.wp_user_id
|
||||
WHERE s.wp_user_id IS NULL AND wu.user_email != ''
|
||||
ON DUPLICATE KEY UPDATE wp_user_id = wu.id";
|
||||
$stmt = $this->databaseConnection->prepare($insertSql);
|
||||
$stmt->bindValue('subscriberStatus', $subscriberStatus);
|
||||
$stmt->bindValue('source', Source::WORDPRESS_USER);
|
||||
$stmt->executeStatement();
|
||||
|
||||
return $insertedUserIds;
|
||||
}
|
||||
|
||||
private function updateFirstNames(): void {
|
||||
global $wpdb;
|
||||
$subscribersTable = Subscriber::$_table;
|
||||
Subscriber::rawExecute(sprintf('
|
||||
UPDATE %1$s
|
||||
JOIN %2$s as wpum ON %1$s.wp_user_id = wpum.user_id AND wpum.meta_key = "first_name"
|
||||
SET %1$s.first_name = SUBSTRING(wpum.meta_value, 1, 255)
|
||||
WHERE %1$s.first_name = ""
|
||||
AND %1$s.wp_user_id IS NOT NULL
|
||||
AND wpum.meta_value IS NOT NULL
|
||||
', $subscribersTable, $wpdb->usermeta));
|
||||
|
||||
$sql =
|
||||
"UPDATE {$this->subscribersTable} s
|
||||
JOIN {$wpdb->usermeta} as wpum ON s.wp_user_id = wpum.user_id AND wpum.meta_key = 'first_name'
|
||||
SET s.first_name = SUBSTRING(wpum.meta_value, 1, 255)
|
||||
WHERE s.first_name = ''
|
||||
AND s.wp_user_id IS NOT NULL
|
||||
AND wpum.meta_value IS NOT NULL";
|
||||
|
||||
$this->databaseConnection->executeStatement($sql);
|
||||
}
|
||||
|
||||
private function updateLastNames(): void {
|
||||
global $wpdb;
|
||||
$subscribersTable = Subscriber::$_table;
|
||||
Subscriber::rawExecute(sprintf('
|
||||
UPDATE %1$s
|
||||
JOIN %2$s as wpum ON %1$s.wp_user_id = wpum.user_id AND wpum.meta_key = "last_name"
|
||||
SET %1$s.last_name = SUBSTRING(wpum.meta_value, 1, 255)
|
||||
WHERE %1$s.last_name = ""
|
||||
AND %1$s.wp_user_id IS NOT NULL
|
||||
AND wpum.meta_value IS NOT NULL
|
||||
', $subscribersTable, $wpdb->usermeta));
|
||||
|
||||
$sql =
|
||||
"UPDATE {$this->subscribersTable} s
|
||||
JOIN {$wpdb->usermeta} as wpum ON s.wp_user_id = wpum.user_id AND wpum.meta_key = 'last_name'
|
||||
SET s.last_name = SUBSTRING(wpum.meta_value, 1, 255)
|
||||
WHERE s.last_name = ''
|
||||
AND s.wp_user_id IS NOT NULL
|
||||
AND wpum.meta_value IS NOT NULL";
|
||||
|
||||
$this->databaseConnection->executeStatement($sql);
|
||||
}
|
||||
|
||||
private function updateFirstNameIfMissing(): void {
|
||||
global $wpdb;
|
||||
$subscribersTable = Subscriber::$_table;
|
||||
Subscriber::rawExecute(sprintf('
|
||||
UPDATE %1$s
|
||||
JOIN %2$s wu ON %1$s.wp_user_id = wu.id
|
||||
SET %1$s.first_name = wu.display_name
|
||||
WHERE %1$s.first_name = ""
|
||||
AND %1$s.wp_user_id IS NOT NULL
|
||||
', $subscribersTable, $wpdb->users));
|
||||
|
||||
$sql =
|
||||
"UPDATE {$this->subscribersTable} s
|
||||
JOIN {$wpdb->users} wu ON s.wp_user_id = wu.id
|
||||
SET s.first_name = wu.display_name
|
||||
WHERE s.first_name = ''
|
||||
AND s.wp_user_id IS NOT NULL";
|
||||
|
||||
$this->databaseConnection->executeStatement($sql);
|
||||
}
|
||||
|
||||
private function insertUsersToSegment(): void {
|
||||
$wpSegment = Segment::getWPSegment();
|
||||
$subscribersTable = Subscriber::$_table;
|
||||
$wpMailpoetSubscriberSegmentTable = SubscriberSegment::$_table;
|
||||
Subscriber::rawExecute(sprintf('
|
||||
INSERT IGNORE INTO %s(subscriber_id, segment_id, created_at)
|
||||
SELECT mps.id, "%s", CURRENT_TIMESTAMP() FROM %s mps
|
||||
WHERE mps.wp_user_id > 0
|
||||
', $wpMailpoetSubscriberSegmentTable, $wpSegment->id, $subscribersTable));
|
||||
$wpSegment = $this->segmentsRepository->getWPUsersSegment();
|
||||
$subscribersSegmentTable = $this->entityManager->getClassMetadata(SubscriberSegmentEntity::class)->getTableName();
|
||||
|
||||
$sql =
|
||||
"INSERT IGNORE INTO {$subscribersSegmentTable} (subscriber_id, segment_id, created_at)
|
||||
SELECT s.id, '{$wpSegment->getId()}', CURRENT_TIMESTAMP() FROM {$this->subscribersTable} s
|
||||
WHERE s.wp_user_id > 0";
|
||||
|
||||
$this->databaseConnection->executeStatement($sql);
|
||||
}
|
||||
|
||||
private function removeOrphanedSubscribers(): void {
|
||||
// remove orphaned wp segment subscribers (not having a matching wp user id),
|
||||
// e.g. if wp users were deleted directly from the database
|
||||
global $wpdb;
|
||||
|
||||
$wpSegment = Segment::getWPSegment();
|
||||
|
||||
$wpSegment->subscribers()
|
||||
->leftOuterJoin($wpdb->users, [MP_SUBSCRIBERS_TABLE . '.wp_user_id', '=', 'wu.id'], 'wu')
|
||||
->whereRaw('(wu.id IS NULL OR ' . MP_SUBSCRIBERS_TABLE . '.email = "")')
|
||||
->findResultSet()
|
||||
->set('wp_user_id', null)
|
||||
->delete();
|
||||
$this->subscribersRepository->removeOrphanedSubscribersFromWpSegment();
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,11 @@ use MailPoet\Services\Bridge\API;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Util\License\Features\Subscribers;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
|
||||
class AuthorizedSenderDomainController {
|
||||
const OVERALL_STATUS_VERIFIED = 'verified';
|
||||
const OVERALL_STATUS_PARTIALLY_VERIFIED = 'partially-verified';
|
||||
const OVERALL_STATUS_UNVERIFIED = 'unverified';
|
||||
const DOMAIN_STATUS_VERIFIED = 'verified';
|
||||
const DOMAIN_STATUS_PARTIALLY_VERIFIED = 'partially-verified';
|
||||
const DOMAIN_STATUS_UNVERIFIED = 'unverified';
|
||||
|
||||
const AUTHORIZED_SENDER_DOMAIN_ERROR_ALREADY_CREATED = 'Sender domain exist';
|
||||
const AUTHORIZED_SENDER_DOMAIN_ERROR_NOT_CREATED = 'Sender domain does not exist';
|
||||
@ -22,8 +21,6 @@ class AuthorizedSenderDomainController {
|
||||
const LOWER_LIMIT = 100;
|
||||
const UPPER_LIMIT = 200;
|
||||
|
||||
const ENFORCEMENT_START_TIME = '2024-02-01 00:00:00 UTC';
|
||||
|
||||
const INSTALLED_AFTER_NEW_RESTRICTIONS_OPTION = 'installed_after_new_domain_restrictions';
|
||||
|
||||
const SENDER_DOMAINS_KEY = 'mailpoet_sender_domains';
|
||||
@ -121,6 +118,7 @@ class AuthorizedSenderDomainController {
|
||||
}
|
||||
|
||||
// Reset cached value since a new domain was added
|
||||
$this->currentRecords = null;
|
||||
$this->reloadCache();
|
||||
|
||||
return $response;
|
||||
@ -150,7 +148,6 @@ class AuthorizedSenderDomainController {
|
||||
throw new \InvalidArgumentException(self::AUTHORIZED_SENDER_DOMAIN_ERROR_NOT_CREATED);
|
||||
}
|
||||
|
||||
$this->reloadCache();
|
||||
$verifiedDomains = $this->getFullyVerifiedSenderDomains(true);
|
||||
$alreadyVerified = in_array($domain, $verifiedDomains);
|
||||
|
||||
@ -166,6 +163,9 @@ class AuthorizedSenderDomainController {
|
||||
throw new \InvalidArgumentException($response['message']);
|
||||
}
|
||||
|
||||
$this->currentRecords = null;
|
||||
$this->reloadCache();
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
@ -179,7 +179,7 @@ class AuthorizedSenderDomainController {
|
||||
* Returns sender domains that have all required records, including DMARC.
|
||||
*/
|
||||
public function getFullyVerifiedSenderDomains($domainsOnly = false): array {
|
||||
$domainData = $this->getSenderDomainsByStatus([self::OVERALL_STATUS_VERIFIED]);
|
||||
$domainData = $this->getSenderDomainsByStatus([self::DOMAIN_STATUS_VERIFIED]);
|
||||
return $domainsOnly ? $this->extractDomains($domainData) : $domainData;
|
||||
}
|
||||
|
||||
@ -187,17 +187,17 @@ class AuthorizedSenderDomainController {
|
||||
* Returns sender domains that were verified before DMARC record was required.
|
||||
*/
|
||||
public function getPartiallyVerifiedSenderDomains($domainsOnly = false): array {
|
||||
$domainData = $this->getSenderDomainsByStatus([self::OVERALL_STATUS_PARTIALLY_VERIFIED]);
|
||||
$domainData = $this->getSenderDomainsByStatus([self::DOMAIN_STATUS_PARTIALLY_VERIFIED]);
|
||||
return $domainsOnly ? $this->extractDomains($domainData) : $domainData;
|
||||
}
|
||||
|
||||
public function getUnverifiedSenderDomains($domainsOnly = false): array {
|
||||
$domainData = $this->getSenderDomainsByStatus([self::OVERALL_STATUS_UNVERIFIED]);
|
||||
$domainData = $this->getSenderDomainsByStatus([self::DOMAIN_STATUS_UNVERIFIED]);
|
||||
return $domainsOnly ? $this->extractDomains($domainData) : $domainData;
|
||||
}
|
||||
|
||||
public function getFullyOrPartiallyVerifiedSenderDomains($domainsOnly = false): array {
|
||||
$domainData = $this->getSenderDomainsByStatus([self::OVERALL_STATUS_PARTIALLY_VERIFIED,self::OVERALL_STATUS_VERIFIED]);
|
||||
$domainData = $this->getSenderDomainsByStatus([self::DOMAIN_STATUS_PARTIALLY_VERIFIED,self::DOMAIN_STATUS_VERIFIED]);
|
||||
return $domainsOnly ? $this->extractDomains($domainData) : $domainData;
|
||||
}
|
||||
|
||||
@ -236,7 +236,6 @@ class AuthorizedSenderDomainController {
|
||||
}
|
||||
|
||||
private function reloadCache() {
|
||||
$this->currentRecords = null;
|
||||
$this->currentRawData = $this->bridge->getRawSenderDomainData();
|
||||
$this->wp->setTransient(self::SENDER_DOMAINS_KEY, $this->currentRawData, 60 * 60 * 24);
|
||||
}
|
||||
@ -261,11 +260,6 @@ class AuthorizedSenderDomainController {
|
||||
return $this->currentRecords;
|
||||
}
|
||||
|
||||
// TODO: Remove after the enforcement date has passed
|
||||
public function isEnforcementOfNewRestrictionsInEffect(): bool {
|
||||
return Carbon::now() >= Carbon::parse(self::ENFORCEMENT_START_TIME);
|
||||
}
|
||||
|
||||
public function isNewUser(): bool {
|
||||
$installedVersion = $this->settingsController->get('version');
|
||||
|
||||
@ -291,25 +285,12 @@ class AuthorizedSenderDomainController {
|
||||
return $this->subscribers->getSubscribersCount() > self::UPPER_LIMIT;
|
||||
}
|
||||
|
||||
private function restrictionsApply(): bool {
|
||||
if ($this->settingsController->get('mta.method') !== Mailer::METHOD_MAILPOET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Remove after the enforcement date has passed
|
||||
if (!$this->isNewUser() && !$this->isEnforcementOfNewRestrictionsInEffect()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isAuthorizedDomainRequiredForNewCampaigns(): bool {
|
||||
return $this->restrictionsApply() && !$this->isSmallSender();
|
||||
return $this->settingsController->get('mta.method') === Mailer::METHOD_MAILPOET && !$this->isSmallSender();
|
||||
}
|
||||
|
||||
public function isAuthorizedDomainRequiredForExistingCampaigns(): bool {
|
||||
return $this->restrictionsApply() && $this->isBigSender();
|
||||
return $this->settingsController->get('mta.method') === Mailer::METHOD_MAILPOET && $this->isBigSender();
|
||||
}
|
||||
|
||||
public function getContextData(): array {
|
||||
@ -319,9 +300,6 @@ class AuthorizedSenderDomainController {
|
||||
'allSenderDomains' => $this->getAllSenderDomains(),
|
||||
'senderRestrictions' => [
|
||||
'lowerLimit' => self::LOWER_LIMIT,
|
||||
'upperLimit' => self::UPPER_LIMIT,
|
||||
'isNewUser' => $this->isNewUser(),
|
||||
'isEnforcementOfNewRestrictionsInEffect' => $this->isEnforcementOfNewRestrictionsInEffect(),
|
||||
'alwaysRewrite' => false,
|
||||
],
|
||||
];
|
||||
|
@ -89,4 +89,20 @@ class StatisticsClicksRepository extends Repository {
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
/** @param int[] $ids */
|
||||
public function deleteByNewsletterIds(array $ids): void {
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->delete(StatisticsClickEntity::class, 's')
|
||||
->where('s.newsletter IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// delete was done via DQL, make sure the entities are also detached from the entity manager
|
||||
$this->detachAll(function (StatisticsClickEntity $entity) use ($ids) {
|
||||
$newsletter = $entity->getNewsletter();
|
||||
return $newsletter && in_array($newsletter->getId(), $ids, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Entities\StatisticsNewsletterEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* @extends Repository<StatisticsNewsletterEntity>
|
||||
@ -29,7 +30,8 @@ class StatisticsNewslettersRepository extends Repository {
|
||||
continue;
|
||||
}
|
||||
|
||||
$entity = new StatisticsNewsletterEntity($newsletter, $queue, $subscriber);
|
||||
$sentAt = Carbon::createFromTimestamp((int)current_time('timestamp'));
|
||||
$entity = new StatisticsNewsletterEntity($newsletter, $queue, $subscriber, $sentAt);
|
||||
|
||||
$this->entityManager->persist($entity);
|
||||
$entities[] = $entity;
|
||||
@ -40,4 +42,20 @@ class StatisticsNewslettersRepository extends Repository {
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
}
|
||||
|
||||
/** @param int[] $ids */
|
||||
public function deleteByNewsletterIds(array $ids): void {
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->delete(StatisticsNewsletterEntity::class, 's')
|
||||
->where('s.newsletter IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// delete was done via DQL, make sure the entities are also detached from the entity manager
|
||||
$this->detachAll(function (StatisticsNewsletterEntity $entity) use ($ids) {
|
||||
$newsletter = $entity->getNewsletter();
|
||||
return $newsletter && in_array($newsletter->getId(), $ids, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -90,4 +90,20 @@ class StatisticsOpensRepository extends Repository {
|
||||
->orderBy('queue.newsletterRenderedSubject')
|
||||
->setParameter('subscriber', $subscriber->getId());
|
||||
}
|
||||
|
||||
/** @param int[] $ids */
|
||||
public function deleteByNewsletterIds(array $ids): void {
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->delete(StatisticsOpenEntity::class, 's')
|
||||
->where('s.newsletter IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// delete was done via DQL, make sure the entities are also detached from the entity manager
|
||||
$this->detachAll(function (StatisticsOpenEntity $entity) use ($ids) {
|
||||
$newsletter = $entity->getNewsletter();
|
||||
return $newsletter && in_array($newsletter->getId(), $ids, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -112,4 +112,22 @@ class StatisticsWooCommercePurchasesRepository extends Repository {
|
||||
}, $data);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/** @param int[] $ids */
|
||||
public function removeNewsletterDataByNewsletterIds(array $ids): void {
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->update(StatisticsWooCommercePurchaseEntity::class, 'swp')
|
||||
->set('swp.newsletter', ':newsletter')
|
||||
->where('swp.newsletter IN (:ids)')
|
||||
->setParameter('newsletter', null)
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// update was done via DQL, make sure the entities are also refreshed in the entity manager
|
||||
$this->refreshAll(function (StatisticsWooCommercePurchaseEntity $entity) use ($ids) {
|
||||
$newsletter = $entity->getNewsletter();
|
||||
return $newsletter && in_array($newsletter->getId(), $ids, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -178,23 +178,40 @@ class Import {
|
||||
);
|
||||
}
|
||||
|
||||
if ($existingSubscribers['data'] && $this->updateSubscribers) {
|
||||
$updateExistingSubscribersStatus = false;
|
||||
|
||||
if ($existingSubscribers['data']) {
|
||||
$allowedStatuses = [
|
||||
SubscriberEntity::STATUS_SUBSCRIBED,
|
||||
SubscriberEntity::STATUS_UNSUBSCRIBED,
|
||||
SubscriberEntity::STATUS_INACTIVE,
|
||||
];
|
||||
if (in_array($this->existingSubscribersStatus, $allowedStatuses, true)) {
|
||||
$updateExistingSubscribersStatus = true;
|
||||
$existingSubscribers = $this->addField($existingSubscribers, 'status', $this->existingSubscribersStatus);
|
||||
}
|
||||
$updatedSubscribers =
|
||||
$this->createOrUpdateSubscribers(
|
||||
self::ACTION_UPDATE,
|
||||
$existingSubscribers,
|
||||
$this->subscribersCustomFields
|
||||
);
|
||||
if ($wpUsers) {
|
||||
$this->synchronizeWPUsers($wpUsers);
|
||||
if ($this->updateSubscribers) {
|
||||
// Update existing subscribers' info (first_name, last_name etc.)
|
||||
// as well as status (optionally) if the status column was added above
|
||||
$updatedSubscribers =
|
||||
$this->createOrUpdateSubscribers(
|
||||
self::ACTION_UPDATE,
|
||||
$existingSubscribers,
|
||||
$this->subscribersCustomFields
|
||||
);
|
||||
if ($wpUsers) {
|
||||
$this->synchronizeWPUsers($wpUsers);
|
||||
}
|
||||
} elseif ($updateExistingSubscribersStatus) {
|
||||
// Only update existing subscribers' status
|
||||
// For this we need to remove all other fields except email and status
|
||||
$existingSubscribers['fields'] = array_intersect($existingSubscribers['fields'], ['email', 'status']);
|
||||
$existingSubscribers['data'] = array_intersect_key($existingSubscribers['data'], array_flip(['email', 'status']));
|
||||
$updatedSubscribers =
|
||||
$this->createOrUpdateSubscribers(
|
||||
self::ACTION_UPDATE,
|
||||
$existingSubscribers
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
|
@ -10,6 +10,8 @@ use MailPoet\Entities\SubscriberCustomFieldEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Entities\SubscriberSegmentEntity;
|
||||
use MailPoet\Segments\DynamicSegments\FilterHandler;
|
||||
use MailPoet\Subscribers\SubscriberCustomFieldRepository;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoetVendor\Doctrine\DBAL\Connection;
|
||||
use MailPoetVendor\Doctrine\DBAL\Driver\Statement;
|
||||
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
|
||||
@ -52,14 +54,24 @@ class ImportExportRepository {
|
||||
/** @var FilterHandler */
|
||||
private $filterHandler;
|
||||
|
||||
/** @var SubscribersRepository */
|
||||
private $subscribersRepository;
|
||||
|
||||
/** @var SubscriberCustomFieldRepository */
|
||||
private $subscriberCustomFieldRepository;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
SubscriberChangesNotifier $changesNotifier,
|
||||
FilterHandler $filterHandler
|
||||
FilterHandler $filterHandler,
|
||||
SubscribersRepository $subscribersRepository,
|
||||
SubscriberCustomFieldRepository $subscriberCustomFieldRepository
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->subscriberChangesNotifier = $changesNotifier;
|
||||
$this->filterHandler = $filterHandler;
|
||||
$this->subscribersRepository = $subscribersRepository;
|
||||
$this->subscriberCustomFieldRepository = $subscriberCustomFieldRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -183,6 +195,12 @@ class ImportExportRepository {
|
||||
" . implode(' AND ', $keyColumnsConditions) . "
|
||||
", $parameters, $parameterTypes);
|
||||
$this->notifyUpdates($className, $columns, $data);
|
||||
if ($className === SubscriberEntity::class) {
|
||||
$this->subscribersRepository->refreshAll();
|
||||
}
|
||||
if ($className === SubscriberCustomFieldEntity::class) {
|
||||
$this->subscriberCustomFieldRepository->refreshAll();
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Entities\SubscriberSegmentEntity;
|
||||
use MailPoet\Entities\SubscriberTagEntity;
|
||||
use MailPoet\Entities\TagEntity;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Util\License\Features\Subscribers;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
@ -38,14 +39,19 @@ class SubscribersRepository extends Repository {
|
||||
/** @var SubscriberChangesNotifier */
|
||||
private $changesNotifier;
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentsRepository;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
SubscriberChangesNotifier $changesNotifier,
|
||||
WPFunctions $wp
|
||||
WPFunctions $wp,
|
||||
SegmentsRepository $segmentsRepository
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
parent::__construct($entityManager);
|
||||
$this->changesNotifier = $changesNotifier;
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
}
|
||||
|
||||
protected function getEntityClassName() {
|
||||
@ -553,6 +559,35 @@ class SubscribersRepository extends Repository {
|
||||
return $count;
|
||||
}
|
||||
|
||||
public function removeOrphanedSubscribersFromWpSegment(): void {
|
||||
global $wpdb;
|
||||
|
||||
$segmentId = $this->segmentsRepository->getWpUsersSegment()->getId();
|
||||
|
||||
$subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
|
||||
$subscriberSegmentsTable = $this->entityManager->getClassMetadata(SubscriberSegmentEntity::class)->getTableName();
|
||||
|
||||
$this->entityManager->getConnection()->executeStatement(
|
||||
"DELETE s
|
||||
FROM {$subscribersTable} s
|
||||
INNER JOIN {$subscriberSegmentsTable} ss ON s.id = ss.subscriber_id
|
||||
LEFT JOIN {$wpdb->users} u ON s.wp_user_id = u.id
|
||||
WHERE ss.segment_id = :segmentId AND (u.id IS NULL OR s.email = '')",
|
||||
['segmentId' => $segmentId], ['segmentId' => \PDO::PARAM_INT]
|
||||
);
|
||||
}
|
||||
|
||||
public function removeByWpUserIds(array $wpUserIds) {
|
||||
$queryBuilder = $this->entityManager->createQueryBuilder();
|
||||
|
||||
$queryBuilder
|
||||
->delete(SubscriberEntity::class, 's')
|
||||
->where('s.wpUserId IN (:wpUserIds)')
|
||||
->setParameter('wpUserIds', $wpUserIds);
|
||||
|
||||
return $queryBuilder->getQuery()->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int - number of processed ids
|
||||
*/
|
||||
|
@ -1,342 +0,0 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Tasks;
|
||||
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueAlias;
|
||||
use MailPoet\DI\ContainerWrapper;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Entities\ScheduledTaskSubscriberEntity;
|
||||
use MailPoet\InvalidStateException;
|
||||
use MailPoet\Logging\LoggerFactory;
|
||||
use MailPoet\Models\ScheduledTask;
|
||||
use MailPoet\Models\SendingQueue;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTaskSubscribersRepository;
|
||||
use MailPoet\Newsletter\Sending\SendingQueuesRepository;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoetVendor\Doctrine\ORM\ORMInvalidArgumentException;
|
||||
|
||||
/**
|
||||
* A facade class containing all necessary models to work with a sending queue
|
||||
* @property string|null $status
|
||||
* @property int $taskId
|
||||
* @property int $id
|
||||
* @property int $newsletterId
|
||||
* @property string $newsletterRenderedSubject
|
||||
* @property string|array $newsletterRenderedBody
|
||||
* @property bool $nonExistentColumn
|
||||
* @property string $scheduledAt
|
||||
* @property int $priority
|
||||
*/
|
||||
class Sending {
|
||||
const TASK_TYPE = SendingQueueAlias::TASK_TYPE;
|
||||
|
||||
/** @var ScheduledTask */
|
||||
private $task;
|
||||
|
||||
/** @var SendingQueue */
|
||||
private $queue;
|
||||
|
||||
private $queueFields = [
|
||||
'id',
|
||||
'task_id',
|
||||
'newsletter_id',
|
||||
'newsletter_rendered_subject',
|
||||
'newsletter_rendered_body',
|
||||
'count_total',
|
||||
'count_processed',
|
||||
'count_to_process',
|
||||
'meta',
|
||||
];
|
||||
|
||||
private $commonFields = [
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
];
|
||||
|
||||
/** @var ScheduledTaskSubscribersRepository */
|
||||
private $scheduledTaskSubscribersRepository;
|
||||
|
||||
/** @var ScheduledTasksRepository */
|
||||
private $scheduledTasksRepository;
|
||||
|
||||
/** @var ScheduledTaskEntity */
|
||||
private $scheduledTaskEntity;
|
||||
|
||||
/** @var SendingQueuesRepository */
|
||||
private $sendingQueuesRepository;
|
||||
|
||||
private function __construct(
|
||||
ScheduledTask $task = null,
|
||||
SendingQueue $queue = null
|
||||
) {
|
||||
if (!$task instanceof ScheduledTask) {
|
||||
/** @var ScheduledTask $task */
|
||||
$task = ScheduledTask::create();
|
||||
$task->type = self::TASK_TYPE;
|
||||
$task->save();
|
||||
}
|
||||
if (!$queue instanceof SendingQueue) {
|
||||
/** @var SendingQueue $queue */
|
||||
$queue = SendingQueue::create();
|
||||
$queue->newsletterId = 0;
|
||||
$queue->taskId = $task->id;
|
||||
$queue->save();
|
||||
}
|
||||
|
||||
if ($task->type !== self::TASK_TYPE) {
|
||||
throw new \Exception('Only tasks of type "' . self::TASK_TYPE . '" are accepted by this class');
|
||||
}
|
||||
|
||||
$this->task = $task;
|
||||
$this->queue = $queue;
|
||||
$this->scheduledTaskSubscribersRepository = ContainerWrapper::getInstance()->get(ScheduledTaskSubscribersRepository::class);
|
||||
$this->scheduledTasksRepository = ContainerWrapper::getInstance()->get(ScheduledTasksRepository::class);
|
||||
$this->sendingQueuesRepository = ContainerWrapper::getInstance()->get(SendingQueuesRepository::class);
|
||||
|
||||
// needed to make sure that the task has an ID so that we can retrieve the ScheduledTaskEntity while this class still uses Paris
|
||||
$this->save();
|
||||
|
||||
$scheduledTaskEntity = $this->scheduledTasksRepository->findOneById($this->task->id);
|
||||
|
||||
if (!$scheduledTaskEntity instanceof ScheduledTaskEntity) {
|
||||
throw new InvalidStateException('Scheduled task entity not found');
|
||||
}
|
||||
|
||||
$this->scheduledTaskEntity = $scheduledTaskEntity;
|
||||
}
|
||||
|
||||
public static function create(ScheduledTask $task = null, SendingQueue $queue = null) {
|
||||
return new self($task, $queue);
|
||||
}
|
||||
|
||||
public static function createManyFromTasks($tasks) {
|
||||
if (empty($tasks)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$tasksIds = array_map(function($task) {
|
||||
return $task->id;
|
||||
}, $tasks);
|
||||
|
||||
$queues = SendingQueue::whereIn('task_id', $tasksIds)->findMany();
|
||||
$queuesIndex = [];
|
||||
foreach ($queues as $queue) {
|
||||
$queuesIndex[$queue->taskId] = $queue;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($tasks as $task) {
|
||||
if (!empty($queuesIndex[$task->id])) {
|
||||
$result[] = self::create($task, $queuesIndex[$task->id]);
|
||||
} else {
|
||||
static::handleInvalidTask($task);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function handleInvalidTask(ScheduledTask $task) {
|
||||
$loggerFactory = LoggerFactory::getInstance();
|
||||
$loggerFactory->getLogger(LoggerFactory::TOPIC_NEWSLETTERS)->error(
|
||||
'invalid sending task found',
|
||||
['task_id' => $task->id]
|
||||
);
|
||||
$task->status = ScheduledTask::STATUS_INVALID;
|
||||
$task->save();
|
||||
}
|
||||
|
||||
public static function createFromScheduledTask(ScheduledTask $task) {
|
||||
$queue = SendingQueue::where('task_id', $task->id)->findOne();
|
||||
if (!$queue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::create($task, $queue);
|
||||
}
|
||||
|
||||
public static function createFromQueue(SendingQueue $queue) {
|
||||
$task = $queue->task()->findOne();
|
||||
if (!$task) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::create($task, $queue);
|
||||
}
|
||||
|
||||
public static function getByNewsletterId($newsletterId) {
|
||||
$queue = SendingQueue::where('newsletter_id', $newsletterId)
|
||||
->orderByDesc('updated_at')
|
||||
->findOne();
|
||||
if (!$queue instanceof SendingQueue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::createFromQueue($queue);
|
||||
}
|
||||
|
||||
public function asArray() {
|
||||
$queue = array_intersect_key(
|
||||
$this->queue->asArray(),
|
||||
array_flip($this->queueFields)
|
||||
);
|
||||
$task = $this->task->asArray();
|
||||
return array_merge($task, $queue);
|
||||
}
|
||||
|
||||
public function getErrors() {
|
||||
$queueErrors = $this->queue->getErrors();
|
||||
$taskErrors = $this->task->getErrors();
|
||||
if (empty($queueErrors) && empty($taskErrors)) {
|
||||
return false;
|
||||
}
|
||||
return array_merge((array)$queueErrors, (array)$taskErrors);
|
||||
}
|
||||
|
||||
public function save() {
|
||||
$this->queue->save();
|
||||
$this->task->save();
|
||||
$errors = $this->getErrors();
|
||||
if ($errors) {
|
||||
$loggerFactory = LoggerFactory::getInstance();
|
||||
$loggerFactory->getLogger(LoggerFactory::TOPIC_NEWSLETTERS)->error(
|
||||
'error saving sending task',
|
||||
['task_id' => $this->task->id, 'queue_id' => $this->queue->id, 'errors' => $errors]
|
||||
);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
$this->scheduledTaskSubscribersRepository->deleteByScheduledTask($this->scheduledTaskEntity);
|
||||
$this->scheduledTasksRepository->remove($this->scheduledTaskEntity);
|
||||
|
||||
try {
|
||||
$sendingQueueEntity = $this->scheduledTaskEntity->getSendingQueue();
|
||||
if ($sendingQueueEntity) {
|
||||
$this->sendingQueuesRepository->remove($sendingQueueEntity);
|
||||
}
|
||||
} catch (ORMInvalidArgumentException $e) {
|
||||
// This entity can already be removed. E.g. in the NewslettersRepository when deleting newsletters in bulk
|
||||
;
|
||||
}
|
||||
|
||||
$this->scheduledTasksRepository->flush();
|
||||
}
|
||||
|
||||
public function queue() {
|
||||
return $this->queue;
|
||||
}
|
||||
|
||||
public function task() {
|
||||
return $this->task;
|
||||
}
|
||||
|
||||
public function getSubscribers($processed = null) {
|
||||
if (is_null($processed)) {
|
||||
$subscribers = $this->scheduledTaskSubscribersRepository->findBy(['task' => $this->task->id]);
|
||||
} else if ($processed) {
|
||||
$subscribers = $this->scheduledTaskSubscribersRepository->findBy(
|
||||
['task' => $this->task->id, 'processed' => ScheduledTaskSubscriberEntity::STATUS_PROCESSED]
|
||||
);
|
||||
} else {
|
||||
$subscribers = $this->scheduledTaskSubscribersRepository->findBy(
|
||||
['task' => $this->task->id, 'processed' => ScheduledTaskSubscriberEntity::STATUS_UNPROCESSED]
|
||||
);
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function(ScheduledTaskSubscriberEntity $scheduledTaskSubscriber) {
|
||||
return (string)$scheduledTaskSubscriber->getSubscriberId();
|
||||
},
|
||||
$subscribers
|
||||
);
|
||||
}
|
||||
|
||||
public function setSubscribers(array $subscriberIds) {
|
||||
$this->scheduledTaskSubscribersRepository->setSubscribers($this->scheduledTaskEntity, $subscriberIds);
|
||||
$this->updateCount();
|
||||
}
|
||||
|
||||
public function updateProcessedSubscribers(array $processedSubscribers): bool {
|
||||
$this->scheduledTaskSubscribersRepository->updateProcessedSubscribers($this->scheduledTaskEntity, $processedSubscribers);
|
||||
$this->scheduledTasksRepository->refresh($this->scheduledTaskEntity); // needed while Sending still uses Paris
|
||||
$this->status = $this->scheduledTaskEntity->getStatus();
|
||||
return $this->updateCount(count($processedSubscribers))->getErrors() === false;
|
||||
}
|
||||
|
||||
public function updateCount(?int $count = null) {
|
||||
if ($count) {
|
||||
// increment/decrement counts based on known subscriber count, don't exceed the bounds
|
||||
$this->queue->countProcessed = min($this->queue->countProcessed + $count, $this->queue->countTotal);
|
||||
$this->queue->countToProcess = max($this->queue->countToProcess - $count, 0);
|
||||
} else {
|
||||
// query DB to update counts, slower but more accurate, to be used if count isn't known
|
||||
$this->queue->countProcessed = $this->scheduledTaskSubscribersRepository->countProcessed($this->scheduledTaskEntity);
|
||||
$this->queue->countToProcess = $this->scheduledTaskSubscribersRepository->countUnprocessed($this->scheduledTaskEntity);
|
||||
$this->queue->countTotal = $this->queue->countProcessed + $this->queue->countToProcess;
|
||||
}
|
||||
return $this->queue->save();
|
||||
}
|
||||
|
||||
public function hydrate(array $data) {
|
||||
foreach ($data as $k => $v) {
|
||||
$this->__set($k, $v);
|
||||
}
|
||||
}
|
||||
|
||||
public function validate() {
|
||||
return $this->queue->validate() && $this->task->validate();
|
||||
}
|
||||
|
||||
public function getMeta() {
|
||||
return $this->queue->getMeta();
|
||||
}
|
||||
|
||||
public function __isset($prop) {
|
||||
$prop = Helpers::camelCaseToUnderscore($prop);
|
||||
if ($this->isQueueProperty($prop)) {
|
||||
return isset($this->queue->$prop);
|
||||
} else {
|
||||
return isset($this->task->$prop);
|
||||
}
|
||||
}
|
||||
|
||||
public function __get($prop) {
|
||||
$prop = Helpers::camelCaseToUnderscore($prop);
|
||||
if ($this->isQueueProperty($prop)) {
|
||||
return $this->queue->$prop;
|
||||
} else {
|
||||
return $this->task->$prop;
|
||||
}
|
||||
}
|
||||
|
||||
public function __set($prop, $value) {
|
||||
$prop = Helpers::camelCaseToUnderscore($prop);
|
||||
if ($this->isCommonProperty($prop)) {
|
||||
$this->queue->$prop = $value;
|
||||
$this->task->$prop = $value;
|
||||
} elseif ($this->isQueueProperty($prop)) {
|
||||
$this->queue->$prop = $value;
|
||||
} else {
|
||||
$this->task->$prop = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function __call($name, $args) {
|
||||
$obj = method_exists($this->queue, $name) ? $this->queue : $this->task;
|
||||
$callback = [$obj, $name];
|
||||
if (is_callable($callback)) {
|
||||
return call_user_func_array($callback, $args);
|
||||
}
|
||||
}
|
||||
|
||||
private function isQueueProperty($prop) {
|
||||
return in_array($prop, $this->queueFields);
|
||||
}
|
||||
|
||||
private function isCommonProperty($prop) {
|
||||
return in_array($prop, $this->commonFields);
|
||||
}
|
||||
}
|
@ -82,9 +82,6 @@ class SenderDomainAuthenticationNotices {
|
||||
}
|
||||
|
||||
public function isErrorStyle(): bool {
|
||||
if (!$this->authorizedSenderDomainController->isEnforcementOfNewRestrictionsInEffect()) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
$this->subscribersFeatures->getSubscribersCount() < AuthorizedSenderDomainController::UPPER_LIMIT
|
||||
|| $this->isPartiallyVerified()
|
||||
@ -100,17 +97,6 @@ class SenderDomainAuthenticationNotices {
|
||||
}
|
||||
|
||||
public function getNoticeContentForFreeMailUsers(int $contactCount): string {
|
||||
if (!$this->authorizedSenderDomainController->isEnforcementOfNewRestrictionsInEffect()) {
|
||||
// translators: %1$s is the domain of the user's default from address, %2$s is a rewritten version of their default from address, %3$s is HTML for an 'update sender' button, and %4$s is HTML for a Learn More button
|
||||
return sprintf(__("<strong>Update your sender email address to a branded domain by February 1st, 2024 to continue sending your campaigns.</strong>
|
||||
<span>Starting on February 1st, 2024, MailPoet will no longer be able to send from email addresses on shared 3rd party domains like <strong>%1\$s</strong>. Please change your campaigns to send from an email address on your site's branded domain. Your emails will temporarily be sent from <strong>%2\$s</strong>.</span> <p>%3\$s %4\$s</p>", 'mailpoet'),
|
||||
"@" . $this->getDefaultFromDomain(),
|
||||
$this->authorizedSenderDomainController->getRewrittenEmailAddress($this->getDefaultFromAddress()),
|
||||
$this->getUpdateSenderButton(),
|
||||
$this->getLearnMoreAboutFreeMailButton()
|
||||
);
|
||||
}
|
||||
|
||||
if ($contactCount <= AuthorizedSenderDomainController::UPPER_LIMIT) {
|
||||
// translators: %1$s is the domain of the user's default from address, %2$s is a rewritten version of their default from address, %3$s is HTML for an 'update sender' button, and %4$s is HTML for a Learn More button
|
||||
return sprintf(__("<strong>Update your sender email address to a branded domain to continue sending your campaigns.</strong>
|
||||
@ -133,7 +119,7 @@ class SenderDomainAuthenticationNotices {
|
||||
}
|
||||
|
||||
public function getNoticeContentForBrandedDomainUsers(bool $isPartiallyVerified, int $contactCount): string {
|
||||
if (!$this->authorizedSenderDomainController->isEnforcementOfNewRestrictionsInEffect() || $isPartiallyVerified || $contactCount <= AuthorizedSenderDomainController::LOWER_LIMIT) {
|
||||
if ($isPartiallyVerified || $contactCount <= AuthorizedSenderDomainController::LOWER_LIMIT) {
|
||||
// translators: %1$s is HTML for an 'authenticate domain' button, %2$s is HTML for a Learn More button
|
||||
return sprintf(__("<strong>Authenticate your sender domain to improve email delivery rates.</strong>
|
||||
<span>Major mailbox providers require you to authenticate your sender domain to confirm you sent the emails, and may place unauthenticated emails in the “Spam” folder. Please authenticate your sender domain to ensure your marketing campaigns are compliant and will reach your contacts.</span><p>%1\$s %2\$s</p>", 'mailpoet'),
|
||||
|
@ -50,13 +50,13 @@ class Tracker {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array{revenue: float, campaign_id: string, campaign_type: string, orders_count: int}> $campaignsData
|
||||
* @param array<int, array{revenue: float, campaign_id: string|null, campaign_type: string, orders_count: int}> $campaignsData
|
||||
* @return array<string, string|int|float>
|
||||
*/
|
||||
private function formatCampaignsData(array $campaignsData): array {
|
||||
return array_reduce($campaignsData, function($result, array $campaign): array {
|
||||
$newsletter = $this->newslettersRepository->findOneById((int)$campaign['campaign_id']);
|
||||
$keyPrefix = 'campaign_' . $campaign['campaign_id'];
|
||||
$keyPrefix = 'campaign_' . ($campaign['campaign_id'] ?? 0);
|
||||
$result[$keyPrefix . '_revenue'] = $campaign['revenue'];
|
||||
$result[$keyPrefix . '_orders_count'] = $campaign['orders_count'];
|
||||
$result[$keyPrefix . '_type'] = $campaign['campaign_type'];
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
/*
|
||||
* Plugin Name: MailPoet
|
||||
* Version: 4.42.0
|
||||
* Version: 4.43.1
|
||||
* Plugin URI: https://www.mailpoet.com
|
||||
* Description: Create and send newsletters, post notifications and welcome emails from your WordPress.
|
||||
* Author: MailPoet
|
||||
@ -20,7 +20,7 @@
|
||||
*/
|
||||
|
||||
$mailpoetPlugin = [
|
||||
'version' => '4.42.0',
|
||||
'version' => '4.43.1',
|
||||
'filename' => __FILE__,
|
||||
'path' => dirname(__FILE__),
|
||||
'autoloader' => dirname(__FILE__) . '/vendor/autoload.php',
|
||||
|
@ -17,14 +17,9 @@
|
||||
"prepare": "cd .. && husky install"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{scss,css}": "pnpm run stylelint",
|
||||
"*.{js,jsx,ts,tsx}": "eslint --max-warnings 0",
|
||||
"*.php": [
|
||||
"phplint",
|
||||
"./do qa:code-sniffer",
|
||||
"./do qa:minimal-plugin-standard",
|
||||
"bash -c './do qa:phpstan'"
|
||||
]
|
||||
"*.{scss,css}": "tasks/lint-staged-css.sh",
|
||||
"*.{js,jsx,ts,tsx}": "tasks/lint-staged-js.sh",
|
||||
"*.php": "tasks/lint-staged-php.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.22.11",
|
||||
|
@ -3,7 +3,7 @@ Contributors: mailpoet, woocommerce, automattic
|
||||
Tags: email, email marketing, post notification, woocommerce emails, email automation, newsletter, newsletter builder, newsletter subscribers
|
||||
Requires at least: 6.3
|
||||
Tested up to: 6.4
|
||||
Stable tag: 4.42.0
|
||||
Stable tag: 4.43.1
|
||||
Requires PHP: 7.4
|
||||
License: GPLv3
|
||||
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
@ -228,6 +228,25 @@ Check our [Knowledge Base](https://kb.mailpoet.com) or contact us through our [s
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 4.43.1 - 2024-02-12 =
|
||||
* Fixed: automation and legacy emails not rendering if one has an invalid option;
|
||||
* Fixed: sending tasks incorrectly marked as invalid;
|
||||
* Fixed: tutorial button on email templates and send email pages.
|
||||
|
||||
= 4.43.0 - 2024-02-05 =
|
||||
* Improved: during import, "Update existing subscriber status" no longer requires "Update existing subscriber info" to be enabled;
|
||||
* Fixed: error "EntityManager is Closed" during sending on MySQL 8;
|
||||
* Fixed: error "new entity was found through the relationship" during email sending;
|
||||
* Fixed: Some strings where not translateable;
|
||||
* Fixed: errors when sending some emails;
|
||||
* Fixed: automation listing trash and delete action UI behavior.
|
||||
|
||||
= 4.42.1 - 2024-01-30 =
|
||||
* Improved: Display sender domain authentication notices in Automations;
|
||||
* Improved: Added Domain Authentication to the onboarding tasks;
|
||||
* Fixed: Post notification emails can become stuck;
|
||||
* Fixed: Some legacy automatic emails where not visible in the new automations listing page.
|
||||
|
||||
= 4.42.0 - 2024-01-22 =
|
||||
* Updated: minimum required WooCommerce version to 8.4;
|
||||
* Improved: Made it obvious that list names may be visible to subscribers;
|
||||
|
16
mailpoet/tasks/lint-staged-css.sh
Executable file
16
mailpoet/tasks/lint-staged-css.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
source $PWD/.env
|
||||
|
||||
if [ "$MP_GIT_HOOKS_ENABLE" != "true" ]; then
|
||||
echo "MP_GIT_HOOKS_ENABLE is not set to 'true', skipping lint-staged-css."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$MP_GIT_HOOKS_STYLELINT" = "true" ]; then
|
||||
pnpm run stylelint $@
|
||||
else
|
||||
echo "MP_GIT_HOOKS_STYLELINT is not set to 'true', skipping stylelint."
|
||||
fi
|
16
mailpoet/tasks/lint-staged-js.sh
Executable file
16
mailpoet/tasks/lint-staged-js.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
source $PWD/.env
|
||||
|
||||
if [ "$MP_GIT_HOOKS_ENABLE" != "true" ]; then
|
||||
echo "MP_GIT_HOOKS_ENABLE is not set to 'true', skipping lint-staged-js"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$MP_GIT_HOOKS_ESLINT" = "true" ]; then
|
||||
eslint --max-warnings 0 $@
|
||||
else
|
||||
echo "MP_GIT_HOOKS_ESLINT is not set to 'true', skipping eslint"
|
||||
fi
|
35
mailpoet/tasks/lint-staged-php.sh
Executable file
35
mailpoet/tasks/lint-staged-php.sh
Executable file
@ -0,0 +1,35 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
source $PWD/.env
|
||||
|
||||
if [ "$MP_GIT_HOOKS_ENABLE" != "true" ]; then
|
||||
echo "MP_GIT_HOOKS_ENABLE is not set to 'true'. Skipping lint-staged-php."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$MP_GIT_HOOKS_PHPLINT" = "true" ]; then
|
||||
phplint $@
|
||||
else
|
||||
echo "MP_GIT_HOOKS_PHPLINT not set to 'true', skipping phplint"
|
||||
fi
|
||||
|
||||
if [ "$MP_GIT_HOOKS_CODE_SNIFFER" = "true" ]; then
|
||||
./do qa:code-sniffer $@
|
||||
else
|
||||
echo "MP_GIT_HOOKS_CODE_SNIFFER not set to 'true', skipping code sniffer"
|
||||
fi
|
||||
|
||||
if [ "$MP_GIT_HOOKS_MINIMAL_PLUGIN_STANDARDS" = "true" ]; then
|
||||
./do qa:minimal-plugin-standard $@
|
||||
else
|
||||
echo "MP_GIT_HOOKS_MINIMAL_PLUGIN_STANDARDS not set to 'true', skipping minimal plugin standards"
|
||||
fi
|
||||
|
||||
if [ "$MP_GIT_HOOKS_PHPSTAN" = "true" ]; then
|
||||
bash -c './do qa:phpstan' $@
|
||||
else
|
||||
echo "MP_GIT_HOOKS_PHPSTAN not set to 'true', skipping PHPStan"
|
||||
fi
|
||||
|
@ -580,11 +580,6 @@ parameters:
|
||||
count: 1
|
||||
path: ../../lib/Segments/SegmentsSimpleListRepository.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#"
|
||||
count: 1
|
||||
path: ../../lib/Segments/WP.php
|
||||
|
||||
-
|
||||
message: "#^Cannot cast mixed to int\\.$#"
|
||||
count: 1
|
||||
|
@ -570,11 +570,6 @@ parameters:
|
||||
count: 1
|
||||
path: ../../lib/Segments/SegmentsSimpleListRepository.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#"
|
||||
count: 1
|
||||
path: ../../lib/Segments/WP.php
|
||||
|
||||
-
|
||||
message: "#^Cannot cast mixed to int\\.$#"
|
||||
count: 1
|
||||
|
@ -83,6 +83,19 @@ class IntegrationTester extends \Codeception\Actor {
|
||||
return $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a WP user directly from the database without triggering any hooks.
|
||||
* Needed to be able to test deleting orphaned subscribers.
|
||||
*/
|
||||
public function deleteWPUserFromDatabase(int $id): void {
|
||||
global $wpdb;
|
||||
|
||||
$this->entityManager->getConnection()->executeStatement(
|
||||
"DELETE FROM {$wpdb->users} WHERE id = :id",
|
||||
['id' => $id], ['id' => \PDO::PARAM_INT]
|
||||
);
|
||||
}
|
||||
|
||||
public function createWordPressTerm(string $term, string $taxonomy, array $args = []): int {
|
||||
$term = wp_insert_term($term, $taxonomy, $args);
|
||||
if ($term instanceof WP_Error) {
|
||||
|
@ -9,12 +9,13 @@ use MailPoet\API\JSON\Response as APIResponse;
|
||||
use MailPoet\API\JSON\ResponseBuilders\NewslettersResponseBuilder;
|
||||
use MailPoet\API\JSON\v1\Newsletters;
|
||||
use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
||||
use MailPoet\DI\ContainerWrapper;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\NewsletterOptionFieldEntity;
|
||||
use MailPoet\Entities\NewsletterSegmentEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Logging\LogRepository;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Newsletter\Preview\SendPreviewController;
|
||||
@ -29,9 +30,10 @@ use MailPoet\Router\Router;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Services\AuthorizedEmailsController;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Tasks\Sending as SendingTask;
|
||||
use MailPoet\Test\DataFactories\Newsletter;
|
||||
use MailPoet\Test\DataFactories\NewsletterOption;
|
||||
use MailPoet\Test\DataFactories\ScheduledTask as ScheduledTaskFactory;
|
||||
use MailPoet\Test\DataFactories\SendingQueue as SendingQueueFactory;
|
||||
use MailPoet\Util\License\Features\Subscribers;
|
||||
use MailPoet\WooCommerce\Helper as WCHelper;
|
||||
use MailPoet\WP\Emoji;
|
||||
@ -201,13 +203,7 @@ class NewslettersTest extends \MailPoetTest {
|
||||
verify($response->status)->equals(APIResponse::STATUS_OK);
|
||||
$updatedNewsletter = $this->newsletterRepository->findOneById($this->newsletter->getId());
|
||||
$this->assertInstanceOf(NewsletterEntity::class, $updatedNewsletter); // PHPStan
|
||||
verify($response->data)->equals(
|
||||
$this->newslettersResponseBuilder->build($updatedNewsletter, [
|
||||
NewslettersResponseBuilder::RELATION_SEGMENTS,
|
||||
NewslettersResponseBuilder::RELATION_OPTIONS,
|
||||
NewslettersResponseBuilder::RELATION_QUEUE,
|
||||
])
|
||||
);
|
||||
verify($response->data)->equals($this->newslettersResponseBuilder->build($updatedNewsletter, [NewslettersResponseBuilder::RELATION_SEGMENTS]));
|
||||
verify($updatedNewsletter->getType())->equals('Updated type');
|
||||
verify($updatedNewsletter->getSubject())->equals('Updated subject');
|
||||
verify($updatedNewsletter->getPreheader())->equals('Updated preheader');
|
||||
@ -290,23 +286,32 @@ class NewslettersTest extends \MailPoetTest {
|
||||
|
||||
public function testItReschedulesPastDuePostNotificationsWhenStatusIsSetBackToActive() {
|
||||
$schedule = sprintf('0 %d * * *', Carbon::createFromTimestamp(WPFunctions::get()->currentTime('timestamp'))->hour); // every day at current hour
|
||||
$randomFutureDate = Carbon::createFromTimestamp(WPFunctions::get()->currentTime('timestamp'))->addDays(10)->format('Y-m-d H:i:s'); // 10 days from now
|
||||
$randomFutureDate = Carbon::createFromTimestamp(WPFunctions::get()->currentTime('timestamp'))->addDays(10); // 10 days from now
|
||||
(new NewsletterOption())->create($this->postNotification, NewsletterOptionFieldEntity::NAME_SCHEDULE, $schedule);
|
||||
|
||||
$sendingQueue1 = SendingTask::create();
|
||||
$sendingQueue1->newsletterId = $this->postNotification->getId();
|
||||
$sendingQueue1->scheduledAt = $this->scheduler->getPreviousRunDate($schedule);
|
||||
$sendingQueue1->status = SendingQueueEntity::STATUS_SCHEDULED;
|
||||
$sendingQueue1->save();
|
||||
$sendingQueue2 = SendingTask::create();
|
||||
$sendingQueue2->newsletterId = $this->postNotification->getId();
|
||||
$sendingQueue2->scheduledAt = $randomFutureDate;
|
||||
$sendingQueue2->status = SendingQueueEntity::STATUS_SCHEDULED;
|
||||
$sendingQueue2->save();
|
||||
$sendingQueue3 = SendingTask::create();
|
||||
$sendingQueue3->newsletterId = $this->postNotification->getId();
|
||||
$sendingQueue3->scheduledAt = $this->scheduler->getPreviousRunDate($schedule);
|
||||
$sendingQueue3->save();
|
||||
$scheduledTask1 = (new ScheduledTaskFactory())
|
||||
->create(
|
||||
SendingQueueWorker::TASK_TYPE,
|
||||
ScheduledTaskEntity::STATUS_SCHEDULED,
|
||||
new Carbon($this->scheduler->getPreviousRunDate($schedule))
|
||||
);
|
||||
(new SendingQueueFactory())->create($scheduledTask1, $this->postNotification);
|
||||
|
||||
$scheduledTask2 = (new ScheduledTaskFactory())
|
||||
->create(
|
||||
SendingQueueWorker::TASK_TYPE,
|
||||
ScheduledTaskEntity::STATUS_SCHEDULED,
|
||||
$randomFutureDate
|
||||
);
|
||||
(new SendingQueueFactory())->create($scheduledTask2, $this->postNotification);
|
||||
|
||||
$scheduledTask3 = (new ScheduledTaskFactory())
|
||||
->create(
|
||||
SendingQueueWorker::TASK_TYPE,
|
||||
null,
|
||||
new Carbon($this->scheduler->getPreviousRunDate($schedule))
|
||||
);
|
||||
(new SendingQueueFactory())->create($scheduledTask3, $this->postNotification);
|
||||
|
||||
$this->entityManager->clear();
|
||||
$this->endpoint->setStatus(
|
||||
@ -321,7 +326,7 @@ class NewslettersTest extends \MailPoetTest {
|
||||
verify($tasks[0]->getScheduledAt()->format('Y-m-d H:i:s'))->equals($this->scheduler->getNextRunDate($schedule));
|
||||
// future scheduled notifications are left intact
|
||||
$this->assertInstanceOf(\DateTimeInterface::class, $tasks[1]->getScheduledAt());
|
||||
verify($tasks[1]->getScheduledAt()->format('Y-m-d H:i:s'))->equals($randomFutureDate);
|
||||
verify($tasks[1]->getScheduledAt())->equals($randomFutureDate);
|
||||
// previously unscheduled (e.g., sent/sending) notifications are left intact
|
||||
$this->assertInstanceOf(\DateTimeInterface::class, $tasks[2]->getScheduledAt());
|
||||
verify($tasks[2]->getScheduledAt()->format('Y-m-d H:i:s'))->equals($this->scheduler->getPreviousRunDate($schedule));
|
||||
@ -353,13 +358,7 @@ class NewslettersTest extends \MailPoetTest {
|
||||
verify($response->status)->equals(APIResponse::STATUS_OK);
|
||||
$newsletter = $this->newsletterRepository->findOneById($this->newsletter->getId());
|
||||
$this->assertInstanceOf(NewsletterEntity::class, $newsletter);
|
||||
verify($response->data)->equals(
|
||||
$this->newslettersResponseBuilder->build($newsletter, [
|
||||
NewslettersResponseBuilder::RELATION_SEGMENTS,
|
||||
NewslettersResponseBuilder::RELATION_OPTIONS,
|
||||
NewslettersResponseBuilder::RELATION_QUEUE,
|
||||
])
|
||||
);
|
||||
verify($response->data)->equals($this->newslettersResponseBuilder->build($newsletter));
|
||||
verify($response->data['deleted_at'])->null();
|
||||
verify($response->meta['count'])->equals(1);
|
||||
}
|
||||
@ -369,13 +368,7 @@ class NewslettersTest extends \MailPoetTest {
|
||||
verify($response->status)->equals(APIResponse::STATUS_OK);
|
||||
$newsletter = $this->newsletterRepository->findOneById($this->newsletter->getId());
|
||||
$this->assertInstanceOf(NewsletterEntity::class, $newsletter);
|
||||
verify($response->data)->equals(
|
||||
$this->newslettersResponseBuilder->build($newsletter, [
|
||||
NewslettersResponseBuilder::RELATION_SEGMENTS,
|
||||
NewslettersResponseBuilder::RELATION_OPTIONS,
|
||||
NewslettersResponseBuilder::RELATION_QUEUE,
|
||||
])
|
||||
);
|
||||
verify($response->data)->equals($this->newslettersResponseBuilder->build($newsletter));
|
||||
verify($response->data['deleted_at'])->notNull();
|
||||
verify($response->meta['count'])->equals(1);
|
||||
}
|
||||
@ -400,13 +393,7 @@ class NewslettersTest extends \MailPoetTest {
|
||||
verify($response->status)->equals(APIResponse::STATUS_OK);
|
||||
$newsletterCopy = $this->newsletterRepository->findOneBy(['subject' => 'Copy of My Standard Newsletter']);
|
||||
$this->assertInstanceOf(NewsletterEntity::class, $newsletterCopy);
|
||||
verify($response->data)->equals(
|
||||
$this->newslettersResponseBuilder->build($newsletterCopy, [
|
||||
NewslettersResponseBuilder::RELATION_SEGMENTS,
|
||||
NewslettersResponseBuilder::RELATION_OPTIONS,
|
||||
NewslettersResponseBuilder::RELATION_QUEUE,
|
||||
])
|
||||
);
|
||||
verify($response->data)->equals($this->newslettersResponseBuilder->build($newsletterCopy));
|
||||
verify($response->meta['count'])->equals(1);
|
||||
|
||||
$hookName = 'mailpoet_api_newsletters_duplicate_after';
|
||||
@ -417,13 +404,7 @@ class NewslettersTest extends \MailPoetTest {
|
||||
verify($response->status)->equals(APIResponse::STATUS_OK);
|
||||
$newsletterCopy = $this->newsletterRepository->findOneBy(['subject' => 'Copy of My Post Notification']);
|
||||
$this->assertInstanceOf(NewsletterEntity::class, $newsletterCopy);
|
||||
verify($response->data)->equals(
|
||||
$this->newslettersResponseBuilder->build($newsletterCopy, [
|
||||
NewslettersResponseBuilder::RELATION_SEGMENTS,
|
||||
NewslettersResponseBuilder::RELATION_OPTIONS,
|
||||
NewslettersResponseBuilder::RELATION_QUEUE,
|
||||
])
|
||||
);
|
||||
verify($response->data)->equals($this->newslettersResponseBuilder->build($newsletterCopy));
|
||||
verify($response->meta['count'])->equals(1);
|
||||
}
|
||||
|
||||
|
@ -8,11 +8,16 @@ use MailPoet\API\JSON\v1\SendingQueue as SendingQueueAPI;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Newsletter\NewsletterValidator;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Test\DataFactories\Newsletter;
|
||||
use MailPoet\Test\DataFactories\Newsletter as NewsletterFactory;
|
||||
use MailPoet\Test\DataFactories\NewsletterOption;
|
||||
use MailPoet\Test\DataFactories\ScheduledTask as ScheduledTaskFactory;
|
||||
use MailPoet\Test\DataFactories\Segment as SegmentFactory;
|
||||
use MailPoet\Test\DataFactories\SendingQueue as SendingQueueFactory;
|
||||
use MailPoet\Test\DataFactories\Subscriber as SubscriberFactory;
|
||||
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
|
||||
|
||||
class SendingQueueTest extends \MailPoetTest {
|
||||
@ -26,7 +31,7 @@ class SendingQueueTest extends \MailPoetTest {
|
||||
parent::_before();
|
||||
$this->newsletterOptionsFactory = new NewsletterOption();
|
||||
|
||||
$this->newsletter = (new Newsletter())
|
||||
$this->newsletter = (new NewsletterFactory())
|
||||
->withSubject('My Standard Newsletter')
|
||||
->withDefaultBody()
|
||||
->create();
|
||||
@ -48,16 +53,38 @@ class SendingQueueTest extends \MailPoetTest {
|
||||
];
|
||||
$this->newsletterOptionsFactory->createMultipleOptions($newsletter, $newsletterOptions);
|
||||
|
||||
$sendingQueue = $this->diContainer->get(SendingQueueAPI::class);
|
||||
$result = $sendingQueue->add(['newsletter_id' => $newsletter->getId()]);
|
||||
$repo = $this->diContainer->get(ScheduledTasksRepository::class);
|
||||
$scheduledTask = $repo->findOneById($result->data['task_id']);
|
||||
$sendingQueueApi = $this->diContainer->get(SendingQueueAPI::class);
|
||||
$result = $sendingQueueApi->add(['newsletter_id' => $newsletter->getId()]);
|
||||
$sendingQueue = $newsletter->getLatestQueue();
|
||||
$this->assertInstanceOf(SendingQueueEntity::class, $sendingQueue);
|
||||
$scheduledTask = $sendingQueue->getTask();
|
||||
$this->assertInstanceOf(ScheduledTaskEntity::class, $scheduledTask);
|
||||
verify($scheduledTask->getStatus())->equals(ScheduledTaskEntity::STATUS_SCHEDULED);
|
||||
$scheduled = $scheduledTask->getScheduledAt();
|
||||
$this->assertInstanceOf(\DateTimeInterface::class, $scheduled);
|
||||
verify($scheduled->format('Y-m-d H:i:s'))->equals($newsletterOptions['scheduledAt']);
|
||||
verify($scheduledTask->getType())->equals(SendingQueue::TASK_TYPE);
|
||||
|
||||
$this->assertSame($sendingQueue->getId(), $result->data['id']);
|
||||
$this->assertSame(SendingQueue::TASK_TYPE, $result->data['type']);
|
||||
$this->assertSame(ScheduledTaskEntity::STATUS_SCHEDULED, $result->data['status']);
|
||||
$this->assertSame(5, $result->data['priority']);
|
||||
$this->assertMatchesRegularExpression('/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $result->data['scheduled_at']);
|
||||
$this->assertNull($result->data['processed_at']);
|
||||
$this->assertMatchesRegularExpression('/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $result->data['created_at']);
|
||||
$this->assertMatchesRegularExpression('/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $result->data['updated_at']);
|
||||
$this->assertNull($result->data['deleted_at']);
|
||||
$this->assertNull($result->data['in_progress']);
|
||||
$this->assertSame(0, $result->data['reschedule_count']);
|
||||
$this->assertNull($result->data['meta']);
|
||||
$this->assertSame($scheduledTask->getId(), $result->data['task_id']);
|
||||
$this->assertSame($newsletter->getId(), $result->data['newsletter_id']);
|
||||
$this->assertNull($result->data['newsletter_rendered_body']);
|
||||
$this->assertNull($result->data['newsletter_rendered_subject']);
|
||||
$this->assertSame(0, $result->data['count_total']);
|
||||
$this->assertSame(0, $result->data['count_processed']);
|
||||
$this->assertSame(0, $result->data['count_to_process']);
|
||||
$this->assertSame(200, $result->status);
|
||||
}
|
||||
|
||||
public function testItReturnsErrorIfSubscribersLimitReached() {
|
||||
@ -110,6 +137,67 @@ class SendingQueueTest extends \MailPoetTest {
|
||||
verify($scheduled->format('Y-m-d H:i:s'))->equals('2018-11-11 11:00:00');
|
||||
}
|
||||
|
||||
public function testAddReturnsErrorIfThereAreNoSubscribersAssociatedWithTheNewsletter() {
|
||||
$sendingQueue = $this->diContainer->get(SendingQueueAPI::class);
|
||||
$expectedResult = [
|
||||
'errors' => [
|
||||
[
|
||||
'error' => 'unknown',
|
||||
'message' => 'There are no subscribers in that list!',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$data = $sendingQueue->add(['newsletter_id' => $this->newsletter->getId()])->getData();
|
||||
$this->assertSame($expectedResult, $data);
|
||||
}
|
||||
|
||||
public function testAddChangesNewsletterStatus() {
|
||||
$sendingQueueApi = $this->diContainer->get(SendingQueueAPI::class);
|
||||
|
||||
$segment = (new SegmentFactory())->create();
|
||||
$subscriber = (new SubscriberFactory())->withSegments([$segment])->create();
|
||||
$newsletter = (new NewsletterFactory())
|
||||
->withSegments([$segment])
|
||||
->withSubscriber($subscriber)
|
||||
->create();
|
||||
|
||||
$this->assertSame(NewsletterEntity::STATUS_DRAFT, $newsletter->getStatus());
|
||||
|
||||
$result = $sendingQueueApi->add(['newsletter_id' => $newsletter->getId()]);
|
||||
$sendingQueue = $newsletter->getLatestQueue();
|
||||
$this->assertInstanceOf(SendingQueueEntity::class, $sendingQueue);
|
||||
$scheduledTask = $sendingQueue->getTask();
|
||||
$this->assertInstanceOf(ScheduledTaskEntity::class, $scheduledTask);
|
||||
$this->assertSame(NewsletterEntity::STATUS_SENDING, $newsletter->getStatus());
|
||||
$this->assertSame(1, $sendingQueue->getCountTotal());
|
||||
$this->assertSame(0, $sendingQueue->getCountProcessed());
|
||||
$this->assertSame(1, $sendingQueue->getCountToProcess());
|
||||
$this->assertNull($scheduledTask->getStatus());
|
||||
$this->assertNull($scheduledTask->getScheduledAt());
|
||||
|
||||
$this->assertSame($sendingQueue->getId(), $result->data['id']);
|
||||
$this->assertSame(SendingQueue::TASK_TYPE, $result->data['type']);
|
||||
$this->assertNull($result->data['status']);
|
||||
$this->assertSame(5, $result->data['priority']);
|
||||
$this->assertNull($result->data['scheduled_at']);
|
||||
$this->assertNull($result->data['processed_at']);
|
||||
$this->assertMatchesRegularExpression('/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $result->data['created_at']);
|
||||
$this->assertMatchesRegularExpression('/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $result->data['updated_at']);
|
||||
$this->assertNull($result->data['deleted_at']);
|
||||
$this->assertNull($result->data['in_progress']);
|
||||
$this->assertSame(0, $result->data['reschedule_count']);
|
||||
$this->assertNull($result->data['meta']);
|
||||
$this->assertSame($scheduledTask->getId(), $result->data['task_id']);
|
||||
$this->assertSame($newsletter->getId(), $result->data['newsletter_id']);
|
||||
$this->assertNull($result->data['newsletter_rendered_body']);
|
||||
$this->assertNull($result->data['newsletter_rendered_subject']);
|
||||
$this->assertSame(1, $result->data['count_total']);
|
||||
$this->assertSame(0, $result->data['count_processed']);
|
||||
$this->assertSame(1, $result->data['count_to_process']);
|
||||
$this->assertSame(200, $result->status);
|
||||
}
|
||||
|
||||
public function testItRejectsInvalidNewsletters() {
|
||||
$sendingQueue = $this->getServiceWithOverrides(SendingQueueAPI::class, [
|
||||
'newsletterValidator' => Stub::make(NewsletterValidator::class, ['validate' => 'some error']),
|
||||
@ -120,4 +208,24 @@ class SendingQueueTest extends \MailPoetTest {
|
||||
verify($response['errors'][0]['message'])->stringContainsString('some error');
|
||||
verify($response['errors'][0]['error'])->stringContainsString('bad_request');
|
||||
}
|
||||
|
||||
public function testAddReturnsErrorIfNewsletterIsAlreadyBeingSent() {
|
||||
$scheduledTask = (new ScheduledTaskFactory())->create(SendingQueue::TASK_TYPE, null);
|
||||
(new SendingQueueFactory())->create($scheduledTask, $this->newsletter);
|
||||
|
||||
$expectedResult = [
|
||||
'errors' => [
|
||||
[
|
||||
'error' => 'not_found',
|
||||
'message' => 'This newsletter is already being sent.',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$sendingQueue = $this->diContainer->get(SendingQueueAPI::class);
|
||||
|
||||
$data = $sendingQueue->add(['newsletter_id' => $this->newsletter->getId()])->getData();
|
||||
|
||||
$this->assertSame($expectedResult, $data);
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ use MailPoet\Cron\DaemonHttpRunner;
|
||||
use MailPoet\Cron\Triggers\WordPress;
|
||||
use MailPoet\Cron\Workers\SimpleWorker;
|
||||
use MailPoet\Cron\Workers\WorkersFactory;
|
||||
use MailPoet\Logging\LoggerFactory;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
@ -82,7 +81,10 @@ class DaemonHttpRunnerTest extends \MailPoetTest {
|
||||
}
|
||||
});
|
||||
|
||||
$daemon = new Daemon($this->cronHelper, $cronWorkerRunnerMock, $this->createWorkersFactoryMock(), $this->diContainer->get(LoggerFactory::class));
|
||||
$daemon = $this->getServiceWithOverrides(Daemon::class, [
|
||||
'cronWorkerRunner' => $cronWorkerRunnerMock,
|
||||
'workersFactory' => $this->createWorkersFactoryMock(),
|
||||
]);
|
||||
$daemonHttpRunner = $this->make(DaemonHttpRunner::class, [
|
||||
'pauseExecution' => null,
|
||||
'callSelf' => null,
|
||||
@ -208,7 +210,10 @@ class DaemonHttpRunnerTest extends \MailPoetTest {
|
||||
$cronWorkerRunner = $this->make(CronWorkerRunner::class, [
|
||||
'run' => null,
|
||||
]);
|
||||
$daemon = new Daemon($this->cronHelper, $cronWorkerRunner, $this->createWorkersFactoryMock(), $this->diContainer->get(LoggerFactory::class));
|
||||
$daemon = $this->getServiceWithOverrides(Daemon::class, [
|
||||
'cronWorkerRunner' => $cronWorkerRunner,
|
||||
'workersFactory' => $this->createWorkersFactoryMock(),
|
||||
]);
|
||||
$daemonHttpRunner->__construct($daemon, $this->cronHelper, SettingsController::getInstance(), $this->diContainer->get(WordPress::class));
|
||||
$daemonHttpRunner->run($data);
|
||||
$updatedDaemon = $this->settings->get(CronHelper::DAEMON_SETTING);
|
||||
@ -228,7 +233,10 @@ class DaemonHttpRunnerTest extends \MailPoetTest {
|
||||
throw new \Exception();
|
||||
}
|
||||
});
|
||||
$daemon = new Daemon($this->cronHelper, $cronWorkerRunnerMock, $this->createWorkersFactoryMock(), $this->diContainer->get(LoggerFactory::class));
|
||||
$daemon = $this->getServiceWithOverrides(Daemon::class, [
|
||||
'cronWorkerRunner' => $cronWorkerRunnerMock,
|
||||
'workersFactory' => $this->createWorkersFactoryMock(),
|
||||
]);
|
||||
$daemonHttpRunner = $this->make(DaemonHttpRunner::class, [
|
||||
'pauseExecution' => null,
|
||||
'callSelf' => null,
|
||||
@ -264,7 +272,10 @@ class DaemonHttpRunnerTest extends \MailPoetTest {
|
||||
$cronWorkerRunnerMock = $this->make(CronWorkerRunner::class, [
|
||||
'run' => null,
|
||||
]);
|
||||
$daemon = new Daemon($this->cronHelper, $cronWorkerRunnerMock, $this->createWorkersFactoryMock(), $this->diContainer->get(LoggerFactory::class));
|
||||
$daemon = $this->getServiceWithOverrides(Daemon::class, [
|
||||
'cronWorkerRunner' => $cronWorkerRunnerMock,
|
||||
'workersFactory' => $this->createWorkersFactoryMock(),
|
||||
]);
|
||||
$daemonHttpRunner->__construct($daemon, $this->cronHelper, SettingsController::getInstance(), $this->diContainer->get(WordPress::class));
|
||||
$daemonHttpRunner->run($data);
|
||||
verify(ignore_user_abort())->equals(true);
|
||||
|
@ -9,15 +9,11 @@ use MailPoet\Cron\Daemon;
|
||||
use MailPoet\Cron\Workers\SimpleWorker;
|
||||
use MailPoet\Cron\Workers\WorkersFactory;
|
||||
use MailPoet\Entities\LogEntity;
|
||||
use MailPoet\Logging\LoggerFactory;
|
||||
use MailPoet\Logging\LogRepository;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\WP\Functions as WpFunctions;
|
||||
|
||||
class DaemonTest extends \MailPoetTest {
|
||||
/** @var CronHelper */
|
||||
private $cronHelper;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
@ -30,7 +26,6 @@ class DaemonTest extends \MailPoetTest {
|
||||
public function _before() {
|
||||
parent::_before();
|
||||
$this->settings = SettingsController::getInstance();
|
||||
$this->cronHelper = $this->diContainer->get(CronHelper::class);
|
||||
$this->logRepository = $this->diContainer->get(LogRepository::class);
|
||||
$this->wp = $this->diContainer->get(WpFunctions::class);
|
||||
}
|
||||
@ -43,7 +38,10 @@ class DaemonTest extends \MailPoetTest {
|
||||
'token' => 123,
|
||||
];
|
||||
$this->settings->set(CronHelper::DAEMON_SETTING, $data);
|
||||
$daemon = new Daemon($this->cronHelper, $cronWorkerRunner, $this->createWorkersFactoryMock(), $this->diContainer->get(LoggerFactory::class));
|
||||
$daemon = $this->getServiceWithOverrides(Daemon::class, [
|
||||
'cronWorkerRunner' => $cronWorkerRunner,
|
||||
'workersFactory' => $this->createWorkersFactoryMock(),
|
||||
]);
|
||||
$daemon->run($data);
|
||||
}
|
||||
|
||||
@ -57,7 +55,10 @@ class DaemonTest extends \MailPoetTest {
|
||||
'token' => 123,
|
||||
];
|
||||
$this->settings->set(CronHelper::DAEMON_SETTING, $data);
|
||||
$daemon = new Daemon($this->cronHelper, $cronWorkerRunner, $this->createWorkersFactoryMock(), $this->diContainer->get(LoggerFactory::class));
|
||||
$daemon = $this->getServiceWithOverrides(Daemon::class, [
|
||||
'cronWorkerRunner' => $cronWorkerRunner,
|
||||
'workersFactory' => $this->createWorkersFactoryMock(),
|
||||
]);
|
||||
$daemon->run($data);
|
||||
$log = $this->logRepository->findOneBy(['name' => 'cron', 'level' => 400]);
|
||||
$this->assertInstanceOf(LogEntity::class, $log);
|
||||
@ -82,7 +83,10 @@ class DaemonTest extends \MailPoetTest {
|
||||
'createScheduleWorker' => function () {throw new \Exception('createScheduleWorker should not be called');
|
||||
},
|
||||
]);
|
||||
$daemon = new Daemon($this->cronHelper, $cronWorkerRunner, $factoryMock, $this->diContainer->get(LoggerFactory::class));
|
||||
$daemon = $this->getServiceWithOverrides(Daemon::class, [
|
||||
'cronWorkerRunner' => $cronWorkerRunner,
|
||||
'workersFactory' => $factoryMock,
|
||||
]);
|
||||
$daemon->run($data);
|
||||
$log = $this->logRepository->findOneBy(['name' => 'cron', 'level' => 400]);
|
||||
verify($log)->null();
|
||||
|
@ -1395,6 +1395,30 @@ class SendingQueueTest extends \MailPoetTest {
|
||||
$this->assertSame(false, $this->scheduledTask->getInProgress());
|
||||
}
|
||||
|
||||
public function testItCanProcessPostNotificationHistoryWithoutPosts() {
|
||||
// Cleanup data from self::before
|
||||
$this->truncateEntity(NewsletterEntity::class);
|
||||
$this->truncateEntity(SendingQueueEntity::class);
|
||||
$this->truncateEntity(ScheduledTaskEntity::class);
|
||||
$this->entityManager->clear();
|
||||
|
||||
// Prepare post notification history for sending
|
||||
// The body added via createNewsletter doesn't contain ALC block so there will be no posts inserted and the newsletter will be deleted
|
||||
$parentNewsletter = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION, 'Post Notification', NewsletterEntity::STATUS_ACTIVE);
|
||||
$newsletter = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION_HISTORY, 'To Delete', NewsletterEntity::STATUS_SENDING);
|
||||
$newsletter->setParent($parentNewsletter);
|
||||
$queue = $this->createQueueWithTask($newsletter);
|
||||
$sendingQueueWorker = $this->getSendingQueueWorker();
|
||||
$sendingQueueWorker->process();
|
||||
|
||||
$task = $queue->getTask();
|
||||
$this->assertInstanceOf(ScheduledTaskEntity::class, $task);
|
||||
// Re-fetch newsletter, task and queue from DB and verify they were deleted
|
||||
verify($this->entityManager->find(ScheduledTaskEntity::class, $task->getId()))->null();
|
||||
verify($this->entityManager->find(SendingQueueEntity::class, $queue->getId()))->null();
|
||||
verify($this->entityManager->find(NewsletterEntity::class, $newsletter->getId()))->null();
|
||||
}
|
||||
|
||||
private function createNewsletter(string $type, $subject, string $status = NewsletterEntity::STATUS_DRAFT): NewsletterEntity {
|
||||
$newsletter = new NewsletterEntity();
|
||||
$newsletter->setType($type);
|
||||
|
@ -26,6 +26,7 @@ use MailPoet\Newsletter\Renderer\Blocks\Coupon;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
|
||||
use MailPoet\Newsletter\Sending\SendingQueuesRepository;
|
||||
use MailPoet\Router\Router;
|
||||
use MailPoet\RuntimeException;
|
||||
use MailPoet\Test\DataFactories\DynamicSegment;
|
||||
use MailPoet\Test\DataFactories\Newsletter as NewsletterFactory;
|
||||
use MailPoet\Test\DataFactories\ScheduledTask as ScheduledTaskFactory;
|
||||
@ -308,7 +309,9 @@ class NewsletterTest extends \MailPoetTest {
|
||||
public function testItDoesNotRenderSubscriberShortcodeInSubjectWhenPreprocessingNewsletter() {
|
||||
$this->newsletter->setSubject('Newsletter for [subscriber:firstname] [date:dordinal]');
|
||||
$this->newslettersRepository->persist($this->newsletter);
|
||||
$this->newsletter = $this->newsletterTask->preProcessNewsletter($this->newsletter, $this->scheduledTaskEntity);
|
||||
$newsletter = $this->newsletterTask->preProcessNewsletter($this->newsletter, $this->scheduledTaskEntity);
|
||||
$this->assertInstanceOf(NewsletterEntity::class, $newsletter);
|
||||
$this->newsletter = $newsletter;
|
||||
|
||||
$sendingQueue = $this->sendingQueuesRepository->findOneBy(['newsletter' => $this->newsletter]);
|
||||
$this->assertInstanceOf(SendingQueueEntity::class, $sendingQueue);
|
||||
@ -320,7 +323,9 @@ class NewsletterTest extends \MailPoetTest {
|
||||
public function testItUsesADefaultSubjectIfRenderedSubjectIsEmptyWhenPreprocessingNewsletter() {
|
||||
$this->newsletter->setSubject(' [custom_shortcode:should_render_empty] ');
|
||||
$this->newslettersRepository->persist($this->newsletter);
|
||||
$this->newsletter = $this->newsletterTask->preProcessNewsletter($this->newsletter, $this->scheduledTaskEntity);
|
||||
$newsletter = $this->newsletterTask->preProcessNewsletter($this->newsletter, $this->scheduledTaskEntity);
|
||||
$this->assertInstanceOf(NewsletterEntity::class, $newsletter);
|
||||
$this->newsletter = $newsletter;
|
||||
|
||||
$sendingQueue = $this->sendingQueuesRepository->findOneBy(['newsletter' => $this->newsletter]);
|
||||
$this->assertInstanceOf(SendingQueueEntity::class, $sendingQueue);
|
||||
@ -357,8 +362,7 @@ class NewsletterTest extends \MailPoetTest {
|
||||
}
|
||||
|
||||
public function testItRendersShortcodesAndReplacesSubscriberDataInLinks() {
|
||||
$newsletter = $this->newsletterTask->preProcessNewsletter($this->newsletter, $this->scheduledTaskEntity);
|
||||
$newsletterEntity = $this->newslettersRepository->findOneById($newsletter->getId());
|
||||
$newsletterEntity = $this->newsletterTask->preProcessNewsletter($this->newsletter, $this->scheduledTaskEntity);
|
||||
$this->assertInstanceOf(NewsletterEntity::class, $newsletterEntity);
|
||||
$result = $this->newsletterTask->prepareNewsletterForSending(
|
||||
$newsletterEntity,
|
||||
@ -375,8 +379,7 @@ class NewsletterTest extends \MailPoetTest {
|
||||
public function testItDoesNotReplaceSubscriberDataInLinksWhenTrackingIsNotEnabled() {
|
||||
$newsletterTask = $this->newsletterTask;
|
||||
$newsletterTask->trackingEnabled = false;
|
||||
$newsletter = $newsletterTask->preProcessNewsletter($this->newsletter, $this->scheduledTaskEntity);
|
||||
$newsletterEntity = $this->newslettersRepository->findOneById($newsletter->getId());
|
||||
$newsletterEntity = $newsletterTask->preProcessNewsletter($this->newsletter, $this->scheduledTaskEntity);
|
||||
$this->assertInstanceOf(NewsletterEntity::class, $newsletterEntity);
|
||||
$result = $newsletterTask->prepareNewsletterForSending(
|
||||
$newsletterEntity,
|
||||
@ -455,8 +458,7 @@ class NewsletterTest extends \MailPoetTest {
|
||||
$this->sendingQueueEntity->setNewsletter($newsletter);
|
||||
$this->sendingQueuesRepository->persist($this->sendingQueueEntity);
|
||||
|
||||
$newsletter = $newsletterTask->preProcessNewsletter($newsletter, $this->scheduledTaskEntity);
|
||||
$newsletterEntity = $this->newslettersRepository->findOneById($newsletter->getId());
|
||||
$newsletterEntity = $newsletterTask->preProcessNewsletter($newsletter, $this->scheduledTaskEntity);
|
||||
$this->assertInstanceOf(NewsletterEntity::class, $newsletterEntity);
|
||||
$result = $newsletterTask->prepareNewsletterForSending(
|
||||
$newsletterEntity,
|
||||
@ -618,4 +620,18 @@ class NewsletterTest extends \MailPoetTest {
|
||||
$this->assertNull($this->scheduledTasksRepository->findOneById($scheduledTaskId));
|
||||
$this->assertNull($this->sendingQueuesRepository->findOneById($sendingQueueId));
|
||||
}
|
||||
|
||||
public function testItThrowsExceptionWhenTaskHasNoQueue(): void {
|
||||
$scheduledTask = new ScheduledTaskEntity();
|
||||
$this->entityManager->persist($scheduledTask);
|
||||
$newsletter = (new NewsletterFactory())
|
||||
->withType(NewsletterEntity::TYPE_STANDARD)
|
||||
->withStatus(NewsletterEntity::STATUS_SENDING)
|
||||
->withSubject(Fixtures::get('newsletter_subject_template'))
|
||||
->withBody(json_decode(Fixtures::get('newsletter_body_template'), true))
|
||||
->create();
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('Can‘t pre-process newsletter without queue.');
|
||||
$this->newsletterTask->preProcessNewsletter($newsletter, $scheduledTask);
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,80 @@ class RepositoryTest extends \MailPoetTest {
|
||||
$this->assertSame($this->getEntityFromIdentityMap($setting3->getId()), $setting3);
|
||||
}
|
||||
|
||||
public function testItCanRefreshAll(): void {
|
||||
$repository = $this->createRepository();
|
||||
|
||||
$setting1 = $this->createSetting('name-1', 'value-1');
|
||||
$setting2 = $this->createSetting('name-2', 'value-2');
|
||||
$setting3 = $this->createSetting('name-3', 'value-3');
|
||||
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->update(SettingEntity::class, 's')
|
||||
->set('s.value', ':value')
|
||||
->where('s.name = :name')
|
||||
->setParameter('value', 'new-value-1')
|
||||
->setParameter('name', 'name-1')
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->update(SettingEntity::class, 's')
|
||||
->set('s.value', ':value')
|
||||
->where('s.name = :name')
|
||||
->setParameter('value', 'new-value-2')
|
||||
->setParameter('name', 'name-2')
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
$this->assertSame($setting1->getValue(), 'value-1');
|
||||
$this->assertSame($setting2->getValue(), 'value-2');
|
||||
$this->assertSame($setting3->getValue(), 'value-3');
|
||||
|
||||
$repository->refreshAll();
|
||||
|
||||
$this->assertSame($setting1->getValue(), 'new-value-1');
|
||||
$this->assertSame($setting2->getValue(), 'new-value-2');
|
||||
$this->assertSame($setting3->getValue(), 'value-3');
|
||||
}
|
||||
|
||||
public function testItCanRefreshSelectively(): void {
|
||||
$repository = $this->createRepository();
|
||||
|
||||
$setting1 = $this->createSetting('name-1', 'value-1');
|
||||
$setting2 = $this->createSetting('name-2', 'value-2');
|
||||
$setting3 = $this->createSetting('name-3', 'value-3');
|
||||
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->update(SettingEntity::class, 's')
|
||||
->set('s.value', ':value')
|
||||
->where('s.name = :name')
|
||||
->setParameter('value', 'new-value-1')
|
||||
->setParameter('name', 'name-1')
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->update(SettingEntity::class, 's')
|
||||
->set('s.value', ':value')
|
||||
->where('s.name = :name')
|
||||
->setParameter('value', 'new-value-2')
|
||||
->setParameter('name', 'name-2')
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
$this->assertSame($setting1->getValue(), 'value-1');
|
||||
$this->assertSame($setting2->getValue(), 'value-2');
|
||||
$this->assertSame($setting3->getValue(), 'value-3');
|
||||
|
||||
$repository->refreshAll(function (SettingEntity $setting) {
|
||||
return in_array($setting->getName(), ['name-1', 'name-3'], true);
|
||||
});
|
||||
|
||||
$this->assertSame($setting1->getValue(), 'new-value-1');
|
||||
$this->assertSame($setting2->getValue(), 'value-2');
|
||||
$this->assertSame($setting3->getValue(), 'value-3');
|
||||
}
|
||||
|
||||
private function createSetting(string $name, string $value): SettingEntity {
|
||||
$setting = new SettingEntity();
|
||||
$setting->setName($name);
|
||||
|
@ -79,6 +79,27 @@ class MailerTest extends \MailPoetTest {
|
||||
'email' => 'test@email.com',
|
||||
])
|
||||
)->equals('First Last <test@email.com>');
|
||||
|
||||
$subscriber = (new SubscriberFactory())
|
||||
->withFirstName('First')
|
||||
->withLastName('Last')
|
||||
->withEmail('test1@email.com')
|
||||
->create();
|
||||
verify($mailer->formatSubscriberNameAndEmailAddress($subscriber))
|
||||
->equals('First Last <test1@email.com>');
|
||||
|
||||
$subscriber = (new SubscriberFactory())
|
||||
->withEmail('test2@email.com')
|
||||
->create();
|
||||
verify($mailer->formatSubscriberNameAndEmailAddress($subscriber))
|
||||
->equals('test2@email.com');
|
||||
|
||||
$subscriber = (new SubscriberFactory())
|
||||
->withLastName('Last')
|
||||
->withEmail('test3@email.com')
|
||||
->create();
|
||||
verify($mailer->formatSubscriberNameAndEmailAddress($subscriber))
|
||||
->equals('Last <test3@email.com>');
|
||||
}
|
||||
|
||||
public function testItCanSend() {
|
||||
|
@ -0,0 +1,61 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Test\DataFactories\Newsletter as NewsletterFactory;
|
||||
|
||||
//phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps
|
||||
class Migration_20240202_130053_App_Test extends \MailPoetTest {
|
||||
/** @var Migration_20240202_130053_App */
|
||||
private $migration;
|
||||
|
||||
public function _before() {
|
||||
parent::_before();
|
||||
$this->migration = new Migration_20240202_130053_App($this->diContainer);
|
||||
}
|
||||
|
||||
public function testItMigratesIncorrectlyMarkedSentNewslettersAsSent() {
|
||||
$incorrectStandardNewsletter = (new NewsletterFactory())
|
||||
->withStatus(NewsletterEntity::STATUS_SENDING)
|
||||
->withSendingQueue(['status' => ScheduledTaskEntity::STATUS_COMPLETED])
|
||||
->create();
|
||||
$incorrectPostNotification = (new NewsletterFactory())
|
||||
->withStatus(NewsletterEntity::STATUS_SENDING)
|
||||
->withType(NewsletterEntity::TYPE_NOTIFICATION_HISTORY)
|
||||
->withSendingQueue(['status' => ScheduledTaskEntity::STATUS_COMPLETED])
|
||||
->create();
|
||||
$correctlySendingNewsletter = (new NewsletterFactory())
|
||||
->withStatus(NewsletterEntity::STATUS_SENDING)
|
||||
->withSendingQueue(['status' => null]) // Running task
|
||||
->create();
|
||||
$correctlySentNewsletter = (new NewsletterFactory())
|
||||
->withStatus(NewsletterEntity::STATUS_SENT)
|
||||
->withSendingQueue(['status' => ScheduledTaskEntity::STATUS_COMPLETED])
|
||||
->create();
|
||||
$draftNewsletter = (new NewsletterFactory())
|
||||
->withStatus(NewsletterEntity::STATUS_DRAFT)
|
||||
->create();
|
||||
$welcomeEmailNewsletter = (new NewsletterFactory())
|
||||
->withStatus(NewsletterEntity::STATUS_ACTIVE)
|
||||
->withSendingQueue(['status' => ScheduledTaskEntity::STATUS_COMPLETED])
|
||||
->create();
|
||||
|
||||
$this->migration->run();
|
||||
|
||||
$this->entityManager->refresh($incorrectStandardNewsletter);
|
||||
$this->entityManager->refresh($incorrectPostNotification);
|
||||
$this->entityManager->refresh($correctlySendingNewsletter);
|
||||
$this->entityManager->refresh($correctlySentNewsletter);
|
||||
$this->entityManager->refresh($draftNewsletter);
|
||||
$this->entityManager->refresh($welcomeEmailNewsletter);
|
||||
|
||||
verify($incorrectStandardNewsletter->getStatus())->equals(NewsletterEntity::STATUS_SENT);
|
||||
verify($incorrectPostNotification->getStatus())->equals(NewsletterEntity::STATUS_SENT);
|
||||
verify($correctlySendingNewsletter->getStatus())->equals(NewsletterEntity::STATUS_SENDING);
|
||||
verify($correctlySentNewsletter->getStatus())->equals(NewsletterEntity::STATUS_SENT);
|
||||
verify($draftNewsletter->getStatus())->equals(NewsletterEntity::STATUS_DRAFT);
|
||||
verify($welcomeEmailNewsletter->getStatus())->equals(NewsletterEntity::STATUS_ACTIVE);
|
||||
}
|
||||
}
|
@ -0,0 +1,327 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace integration\Migrations\App;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Entities\ScheduledTaskSubscriberEntity;
|
||||
use MailPoet\Migrations\App\Migration_20240207_105912_App;
|
||||
use MailPoet\Statistics\StatisticsNewslettersRepository;
|
||||
use MailPoet\Test\DataFactories\Newsletter as NewsletterFactory;
|
||||
use MailPoet\Test\DataFactories\ScheduledTask;
|
||||
use MailPoet\Test\DataFactories\ScheduledTaskSubscriber;
|
||||
use MailPoet\Test\DataFactories\SendingQueue;
|
||||
use MailPoet\Test\DataFactories\Subscriber;
|
||||
|
||||
// phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps
|
||||
class Migration_20240207_105912_App_Test extends \MailPoetTest {
|
||||
/** @var Migration_20240207_105912_App */
|
||||
private $migration;
|
||||
|
||||
public function _before() {
|
||||
parent::_before();
|
||||
$this->migration = new Migration_20240207_105912_App($this->diContainer);
|
||||
}
|
||||
|
||||
public function testItPausesInvalidTasksWithUnprocessedSubscribers(): void {
|
||||
$newsletter = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION_HISTORY, NewsletterEntity::STATUS_SENDING);
|
||||
$task = $this->createTask($newsletter, [
|
||||
'status' => ScheduledTaskEntity::STATUS_INVALID,
|
||||
'processedSubscribers' => 3,
|
||||
'unprocessedSubscribers' => 1,
|
||||
'failedSubscribers' => 1,
|
||||
]);
|
||||
|
||||
$this->migration->run();
|
||||
|
||||
$this->refreshAll([$newsletter, $task]);
|
||||
$this->assertSame(NewsletterEntity::STATUS_SENDING, $newsletter->getStatus());
|
||||
$this->assertSame(ScheduledTaskEntity::STATUS_PAUSED, $task->getStatus());
|
||||
}
|
||||
|
||||
public function testItCompletesInvalidTasksWithAllProcessedSubscribers(): void {
|
||||
$newsletter = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION_HISTORY, NewsletterEntity::STATUS_SENDING);
|
||||
$task = $this->createTask($newsletter, [
|
||||
'status' => ScheduledTaskEntity::STATUS_INVALID,
|
||||
'processedSubscribers' => 3,
|
||||
'unprocessedSubscribers' => 0,
|
||||
'failedSubscribers' => 1,
|
||||
]);
|
||||
|
||||
$this->migration->run();
|
||||
|
||||
$this->refreshAll([$newsletter, $task]);
|
||||
$this->assertSame(NewsletterEntity::STATUS_SENT, $newsletter->getStatus());
|
||||
$this->assertEquals($this->getMaxUpdatedDateFromTaskSubscribers($task), $newsletter->getSentAt());
|
||||
$this->assertSame(ScheduledTaskEntity::STATUS_COMPLETED, $task->getStatus());
|
||||
}
|
||||
|
||||
public function testItIgnoresNonSendingNewsletters(): void {
|
||||
$newsletter1 = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION_HISTORY, NewsletterEntity::STATUS_SCHEDULED);
|
||||
$task1 = $this->createTask($newsletter1, [
|
||||
'status' => ScheduledTaskEntity::STATUS_INVALID,
|
||||
'processedSubscribers' => 3,
|
||||
'unprocessedSubscribers' => 1,
|
||||
'failedSubscribers' => 1,
|
||||
]);
|
||||
|
||||
$newsletter2 = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION_HISTORY, NewsletterEntity::STATUS_ACTIVE);
|
||||
$task2 = $this->createTask($newsletter1, [
|
||||
'status' => ScheduledTaskEntity::STATUS_INVALID,
|
||||
'processedSubscribers' => 3,
|
||||
'unprocessedSubscribers' => 1,
|
||||
'failedSubscribers' => 1,
|
||||
]);
|
||||
|
||||
$this->migration->run();
|
||||
|
||||
$this->refreshAll([$newsletter1, $task1, $newsletter2, $task2]);
|
||||
$this->assertSame(NewsletterEntity::STATUS_SCHEDULED, $newsletter1->getStatus());
|
||||
$this->assertNull($newsletter1->getSentAt());
|
||||
$this->assertSame(ScheduledTaskEntity::STATUS_INVALID, $task1->getStatus());
|
||||
$this->assertSame(NewsletterEntity::STATUS_ACTIVE, $newsletter2->getStatus());
|
||||
$this->assertNull($newsletter2->getSentAt());
|
||||
$this->assertSame(ScheduledTaskEntity::STATUS_INVALID, $task2->getStatus());
|
||||
}
|
||||
|
||||
public function testItIgnoresNonCampaignNewsletters(): void {
|
||||
$newsletter = $this->createNewsletter(NewsletterEntity::TYPE_WELCOME, NewsletterEntity::STATUS_SENDING);
|
||||
$task = $this->createTask($newsletter, [
|
||||
'status' => ScheduledTaskEntity::STATUS_INVALID,
|
||||
'processedSubscribers' => 3,
|
||||
'unprocessedSubscribers' => 1,
|
||||
'failedSubscribers' => 1,
|
||||
]);
|
||||
|
||||
$this->migration->run();
|
||||
|
||||
$this->refreshAll([$newsletter, $task]);
|
||||
$this->assertSame(NewsletterEntity::STATUS_SENDING, $newsletter->getStatus());
|
||||
$this->assertNull($newsletter->getSentAt());
|
||||
$this->assertSame(ScheduledTaskEntity::STATUS_INVALID, $task->getStatus());
|
||||
}
|
||||
|
||||
public function testItIgnoresDeletedTasks(): void {
|
||||
$newsletter = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION_HISTORY, NewsletterEntity::STATUS_SENDING);
|
||||
$task = $this->createTask($newsletter, [
|
||||
'status' => ScheduledTaskEntity::STATUS_INVALID,
|
||||
'processedSubscribers' => 3,
|
||||
'unprocessedSubscribers' => 1,
|
||||
'failedSubscribers' => 1,
|
||||
'isDeleted' => true,
|
||||
]);
|
||||
|
||||
$this->migration->run();
|
||||
|
||||
$this->refreshAll([$newsletter, $task]);
|
||||
$this->assertSame(NewsletterEntity::STATUS_SENDING, $newsletter->getStatus());
|
||||
$this->assertNull($newsletter->getSentAt());
|
||||
$this->assertSame(ScheduledTaskEntity::STATUS_INVALID, $task->getStatus());
|
||||
$this->assertNotNull($task->getDeletedAt());
|
||||
}
|
||||
|
||||
public function testItIgnoresDeletedNewsletters(): void {
|
||||
$newsletter = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION_HISTORY, NewsletterEntity::STATUS_SENDING, true);
|
||||
$task = $this->createTask($newsletter, [
|
||||
'status' => ScheduledTaskEntity::STATUS_INVALID,
|
||||
'processedSubscribers' => 3,
|
||||
'unprocessedSubscribers' => 1,
|
||||
'failedSubscribers' => 1,
|
||||
]);
|
||||
|
||||
$this->migration->run();
|
||||
|
||||
$this->refreshAll([$newsletter, $task]);
|
||||
$this->assertSame(NewsletterEntity::STATUS_SENDING, $newsletter->getStatus());
|
||||
$this->assertNull($newsletter->getSentAt());
|
||||
$this->assertSame(ScheduledTaskEntity::STATUS_INVALID, $task->getStatus());
|
||||
}
|
||||
|
||||
public function testMultipleNewslettersAtOnce(): void {
|
||||
// invalid task with unprocessed subscribers
|
||||
$newsletter1 = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION_HISTORY, NewsletterEntity::STATUS_SENDING);
|
||||
$task1 = $this->createTask($newsletter1, [
|
||||
'status' => ScheduledTaskEntity::STATUS_INVALID,
|
||||
'processedSubscribers' => 3,
|
||||
'unprocessedSubscribers' => 1,
|
||||
'failedSubscribers' => 1,
|
||||
]);
|
||||
|
||||
// invalid task with all subscribers processed
|
||||
$newsletter2 = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION_HISTORY, NewsletterEntity::STATUS_SENDING);
|
||||
$task2 = $this->createTask($newsletter2, [
|
||||
'status' => ScheduledTaskEntity::STATUS_INVALID,
|
||||
'processedSubscribers' => 3,
|
||||
'unprocessedSubscribers' => 0,
|
||||
'failedSubscribers' => 1,
|
||||
]);
|
||||
|
||||
// invalid task with non-sending newsletter
|
||||
$newsletter3 = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION_HISTORY, NewsletterEntity::STATUS_SCHEDULED);
|
||||
$task3 = $this->createTask($newsletter3, [
|
||||
'status' => ScheduledTaskEntity::STATUS_INVALID,
|
||||
'processedSubscribers' => 3,
|
||||
'unprocessedSubscribers' => 1,
|
||||
'failedSubscribers' => 1,
|
||||
]);
|
||||
|
||||
// invalid task with non-campaign newsletter
|
||||
$newsletter4 = $this->createNewsletter(NewsletterEntity::TYPE_WELCOME, NewsletterEntity::STATUS_SENDING);
|
||||
$task4 = $this->createTask($newsletter4, [
|
||||
'status' => ScheduledTaskEntity::STATUS_INVALID,
|
||||
'processedSubscribers' => 3,
|
||||
'unprocessedSubscribers' => 1,
|
||||
'failedSubscribers' => 1,
|
||||
]);
|
||||
|
||||
$this->migration->run();
|
||||
|
||||
$this->refreshAll([$newsletter1, $task1, $newsletter2, $task2, $newsletter3, $task3, $newsletter4, $task4]);
|
||||
$this->assertSame(NewsletterEntity::STATUS_SENDING, $newsletter1->getStatus());
|
||||
$this->assertSame(ScheduledTaskEntity::STATUS_PAUSED, $task1->getStatus());
|
||||
$this->assertNull($newsletter1->getSentAt());
|
||||
$this->assertSame(NewsletterEntity::STATUS_SENT, $newsletter2->getStatus());
|
||||
$this->assertSame(ScheduledTaskEntity::STATUS_COMPLETED, $task2->getStatus());
|
||||
$this->assertEquals($this->getMaxUpdatedDateFromTaskSubscribers($task2), $newsletter2->getSentAt());
|
||||
$this->assertSame(NewsletterEntity::STATUS_SCHEDULED, $newsletter3->getStatus());
|
||||
$this->assertSame(ScheduledTaskEntity::STATUS_INVALID, $task3->getStatus());
|
||||
$this->assertNull($newsletter3->getSentAt());
|
||||
$this->assertSame(NewsletterEntity::STATUS_SENDING, $newsletter4->getStatus());
|
||||
$this->assertSame(ScheduledTaskEntity::STATUS_INVALID, $task4->getStatus());
|
||||
$this->assertNull($newsletter4->getSentAt());
|
||||
}
|
||||
|
||||
public function testItUpdatesCountsWhenCompletingInvalidTasks(): void {
|
||||
$newsletter = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION_HISTORY, NewsletterEntity::STATUS_SENDING);
|
||||
$task = $this->createTask($newsletter, [
|
||||
'status' => ScheduledTaskEntity::STATUS_INVALID,
|
||||
'processedSubscribers' => 3,
|
||||
'unprocessedSubscribers' => 0,
|
||||
'failedSubscribers' => 1,
|
||||
]);
|
||||
|
||||
$queue = $task->getSendingQueue();
|
||||
$this->assertNotNull($queue);
|
||||
|
||||
// incorrect processed/to-process counts
|
||||
$queue->setCountProcessed(0);
|
||||
$queue->setCountToProcess(4);
|
||||
$queue->setCountTotal(4);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->migration->run();
|
||||
|
||||
$this->refreshAll([$newsletter, $task, $queue]);
|
||||
$this->assertSame(NewsletterEntity::STATUS_SENT, $newsletter->getStatus());
|
||||
$this->assertEquals($this->getMaxUpdatedDateFromTaskSubscribers($task), $newsletter->getSentAt());
|
||||
$this->assertSame(ScheduledTaskEntity::STATUS_COMPLETED, $task->getStatus());
|
||||
|
||||
// counts should be correct now
|
||||
$this->assertSame(4, $queue->getCountProcessed());
|
||||
$this->assertSame(0, $queue->getCountToProcess());
|
||||
$this->assertSame(4, $queue->getCountTotal());
|
||||
}
|
||||
|
||||
public function testItBackfillsMissingNewsletterStatistics(): void {
|
||||
$newsletter = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION_HISTORY, NewsletterEntity::STATUS_SENT);
|
||||
$task = $this->createTask($newsletter, [
|
||||
'status' => ScheduledTaskEntity::STATUS_COMPLETED,
|
||||
'processedSubscribers' => 3,
|
||||
'unprocessedSubscribers' => 2,
|
||||
'failedSubscribers' => 1,
|
||||
]);
|
||||
|
||||
$repository = $this->diContainer->get(StatisticsNewslettersRepository::class);
|
||||
$this->assertCount(0, $repository->findAll());
|
||||
$this->assertNull($newsletter->getSentAt());
|
||||
|
||||
$this->migration->run();
|
||||
|
||||
$this->refreshAll([$newsletter, $task]);
|
||||
$this->assertSame(NewsletterEntity::STATUS_SENT, $newsletter->getStatus());
|
||||
$this->assertEquals($this->getMaxUpdatedDateFromTaskSubscribers($task), $newsletter->getSentAt());
|
||||
$this->assertSame(ScheduledTaskEntity::STATUS_COMPLETED, $task->getStatus());
|
||||
|
||||
$stats = $repository->findAll();
|
||||
$processedSubscribers = $task->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_PROCESSED);
|
||||
$this->assertCount(4, $stats); // 3 ok + 1 failed
|
||||
$this->assertCount(4, $processedSubscribers); // 3 ok + 1 failed
|
||||
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$this->assertSame($newsletter, $stats[$i]->getNewsletter());
|
||||
$this->assertSame($task->getSendingQueue(), $stats[$i]->getQueue());
|
||||
|
||||
$subscriberIndex = array_search($stats[$i]->getSubscriber(), $processedSubscribers, true);
|
||||
$subscriber = $processedSubscribers[$subscriberIndex] ?? null;
|
||||
$this->assertNotNull($subscriber);
|
||||
$this->assertSame($subscriber, $stats[$i]->getSubscriber());
|
||||
$this->assertEquals($subscriber->getUpdatedAt(), $stats[$i]->getSentAt());
|
||||
}
|
||||
}
|
||||
|
||||
private function createNewsletter(string $newsletterType, string $newsletterStatus, bool $isDeleted = false): NewsletterEntity {
|
||||
$newsletterFactory = (new NewsletterFactory())
|
||||
->withType($newsletterType)
|
||||
->withStatus($newsletterStatus);
|
||||
|
||||
if ($isDeleted) {
|
||||
$newsletterFactory->withDeleted();
|
||||
}
|
||||
return $newsletterFactory->create();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param NewsletterEntity $newsletter
|
||||
* @param array{
|
||||
* status?: string,
|
||||
* processedSubscribers?: int,
|
||||
* unprocessedSubscribers?: int,
|
||||
* failedSubscribers?: int,
|
||||
* } $params
|
||||
*/
|
||||
private function createTask(NewsletterEntity $newsletter, array $params = []): ScheduledTaskEntity {
|
||||
$taskStatus = $params['status'] ?? ScheduledTaskEntity::STATUS_INVALID;
|
||||
$processedSubscribers = $params['processedSubscribers'] ?? 0;
|
||||
$unprocessedSubscribers = $params['unprocessedSubscribers'] ?? 0;
|
||||
$failedSubscribers = $params['failedSubscribers'] ?? 0;
|
||||
$isDeleted = $params['isDeleted'] ?? false;
|
||||
|
||||
$task = (new ScheduledTask())->create(SendingQueueWorker::TASK_TYPE, $taskStatus);
|
||||
if ($isDeleted) {
|
||||
$task->setDeletedAt(new DateTimeImmutable());
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $processedSubscribers; $i++) {
|
||||
(new ScheduledTaskSubscriber())->createProcessed($task, (new Subscriber())->create());
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $unprocessedSubscribers; $i++) {
|
||||
(new ScheduledTaskSubscriber())->createUnprocessed($task, (new Subscriber())->create());
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $failedSubscribers; $i++) {
|
||||
(new ScheduledTaskSubscriber())->createFailed($task, (new Subscriber())->create());
|
||||
}
|
||||
|
||||
(new SendingQueue())->create($task, $newsletter);
|
||||
return $task;
|
||||
}
|
||||
|
||||
private function refreshAll(array $entities) {
|
||||
foreach ($entities as $entity) {
|
||||
$this->entityManager->refresh($entity);
|
||||
}
|
||||
}
|
||||
|
||||
private function getMaxUpdatedDateFromTaskSubscribers(ScheduledTaskEntity $task): DateTimeInterface {
|
||||
$date = new DateTimeImmutable('1900-01-01');
|
||||
foreach ($task->getSubscribers() as $subscriber) {
|
||||
$date = max($date, $subscriber->getUpdatedAt());
|
||||
}
|
||||
return $date;
|
||||
}
|
||||
}
|
@ -2,19 +2,24 @@
|
||||
|
||||
namespace MailPoet\Test\Models;
|
||||
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Models\Newsletter;
|
||||
use MailPoet\Models\NewsletterSegment;
|
||||
use MailPoet\Models\ScheduledTask;
|
||||
use MailPoet\Models\Segment;
|
||||
use MailPoet\Models\SendingQueue;
|
||||
use MailPoet\Tasks\Sending as SendingTask;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Test\DataFactories\NewsletterOption as NewsletterOptionFactory;
|
||||
use MailPoet\Test\DataFactories\ScheduledTask as ScheduledTaskFactory;
|
||||
use MailPoet\Test\DataFactories\SendingQueue as SendingQueueFactory;
|
||||
use MailPoet\Util\Security;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
|
||||
class NewsletterTest extends \MailPoetTest {
|
||||
/** @var SendingQueueEntity */
|
||||
public $sendingQueue;
|
||||
public $segment2;
|
||||
public $segment1;
|
||||
@ -24,8 +29,15 @@ class NewsletterTest extends \MailPoetTest {
|
||||
/** @var NewsletterOptionFactory */
|
||||
private $newsletterOptionFactory;
|
||||
|
||||
/** @var NewslettersRepository */
|
||||
private $newslettersRepository;
|
||||
|
||||
/** @var NewsletterEntity */
|
||||
private $newsletterEntity;
|
||||
|
||||
public function _before() {
|
||||
parent::_before();
|
||||
$this->newslettersRepository = $this->diContainer->get(NewslettersRepository::class);
|
||||
$this->newsletter = Newsletter::createOrUpdate([
|
||||
'subject' => 'My Standard Newsletter',
|
||||
'preheader' => 'Pre Header',
|
||||
@ -48,10 +60,11 @@ class NewsletterTest extends \MailPoetTest {
|
||||
$association->segmentId = $this->segment2->id;
|
||||
$association->save();
|
||||
|
||||
$this->sendingQueue = SendingTask::create();
|
||||
$this->sendingQueue->newsletter_id = $this->newsletter->id;
|
||||
$this->sendingQueue->status = ScheduledTask::STATUS_SCHEDULED;
|
||||
$this->sendingQueue->save();
|
||||
$newsletterEntity = $this->newslettersRepository->findOneById($this->newsletter->id);
|
||||
$this->assertInstanceOf(NewsletterEntity::class, $newsletterEntity);
|
||||
$this->newsletterEntity = $newsletterEntity;
|
||||
$scheduledTask = (new ScheduledTaskFactory())->create(SendingQueueWorker::TASK_TYPE, ScheduledTask::STATUS_SCHEDULED);
|
||||
$this->sendingQueue = (new SendingQueueFactory())->create($scheduledTask, $this->newsletterEntity);
|
||||
|
||||
$this->newsletterOptionFactory = new NewsletterOptionFactory();
|
||||
}
|
||||
@ -116,12 +129,6 @@ class NewsletterTest extends \MailPoetTest {
|
||||
verify($isTimeUpdated)->true();
|
||||
}
|
||||
|
||||
public function testItCanBeQueued() {
|
||||
$queue = $this->newsletter->getQueue();
|
||||
verify($queue->id > 0)->true();
|
||||
verify($queue->newsletterId)->equals($this->newsletter->id);
|
||||
}
|
||||
|
||||
public function testItCanHaveSegments() {
|
||||
$newsletterSegments = $this->newsletter->segments()->findArray();
|
||||
verify($newsletterSegments)->arrayCount(2);
|
||||
@ -203,7 +210,7 @@ class NewsletterTest extends \MailPoetTest {
|
||||
}
|
||||
|
||||
public function testItGetsQueueFromNewsletter() {
|
||||
verify($this->newsletter->queue()->findOne()->id)->equals($this->sendingQueue->id);
|
||||
verify($this->newsletter->queue()->findOne()->id)->equals($this->sendingQueue->getId());
|
||||
}
|
||||
|
||||
public function testItCanBeRestored() {
|
||||
@ -248,9 +255,8 @@ class NewsletterTest extends \MailPoetTest {
|
||||
|
||||
// create multiple sending queues
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$sendingQueue = SendingTask::create();
|
||||
$sendingQueue->newsletterId = $newsletter->id;
|
||||
$sendingQueue->save();
|
||||
$scheduledTask = (new ScheduledTaskFactory())->create(SendingQueueWorker::TASK_TYPE, null);
|
||||
(new SendingQueueFactory())->create($scheduledTask, $this->newsletterEntity);
|
||||
}
|
||||
|
||||
// make sure relations exist
|
||||
@ -276,9 +282,9 @@ class NewsletterTest extends \MailPoetTest {
|
||||
'parent_id' => $parentNewsletter->id,
|
||||
]
|
||||
);
|
||||
$sendingQueue = SendingTask::create();
|
||||
$sendingQueue->newsletterId = $newsletter->id;
|
||||
$sendingQueue->save();
|
||||
$newsletterEntity = $this->newslettersRepository->findOneById($newsletter->id);
|
||||
$scheduledTask = (new ScheduledTaskFactory())->create(SendingQueueWorker::TASK_TYPE, null);
|
||||
(new SendingQueueFactory())->create($scheduledTask, $newsletterEntity);
|
||||
$newsletterSegment = NewsletterSegment::create();
|
||||
$newsletterSegment->newsletterId = $newsletter->id;
|
||||
$newsletterSegment->segmentId = 1;
|
||||
@ -302,9 +308,8 @@ class NewsletterTest extends \MailPoetTest {
|
||||
// create multiple sending queues
|
||||
$newsletter = $this->newsletter;
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$sendingQueue = SendingTask::create();
|
||||
$sendingQueue->newsletterId = $newsletter->id;
|
||||
$sendingQueue->save();
|
||||
$scheduledTask = (new ScheduledTaskFactory())->create(SendingQueueWorker::TASK_TYPE, null);
|
||||
(new SendingQueueFactory())->create($scheduledTask, $this->newsletterEntity);
|
||||
}
|
||||
verify(SendingQueue::whereNull('deleted_at')->findArray())->arrayCount(6);
|
||||
|
||||
@ -325,9 +330,8 @@ class NewsletterTest extends \MailPoetTest {
|
||||
'parent_id' => $parentNewsletter->id,
|
||||
]
|
||||
);
|
||||
$sendingQueue = SendingTask::create();
|
||||
$sendingQueue->newsletterId = $newsletter->id;
|
||||
$sendingQueue->save();
|
||||
$scheduledTask = (new ScheduledTaskFactory())->create(SendingQueueWorker::TASK_TYPE, null);
|
||||
(new SendingQueueFactory())->create($scheduledTask, $this->newsletterEntity);
|
||||
}
|
||||
// 1 parent and 5 children queues/newsletters
|
||||
verify(Newsletter::whereNull('deleted_at')->findArray())->arrayCount(6);
|
||||
@ -342,19 +346,19 @@ class NewsletterTest extends \MailPoetTest {
|
||||
|
||||
public function testItRestoresTrashedQueueAssociationsWhenNewsletterIsRestored() {
|
||||
// create multiple sending queues
|
||||
$sendingTasks = [];
|
||||
$scheduledTasks = [];
|
||||
$newsletter = $this->newsletter;
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$sendingTask = SendingTask::create();
|
||||
$sendingTask->newsletterId = $newsletter->id;
|
||||
$sendingTask->deletedAt = date('Y-m-d H:i:s');
|
||||
$sendingTask->status = ScheduledTask::STATUS_SCHEDULED;
|
||||
$sendingTask->save();
|
||||
$sendingTasks[] = $sendingTask;
|
||||
$scheduledTask = (new ScheduledTaskFactory())->create(SendingQueueWorker::TASK_TYPE, ScheduledTask::STATUS_SCHEDULED);
|
||||
(new SendingQueueFactory())->create($scheduledTask, $this->newsletterEntity, new Carbon());
|
||||
|
||||
$scheduledTasks[] = $scheduledTask;
|
||||
}
|
||||
$inProgressTask = $sendingTasks[1];
|
||||
$inProgressTask->status = null;
|
||||
$inProgressTask->save();
|
||||
$inProgressTask = $scheduledTasks[1];
|
||||
$inProgressTask->setStatus(null);
|
||||
$this->entityManager->persist($inProgressTask);
|
||||
$this->entityManager->flush();
|
||||
|
||||
verify(SendingQueue::whereNotNull('deleted_at')->findArray())->arrayCount(5);
|
||||
// restore newsletter and check that relations are restored
|
||||
$newsletter->restore();
|
||||
@ -369,9 +373,9 @@ class NewsletterTest extends \MailPoetTest {
|
||||
$parentNewsletter = $this->newsletter;
|
||||
$parentNewsletter->deletedAt = date('Y-m-d H:i:s');
|
||||
$parentNewsletter->save();
|
||||
$parentSendingQueue = $this->sendingQueue;
|
||||
$parentSendingQueue->deletedAt = date('Y-m-d H:i:s');
|
||||
$parentSendingQueue->save();
|
||||
$this->sendingQueue->setDeletedAt(new Carbon());
|
||||
$this->entityManager->persist($this->sendingQueue);
|
||||
$this->entityManager->flush();
|
||||
|
||||
// create multiple children (post notification history) newsletters and sending queues
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
@ -383,10 +387,8 @@ class NewsletterTest extends \MailPoetTest {
|
||||
'deleted_at' => date('Y-m-d H:i:s'),
|
||||
]
|
||||
);
|
||||
$sendingQueue = SendingTask::create();
|
||||
$sendingQueue->newsletterId = $newsletter->id;
|
||||
$sendingQueue->deletedAt = date('Y-m-d H:i:s');
|
||||
$sendingQueue->save();
|
||||
$scheduledTask = (new ScheduledTaskFactory())->create(SendingQueueWorker::TASK_TYPE, null);
|
||||
(new SendingQueueFactory())->create($scheduledTask, $this->newsletterEntity, new Carbon());
|
||||
}
|
||||
// 1 parent and 5 children queues/newsletters
|
||||
verify(Newsletter::whereNotNull('deleted_at')->findArray())->arrayCount(6);
|
||||
|
@ -0,0 +1,317 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Test\Newsletter;
|
||||
|
||||
use Codeception\Util\Fixtures;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\NewsletterLinkEntity;
|
||||
use MailPoet\Entities\NewsletterOptionEntity;
|
||||
use MailPoet\Entities\NewsletterOptionFieldEntity;
|
||||
use MailPoet\Entities\NewsletterPostEntity;
|
||||
use MailPoet\Entities\NewsletterSegmentEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Entities\ScheduledTaskSubscriberEntity;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Entities\StatisticsClickEntity;
|
||||
use MailPoet\Entities\StatisticsNewsletterEntity;
|
||||
use MailPoet\Entities\StatisticsOpenEntity;
|
||||
use MailPoet\Entities\StatisticsWooCommercePurchaseEntity;
|
||||
use MailPoet\Entities\StatsNotificationEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Entities\WpPostEntity;
|
||||
use MailPoet\Newsletter\NewsletterDeleteController;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTaskSubscribersRepository;
|
||||
use MailPoet\Test\DataFactories\NewsletterOptionField;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class NewsletterDeleteControllerTest extends \MailPoetTest {
|
||||
private NewsletterDeleteController $controller;
|
||||
private NewslettersRepository $repository;
|
||||
private ScheduledTaskSubscribersRepository $taskSubscribersRepository;
|
||||
private WPFunctions $wp;
|
||||
|
||||
public function _before() {
|
||||
parent::_before();
|
||||
$this->controller = $this->diContainer->get(NewsletterDeleteController::class);
|
||||
$this->repository = $this->diContainer->get(NewslettersRepository::class);
|
||||
$this->taskSubscribersRepository = $this->diContainer->get(ScheduledTaskSubscribersRepository::class);
|
||||
$this->wp = $this->diContainer->get(WPFunctions::class);
|
||||
}
|
||||
|
||||
public function testItBulkDeleteNewslettersAndChildren() {
|
||||
$standardNewsletter = $this->createNewsletter(NewsletterEntity::TYPE_STANDARD, NewsletterEntity::STATUS_SENDING);
|
||||
$standardQueue = $this->createQueueWithTaskAndSegmentAndSubscribers($standardNewsletter, null); // Null for scheduled task being processed
|
||||
$notification = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION, NewsletterEntity::STATUS_ACTIVE);
|
||||
$notificationHistory = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION_HISTORY, NewsletterEntity::STATUS_SCHEDULED, $notification);
|
||||
$notificationHistoryQueue = $this->createQueueWithTaskAndSegmentAndSubscribers($notificationHistory);
|
||||
|
||||
$standardSegment = $standardNewsletter->getNewsletterSegments()->first();
|
||||
$this->assertInstanceOf(NewsletterSegmentEntity::class, $standardSegment);
|
||||
$standardScheduledTaks = $standardQueue->getTask();
|
||||
$this->assertInstanceOf(ScheduledTaskEntity::class, $standardScheduledTaks);
|
||||
$standardScheduledTaskSubscriber = $this->taskSubscribersRepository->findOneBy(['task' => $standardScheduledTaks]);
|
||||
$this->assertInstanceOf(ScheduledTaskSubscriberEntity::class, $standardScheduledTaskSubscriber);
|
||||
$notificationHistoryScheduledTask = $notificationHistoryQueue->getTask();
|
||||
$this->assertInstanceOf(ScheduledTaskEntity::class, $notificationHistoryScheduledTask);
|
||||
$notificationHistorySegment = $notificationHistory->getNewsletterSegments()->first();
|
||||
$this->assertInstanceOf(NewsletterSegmentEntity::class, $notificationHistorySegment);
|
||||
$notificationHistoryScheduledTaskSubscriber = $this->taskSubscribersRepository->findOneBy(['task' => $notificationHistoryScheduledTask]);
|
||||
$this->assertInstanceOf(ScheduledTaskSubscriberEntity::class, $notificationHistoryScheduledTaskSubscriber);
|
||||
$standardStatsNotification = $this->createStatNotification($standardNewsletter);
|
||||
$standardStatsNotificationScheduledTask = $standardStatsNotification->getTask();
|
||||
$this->assertInstanceOf(ScheduledTaskEntity::class, $standardStatsNotificationScheduledTask);
|
||||
$notificationHistoryStatsNotification = $this->createStatNotification($notificationHistory);
|
||||
$notificationHistoryStatsNotificationScheduledTask = $notificationHistoryStatsNotification->getTask();
|
||||
$this->assertInstanceOf(ScheduledTaskEntity::class, $notificationHistoryStatsNotificationScheduledTask);
|
||||
$standardLink = $this->createNewsletterLink($standardNewsletter, $standardQueue);
|
||||
$notificationHistoryLink = $this->createNewsletterLink($notificationHistory, $notificationHistoryQueue);
|
||||
$optionField = (new NewsletterOptionField())->findOrCreate('name', NewsletterEntity::TYPE_NOTIFICATION);
|
||||
$optionValue = $this->createNewsletterOption($notificationHistory, $optionField, 'value');
|
||||
$newsletterPost = $this->createNewsletterPost($notification, 1);
|
||||
|
||||
$subscriber = $standardScheduledTaskSubscriber->getSubscriber();
|
||||
$this->assertInstanceOf(SubscriberEntity::class, $subscriber);
|
||||
$statisticsNewsletter = $this->createNewsletterStatistics($standardNewsletter, $standardQueue, $subscriber);
|
||||
$statisticsOpen = $this->createOpenStatistics($standardNewsletter, $standardQueue, $subscriber);
|
||||
$statisticsClick = $this->createClickStatistics($standardNewsletter, $standardQueue, $subscriber, $standardLink);
|
||||
$statisticsPurchase = $this->createPurchaseStatistics($standardNewsletter, $standardQueue, $statisticsClick, $subscriber);
|
||||
|
||||
// Trash
|
||||
$this->repository->bulkTrash([(int)$standardNewsletter->getId(), (int)$notification->getId()]);
|
||||
// Delete
|
||||
$this->controller->bulkDelete([(int)$standardNewsletter->getId(), (int)$notification->getId()]);
|
||||
|
||||
// Clear entity manager to forget all entities
|
||||
$this->entityManager->clear();
|
||||
|
||||
// Check they were all deleted
|
||||
// Newsletters
|
||||
verify($this->repository->findOneById($standardNewsletter->getId()))->null();
|
||||
verify($this->repository->findOneById($notification->getId()))->null();
|
||||
verify($this->repository->findOneById($notificationHistory->getId()))->null();
|
||||
|
||||
// Sending queues
|
||||
verify($this->entityManager->find(SendingQueueEntity::class, $standardQueue->getId()))->null();
|
||||
verify($this->entityManager->find(SendingQueueEntity::class, $notificationHistoryQueue->getId()))->null();
|
||||
|
||||
// Scheduled tasks subscribers
|
||||
verify($this->taskSubscribersRepository->findOneBy(['task' => $standardScheduledTaks]))->null();
|
||||
verify($this->taskSubscribersRepository->findOneBy(['task' => $notificationHistoryScheduledTask]))->null();
|
||||
|
||||
// Scheduled tasks
|
||||
verify($this->entityManager->find(ScheduledTaskEntity::class, $standardScheduledTaks->getId()))->null();
|
||||
verify($this->entityManager->find(ScheduledTaskEntity::class, $notificationHistoryScheduledTask->getId()))->null();
|
||||
|
||||
// Newsletter segments
|
||||
verify($this->entityManager->find(NewsletterSegmentEntity::class, $standardSegment->getId()))->null();
|
||||
verify($this->entityManager->find(NewsletterSegmentEntity::class, $notificationHistorySegment->getId()))->null();
|
||||
|
||||
// Newsletter stats notifications
|
||||
verify($this->entityManager->find(StatsNotificationEntity::class, $standardStatsNotificationScheduledTask->getId()))->null();
|
||||
verify($this->entityManager->find(StatsNotificationEntity::class, $notificationHistoryStatsNotification->getId()))->null();
|
||||
|
||||
// Newsletter stats notifications scheduled tasks
|
||||
verify($this->entityManager->find(ScheduledTaskEntity::class, $standardStatsNotificationScheduledTask->getId()))->null();
|
||||
verify($this->entityManager->find(ScheduledTaskEntity::class, $notificationHistoryStatsNotificationScheduledTask->getId()))->null();
|
||||
|
||||
// Newsletter links
|
||||
verify($this->entityManager->find(NewsletterLinkEntity::class, $standardLink->getId()))->null();
|
||||
verify($this->entityManager->find(NewsletterLinkEntity::class, $notificationHistoryLink->getId()))->null();
|
||||
|
||||
// Option fields values
|
||||
verify($this->entityManager->find(NewsletterOptionEntity::class, $optionValue->getId()))->null();
|
||||
|
||||
// Newsletter post
|
||||
verify($this->entityManager->find(NewsletterPostEntity::class, $newsletterPost->getId()))->null();
|
||||
|
||||
// Statistics data
|
||||
verify($this->entityManager->find(StatisticsNewsletterEntity::class, $statisticsNewsletter->getId()))->null();
|
||||
verify($this->entityManager->find(StatisticsOpenEntity::class, $statisticsOpen->getId()))->null();
|
||||
verify($this->entityManager->find(StatisticsClickEntity::class, $statisticsClick->getId()))->null();
|
||||
$statisticsPurchase = $this->entityManager->find(StatisticsWooCommercePurchaseEntity::class, $statisticsPurchase->getId());
|
||||
$this->assertNotNull($statisticsPurchase);
|
||||
verify($statisticsPurchase->getNewsletter())->null();
|
||||
}
|
||||
|
||||
public function testItDeletesMultipleNewslettersWithPurchaseStatsAndKeepsStats() {
|
||||
$standardNewsletter1 = $this->createNewsletter(NewsletterEntity::TYPE_STANDARD, NewsletterEntity::STATUS_SENT);
|
||||
$statisticsPurchase1 = $this->createPurchaseStatsForNewsletter($standardNewsletter1);
|
||||
$standardNewsletter2 = $this->createNewsletter(NewsletterEntity::TYPE_STANDARD, NewsletterEntity::STATUS_SENT);
|
||||
$statisticsPurchase2 = $this->createPurchaseStatsForNewsletter($standardNewsletter2);
|
||||
|
||||
// Delete
|
||||
$this->controller->bulkDelete([(int)$standardNewsletter1->getId(), (int)$standardNewsletter2->getId()]);
|
||||
|
||||
// Clear entity manager to forget all entities
|
||||
$this->entityManager->clear();
|
||||
|
||||
// Check Newsletters were deleted
|
||||
verify($this->repository->findOneById($standardNewsletter1->getId()))->null();
|
||||
verify($this->repository->findOneById($standardNewsletter2->getId()))->null();
|
||||
|
||||
// Check purchase stats were not deleted
|
||||
$statisticsPurchase1 = $this->entityManager->find(StatisticsWooCommercePurchaseEntity::class, $statisticsPurchase1->getId());
|
||||
$statisticsPurchase2 = $this->entityManager->find(StatisticsWooCommercePurchaseEntity::class, $statisticsPurchase2->getId());
|
||||
$this->assertNotNull($statisticsPurchase1);
|
||||
verify($statisticsPurchase1->getNewsletter())->null();
|
||||
$this->assertNotNull($statisticsPurchase2);
|
||||
verify($statisticsPurchase2->getNewsletter())->null();
|
||||
}
|
||||
|
||||
public function testItDeletesWpPostsBulkDelete() {
|
||||
$newsletter1 = $this->createNewsletter(NewsletterEntity::TYPE_STANDARD, NewsletterEntity::STATUS_SENDING);
|
||||
$post1Id = $this->wp->wpInsertPost(['post_title' => 'Post 1']);
|
||||
$newsletter1->setWpPost($this->entityManager->getReference(WpPostEntity::class, $post1Id));
|
||||
$newsletter2 = $this->createNewsletter(NewsletterEntity::TYPE_WELCOME, NewsletterEntity::STATUS_SENDING);
|
||||
$post2Id = $this->wp->wpInsertPost(['post_title' => 'Post 2']);
|
||||
$newsletter2->setWpPost($this->entityManager->getReference(WpPostEntity::class, $post2Id));
|
||||
$newsletter3 = $this->createNewsletter(NewsletterEntity::TYPE_STANDARD, NewsletterEntity::STATUS_SENDING);
|
||||
|
||||
$blogPost = $this->wp->wpInsertPost(['post_title' => 'Regular blog post']);
|
||||
|
||||
verify($this->wp->getPost($post1Id))->instanceOf(\WP_Post::class);
|
||||
verify($this->wp->getPost($post2Id))->instanceOf(\WP_Post::class);
|
||||
|
||||
$this->entityManager->flush();
|
||||
$this->entityManager->clear();
|
||||
|
||||
$this->controller->bulkDelete([(int)$newsletter1->getId(), (int)$newsletter2->getId(), (int)$newsletter3->getId()]);
|
||||
verify($this->wp->getPost($post1Id))->null();
|
||||
verify($this->wp->getPost($post2Id))->null();
|
||||
verify($this->wp->getPost($blogPost))->instanceOf(\WP_Post::class);
|
||||
}
|
||||
|
||||
private function createNewsletter(string $type, string $status = NewsletterEntity::STATUS_DRAFT, $parent = null): NewsletterEntity {
|
||||
$newsletter = new NewsletterEntity();
|
||||
$newsletter->setType($type);
|
||||
$newsletter->setSubject('My Standard Newsletter');
|
||||
$newsletter->setBody(Fixtures::get('newsletter_body_template'));
|
||||
$newsletter->setStatus($status);
|
||||
$newsletter->setParent($parent);
|
||||
$this->entityManager->persist($newsletter);
|
||||
$this->entityManager->flush();
|
||||
return $newsletter;
|
||||
}
|
||||
|
||||
private function createQueueWithTaskAndSegmentAndSubscribers(NewsletterEntity $newsletter, $status = ScheduledTaskEntity::STATUS_SCHEDULED): SendingQueueEntity {
|
||||
$task = new ScheduledTaskEntity();
|
||||
$task->setType(SendingQueue::TASK_TYPE);
|
||||
$task->setStatus($status);
|
||||
$this->entityManager->persist($task);
|
||||
|
||||
$queue = new SendingQueueEntity();
|
||||
$queue->setNewsletter($newsletter);
|
||||
$queue->setTask($task);
|
||||
$this->entityManager->persist($queue);
|
||||
$newsletter->getQueues()->add($queue);
|
||||
|
||||
$segment = new SegmentEntity("List for newsletter id {$newsletter->getId()}", SegmentEntity::TYPE_DEFAULT, 'Description');
|
||||
$this->entityManager->persist($segment);
|
||||
|
||||
$subscriber = new SubscriberEntity();
|
||||
$subscriber->setEmail("sub{$newsletter->getId()}@mailpoet.com");
|
||||
$subscriber->setStatus(SubscriberEntity::STATUS_SUBSCRIBED);
|
||||
$this->entityManager->persist($subscriber);
|
||||
$this->entityManager->flush();
|
||||
$scheduledTaskSubscriber = new ScheduledTaskSubscriberEntity($task, $subscriber);
|
||||
$this->entityManager->persist($scheduledTaskSubscriber);
|
||||
|
||||
$newsletterSegment = new NewsletterSegmentEntity($newsletter, $segment);
|
||||
$newsletter->getNewsletterSegments()->add($newsletterSegment);
|
||||
$this->entityManager->persist($newsletterSegment);
|
||||
$this->entityManager->flush();
|
||||
return $queue;
|
||||
}
|
||||
|
||||
private function createStatNotification(NewsletterEntity $newsletter): StatsNotificationEntity {
|
||||
$task = new ScheduledTaskEntity();
|
||||
$task->setType('stats_notification');
|
||||
$task->setStatus(ScheduledTaskEntity::STATUS_SCHEDULED);
|
||||
$this->entityManager->persist($task);
|
||||
$statsNotification = new StatsNotificationEntity($newsletter, $task);
|
||||
$this->entityManager->persist($statsNotification);
|
||||
$this->entityManager->flush();
|
||||
return $statsNotification;
|
||||
}
|
||||
|
||||
private function createNewsletterLink(NewsletterEntity $newsletter, SendingQueueEntity $queue): NewsletterLinkEntity {
|
||||
$link = new NewsletterLinkEntity($newsletter, $queue, 'http://example.com', 'abcd');
|
||||
$this->entityManager->persist($link);
|
||||
$this->entityManager->flush();
|
||||
return $link;
|
||||
}
|
||||
|
||||
private function createNewsletterOption(NewsletterEntity $newsletter, NewsletterOptionFieldEntity $field, $value): NewsletterOptionEntity {
|
||||
$option = new NewsletterOptionEntity($newsletter, $field);
|
||||
$option->setValue($value);
|
||||
$this->entityManager->persist($option);
|
||||
$this->entityManager->flush();
|
||||
return $option;
|
||||
}
|
||||
|
||||
private function createNewsletterPost(NewsletterEntity $newsletter, int $postId): NewsletterPostEntity {
|
||||
$post = new NewsletterPostEntity($newsletter, $postId);
|
||||
$this->entityManager->persist($post);
|
||||
$this->entityManager->flush();
|
||||
return $post;
|
||||
}
|
||||
|
||||
private function createNewsletterStatistics(NewsletterEntity $newsletter, SendingQueueEntity $queue, SubscriberEntity $subscriber): StatisticsNewsletterEntity {
|
||||
$statisticsNewsletter = new StatisticsNewsletterEntity($newsletter, $queue, $subscriber);
|
||||
$this->entityManager->persist($statisticsNewsletter);
|
||||
$this->entityManager->flush();
|
||||
return $statisticsNewsletter;
|
||||
}
|
||||
|
||||
private function createOpenStatistics(NewsletterEntity $newsletter, SendingQueueEntity $queue, SubscriberEntity $subscriber): StatisticsOpenEntity {
|
||||
$statistics = new StatisticsOpenEntity($newsletter, $queue, $subscriber);
|
||||
$this->entityManager->persist($statistics);
|
||||
$this->entityManager->flush();
|
||||
return $statistics;
|
||||
}
|
||||
|
||||
private function createClickStatistics(
|
||||
NewsletterEntity $newsletter,
|
||||
SendingQueueEntity $queue,
|
||||
SubscriberEntity $subscriber,
|
||||
NewsletterLinkEntity $link
|
||||
): StatisticsClickEntity {
|
||||
$statistics = new StatisticsClickEntity($newsletter, $queue, $subscriber, $link, 1);
|
||||
$this->entityManager->persist($statistics);
|
||||
$this->entityManager->flush();
|
||||
return $statistics;
|
||||
}
|
||||
|
||||
private function createPurchaseStatistics(
|
||||
NewsletterEntity $newsletter,
|
||||
SendingQueueEntity $queue,
|
||||
StatisticsClickEntity $click,
|
||||
SubscriberEntity $subscriber
|
||||
): StatisticsWooCommercePurchaseEntity {
|
||||
$statistics = new StatisticsWooCommercePurchaseEntity($newsletter, $queue, $click, 1, 'EUR', 100, 'completed');
|
||||
$statistics->setSubscriber($subscriber);
|
||||
$this->entityManager->persist($statistics);
|
||||
$this->entityManager->flush();
|
||||
return $statistics;
|
||||
}
|
||||
|
||||
private function createPurchaseStatsForNewsletter(NewsletterEntity $newsletter): StatisticsWooCommercePurchaseEntity {
|
||||
$queue = $this->createQueueWithTaskAndSegmentAndSubscribers($newsletter, NewsletterEntity::STATUS_SENT); // Null for scheduled task being processed
|
||||
$segment = $newsletter->getNewsletterSegments()->first();
|
||||
$this->assertInstanceOf(NewsletterSegmentEntity::class, $segment);
|
||||
$scheduledTask = $queue->getTask();
|
||||
$this->assertInstanceOf(ScheduledTaskEntity::class, $scheduledTask);
|
||||
$scheduledTaskSubscriber = $this->taskSubscribersRepository->findOneBy(['task' => $scheduledTask]);
|
||||
$this->assertInstanceOf(ScheduledTaskSubscriberEntity::class, $scheduledTaskSubscriber);
|
||||
$link = $this->createNewsletterLink($newsletter, $queue);
|
||||
$this->assertInstanceOf(NewsletterLinkEntity::class, $link);
|
||||
$subscriber = $scheduledTaskSubscriber->getSubscriber();
|
||||
$this->assertInstanceOf(SubscriberEntity::class, $subscriber);
|
||||
$statisticsClick = $this->createClickStatistics($newsletter, $queue, $subscriber, $link);
|
||||
$this->assertInstanceOf(StatisticsClickEntity::class, $statisticsClick);
|
||||
return $this->createPurchaseStatistics($newsletter, $queue, $statisticsClick, $subscriber);
|
||||
}
|
||||
}
|
@ -5,43 +5,23 @@ namespace MailPoet\Newsletter;
|
||||
use Codeception\Util\Fixtures;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\NewsletterLinkEntity;
|
||||
use MailPoet\Entities\NewsletterOptionEntity;
|
||||
use MailPoet\Entities\NewsletterOptionFieldEntity;
|
||||
use MailPoet\Entities\NewsletterPostEntity;
|
||||
use MailPoet\Entities\NewsletterSegmentEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Entities\ScheduledTaskSubscriberEntity;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Entities\StatisticsClickEntity;
|
||||
use MailPoet\Entities\StatisticsNewsletterEntity;
|
||||
use MailPoet\Entities\StatisticsOpenEntity;
|
||||
use MailPoet\Entities\StatisticsWooCommercePurchaseEntity;
|
||||
use MailPoet\Entities\StatsNotificationEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Entities\WpPostEntity;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTaskSubscribersRepository;
|
||||
use MailPoet\Tasks\Sending as SendingTask;
|
||||
use MailPoet\Test\DataFactories\NewsletterOptionField;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoet\Test\DataFactories\ScheduledTask as ScheduledTaskFactory;
|
||||
use MailPoet\Test\DataFactories\SendingQueue as SendingQueueFactory;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
|
||||
class NewsletterRepositoryTest extends \MailPoetTest {
|
||||
/** @var NewslettersRepository */
|
||||
private $repository;
|
||||
|
||||
/** @var ScheduledTaskSubscribersRepository */
|
||||
private $taskSubscribersRepository;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
public function _before() {
|
||||
parent::_before();
|
||||
$this->repository = $this->diContainer->get(NewslettersRepository::class);
|
||||
$this->taskSubscribersRepository = $this->diContainer->get(ScheduledTaskSubscribersRepository::class);
|
||||
$this->wp = $this->diContainer->get(WPFunctions::class);
|
||||
}
|
||||
|
||||
public function testItBulkTrashNewslettersAndChildren() {
|
||||
@ -126,149 +106,6 @@ class NewsletterRepositoryTest extends \MailPoetTest {
|
||||
verify($scheduledTask->getStatus())->equals(ScheduledTaskEntity::STATUS_SCHEDULED);
|
||||
}
|
||||
|
||||
public function testItBulkDeleteNewslettersAndChildren() {
|
||||
$standardNewsletter = $this->createNewsletter(NewsletterEntity::TYPE_STANDARD, NewsletterEntity::STATUS_SENDING);
|
||||
$standardQueue = $this->createQueueWithTaskAndSegmentAndSubscribers($standardNewsletter, null); // Null for scheduled task being processed
|
||||
$notification = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION, NewsletterEntity::STATUS_ACTIVE);
|
||||
$notificationHistory = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION_HISTORY, NewsletterEntity::STATUS_SCHEDULED, $notification);
|
||||
$notificationHistoryQueue = $this->createQueueWithTaskAndSegmentAndSubscribers($notificationHistory);
|
||||
|
||||
$standardSegment = $standardNewsletter->getNewsletterSegments()->first();
|
||||
$this->assertInstanceOf(NewsletterSegmentEntity::class, $standardSegment);
|
||||
$standardScheduledTaks = $standardQueue->getTask();
|
||||
$this->assertInstanceOf(ScheduledTaskEntity::class, $standardScheduledTaks);
|
||||
$standardScheduledTaskSubscriber = $this->taskSubscribersRepository->findOneBy(['task' => $standardScheduledTaks]);
|
||||
$this->assertInstanceOf(ScheduledTaskSubscriberEntity::class, $standardScheduledTaskSubscriber);
|
||||
$notificationHistoryScheduledTask = $notificationHistoryQueue->getTask();
|
||||
$this->assertInstanceOf(ScheduledTaskEntity::class, $notificationHistoryScheduledTask);
|
||||
$notificationHistorySegment = $notificationHistory->getNewsletterSegments()->first();
|
||||
$this->assertInstanceOf(NewsletterSegmentEntity::class, $notificationHistorySegment);
|
||||
$notificationHistoryScheduledTaskSubscriber = $this->taskSubscribersRepository->findOneBy(['task' => $notificationHistoryScheduledTask]);
|
||||
$this->assertInstanceOf(ScheduledTaskSubscriberEntity::class, $notificationHistoryScheduledTaskSubscriber);
|
||||
$standardStatsNotification = $this->createStatNotification($standardNewsletter);
|
||||
$standardStatsNotificationScheduledTask = $standardStatsNotification->getTask();
|
||||
$this->assertInstanceOf(ScheduledTaskEntity::class, $standardStatsNotificationScheduledTask);
|
||||
$notificationHistoryStatsNotification = $this->createStatNotification($notificationHistory);
|
||||
$notificationHistoryStatsNotificationScheduledTask = $notificationHistoryStatsNotification->getTask();
|
||||
$this->assertInstanceOf(ScheduledTaskEntity::class, $notificationHistoryStatsNotificationScheduledTask);
|
||||
$standardLink = $this->createNewsletterLink($standardNewsletter, $standardQueue);
|
||||
$notificationHistoryLink = $this->createNewsletterLink($notificationHistory, $notificationHistoryQueue);
|
||||
$optionField = (new NewsletterOptionField())->findOrCreate('name', NewsletterEntity::TYPE_NOTIFICATION);
|
||||
$optionValue = $this->createNewsletterOption($notificationHistory, $optionField, 'value');
|
||||
$newsletterPost = $this->createNewsletterPost($notification, 1);
|
||||
|
||||
$subscriber = $standardScheduledTaskSubscriber->getSubscriber();
|
||||
$this->assertInstanceOf(SubscriberEntity::class, $subscriber);
|
||||
$statisticsNewsletter = $this->createNewsletterStatistics($standardNewsletter, $standardQueue, $subscriber);
|
||||
$statisticsOpen = $this->createOpenStatistics($standardNewsletter, $standardQueue, $subscriber);
|
||||
$statisticsClick = $this->createClickStatistics($standardNewsletter, $standardQueue, $subscriber, $standardLink);
|
||||
$statisticsPurchase = $this->createPurchaseStatistics($standardNewsletter, $standardQueue, $statisticsClick, $subscriber);
|
||||
|
||||
// Trash
|
||||
$this->repository->bulkTrash([$standardNewsletter->getId(), $notification->getId()]);
|
||||
// Delete
|
||||
$this->repository->bulkDelete([$standardNewsletter->getId(), $notification->getId()]);
|
||||
|
||||
// Clear entity manager to forget all entities
|
||||
$this->entityManager->clear();
|
||||
|
||||
// Check they were all deleted
|
||||
// Newsletters
|
||||
verify($this->repository->findOneById($standardNewsletter->getId()))->null();
|
||||
verify($this->repository->findOneById($notification->getId()))->null();
|
||||
verify($this->repository->findOneById($notificationHistory->getId()))->null();
|
||||
|
||||
// Sending queues
|
||||
verify($this->entityManager->find(SendingQueueEntity::class, $standardQueue->getId()))->null();
|
||||
verify($this->entityManager->find(SendingQueueEntity::class, $notificationHistoryQueue->getId()))->null();
|
||||
|
||||
// Scheduled tasks subscribers
|
||||
verify($this->taskSubscribersRepository->findOneBy(['task' => $standardScheduledTaks]))->null();
|
||||
verify($this->taskSubscribersRepository->findOneBy(['task' => $notificationHistoryScheduledTask]))->null();
|
||||
|
||||
// Scheduled tasks
|
||||
verify($this->entityManager->find(ScheduledTaskEntity::class, $standardScheduledTaks->getId()))->null();
|
||||
verify($this->entityManager->find(ScheduledTaskEntity::class, $notificationHistoryScheduledTask->getId()))->null();
|
||||
|
||||
// Newsletter segments
|
||||
verify($this->entityManager->find(NewsletterSegmentEntity::class, $standardSegment->getId()))->null();
|
||||
verify($this->entityManager->find(NewsletterSegmentEntity::class, $notificationHistorySegment->getId()))->null();
|
||||
|
||||
// Newsletter stats notifications
|
||||
verify($this->entityManager->find(StatsNotificationEntity::class, $standardStatsNotificationScheduledTask->getId()))->null();
|
||||
verify($this->entityManager->find(StatsNotificationEntity::class, $notificationHistoryStatsNotification->getId()))->null();
|
||||
|
||||
// Newsletter stats notifications scheduled tasks
|
||||
verify($this->entityManager->find(ScheduledTaskEntity::class, $standardStatsNotificationScheduledTask->getId()))->null();
|
||||
verify($this->entityManager->find(ScheduledTaskEntity::class, $notificationHistoryStatsNotificationScheduledTask->getId()))->null();
|
||||
|
||||
// Newsletter links
|
||||
verify($this->entityManager->find(NewsletterLinkEntity::class, $standardLink->getId()))->null();
|
||||
verify($this->entityManager->find(NewsletterLinkEntity::class, $notificationHistoryLink->getId()))->null();
|
||||
|
||||
// Option fields values
|
||||
verify($this->entityManager->find(NewsletterOptionEntity::class, $optionValue->getId()))->null();
|
||||
|
||||
// Newsletter post
|
||||
verify($this->entityManager->find(NewsletterPostEntity::class, $newsletterPost->getId()))->null();
|
||||
|
||||
// Statistics data
|
||||
verify($this->entityManager->find(StatisticsNewsletterEntity::class, $statisticsNewsletter->getId()))->null();
|
||||
verify($this->entityManager->find(StatisticsOpenEntity::class, $statisticsOpen->getId()))->null();
|
||||
verify($this->entityManager->find(StatisticsClickEntity::class, $statisticsClick->getId()))->null();
|
||||
$statisticsPurchase = $this->entityManager->find(StatisticsWooCommercePurchaseEntity::class, $statisticsPurchase->getId());
|
||||
$this->assertNotNull($statisticsPurchase);
|
||||
verify($statisticsPurchase->getNewsletter())->null();
|
||||
}
|
||||
|
||||
public function testItDeletesMultipleNewslettersWithPurchaseStatsAndKeepsStats() {
|
||||
$standardNewsletter1 = $this->createNewsletter(NewsletterEntity::TYPE_STANDARD, NewsletterEntity::STATUS_SENT);
|
||||
$statisticsPurchase1 = $this->createPurchaseStatsForNewsletter($standardNewsletter1);
|
||||
$standardNewsletter2 = $this->createNewsletter(NewsletterEntity::TYPE_STANDARD, NewsletterEntity::STATUS_SENT);
|
||||
$statisticsPurchase2 = $this->createPurchaseStatsForNewsletter($standardNewsletter2);
|
||||
|
||||
// Delete
|
||||
$this->repository->bulkDelete([$standardNewsletter1->getId(), $standardNewsletter2->getId()]);
|
||||
|
||||
// Clear entity manager to forget all entities
|
||||
$this->entityManager->clear();
|
||||
|
||||
// Check Newsletters were deleted
|
||||
verify($this->repository->findOneById($standardNewsletter1->getId()))->null();
|
||||
verify($this->repository->findOneById($standardNewsletter2->getId()))->null();
|
||||
|
||||
// Check purchase stats were not deleted
|
||||
$statisticsPurchase1 = $this->entityManager->find(StatisticsWooCommercePurchaseEntity::class, $statisticsPurchase1->getId());
|
||||
$statisticsPurchase2 = $this->entityManager->find(StatisticsWooCommercePurchaseEntity::class, $statisticsPurchase2->getId());
|
||||
$this->assertNotNull($statisticsPurchase1);
|
||||
verify($statisticsPurchase1->getNewsletter())->null();
|
||||
$this->assertNotNull($statisticsPurchase2);
|
||||
verify($statisticsPurchase2->getNewsletter())->null();
|
||||
}
|
||||
|
||||
public function testItDeletesWpPostsBulkDelete() {
|
||||
$newsletter1 = $this->createNewsletter(NewsletterEntity::TYPE_STANDARD, NewsletterEntity::STATUS_SENDING);
|
||||
$post1Id = $this->wp->wpInsertPost(['post_title' => 'Post 1']);
|
||||
$newsletter1->setWpPost($this->entityManager->getReference(WpPostEntity::class, $post1Id));
|
||||
$newsletter2 = $this->createNewsletter(NewsletterEntity::TYPE_WELCOME, NewsletterEntity::STATUS_SENDING);
|
||||
$post2Id = $this->wp->wpInsertPost(['post_title' => 'Post 2']);
|
||||
$newsletter2->setWpPost($this->entityManager->getReference(WpPostEntity::class, $post2Id));
|
||||
$newsletter3 = $this->createNewsletter(NewsletterEntity::TYPE_STANDARD, NewsletterEntity::STATUS_SENDING);
|
||||
|
||||
$blogPost = $this->wp->wpInsertPost(['post_title' => 'Regular blog post']);
|
||||
|
||||
verify($this->wp->getPost($post1Id))->instanceOf(\WP_Post::class);
|
||||
verify($this->wp->getPost($post2Id))->instanceOf(\WP_Post::class);
|
||||
|
||||
$this->entityManager->flush();
|
||||
$this->entityManager->clear();
|
||||
|
||||
$this->repository->bulkDelete([$newsletter1->getId(), $newsletter2->getId(), $newsletter3->getId()]);
|
||||
verify($this->wp->getPost($post1Id))->null();
|
||||
verify($this->wp->getPost($post2Id))->null();
|
||||
verify($this->wp->getPost($blogPost))->instanceOf(\WP_Post::class);
|
||||
}
|
||||
|
||||
public function testItGetsArchiveNewslettersForSegments() {
|
||||
$types = [
|
||||
NewsletterEntity::TYPE_STANDARD,
|
||||
@ -306,11 +143,11 @@ class NewsletterRepositoryTest extends \MailPoetTest {
|
||||
NewsletterEntity::TYPE_NOTIFICATION_HISTORY,
|
||||
];
|
||||
|
||||
list($newsletters, $sendingQueues) = $this->createNewslettersAndSendingTasks($types);
|
||||
list($newsletters, $scheduledTasks) = $this->createNewslettersAndSendingTasks($types);
|
||||
|
||||
// set the sending queue status of the first newsletter to null
|
||||
$sendingQueues[0]->status = null;
|
||||
$sendingQueues[0]->save();
|
||||
$scheduledTasks[0]->setStatus(null);
|
||||
$this->entityManager->persist($scheduledTasks[0]);
|
||||
|
||||
// trash the last newsletter
|
||||
end($newsletters)->setDeletedAt(new Carbon());
|
||||
@ -345,17 +182,15 @@ class NewsletterRepositoryTest extends \MailPoetTest {
|
||||
|
||||
private function createNewslettersAndSendingTasks(array $types): array {
|
||||
$newsletters = [];
|
||||
$sendingQueues = [];
|
||||
$scheduledTasks = [];
|
||||
for ($i = 0; $i < count($types); $i++) {
|
||||
$newsletters[$i] = $this->createNewsletter($types[$i]);
|
||||
|
||||
$sendingQueues[$i] = SendingTask::create();
|
||||
$sendingQueues[$i]->newsletter_id = $newsletters[$i]->getId();
|
||||
$sendingQueues[$i]->status = SendingQueueEntity::STATUS_COMPLETED;
|
||||
$sendingQueues[$i]->save();
|
||||
$scheduledTasks[$i] = (new ScheduledTaskFactory())->create(SendingQueue::TASK_TYPE, SendingQueueEntity::STATUS_COMPLETED);
|
||||
(new SendingQueueFactory())->create($scheduledTasks[$i], $newsletters[$i]);
|
||||
}
|
||||
|
||||
return [$newsletters, $sendingQueues];
|
||||
return [$newsletters, $scheduledTasks];
|
||||
}
|
||||
|
||||
private function createQueueWithTaskAndSegmentAndSubscribers(NewsletterEntity $newsletter, $status = ScheduledTaskEntity::STATUS_SCHEDULED): SendingQueueEntity {
|
||||
@ -387,93 +222,4 @@ class NewsletterRepositoryTest extends \MailPoetTest {
|
||||
$this->entityManager->flush();
|
||||
return $queue;
|
||||
}
|
||||
|
||||
private function createStatNotification(NewsletterEntity $newsletter): StatsNotificationEntity {
|
||||
$task = new ScheduledTaskEntity();
|
||||
$task->setType('stats_notification');
|
||||
$task->setStatus(ScheduledTaskEntity::STATUS_SCHEDULED);
|
||||
$this->entityManager->persist($task);
|
||||
$statsNotification = new StatsNotificationEntity($newsletter, $task);
|
||||
$this->entityManager->persist($statsNotification);
|
||||
$this->entityManager->flush();
|
||||
return $statsNotification;
|
||||
}
|
||||
|
||||
private function createNewsletterLink(NewsletterEntity $newsletter, SendingQueueEntity $queue): NewsletterLinkEntity {
|
||||
$link = new NewsletterLinkEntity($newsletter, $queue, 'http://example.com', 'abcd');
|
||||
$this->entityManager->persist($link);
|
||||
$this->entityManager->flush();
|
||||
return $link;
|
||||
}
|
||||
|
||||
private function createNewsletterOption(NewsletterEntity $newsletter, NewsletterOptionFieldEntity $field, $value): NewsletterOptionEntity {
|
||||
$option = new NewsletterOptionEntity($newsletter, $field);
|
||||
$option->setValue($value);
|
||||
$this->entityManager->persist($option);
|
||||
$this->entityManager->flush();
|
||||
return $option;
|
||||
}
|
||||
|
||||
private function createNewsletterPost(NewsletterEntity $newsletter, int $postId): NewsletterPostEntity {
|
||||
$post = new NewsletterPostEntity($newsletter, $postId);
|
||||
$this->entityManager->persist($post);
|
||||
$this->entityManager->flush();
|
||||
return $post;
|
||||
}
|
||||
|
||||
private function createNewsletterStatistics(NewsletterEntity $newsletter, SendingQueueEntity $queue, SubscriberEntity $subscriber): StatisticsNewsletterEntity {
|
||||
$statisticsNewsletter = new StatisticsNewsletterEntity($newsletter, $queue, $subscriber);
|
||||
$this->entityManager->persist($statisticsNewsletter);
|
||||
$this->entityManager->flush();
|
||||
return $statisticsNewsletter;
|
||||
}
|
||||
|
||||
private function createOpenStatistics(NewsletterEntity $newsletter, SendingQueueEntity $queue, SubscriberEntity $subscriber): StatisticsOpenEntity {
|
||||
$statistics = new StatisticsOpenEntity($newsletter, $queue, $subscriber);
|
||||
$this->entityManager->persist($statistics);
|
||||
$this->entityManager->flush();
|
||||
return $statistics;
|
||||
}
|
||||
|
||||
private function createClickStatistics(
|
||||
NewsletterEntity $newsletter,
|
||||
SendingQueueEntity $queue,
|
||||
SubscriberEntity $subscriber,
|
||||
NewsletterLinkEntity $link
|
||||
): StatisticsClickEntity {
|
||||
$statistics = new StatisticsClickEntity($newsletter, $queue, $subscriber, $link, 1);
|
||||
$this->entityManager->persist($statistics);
|
||||
$this->entityManager->flush();
|
||||
return $statistics;
|
||||
}
|
||||
|
||||
private function createPurchaseStatistics(
|
||||
NewsletterEntity $newsletter,
|
||||
SendingQueueEntity $queue,
|
||||
StatisticsClickEntity $click,
|
||||
SubscriberEntity $subscriber
|
||||
): StatisticsWooCommercePurchaseEntity {
|
||||
$statistics = new StatisticsWooCommercePurchaseEntity($newsletter, $queue, $click, 1, 'EUR', 100, 'completed');
|
||||
$statistics->setSubscriber($subscriber);
|
||||
$this->entityManager->persist($statistics);
|
||||
$this->entityManager->flush();
|
||||
return $statistics;
|
||||
}
|
||||
|
||||
private function createPurchaseStatsForNewsletter(NewsletterEntity $newsletter): StatisticsWooCommercePurchaseEntity {
|
||||
$queue = $this->createQueueWithTaskAndSegmentAndSubscribers($newsletter, NewsletterEntity::STATUS_SENT); // Null for scheduled task being processed
|
||||
$segment = $newsletter->getNewsletterSegments()->first();
|
||||
$this->assertInstanceOf(NewsletterSegmentEntity::class, $segment);
|
||||
$scheduledTask = $queue->getTask();
|
||||
$this->assertInstanceOf(ScheduledTaskEntity::class, $scheduledTask);
|
||||
$scheduledTaskSubscriber = $this->taskSubscribersRepository->findOneBy(['task' => $scheduledTask]);
|
||||
$this->assertInstanceOf(ScheduledTaskSubscriberEntity::class, $scheduledTaskSubscriber);
|
||||
$link = $this->createNewsletterLink($newsletter, $queue);
|
||||
$this->assertInstanceOf(NewsletterLinkEntity::class, $link);
|
||||
$subscriber = $scheduledTaskSubscriber->getSubscriber();
|
||||
$this->assertInstanceOf(SubscriberEntity::class, $subscriber);
|
||||
$statisticsClick = $this->createClickStatistics($newsletter, $queue, $subscriber, $link);
|
||||
$this->assertInstanceOf(StatisticsClickEntity::class, $statisticsClick);
|
||||
return $this->createPurchaseStatistics($newsletter, $queue, $statisticsClick, $subscriber);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace MailPoet\Newsletter\Scheduler;
|
||||
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
@ -9,11 +10,13 @@ use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTaskSubscribersRepository;
|
||||
use MailPoet\Newsletter\Sending\SendingQueuesRepository;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\Tasks\Sending as SendingTask;
|
||||
use MailPoet\Test\DataFactories\NewsletterOption;
|
||||
use MailPoet\Test\DataFactories\ScheduledTask as ScheduledTaskFactory;
|
||||
use MailPoet\Test\DataFactories\SendingQueue as SendingQueueFactory;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
||||
@ -41,6 +44,9 @@ class WelcomeTest extends \MailPoetTest {
|
||||
/** @var NewsletterEntity */
|
||||
private $newsletter;
|
||||
|
||||
/** @var ScheduledTaskSubscribersRepository */
|
||||
private $scheduledTaskSubscribersRepository;
|
||||
|
||||
public function _before() {
|
||||
parent::_before();
|
||||
$this->segmentRepository = $this->diContainer->get(SegmentsRepository::class);
|
||||
@ -52,6 +58,7 @@ class WelcomeTest extends \MailPoetTest {
|
||||
$this->wpSegment->setType(SegmentEntity::TYPE_WP_USERS);
|
||||
$this->segmentRepository->flush();
|
||||
$this->newsletter = $this->createWelcomeNewsletter();
|
||||
$this->scheduledTaskSubscribersRepository = $this->diContainer->get(ScheduledTaskSubscribersRepository::class);
|
||||
}
|
||||
|
||||
public function testItDoesNotCreateDuplicateWelcomeNotificationSendingTasks() {
|
||||
@ -63,10 +70,10 @@ class WelcomeTest extends \MailPoetTest {
|
||||
]);
|
||||
|
||||
$existingSubscriber = $this->subscriber->getId();
|
||||
$existingQueue = SendingTask::create();
|
||||
$existingQueue->newsletterId = $newsletter->getId();
|
||||
$existingQueue->setSubscribers([$existingSubscriber]);
|
||||
$existingQueue->save();
|
||||
|
||||
$scheduledTask = (new ScheduledTaskFactory())->create(SendingQueue::TASK_TYPE, null);
|
||||
(new SendingQueueFactory())->create($scheduledTask, $newsletter);
|
||||
$this->scheduledTaskSubscribersRepository->setSubscribers($scheduledTask, [$existingSubscriber]);
|
||||
|
||||
// queue is not scheduled
|
||||
$this->welcomeScheduler->createWelcomeNotificationSendingTask($newsletter, $existingSubscriber);
|
||||
|
@ -3,16 +3,20 @@
|
||||
namespace MailPoet\Newsletter\ViewInBrowser;
|
||||
|
||||
use Codeception\Stub\Expected;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTaskSubscribersRepository;
|
||||
use MailPoet\Newsletter\Sending\SendingQueuesRepository;
|
||||
use MailPoet\Newsletter\Url;
|
||||
use MailPoet\Subscribers\LinkTokens;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\Tasks\Sending as SendingTask;
|
||||
use MailPoet\Test\DataFactories\Newsletter;
|
||||
use MailPoet\Test\DataFactories\ScheduledTask as ScheduledTaskFactory;
|
||||
use MailPoet\Test\DataFactories\SendingQueue as SendingQueueFactory;
|
||||
use MailPoet\Util\Security;
|
||||
|
||||
class ViewInBrowserControllerTest extends \MailPoetTest {
|
||||
@ -28,9 +32,6 @@ class ViewInBrowserControllerTest extends \MailPoetTest {
|
||||
/** @var SubscriberEntity */
|
||||
private $subscriber;
|
||||
|
||||
/** @var SendingTask */
|
||||
private $sendingTask;
|
||||
|
||||
/** @var mixed[] */
|
||||
private $browserPreviewData;
|
||||
|
||||
@ -43,6 +44,15 @@ class ViewInBrowserControllerTest extends \MailPoetTest {
|
||||
/** @var Url */
|
||||
private $newsletterUrl;
|
||||
|
||||
/** @var ScheduledTaskSubscribersRepository */
|
||||
private $scheduledTaskSubscribersRepository;
|
||||
|
||||
/** @var ScheduledTaskEntity */
|
||||
private $scheduledTask;
|
||||
|
||||
/** @var SendingQueueEntity */
|
||||
private $sendingQueue;
|
||||
|
||||
public function _before() {
|
||||
// instantiate class
|
||||
$this->viewInBrowserController = $this->diContainer->get(ViewInBrowserController::class);
|
||||
@ -50,6 +60,7 @@ class ViewInBrowserControllerTest extends \MailPoetTest {
|
||||
$this->subscribersRepository = $this->diContainer->get(SubscribersRepository::class);
|
||||
$this->sendingQueuesRepository = $this->diContainer->get(SendingQueuesRepository::class);
|
||||
$this->newslettersRepository = $this->diContainer->get(NewslettersRepository::class);
|
||||
$this->scheduledTaskSubscribersRepository = $this->diContainer->get(ScheduledTaskSubscribersRepository::class);
|
||||
$this->newsletterUrl = $this->diContainer->get(Url::class);
|
||||
|
||||
// create newsletter
|
||||
@ -66,16 +77,14 @@ class ViewInBrowserControllerTest extends \MailPoetTest {
|
||||
$this->subscribersRepository->flush();
|
||||
$this->subscriber = $subscriber;
|
||||
|
||||
// create task & queue
|
||||
$sendingTask = SendingTask::create();
|
||||
$sendingTask->newsletterId = $newsletter->getId();
|
||||
$sendingTask->setSubscribers([$subscriber->getId()]);
|
||||
$sendingTask->updateProcessedSubscribers([$subscriber->getId()]);
|
||||
$this->sendingTask = $sendingTask->save();
|
||||
$this->scheduledTask = (new ScheduledTaskFactory())->create(SendingQueue::TASK_TYPE, null);
|
||||
$this->sendingQueue = (new SendingQueueFactory())->create($this->scheduledTask, $newsletter);
|
||||
$this->scheduledTaskSubscribersRepository->setSubscribers($this->scheduledTask, [$subscriber->getId()]);
|
||||
$this->scheduledTaskSubscribersRepository->updateProcessedSubscribers($this->scheduledTask, [(int)$subscriber->getId()]);
|
||||
|
||||
// build browser preview data
|
||||
$this->browserPreviewData = [
|
||||
'queue_id' => $sendingTask->queue()->id,
|
||||
'queue_id' => $this->sendingQueue->getId(),
|
||||
'subscriber_id' => $subscriber->getId(),
|
||||
'newsletter_id' => $newsletter->getId(),
|
||||
'newsletter_hash' => $newsletter->getHash(),
|
||||
@ -120,10 +129,8 @@ class ViewInBrowserControllerTest extends \MailPoetTest {
|
||||
|
||||
public function testItThrowsWhenSubscriberIsNotOnProcessedList() {
|
||||
$data = $this->browserPreviewData;
|
||||
$sendingTask = $this->sendingTask;
|
||||
$sendingTask->setSubscribers([]);
|
||||
$sendingTask->updateProcessedSubscribers([]);
|
||||
$sendingTask->save();
|
||||
$this->scheduledTaskSubscribersRepository->setSubscribers($this->scheduledTask, []);
|
||||
$this->scheduledTaskSubscribersRepository->updateProcessedSubscribers($this->scheduledTask, []);
|
||||
$this->expectViewThrowsExceptionWithMessage($this->viewInBrowserController, $data, 'Subscriber did not receive the newsletter yet');
|
||||
}
|
||||
|
||||
@ -171,14 +178,14 @@ class ViewInBrowserControllerTest extends \MailPoetTest {
|
||||
'render' => Expected::once(function (bool $isPreview, NewsletterEntity $newsletter, SubscriberEntity $subscriber = null, SendingQueueEntity $queue = null) {
|
||||
$this->assertNotNull($queue); // PHPStan
|
||||
verify($queue)->notNull();
|
||||
verify($queue->getId())->equals($this->sendingTask->id);
|
||||
verify($queue->getId())->equals($this->sendingQueue->getId());
|
||||
}),
|
||||
]);
|
||||
|
||||
$viewInBrowserController = $this->createController($viewInBrowserRenderer);
|
||||
|
||||
$data = $this->browserPreviewData;
|
||||
$data['queueId'] = $this->sendingTask->queue()->id;
|
||||
$data['queueId'] = $this->sendingQueue->getId();
|
||||
$viewInBrowserController->view($data);
|
||||
}
|
||||
|
||||
@ -187,7 +194,7 @@ class ViewInBrowserControllerTest extends \MailPoetTest {
|
||||
'render' => Expected::once(function (bool $isPreview, NewsletterEntity $newsletter, SubscriberEntity $subscriber = null, SendingQueueEntity $queue = null) {
|
||||
$this->assertNotNull($queue); // PHPStan
|
||||
verify($queue)->notNull();
|
||||
verify($queue->getId())->equals($this->sendingTask->queue()->id);
|
||||
verify($queue->getId())->equals($this->sendingQueue->getId());
|
||||
}),
|
||||
]);
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace MailPoet\Newsletter\ViewInBrowser;
|
||||
|
||||
use Codeception\Stub\Expected;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
@ -10,16 +11,16 @@ use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Newsletter\Links\Links;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Newsletter\Renderer\Renderer;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
|
||||
use MailPoet\Newsletter\Sending\SendingQueuesRepository;
|
||||
use MailPoet\Newsletter\Sending\ScheduledTaskSubscribersRepository;
|
||||
use MailPoet\Newsletter\Shortcodes\Shortcodes;
|
||||
use MailPoet\Router\Router;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Settings\TrackingConfig;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\Tasks\Sending as SendingTask;
|
||||
use MailPoet\Test\DataFactories\Newsletter;
|
||||
use MailPoet\Test\DataFactories\NewsletterLink;
|
||||
use MailPoet\Test\DataFactories\ScheduledTask as ScheduledTaskFactory;
|
||||
use MailPoet\Test\DataFactories\SendingQueue as SendingQueueFactory;
|
||||
use MailPoet\WP\Emoji;
|
||||
|
||||
class ViewInBrowserRendererTest extends \MailPoetTest {
|
||||
@ -32,9 +33,6 @@ class ViewInBrowserRendererTest extends \MailPoetTest {
|
||||
/** @var NewsletterEntity */
|
||||
public $newsletter;
|
||||
|
||||
/** @var SendingTask */
|
||||
private $sendingTask;
|
||||
|
||||
/** @var SubscriberEntity */
|
||||
private $subscriber;
|
||||
|
||||
@ -47,16 +45,19 @@ class ViewInBrowserRendererTest extends \MailPoetTest {
|
||||
/** @var SubscribersRepository */
|
||||
private $subscribersRepository;
|
||||
|
||||
/** @var SendingQueuesRepository */
|
||||
private $sendingQueueRepository;
|
||||
|
||||
/** @var NewslettersRepository */
|
||||
private $newsletterRepository;
|
||||
|
||||
/** @var ScheduledTaskEntity */
|
||||
private $scheduledTask;
|
||||
|
||||
/** @var SendingQueueEntity */
|
||||
private $sendingQueue;
|
||||
|
||||
public function _before() {
|
||||
$this->sendingQueueRepository = $this->diContainer->get(SendingQueuesRepository::class);
|
||||
$this->subscribersRepository = $this->diContainer->get(SubscribersRepository::class);
|
||||
$this->newsletterRepository = $this->diContainer->get(NewslettersRepository::class);
|
||||
$scheduledTaskSubscribersRepository = $this->diContainer->get(ScheduledTaskSubscribersRepository::class);
|
||||
$newsletterBody =
|
||||
json_decode(
|
||||
'{
|
||||
@ -123,16 +124,11 @@ class ViewInBrowserRendererTest extends \MailPoetTest {
|
||||
$this->subscriber = $subscriber;
|
||||
|
||||
// create queue
|
||||
$queue = SendingTask::create();
|
||||
$queue->newsletterId = $newsletter->getId();
|
||||
$queue->newsletterRenderedBody = $this->queueRenderedNewsletterWithoutTracking;
|
||||
$queue->setSubscribers([$subscriber->getId()]);
|
||||
$this->sendingTask = $queue->save();
|
||||
$this->scheduledTask = (new ScheduledTaskFactory())->create(SendingQueue::TASK_TYPE, null);
|
||||
$this->sendingQueue = (new SendingQueueFactory())->create($this->scheduledTask, $newsletter);
|
||||
$this->sendingQueue->setNewsletterRenderedBody($this->queueRenderedNewsletterWithoutTracking);
|
||||
$scheduledTaskSubscribersRepository->setSubscribers($this->scheduledTask, [$subscriber->getId()]);
|
||||
$this->newsletterRepository->refresh($newsletter);
|
||||
$scheduledTasksRepository = $this->diContainer->get(ScheduledTasksRepository::class);
|
||||
$scheduledTask = $scheduledTasksRepository->findOneById($this->sendingTask->task()->id);
|
||||
$this->assertInstanceOf(ScheduledTaskEntity::class, $scheduledTask);
|
||||
$scheduledTasksRepository->refresh($scheduledTask);
|
||||
$this->newsletter = $newsletter;
|
||||
|
||||
// create newsletter link associations
|
||||
@ -178,7 +174,7 @@ class ViewInBrowserRendererTest extends \MailPoetTest {
|
||||
$preview = false,
|
||||
$this->newsletter,
|
||||
$this->subscriber,
|
||||
$this->sendingQueueRepository->findOneById($this->sendingTask->queue()->id)
|
||||
$this->sendingQueue
|
||||
);
|
||||
verify($renderedBody)->stringMatchesRegExp('/Newsletter from queue/');
|
||||
}
|
||||
@ -189,7 +185,7 @@ class ViewInBrowserRendererTest extends \MailPoetTest {
|
||||
$preview = false,
|
||||
$this->newsletter,
|
||||
$this->subscriber,
|
||||
$this->sendingQueueRepository->findOneById($this->sendingTask->queue()->id)
|
||||
$this->sendingQueue
|
||||
);
|
||||
verify($renderedBody)->stringContainsString('Hello, First');
|
||||
verify($renderedBody)->stringContainsString(Router::NAME . '&endpoint=view_in_browser');
|
||||
@ -197,7 +193,7 @@ class ViewInBrowserRendererTest extends \MailPoetTest {
|
||||
|
||||
public function testItRewritesLinksToRouterEndpointWhenTrackingIsEnabled() {
|
||||
$this->settings->set('tracking.level', TrackingConfig::LEVEL_PARTIAL);
|
||||
$queue = $this->sendingQueueRepository->findOneById($this->sendingTask->queue()->id);
|
||||
$queue = $this->sendingQueue;
|
||||
$this->assertInstanceOf(SendingQueueEntity::class, $queue);
|
||||
$queue->setNewsletterRenderedBody($this->queueRenderedNewsletterWithTracking);
|
||||
$renderedBody = $this->viewInBrowserRenderer->render(
|
||||
@ -210,7 +206,7 @@ class ViewInBrowserRendererTest extends \MailPoetTest {
|
||||
}
|
||||
|
||||
public function testItConvertsHashedLinksToUrlsWhenPreviewIsEnabledAndNewsletterWasSent() {
|
||||
$queue = $this->sendingQueueRepository->findOneById($this->sendingTask->queue()->id);
|
||||
$queue = $this->sendingQueue;
|
||||
$this->assertInstanceOf(SendingQueueEntity::class, $queue);
|
||||
$queue->setNewsletterRenderedBody($this->queueRenderedNewsletterWithTracking);
|
||||
$renderedBody = $this->viewInBrowserRenderer->render(
|
||||
@ -225,7 +221,7 @@ class ViewInBrowserRendererTest extends \MailPoetTest {
|
||||
}
|
||||
|
||||
public function testRemovesOpenTrackingTagWhenPreviewIsEnabledAndNewsletterWasSent() {
|
||||
$queue = $this->sendingQueueRepository->findOneById($this->sendingTask->queue()->id);
|
||||
$queue = $this->sendingQueue;
|
||||
$this->assertInstanceOf(SendingQueueEntity::class, $queue);
|
||||
$queue->setNewsletterRenderedBody($this->queueRenderedNewsletterWithTracking);
|
||||
$renderedBody = $this->viewInBrowserRenderer->render(
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user