PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.13.6
Independent Analytics – WordPress Analytics Plugin v2.13.6
2.15.5 2.15.4 2.15.3 2.15.2 2.15.1 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 1 year ago Email_Reports.php 10 months ago Interval.php 1 year ago Interval_Factory.php 2 years ago
Email_Reports.php
243 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\Timezone;
24 use IAWP\Utils\URL;
25 /** @internal */
26 class Email_Reports
27 {
28 public function __construct()
29 {
30 $monitored_options = ['iawp_email_report_interval', 'iawp_email_report_time', 'iawp_email_report_email_addresses'];
31 foreach ($monitored_options as $option) {
32 \add_action('update_option_' . $option, [$this, 'schedule'], 10, 0);
33 \add_action('add_option_' . $option, [$this, 'schedule'], 10, 0);
34 }
35 // Maybe reschedule when starting day of the week is changed
36 \add_action('update_option_iawp_dow', [$this, 'maybe_reschedule'], 10, 0);
37 \add_action('add_option_iawp_dow', [$this, 'maybe_reschedule'], 10, 0);
38 \add_action('iawp_send_email_report', [$this, 'send_email_report']);
39 }
40 public function schedule()
41 {
42 \wp_unschedule_hook('iawp_send_email_report');
43 if (empty(\IAWPSCOPED\iawp()->get_option('iawp_email_report_email_addresses', []))) {
44 return;
45 }
46 \wp_schedule_event($this->interval()->next_interval_start()->getTimestamp(), $this->interval()->id(), 'iawp_send_email_report');
47 }
48 /**
49 * For testing purposes, get the
50 *
51 * @return DateTime
52 */
53 public function next_event_scheduled_at() : ?DateTime
54 {
55 if (!\wp_next_scheduled('iawp_send_email_report')) {
56 return null;
57 }
58 $date = new DateTime();
59 $date->setTimezone(Timezone::site_timezone());
60 $date->setTimestamp(\wp_next_scheduled('iawp_send_email_report'));
61 return $date;
62 }
63 public function next_email_at_for_humans() : string
64 {
65 if (!\wp_next_scheduled('iawp_send_email_report')) {
66 return \esc_html__('There is no email scheduled.', 'independent-analytics');
67 }
68 $date = $this->interval()->next_interval_start();
69 $day = $date->format(\get_option('date_format'));
70 $time = $date->format(\IAWPSCOPED\iawp()->get_option('time_format', 'g:i a'));
71 return \sprintf(\__('Next email scheduled for %s at %s.', 'independent-analytics'), '<span>' . $day . '</span>', '<span>' . $time . '</span>');
72 }
73 public function maybe_reschedule()
74 {
75 if (!\wp_next_scheduled('iawp_send_email_report')) {
76 return;
77 }
78 if (\IAWPSCOPED\iawp()->get_option('iawp_email_report_interval', 'monthly') != 'weekly') {
79 return;
80 }
81 $this->schedule();
82 }
83 public function send_email_report(bool $is_test_email = \false)
84 {
85 // Email reports should be scheduled every time. CRON jobs run on a fixed interval which cannot
86 // account for daylight saving times changes, causing them to send at the wrong hour as daylight
87 // savings time changes. Months also have a varying number of days and need to be rescheduled
88 // to consistently send on the correct day.
89 $this->schedule();
90 $to = \IAWPSCOPED\iawp()->get_option('iawp_email_report_email_addresses', []);
91 if (empty($to)) {
92 return;
93 }
94 $from = \IAWPSCOPED\iawp()->get_option('iawp_email_report_from_address', \get_option('admin_email'));
95 $reply_to = \IAWPSCOPED\iawp()->get_option('iawp_email_report_reply_to_address', \get_option('admin_email'));
96 $body = $this->get_email_body();
97 if (\count($to) > 1) {
98 for ($i = 0; $i < \count($to); $i++) {
99 if ($i == 0) {
100 continue;
101 }
102 $headers[] = 'Bcc: ' . $to[$i];
103 }
104 }
105 $headers[] = 'From: ' . \get_bloginfo('name') . ' <' . \esc_attr($from) . '>';
106 $headers[] = 'Reply-To: ' . \esc_attr($reply_to);
107 $headers[] = 'Content-Type: text/html; charset=UTF-8';
108 // Prevents WP HTML Mail plugin from breaking email design (https://wordpress.org/plugins/wp-html-mail/)
109 \add_filter('haet_mail_use_template', function () {
110 return \false;
111 });
112 return \wp_mail($to[0], $this->subject_line($is_test_email), $body, $headers);
113 }
114 public function get_email_body($colors = '')
115 {
116 $statistics = new Page_Statistics($this->interval()->date_range());
117 $quick_stats = \array_values(\array_filter($statistics->get_statistics(), function (Statistic $statistics) {
118 return $statistics->is_visible() && $statistics->is_group_plugin_enabled();
119 }));
120 $chart = new \IAWP\Email_Reports\Email_Chart($statistics);
121 $colors = $colors == '' ? \IAWPSCOPED\iawp()->get_option('iawp_email_report_colors', ['#5123a0', '#fafafa', '#3a1e6b', '#fafafa', '#5123a0', '#a985e6', '#ece9f2', '#f7f5fa', '#ece9f2', '#dedae6']) : \explode(',', $colors);
122 $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_site_url())));
123 return \IAWPSCOPED\iawp_blade()->run('email.email', [
124 'site_title' => \get_bloginfo('name'),
125 'site_url' => URL::new(\get_site_url())->get_domain(),
126 'date' => $this->interval()->report_time_period_for_humans(),
127 // The value that needs to change
128 'stats' => $quick_stats,
129 'top_ten' => $this->get_top_ten(),
130 'chart_views' => $chart->views,
131 'chart_title' => $this->interval()->chart_title(),
132 'most_views' => $chart->most_views,
133 'y_labels' => $chart->y_labels,
134 'x_labels' => $chart->x_labels,
135 'colors' => $colors,
136 'footer_text' => $footer_text,
137 ]);
138 }
139 private function interval() : \IAWP\Email_Reports\Interval
140 {
141 return \IAWP\Email_Reports\Interval_Factory::from_option();
142 }
143 private function subject_line(bool $is_test_email) : string
144 {
145 $parts = [];
146 if ($is_test_email) {
147 $parts[] = \__('[Test]', 'independent-analytics');
148 }
149 $parts[] = \__('Analytics Report for', 'independent-analytics');
150 $parts[] = \get_bloginfo('name');
151 $parts[] = '[' . $this->interval()->report_time_period_for_humans() . ']';
152 return \esc_html(\implode(' ', $parts));
153 }
154 private function get_top_ten() : array
155 {
156 $date_range = $this->interval()->date_range();
157 $queries = ['pages' => 'title', 'referrers' => 'referrer', 'countries' => 'country', 'devices' => 'device_type', 'campaigns' => 'title', 'forms' => 'form_title', 'clicks' => 'link_name', 'landing_pages' => 'title', 'exit_pages' => 'title'];
158 $top_ten = [];
159 $title = '';
160 foreach ($queries as $type => $title) {
161 if ($type === 'pages') {
162 $pages_table = new Table_Pages();
163 $query = new Pages($date_range, $pages_table->sanitize_sort_parameters(), 10);
164 $title = \esc_html__('Pages', 'independent-analytics');
165 } elseif ($type === 'referrers') {
166 $referrers_table = new Table_Referrers();
167 $query = new Referrers($date_range, $referrers_table->sanitize_sort_parameters(), 10);
168 $title = \esc_html__('Referrers', 'independent-analytics');
169 } elseif ($type === 'countries') {
170 $geo_table = new Table_Geo();
171 $query = new Countries($date_range, $geo_table->sanitize_sort_parameters(), 10);
172 $title = \esc_html__('Countries', 'independent-analytics');
173 } elseif ($type === 'devices') {
174 $devices_table = new Table_Devices();
175 $query = new Device_Types($date_range, $devices_table->sanitize_sort_parameters(), 10);
176 $title = \esc_html__('Devices', 'independent-analytics');
177 } elseif ($type === 'campaigns') {
178 $campaigns_table = new Table_Campaigns();
179 $query = new Campaigns($date_range, $campaigns_table->sanitize_sort_parameters(), 10);
180 $title = \esc_html__('Campaigns', 'independent-analytics');
181 } elseif ($type === 'forms') {
182 // This is a special case for form submissions which doesn't have an associated table
183 $column = new Column(['id' => 'submissions', 'name' => \__('Submissions', 'independent-analytics'), 'type' => 'int']);
184 $sort_configuration = new Sort_Configuration($column);
185 $query = new Forms($date_range, $sort_configuration, 10);
186 $title = \esc_html__('Forms', 'independent-analytics');
187 } elseif ($type === 'clicks') {
188 $clicks_table = new Table_Clicks();
189 $query = new Link_Patterns($date_range, $clicks_table->sanitize_sort_parameters(), 10);
190 $title = \esc_html__('Link Patterns', 'independent-analytics');
191 } elseif ($type === 'landing_pages') {
192 $pages_table = new Table_Pages();
193 $query = new Pages($date_range, $pages_table->sanitize_sort_parameters('entrances'), 10);
194 $title = \esc_html__('Landing Pages', 'independent-analytics');
195 } elseif ($type === 'exit_pages') {
196 $pages_table = new Table_Pages();
197 $query = new Pages($date_range, $pages_table->sanitize_sort_parameters('exits'), 10);
198 $title = \esc_html__('Exit Pages', 'independent-analytics');
199 } else {
200 continue;
201 }
202 $rows = \array_map(function ($row, $index) use($type) {
203 if ($type == 'referrers') {
204 $edited_title = $row->referrer();
205 } elseif ($type == 'countries') {
206 $edited_title = $row->country();
207 } elseif ($type == 'devices') {
208 $edited_title = $row->device_type();
209 } elseif ($type == 'campaigns') {
210 $edited_title = $row->utm_campaign();
211 } elseif ($type == 'forms') {
212 $edited_title = $row->form_title();
213 } elseif ($type == 'clicks') {
214 $edited_title = $row->link_name();
215 } else {
216 $edited_title = $row->title();
217 }
218 $edited_title = \mb_strlen($edited_title) > 30 ? \mb_substr($edited_title, 0, 30) . '...' : $edited_title;
219 $metric = 'views';
220 if ($type == 'clicks') {
221 $metric = 'link_clicks';
222 } elseif ($type == 'forms') {
223 $metric = 'submissions';
224 } elseif ($type == 'landing_pages') {
225 $metric = 'entrances';
226 } elseif ($type == 'exit_pages') {
227 $metric = 'exits';
228 }
229 return ['title' => $edited_title, 'views' => $row->{$metric}()];
230 }, $query->rows(), \array_keys($query->rows()));
231 if (\count($rows) == 0) {
232 continue;
233 }
234 // Remove landing page and exit rows with 0 entrances/exits
235 $rows = \array_filter($rows, function ($row) {
236 return $row['views'] > 0;
237 });
238 $top_ten[$type] = ['title' => $title, 'rows' => $rows];
239 }
240 return $top_ten;
241 }
242 }
243