PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.15.0
Independent Analytics – WordPress Analytics Plugin v2.15.0
2.15.0 2.14.10 trunk 1.1 1.10 1.10.1 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.17.1 1.17.2 1.17.3 1.17.4 1.18 1.18.1 1.19.0 1.19.1 1.2 1.20.0 1.21.0 1.22.0 1.22.1 1.23.0 1.23.1 1.24.0 1.24.1 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.28.1 1.28.2 1.28.3 1.29.0 1.3 1.30.0 1.30.1 1.4 1.5 1.6 1.7 1.8 1.9 2.0.0 2.0.1 2.1.4 2.1.5 2.1.6 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.10 2.11.2 2.11.3 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.11.9 2.12.0 2.12.1 2.12.2 2.13.1 2.13.2 2.13.5 2.13.6 2.14.0 2.14.1 2.14.2 2.14.4 2.14.6 2.14.7 2.14.8 2.14.9 2.2.0 2.2.1 2.3.1 2.3.2 2.4.2 2.4.3 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.7.1 2.7.2 2.7.3 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7
independent-analytics / IAWP / Email_Reports / Email_Reports.php
independent-analytics / IAWP / Email_Reports Last commit date
Email_Chart.php 5 months ago Email_Reports.php 2 months ago Interval.php 5 days ago Interval_Factory.php 2 years ago
Email_Reports.php
263 lines
1 <?php
2
3 namespace IAWP\Email_Reports;
4
5 use DateTime;
6 use IAWP\Rows\Campaigns;
7 use IAWP\Rows\Countries;
8 use IAWP\Rows\Device_Types;
9 use IAWP\Rows\Forms;
10 use IAWP\Rows\Link_Patterns;
11 use IAWP\Rows\Pages;
12 use IAWP\Rows\Referrers;
13 use IAWP\Sort_Configuration;
14 use IAWP\Statistics\Page_Statistics;
15 use IAWP\Statistics\Statistic;
16 use IAWP\Tables\Columns\Column;
17 use IAWP\Tables\Table_Campaigns;
18 use IAWP\Tables\Table_Clicks;
19 use IAWP\Tables\Table_Devices;
20 use IAWP\Tables\Table_Geo;
21 use IAWP\Tables\Table_Pages;
22 use IAWP\Tables\Table_Referrers;
23 use IAWP\Utils\Format;
24 use IAWP\Utils\Timezone;
25 use IAWP\Utils\URL;
26 /** @internal */
27 class Email_Reports
28 {
29 public function __construct()
30 {
31 $monitored_options = ['iawp_email_report_interval', 'iawp_email_report_time', 'iawp_email_report_email_addresses', 'iawp_email_report_paused'];
32 foreach ($monitored_options as $option) {
33 \add_action('update_option_' . $option, [$this, 'schedule'], 10, 0);
34 \add_action('add_option_' . $option, [$this, 'schedule'], 10, 0);
35 }
36 // Maybe reschedule when starting day of the week is changed
37 \add_action('update_option_iawp_dow', [$this, 'maybe_reschedule'], 10, 0);
38 \add_action('add_option_iawp_dow', [$this, 'maybe_reschedule'], 10, 0);
39 \add_action('iawp_send_email_report', [$this, 'send_email_report']);
40 }
41 public function schedule()
42 {
43 \wp_unschedule_hook('iawp_send_email_report');
44 if (\get_option('iawp_email_report_paused', '0') === '1') {
45 return;
46 }
47 if (empty(\IAWPSCOPED\iawp()->get_option('iawp_email_report_email_addresses', []))) {
48 return;
49 }
50 \wp_schedule_event($this->interval()->next_interval_start()->getTimestamp(), $this->interval()->id(), 'iawp_send_email_report');
51 }
52 /**
53 * For testing purposes, get the
54 *
55 * @return DateTime
56 */
57 public function next_event_scheduled_at() : ?DateTime
58 {
59 if (!\wp_next_scheduled('iawp_send_email_report')) {
60 return null;
61 }
62 $date = new DateTime('now', Timezone::utc_timezone());
63 $date->setTimezone(Timezone::site_timezone());
64 $date->setTimestamp(\wp_next_scheduled('iawp_send_email_report'));
65 return $date;
66 }
67 public function next_email_at_for_humans() : string
68 {
69 if (!\wp_next_scheduled('iawp_send_email_report')) {
70 return \esc_html__('There is no email scheduled.', 'independent-analytics');
71 }
72 $date = $this->interval()->next_interval_start();
73 $day = $date->format(Format::date());
74 $time = $date->format(Format::time());
75 return \sprintf(\__('Next email scheduled for %s at %s.', 'independent-analytics'), '<span>' . $day . '</span>', '<span>' . $time . '</span>');
76 }
77 public function maybe_reschedule()
78 {
79 if (!\wp_next_scheduled('iawp_send_email_report')) {
80 return;
81 }
82 if (\IAWPSCOPED\iawp()->get_option('iawp_email_report_interval', 'monthly') != 'weekly') {
83 return;
84 }
85 $this->schedule();
86 }
87 public function send_email_report(bool $is_test_email = \false, string $recipient = 'all')
88 {
89 // Email reports should be scheduled every time. CRON jobs run on a fixed interval which cannot
90 // account for daylight saving times changes, causing them to send at the wrong hour as daylight
91 // savings time changes. Months also have a varying number of days and need to be rescheduled
92 // to consistently send on the correct day.
93 $this->schedule();
94 $to = \IAWPSCOPED\iawp()->get_option('iawp_email_report_email_addresses', []);
95 if (empty($to)) {
96 return;
97 }
98 if (\get_option('iawp_email_report_paused', '0') === '1') {
99 return;
100 }
101 $from = \IAWPSCOPED\iawp()->get_option('iawp_email_report_from_address', \get_option('admin_email'));
102 $reply_to = \IAWPSCOPED\iawp()->get_option('iawp_email_report_reply_to_address', \get_option('admin_email'));
103 $body = $this->get_email_body();
104 // Sends To first email and BCCs to the rest
105 if ($recipient == 'all') {
106 if (\count($to) > 1) {
107 for ($i = 0; $i < \count($to); $i++) {
108 if ($i == 0) {
109 continue;
110 }
111 $headers[] = 'Bcc: ' . $to[$i];
112 }
113 }
114 }
115 $headers[] = 'From: ' . \get_bloginfo('name') . ' <' . \esc_attr($from) . '>';
116 $headers[] = 'Reply-To: ' . \esc_attr($reply_to);
117 $headers[] = 'Content-Type: text/html; charset=UTF-8';
118 // Prevents WP HTML Mail plugin from breaking email design (https://wordpress.org/plugins/wp-html-mail/)
119 \add_filter('haet_mail_use_template', function () {
120 return \false;
121 });
122 return \wp_mail($to[0], $this->subject_line($is_test_email), $body, $headers);
123 }
124 public function get_email_body(array $preview_colors = [])
125 {
126 $statistics = new Page_Statistics($this->interval()->date_range());
127 $statistics->fetch();
128 $quick_stats = \array_values(\array_filter($statistics->get_statistics(), function (Statistic $statistics) {
129 return $statistics->is_visible() && $statistics->is_group_plugin_enabled();
130 }));
131 $chart = new \IAWP\Email_Reports\Email_Chart($statistics);
132 $footer_text = \IAWPSCOPED\iawp()->get_option('iawp_email_report_footer', \sprintf(\esc_html__('This email was generated and delivered by %s', 'independent-analytics'), \esc_url(\get_home_url())));
133 return \IAWPSCOPED\iawp_render('email.email', [
134 'site_title' => \get_bloginfo('name'),
135 'site_url' => URL::new(\get_home_url())->get_domain(),
136 'date' => $this->interval()->report_time_period_for_humans(),
137 // The value that needs to change
138 'stats' => $quick_stats,
139 'top_ten' => $this->get_top_ten(),
140 'chart_views' => $chart->views,
141 'chart_title' => $this->interval()->chart_title(),
142 'most_views' => $chart->most_views,
143 'y_labels' => $chart->y_labels,
144 'x_labels' => $chart->x_labels,
145 'colors' => $this->get_email_colors($preview_colors),
146 'footer_text' => $footer_text,
147 ]);
148 }
149 // Note: looping over the defaults to prevent missing array key errors if new colors are added
150 private function get_email_colors(array $preview_colors) : array
151 {
152 $colors = ['#5123a0', '#fafafa', '#3a1e6b', '#fafafa', '#5123a0', '#a985e6', '#ece9f2', '#f7f5fa', '#ece9f2', '#dedae6', '#000000'];
153 $saved_colors = !empty($preview_colors) ? $preview_colors : \IAWPSCOPED\iawp()->get_option('iawp_email_report_colors', $colors);
154 for ($i = 0; $i < \count($saved_colors); $i++) {
155 $colors[$i] = $saved_colors[$i];
156 }
157 return $colors;
158 }
159 private function interval() : \IAWP\Email_Reports\Interval
160 {
161 return \IAWP\Email_Reports\Interval_Factory::from_option();
162 }
163 private function subject_line(bool $is_test_email) : string
164 {
165 $parts = [];
166 if ($is_test_email) {
167 $parts[] = \__('[Test]', 'independent-analytics');
168 }
169 $parts[] = \__('Analytics Report for', 'independent-analytics');
170 $parts[] = \get_bloginfo('name');
171 $parts[] = '[' . $this->interval()->report_time_period_for_humans() . ']';
172 return \esc_html(\implode(' ', $parts));
173 }
174 private function get_top_ten() : array
175 {
176 $date_range = $this->interval()->date_range();
177 $queries = ['pages' => 'title', 'referrers' => 'referrer', 'countries' => 'country', 'devices' => 'device_type', 'campaigns' => 'title', 'forms' => 'form_title', 'clicks' => 'link_name', 'landing_pages' => 'title', 'exit_pages' => 'title'];
178 $top_ten = [];
179 $title = '';
180 foreach ($queries as $type => $title) {
181 if ($type === 'pages') {
182 $pages_table = new Table_Pages();
183 $query = new Pages($date_range, $pages_table->sanitize_sort_parameters('views'), 10);
184 $title = \esc_html__('Pages', 'independent-analytics');
185 } elseif ($type === 'referrers') {
186 $referrers_table = new Table_Referrers();
187 $query = new Referrers($date_range, $referrers_table->sanitize_sort_parameters('views'), 10);
188 $title = \esc_html__('Referrers', 'independent-analytics');
189 } elseif ($type === 'countries') {
190 $geo_table = new Table_Geo();
191 $query = new Countries($date_range, $geo_table->sanitize_sort_parameters('views'), 10);
192 $title = \esc_html__('Countries', 'independent-analytics');
193 } elseif ($type === 'devices') {
194 $devices_table = new Table_Devices();
195 $query = new Device_Types($date_range, $devices_table->sanitize_sort_parameters('views'), 10);
196 $title = \esc_html__('Devices', 'independent-analytics');
197 } elseif ($type === 'campaigns') {
198 $campaigns_table = new Table_Campaigns();
199 $query = new Campaigns($date_range, $campaigns_table->sanitize_sort_parameters('views'), 10);
200 $title = \esc_html__('Campaigns', 'independent-analytics');
201 } elseif ($type === 'forms') {
202 // This is a special case for form submissions which doesn't have an associated table
203 $column = new Column(['id' => 'submissions', 'name' => \__('Submissions', 'independent-analytics'), 'type' => 'int']);
204 $sort_configuration = new Sort_Configuration($column);
205 $query = new Forms($date_range, $sort_configuration, 10);
206 $title = \esc_html__('Forms', 'independent-analytics');
207 } elseif ($type === 'clicks') {
208 $clicks_table = new Table_Clicks();
209 $query = new Link_Patterns($date_range, $clicks_table->sanitize_sort_parameters('views'), 10);
210 $title = \esc_html__('Link Patterns', 'independent-analytics');
211 } elseif ($type === 'landing_pages') {
212 $pages_table = new Table_Pages();
213 $query = new Pages($date_range, $pages_table->sanitize_sort_parameters('entrances'), 10);
214 $title = \esc_html__('Landing Pages', 'independent-analytics');
215 } elseif ($type === 'exit_pages') {
216 $pages_table = new Table_Pages();
217 $query = new Pages($date_range, $pages_table->sanitize_sort_parameters('exits'), 10);
218 $title = \esc_html__('Exit Pages', 'independent-analytics');
219 } else {
220 continue;
221 }
222 $rows = \array_map(function ($row, $index) use($type) {
223 if ($type == 'referrers') {
224 $edited_title = $row->referrer();
225 } elseif ($type == 'countries') {
226 $edited_title = $row->country();
227 } elseif ($type == 'devices') {
228 $edited_title = $row->device_type();
229 } elseif ($type == 'campaigns') {
230 $edited_title = $row->utm_campaign();
231 } elseif ($type == 'forms') {
232 $edited_title = $row->form_title();
233 } elseif ($type == 'clicks') {
234 $edited_title = $row->link_name();
235 } else {
236 $edited_title = $row->title();
237 }
238 $edited_title = \mb_strlen($edited_title) > 30 ? \mb_substr($edited_title, 0, 30) . '...' : $edited_title;
239 $metric = 'views';
240 if ($type == 'clicks') {
241 $metric = 'link_clicks';
242 } elseif ($type == 'forms') {
243 $metric = 'submissions';
244 } elseif ($type == 'landing_pages') {
245 $metric = 'entrances';
246 } elseif ($type == 'exit_pages') {
247 $metric = 'exits';
248 }
249 return ['title' => $edited_title, 'views' => $row->{$metric}()];
250 }, $query->rows(), \array_keys($query->rows()));
251 if (\count($rows) == 0) {
252 continue;
253 }
254 // Remove landing page and exit rows with 0 entrances/exits
255 $rows = \array_filter($rows, function ($row) {
256 return $row['views'] > 0;
257 });
258 $top_ten[$type] = ['title' => $title, 'rows' => $rows];
259 }
260 return $top_ten;
261 }
262 }
263