PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.9.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.9.0
2.9.0 2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 All 50 releases
← All changes | includes/seo/class-email-report-mailer.php +50 -13 2.3.0 → 2.9.0 View file →
@@ -7,9 +7,9 @@
7 7 *
8 8 * Responsibilities:
9 9 * - Build From/Reply-To/Content-Type headers
10 10 * - Resolve subject template tokens
11 - * - Truncate recipients to plan-allowed maximum (defense-in-depth)
11 + * - Drop invalid and duplicate recipients
12 12 * - Return a structured result with per-recipient outcome
13 13 *
14 14 * Not its job:
15 15 * - Building HTML (that's Email_Report_Renderer)
@@ -24,10 +24,8 @@
24 24 declare(strict_types=1);
25 25
26 26 namespace ThinkRank\SEO;
27 27
28 -use ThinkRank\Core\Plan_Config;
29 -
30 28 if (!defined('ABSPATH')) {
31 29 exit;
32 30 }
33 31
@@ -38,19 +36,26 @@
38 36 */
39 37 final class Email_Report_Mailer {
40 38
41 39 /**
40 + * Subject line when nothing filters it.
41 + *
42 + * `%headline%` is the report's own summary — "12,480 Google clicks
43 + * (+12.4%) in the last 30 days" — supplied by the generator (#742). A
44 + * subject that carries the number gets opened; a label does not.
45 + */
46 + public const DEFAULT_SUBJECT = '%site_title%: %headline%';
47 +
48 + /**
42 49 * Send a rendered email report.
43 50 *
44 - * @param array $config Per-site config (recipients, subject_template).
51 + * @param array $config Resolved config (recipients).
45 52 * @param string $html Rendered HTML body.
46 53 * @param array $tokens Tokens to substitute in subject (date, period, …).
47 54 * @return array{success:bool,recipients:array<string,bool>,subject:string,error?:string}
48 55 */
49 56 public function send(array $config, string $html, array $tokens = []): array {
50 - $recipients = Plan_Config::clamp_email_report_recipients(
51 - (array) ($config['recipients'] ?? [])
52 - );
57 + $recipients = $this->valid_recipients((array) ($config['recipients'] ?? []));
53 58
54 59 if (empty($recipients)) {
55 60 return [
56 61 'success' => false,
@@ -59,10 +64,22 @@
59 64 'error' => __('No valid recipients configured.', 'thinkrank'),
60 65 ];
61 66 }
62 67
63 - $subject = $this->resolve_subject((string) ($config['subject_template'] ?? ''), $tokens);
68 + /**
69 + * Filter the subject template before token substitution.
70 + *
71 + * ThinkRank Pro returns its custom subject line here.
72 + *
73 + * @since 2.6.0
74 + *
75 + * @param string $template Subject template.
76 + * @param array $config Resolved config.
77 + */
78 + $template = (string) apply_filters('thinkrank_email_report_subject_template', self::DEFAULT_SUBJECT, $config);
64 79
80 + $subject = $this->resolve_subject($template, $tokens);
81 +
65 82 /**
66 83 * Filter the rendered HTML one last time before send.
67 84 *
68 85 * Pro plugin uses this to insert tracking pixels, rewrite links,
@@ -104,16 +121,21 @@
104 121 * token set via the `thinkrank_email_report_tokens` filter.
105 122 */
106 123 private function resolve_subject(string $template, array $tokens): string {
107 124 if ($template === '') {
108 - $template = '%site_title% SEO performance report';
125 + $template = self::DEFAULT_SUBJECT;
109 126 }
110 127
111 128 $defaults = [
112 - '%site_title%' => (string) get_bloginfo('name'),
113 - '%site_url%' => (string) home_url(),
114 - '%date%' => wp_date(get_option('date_format', 'Y-m-d')),
115 - '%period%' => (string) ($tokens['%period%'] ?? ''),
129 + '%site_title%' => (string) get_bloginfo('name'),
130 + '%site_url%' => (string) home_url(),
131 + '%date%' => wp_date(get_option('date_format', 'Y-m-d')),
132 + '%period%' => (string) ($tokens['%period%'] ?? ''),
133 + '%headline%' => __('SEO performance report', 'thinkrank'),
134 + '%clicks%' => '',
135 + '%clicks_change%' => '',
136 + '%impressions%' => '',
137 + '%period_days%' => '',
116 138 ];
117 139
118 140 $merged = array_merge($defaults, $tokens);
119 141
@@ -171,8 +193,23 @@
171 193 */
172 194 $headers = (array) apply_filters('thinkrank_email_report_headers', $headers, $config);
173 195
174 196 return array_values(array_filter($headers, 'is_string'));
197 + }
198 +
199 + /**
200 + * Unique, valid addresses, in the order given.
201 + *
202 + * @param array $recipients Candidate addresses.
203 + * @return string[]
204 + */
205 + private function valid_recipients(array $recipients): array {
206 + $valid = array_filter(
207 + array_map(static fn ($r) => is_string($r) ? trim($r) : '', $recipients),
208 + static fn (string $r): bool => $r !== '' && (bool) is_email($r)
209 + );
210 +
211 + return array_values(array_unique($valid));
175 212 }
176 213
177 214 private function first_recipient(array $recipients): string {
178 215 foreach ($recipients as $r) {