PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.2
MailPoet – Newsletters, Email Marketing, and Automation v5.34.2
5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.0 5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / lib / Newsletter / Embed / NewsletterEmbedService.php
mailpoet / lib / Newsletter / Embed Last commit date
RestApi 1 month ago NewsletterEmbedService.php 1 month ago index.php 1 month ago
NewsletterEmbedService.php
321 lines
1 <?php declare(strict_types = 1);
2
3 namespace MailPoet\Newsletter\Embed;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\Entities\NewsletterEntity;
9 use MailPoet\Entities\SendingQueueEntity;
10 use MailPoet\Newsletter\NewslettersRepository;
11 use MailPoet\Newsletter\Sending\SendingQueuesRepository;
12 use MailPoet\Newsletter\Url as NewsletterUrl;
13 use MailPoet\Router\Endpoints\ViewInBrowser as ViewInBrowserEndpoint;
14 use MailPoet\Router\Router;
15 use MailPoet\WP\Functions as WPFunctions;
16
17 class NewsletterEmbedService {
18 public const DEFAULT_HEIGHT = 800;
19 public const MIN_HEIGHT = 200;
20 public const MAX_HEIGHT = 3000;
21 public const DEFAULT_WIDTH = 640;
22 public const MIN_WIDTH = 320;
23 public const MAX_WIDTH = 1200;
24 public const DEFAULT_SELECTOR_LIMIT = 20;
25 public const MAX_SELECTOR_LIMIT = 100;
26
27 /** @var NewslettersRepository */
28 private $newslettersRepository;
29
30 /** @var SendingQueuesRepository */
31 private $sendingQueuesRepository;
32
33 /** @var NewsletterUrl */
34 private $newsletterUrl;
35
36 /** @var WPFunctions */
37 private $wp;
38
39 public function __construct(
40 NewslettersRepository $newslettersRepository,
41 SendingQueuesRepository $sendingQueuesRepository,
42 NewsletterUrl $newsletterUrl,
43 WPFunctions $wp
44 ) {
45 $this->newslettersRepository = $newslettersRepository;
46 $this->sendingQueuesRepository = $sendingQueuesRepository;
47 $this->newsletterUrl = $newsletterUrl;
48 $this->wp = $wp;
49 }
50
51 public function render(array $rawSettings = []): string {
52 $settings = $this->sanitizeAttributes($rawSettings);
53 return $this->renderSanitized($settings);
54 }
55
56 /**
57 * @param array{newsletterId: int, height: int, width: int, showFallbackLink: bool, fallbackLinkAlignment: string, iframeAlignment: string, showEmailBackground: bool, align: string} $settings
58 */
59 private function renderSanitized(array $settings): string {
60 $newsletterId = $settings['newsletterId'];
61 if ($newsletterId <= 0) {
62 return '';
63 }
64
65 $newsletter = $this->getEmbeddableNewsletter($newsletterId);
66 if (!$newsletter instanceof NewsletterEntity) {
67 return '';
68 }
69
70 $queue = $this->getLatestCompletedQueue($newsletter);
71 if (!$queue instanceof SendingQueueEntity) {
72 return '';
73 }
74
75 $url = $this->getEmbedUrl($newsletter, $queue, $settings);
76 $height = $settings['height'];
77 $width = $settings['width'];
78 $subject = $newsletter->getSubject();
79 if ($subject !== null && $subject !== '') {
80 // translators: %s is the newsletter subject.
81 $title = sprintf(__('MailPoet newsletter: %s', 'mailpoet'), $subject);
82 } else {
83 $title = __('MailPoet newsletter', 'mailpoet');
84 }
85
86 $classNames = 'mailpoet-newsletter-embed';
87 if ($settings['align'] !== '') {
88 $classNames .= ' align' . $settings['align'];
89 }
90
91 $html = '<div'
92 . ' class="' . $this->wp->escAttr($classNames) . '"'
93 . ' style="' . $this->wp->escAttr('text-align:' . $settings['iframeAlignment'] . ';') . '"'
94 . '>';
95 $html .= '<iframe'
96 . ' class="mailpoet-newsletter-embed-iframe"'
97 . ' src="' . $this->wp->escUrl($url) . '"'
98 . ' width="' . $this->wp->escAttr((string)$width) . '"'
99 . ' height="' . $this->wp->escAttr((string)$height) . '"'
100 . ' title="' . $this->wp->escAttr($title) . '"'
101 . ' loading="lazy"'
102 . ' sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox"'
103 . ' style="' . $this->wp->escAttr('width:100%;max-width:' . $width . 'px;height:' . $height . 'px;border:0;background:transparent;') . '"'
104 . '></iframe>';
105
106 if ($settings['showFallbackLink']) {
107 $html .= '<p'
108 . ' class="mailpoet-newsletter-embed-fallback"'
109 . ' style="' . $this->wp->escAttr('text-align:' . $settings['fallbackLinkAlignment'] . ';') . '"'
110 . '>'
111 . '<a href="' . $this->wp->escUrl($url) . '">'
112 . $this->wp->escHtml(__('View full newsletter', 'mailpoet'))
113 . '</a>'
114 . '</p>';
115 }
116
117 $html .= '</div>';
118 return $html;
119 }
120
121 /**
122 * @return array{newsletterId: int, height: int, width: int, showFallbackLink: bool, fallbackLinkAlignment: string, iframeAlignment: string, showEmailBackground: bool, align: string}
123 */
124 public function sanitizeAttributes(array $attributes): array {
125 return [
126 'newsletterId' => $this->sanitizePositiveId($attributes['newsletterId'] ?? null),
127 'height' => $this->sanitizeHeight($attributes['height'] ?? null),
128 'width' => $this->sanitizeWidth($attributes['width'] ?? null),
129 'showFallbackLink' => $this->sanitizeBoolean($attributes['showFallbackLink'] ?? true),
130 'fallbackLinkAlignment' => $this->sanitizeTextAlignment($attributes['fallbackLinkAlignment'] ?? 'center'),
131 'iframeAlignment' => $this->sanitizeTextAlignment($attributes['iframeAlignment'] ?? 'center'),
132 'showEmailBackground' => $this->sanitizeBoolean($attributes['showEmailBackground'] ?? true),
133 'align' => $this->sanitizeAlign($attributes['align'] ?? ''),
134 ];
135 }
136
137 public function getEmbeddableNewsletter(int $newsletterId): ?NewsletterEntity {
138 if ($newsletterId <= 0) {
139 return null;
140 }
141 return $this->newslettersRepository->findEmbeddableNewsletterById($newsletterId);
142 }
143
144 public function getLatestCompletedQueue(NewsletterEntity $newsletter): ?SendingQueueEntity {
145 return $this->sendingQueuesRepository->findLatestCompletedByNewsletter($newsletter);
146 }
147
148 /**
149 * @return array<int, array{id: int, label: string, subject: string, sentAt: ?string, type: string, wpPostId?: int}>
150 */
151 public function getSelectorItems(string $search = '', ?int $limit = null): array {
152 $limit = $this->sanitizeSelectorLimit($limit);
153 $rows = $this->newslettersRepository->findEmbeddableNewsletterRows($this->wp->sanitizeTextField($search), $limit);
154
155 return array_map(function(array $row): array {
156 $sentAt = $this->formatSentAt($row['sentAt'] ?? null);
157 $subject = (string)($row['subject'] ?? '');
158 $label = $subject;
159 if ($sentAt !== null) {
160 $label .= ' - ' . $sentAt;
161 }
162
163 $item = [
164 'id' => (int)$row['id'],
165 'label' => $label,
166 'subject' => $subject,
167 'sentAt' => $sentAt,
168 'type' => (string)($row['type'] ?? ''),
169 ];
170
171 if (!empty($row['wpPostId'])) {
172 $item['wpPostId'] = (int)$row['wpPostId'];
173 }
174
175 return $item;
176 }, $rows);
177 }
178
179 /**
180 * @param mixed $value
181 */
182 private function sanitizePositiveId($value): int {
183 if (!is_scalar($value) || $value === '' || !is_numeric($value)) {
184 return 0;
185 }
186
187 $id = (int)$value;
188 return $id > 0 ? $id : 0;
189 }
190
191 /**
192 * @param mixed $value
193 */
194 private function sanitizeHeight($value): int {
195 if (!is_scalar($value) || $value === '' || !is_numeric($value)) {
196 return self::DEFAULT_HEIGHT;
197 }
198
199 $height = (int)$value;
200 if ($height <= 0) {
201 return self::DEFAULT_HEIGHT;
202 }
203 if ($height < self::MIN_HEIGHT) {
204 return self::MIN_HEIGHT;
205 }
206 if ($height > self::MAX_HEIGHT) {
207 return self::MAX_HEIGHT;
208 }
209 return $height;
210 }
211
212 /**
213 * @param mixed $value
214 */
215 private function sanitizeWidth($value): int {
216 if (!is_scalar($value) || $value === '' || !is_numeric($value)) {
217 return self::DEFAULT_WIDTH;
218 }
219
220 $width = (int)$value;
221 if ($width <= 0) {
222 return self::DEFAULT_WIDTH;
223 }
224 if ($width < self::MIN_WIDTH) {
225 return self::MIN_WIDTH;
226 }
227 if ($width > self::MAX_WIDTH) {
228 return self::MAX_WIDTH;
229 }
230 return $width;
231 }
232
233 /**
234 * @param mixed $value
235 */
236 private function sanitizeBoolean($value): bool {
237 if (is_bool($value)) {
238 return $value;
239 }
240
241 if (is_scalar($value)) {
242 $normalized = strtolower(trim((string)$value));
243 return !in_array($normalized, ['0', 'false', 'no', 'off'], true);
244 }
245
246 return true;
247 }
248
249 /**
250 * @param mixed $value
251 */
252 private function sanitizeTextAlignment($value): string {
253 if (!is_string($value)) {
254 return 'center';
255 }
256
257 return in_array($value, ['left', 'center', 'right'], true) ? $value : 'center';
258 }
259
260 /**
261 * @param mixed $value
262 */
263 private function sanitizeAlign($value): string {
264 if (!is_string($value)) {
265 return '';
266 }
267
268 return in_array($value, ['wide', 'full'], true) ? $value : '';
269 }
270
271 /**
272 * @param mixed $value
273 */
274 private function formatSentAt($value): ?string {
275 if ($value instanceof \DateTimeInterface) {
276 $timestamp = $value->getTimestamp();
277 } elseif (is_string($value) && $value !== '') {
278 $timestamp = strtotime($value);
279 if ($timestamp === false) {
280 return $value;
281 }
282 } else {
283 return null;
284 }
285
286 $dateFormat = (string)$this->wp->getOption('date_format', 'F j, Y');
287 $timeFormat = (string)$this->wp->getOption('time_format', 'g:i a');
288 return $this->wp->dateI18n(trim($dateFormat . ' ' . $timeFormat), $timestamp);
289 }
290
291 private function sanitizeSelectorLimit(?int $limit): int {
292 if (!$limit || $limit < 1) {
293 return self::DEFAULT_SELECTOR_LIMIT;
294 }
295 return min($limit, self::MAX_SELECTOR_LIMIT);
296 }
297
298 /**
299 * @param array{newsletterId: int, height: int, width: int, showFallbackLink: bool, fallbackLinkAlignment: string, iframeAlignment: string, showEmailBackground: bool, align: string} $attributes
300 */
301 private function getEmbedUrl(NewsletterEntity $newsletter, SendingQueueEntity $queue, array $attributes): string {
302 if ($attributes['showEmailBackground']) {
303 return $this->newsletterUrl->getViewInBrowserUrl($newsletter, null, $queue, true);
304 }
305
306 return Router::buildRequest(
307 ViewInBrowserEndpoint::ENDPOINT,
308 ViewInBrowserEndpoint::ACTION_VIEW,
309 [
310 'newsletter_id' => $newsletter->getId(),
311 'newsletter_hash' => $newsletter->getHash(),
312 'subscriber_id' => false,
313 'subscriber_token' => false,
314 'queue_id' => $queue->getId(),
315 'preview' => true,
316 'embed_hide_background' => true,
317 ]
318 );
319 }
320 }
321