PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.8.4
Independent Analytics – WordPress Analytics Plugin v2.8.4
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 / Tables / Table.php
independent-analytics / IAWP / Tables Last commit date
Columns 1 year ago Groups 2 years ago Table.php 1 year ago Table_Campaigns.php 2 years ago Table_Devices.php 2 years ago Table_Geo.php 2 years ago Table_Pages.php 2 years ago Table_Referrers.php 2 years ago
Table.php
441 lines
1 <?php
2
3 namespace IAWP\Tables;
4
5 use IAWP\Campaign_Builder;
6 use IAWP\Dashboard_Options;
7 use IAWP\Date_Picker\Date_Picker;
8 use IAWP\Filters;
9 use IAWP\Form_Submissions\Form;
10 use IAWP\Icon_Directory_Factory;
11 use IAWP\Plugin_Group;
12 use IAWP\Rows\Filter;
13 use IAWP\Sort_Configuration;
14 use IAWP\Statistics\Statistics;
15 use IAWP\Tables\Columns\Column;
16 use IAWP\Tables\Groups\Group;
17 use IAWP\Tables\Groups\Groups;
18 use IAWP\Utils\CSV;
19 use IAWP\Utils\Currency;
20 use IAWP\Utils\Number_Formatter;
21 use IAWP\Utils\Security;
22 use IAWP\Utils\URL;
23 use IAWP\Utils\WordPress_Site_Date_Format_Pattern;
24 use IAWPSCOPED\Illuminate\Support\Str;
25 use IAWPSCOPED\Proper\Timezone;
26 /** @internal */
27 abstract class Table
28 {
29 private $filters;
30 private $visible_columns;
31 private $group;
32 private $is_new_group;
33 /** @var ?Statistics */
34 private $statistics;
35 /**
36 * @param string|null $group_id
37 * @param bool $is_new_group
38 */
39 public function __construct(?string $group_id = null, bool $is_new_group = \false)
40 {
41 $this->visible_columns = Dashboard_Options::getInstance()->visible_columns();
42 $this->group = $this->groups()->find_by_id($group_id);
43 $this->is_new_group = $is_new_group;
44 $this->filters = new Filters();
45 }
46 protected abstract function groups() : Groups;
47 /**
48 * @return array<Column>
49 */
50 protected abstract function local_columns() : array;
51 protected abstract function table_name() : string;
52 public function visible_column_ids()
53 {
54 $visible_columns = [];
55 foreach ($this->get_columns() as $column) {
56 if ($column->is_visible()) {
57 $visible_columns[] = $column->id();
58 }
59 }
60 return $visible_columns;
61 }
62 public function group() : Group
63 {
64 return $this->group;
65 }
66 public function column_picker_html() : string
67 {
68 return \IAWPSCOPED\iawp_blade()->run('plugin-group-options', ['option_type' => 'columns', 'option_name' => \__('Toggle Columns', 'independent-analytics'), 'option_icon' => 'columns', 'plugin_groups' => Plugin_Group::get_plugin_groups(), 'options' => $this->get_columns(\true)]);
69 }
70 public function get_table_toolbar_markup()
71 {
72 return \IAWPSCOPED\iawp_blade()->run('tables.table-toolbar', ['plugin_groups' => Plugin_Group::get_plugin_groups(), 'columns' => $this->get_columns(\true), 'groups' => $this->groups(), 'current_group' => $this->group()]);
73 }
74 public function get_table_markup(string $sort_column, string $sort_direction)
75 {
76 return \IAWPSCOPED\iawp_blade()->run('tables.table', ['table' => $this, 'table_name' => $this->table_name(), 'all_columns' => $this->get_columns(), 'visible_column_count' => $this->visible_column_count(), 'number_of_shown_rows' => 0, 'rows' => [], 'render_skeleton' => \true, 'page_size' => \IAWPSCOPED\iawp()->pagination_page_size(), 'sort_column' => $sort_column, 'sort_direction' => $sort_direction, 'has_campaigns' => Campaign_Builder::has_campaigns()]);
77 }
78 public function set_statistics(Statistics $statistics)
79 {
80 $this->statistics = $statistics;
81 }
82 public function get_row_data_attributes($row)
83 {
84 $html = '';
85 foreach ($this->get_columns() as $column) {
86 $id = $column->id();
87 $data_val = $row->{$id}();
88 $html .= ' data-' . \esc_attr($column->id()) . '="' . \esc_attr($data_val) . '"';
89 }
90 return $html;
91 }
92 public function get_cell_content($row, Column $column)
93 {
94 $column_id = $column->id();
95 if (\is_null($row->{$column_id}())) {
96 return '-';
97 }
98 if ($column_id == 'title' && $row->is_deleted()) {
99 return Security::string($row->{$column_id}()) . ' <span class="deleted-label">' . \esc_html__('(deleted)', 'independent-analytics') . '</span>';
100 } elseif ($column_id == 'views') {
101 $views = Number_Formatter::decimal($row->views());
102 // Getting a divide by zero error from the line below?
103 // It's likely an issue with $this->views which is an instance of Views. Make sure the queries there are working.
104 $views_percentage = Number_Formatter::percent($row->views() / $this->statistics->get_statistic('views')->value() * 100, 2);
105 return '<span class="no-wrap">' . Security::string($views) . '</span> <span class="percentage">(' . Security::string($views_percentage) . ')</span>';
106 } elseif ($column_id == 'visitors') {
107 $visitors = Number_Formatter::decimal($row->visitors());
108 $visitors_percentage = Number_Formatter::percent($row->visitors() / $this->statistics->get_statistic('visitors')->value() * 100, 2);
109 return '<span class="no-wrap">' . Security::string($visitors) . '</span> <span class="percentage">(' . Security::string($visitors_percentage) . ')</span>';
110 } elseif ($column_id == 'sessions') {
111 $sessions = Number_Formatter::decimal($row->sessions());
112 $sessions_percentage = Number_Formatter::percent($row->sessions() / $this->statistics->get_statistic('sessions')->value() * 100, 2);
113 return '<span class="no-wrap">' . Security::string($sessions) . '</span> <span class="percentage">(' . Security::string($sessions_percentage) . ')</span>';
114 } elseif ($column_id === 'entrances') {
115 $entrances = Number_Formatter::decimal($row->entrances());
116 $entrances_percentage = Number_Formatter::percent($row->entrances() / $this->statistics->get_statistic('sessions')->value() * 100, 2);
117 return '<span class="no-wrap">' . Security::string($entrances) . '</span> <span class="percentage">(' . Security::string($entrances_percentage) . ')</span>';
118 } elseif ($column_id === 'exits') {
119 $exits = Number_Formatter::decimal($row->exits());
120 $exits_percentage = Number_Formatter::percent($row->exits() / $this->statistics->get_statistic('sessions')->value() * 100, 2);
121 return '<span class="no-wrap">' . Security::string($exits) . '</span> <span class="percentage">(' . Security::string($exits_percentage) . ')</span>';
122 } elseif ($column_id === 'bounce_rate') {
123 return Security::string(Number_Formatter::percent($row->bounce_rate()));
124 } elseif ($column_id === 'average_session_duration' || $column_id === 'average_view_duration') {
125 return Number_Formatter::second_to_minute_timestamp($row->{$column_id}());
126 } elseif ($column_id === 'views_growth' || $column_id === 'visitors_growth' || $column_id === 'wc_conversion_rate' || $column_id === 'exit_percent' || Str::startsWith($column_id, 'form_conversion_rate')) {
127 return Number_Formatter::percent($row->{$column_id}(), 2);
128 } elseif ($column_id == 'url') {
129 if ($row->is_deleted()) {
130 return \urldecode(\esc_url($row->url()));
131 } else {
132 return '<a href="' . \esc_url($row->url(\true)) . '" target="_blank" class="external-link">' . \urldecode(\esc_url($row->url())) . '<span class="dashicons dashicons-external"></span></a>';
133 }
134 } elseif ($column_id == 'author') {
135 return Security::html($row->avatar()) . ' ' . Security::string($row->author());
136 } elseif ($column_id == 'date') {
137 return Security::string(\date(WordPress_Site_Date_Format_Pattern::for_php(), \strtotime($row->date())));
138 } elseif ($column_id == 'type' && \method_exists($row, 'icon') && \method_exists($row, 'type')) {
139 return $row->icon(0) . ' ' . Security::string($row->type());
140 } elseif ($column_id == 'referrer' && $row->has_link()) {
141 return '<a href="' . \esc_url($row->referrer_url()) . '" target="_blank" class="external-link">' . Security::string($row->referrer()) . '<span class="dashicons dashicons-external"></span></a>';
142 } elseif ($column_id === 'device_type') {
143 return Icon_Directory_Factory::device_types()->find($row->device_type()) . Security::string($row->device_type());
144 } elseif ($column_id === 'browser') {
145 return Icon_Directory_Factory::browsers()->find($row->browser()) . Security::string($row->browser());
146 } elseif ($column_id === 'os') {
147 return Icon_Directory_Factory::operating_systems()->find($row->os()) . Security::string($row->os());
148 } elseif ($column_id === 'country') {
149 return Icon_Directory_Factory::flags()->find($row->country_code()) . Security::string($row->country());
150 } elseif ($column_id === 'wc_gross_sales' || $column_id === 'wc_refunded_amount' || $column_id === 'wc_net_sales' || $column_id === 'wc_average_order_volume') {
151 return Security::string(Currency::format($row->{$column_id}()));
152 } elseif ($column_id === 'wc_earnings_per_visitor') {
153 return Security::string(Currency::format($row->{$column_id}(), \false));
154 } elseif ($column_id === 'views_per_session') {
155 return Number_Formatter::decimal($row->{$column_id}(), 2);
156 } else {
157 return Security::string($row->{$column_id}());
158 }
159 }
160 public function output_report_toolbar()
161 {
162 $options = Dashboard_Options::getInstance();
163 $start = $options->get_date_range()->start()->setTimezone(Timezone::site_timezone());
164 $end = $options->get_date_range()->end()->setTimezone(Timezone::site_timezone());
165 ?>
166 <div id="toolbar" class="toolbar" data-filter-count="<?php
167 echo \count($options->filters());
168 ?>">
169 <div class="date-picker-parent">
170 <div class="modal-parent dates">
171 <button id="dates-button"
172 data-testid="open-calendar"
173 class="iawp-button"
174 data-action="dates#toggleModal"
175 data-dates-target="modalButton"
176 >
177 <span class="dashicons dashicons-calendar-alt"></span>
178 <span class="iawp-label"><?php
179 echo \esc_html($options->get_date_range()->label());
180 ?></span>
181 </button>
182 <div id="modal-dates"
183 class="iawp-modal large dates"
184 data-dates-target="modal"
185 >
186 <?php
187 echo (new Date_Picker($start, $end, $options->relative_range_id()))->calendar_html();
188 ?>
189 </div>
190 </div>
191 </div>
192 <div class="filter-parent">
193 <?php
194 echo $this->filters()->get_filters_html($this->get_columns());
195 ?>
196 </div>
197 <div class="download-options-parent" data-controller="modal">
198 <div class="modal-parent downloads">
199 <button id="download-options" data-modal-target="modalButton" data-action="click->modal#toggleModal" class="download-options">
200 <?php
201 \esc_html_e('Download Report', 'independent-analytics');
202 ?>
203 </button>
204 <div class="iawp-modal small downloads" data-modal-target="modal">
205 <div class="modal-inner">
206 <div class="title-small">
207 <?php
208 \esc_html_e('Choose a format', 'independent-analytics');
209 ?>
210 <span data-report-target="spinner" class="dashicons dashicons-update spin hidden"></span>
211 </div>
212 <button id="download-csv" class="iawp-button" data-report-target="exportCSV" data-action="report#exportCSV">
213 <span class="dashicons dashicons-media-spreadsheet"></span>
214 <span class="iawp-label">
215 <?php
216 \esc_html_e('Download CSV', 'independent-analytics');
217 ?>
218 </span>
219 </button>
220 <button id="download-pdf" class="iawp-button" data-report-target="exportPDF" data-action="report#exportPDF" disabled="disabled">
221 <span class="dashicons dashicons-pdf"></span>
222 <span class="iawp-label">
223 <?php
224 \esc_html_e('Download PDF', 'independent-analytics');
225 ?>
226 </span>
227 </button>
228 </div>
229 </div>
230 </div>
231 </div>
232 </div><?php
233 }
234 public function filters_template_html() : string
235 {
236 return $this->filters()->get_condition_html($this->get_columns());
237 }
238 public function filters_condition_buttons_html(array $filters) : string
239 {
240 return $this->filters()->condition_buttons_html($filters);
241 }
242 public final function csv(array $rows, bool $is_dashboard_export = \false) : CSV
243 {
244 $columns = $this->get_columns();
245 $csv_header = [];
246 $csv_rows = [];
247 foreach ($columns as $column) {
248 if (!$this->include_column_in_csv($column, $is_dashboard_export)) {
249 continue;
250 }
251 $csv_header[] = $column->name();
252 }
253 foreach ($rows as $row) {
254 $csv_row = [];
255 foreach ($columns as $column) {
256 if (!$this->include_column_in_csv($column, $is_dashboard_export)) {
257 continue;
258 }
259 $column_id = $column->id();
260 $value = $row->{$column_id}();
261 // Todo - This logic is similar to the rendering logic for table cells. This should
262 // all be handled via the column class itself.
263 if (\is_null($value)) {
264 $csv_row[] = '-';
265 } elseif ($column_id === 'date') {
266 $csv_row[] = \date(WordPress_Site_Date_Format_Pattern::for_php(), \strtotime($value));
267 } elseif ($column_id === 'average_session_duration' || $column_id === 'average_view_duration') {
268 $csv_row[] = Number_Formatter::second_to_minute_timestamp($value);
269 } elseif ($column_id === 'views_per_session') {
270 $csv_row[] = Number_Formatter::decimal($value, 2);
271 } else {
272 $csv_row[] = $row->{$column_id}();
273 }
274 }
275 $csv_rows[] = $csv_row;
276 }
277 $csv = new CSV($csv_header, $csv_rows);
278 return $csv;
279 }
280 /**
281 * @param array[] $filters Raw filter associative arrays
282 *
283 * @return Filter[]
284 */
285 public function sanitize_filters(array $filters) : array
286 {
287 return \array_values(\array_filter(\array_map(function ($filter) {
288 return $this->sanitize_filter($filter);
289 }, $filters)));
290 }
291 public function sanitize_filter(array $filter) : ?Filter
292 {
293 $column = $this->get_column($filter['column']);
294 if (\is_null($column)) {
295 return null;
296 }
297 $valid_inclusions = ['include', 'exclude'];
298 if (!\in_array($filter['inclusion'], $valid_inclusions)) {
299 return null;
300 }
301 if (!$column->is_valid_filter_operator($filter['operator'])) {
302 return null;
303 }
304 if (!$column->is_enabled_for_group($this->group())) {
305 return null;
306 }
307 $operand = \trim(Security::string($filter['operand']));
308 if (\strlen($operand) === 0) {
309 return null;
310 }
311 if ($column->database_column() === 'cached_url' && $filter['operator'] === 'exact') {
312 $url = new URL($filter['operand']);
313 if (!$url->is_valid_url()) {
314 $filter['operand'] = \site_url($filter['operand']);
315 }
316 }
317 return new Filter(['inclusion' => Security::string($filter['inclusion']), 'column' => $column->id(), 'operator' => Security::string($filter['operator']), 'operand' => Security::string($filter['operand']), 'database_column' => $column->database_column()]);
318 }
319 public function get_column(string $id) : ?Column
320 {
321 $matches = \array_filter($this->local_columns(), function (Column $column) use($id) {
322 return $column->id() === $id;
323 });
324 return \count($matches) === 1 ? \reset($matches) : null;
325 }
326 public function sanitize_sort_parameters(?string $sort_column, ?string $sort_direction) : Sort_Configuration
327 {
328 $column = $this->get_column($sort_column);
329 if (\is_null($column) || !$column->is_enabled_for_group($this->group)) {
330 return new Sort_Configuration();
331 }
332 return new Sort_Configuration($sort_column, $sort_direction, $column->is_nullable());
333 }
334 public function get_rendered_template($rows, $just_rows = \false, string $sort_column = 'visitors', string $sort_direction = 'desc')
335 {
336 if ($just_rows) {
337 return \IAWPSCOPED\iawp_blade()->run('tables.rows', ['table' => $this, 'table_name' => $this->table_name(), 'all_columns' => $this->get_columns(), 'visible_column_count' => $this->visible_column_count(), 'number_of_shown_rows' => \count($rows), 'rows' => $rows, 'render_skeleton' => \false, 'page_size' => \IAWPSCOPED\iawp()->pagination_page_size(), 'sort_column' => $sort_column, 'sort_direction' => $sort_direction, 'has_campaigns' => Campaign_Builder::has_campaigns()]);
338 }
339 return \IAWPSCOPED\iawp_blade()->run('tables.table', ['table' => $this, 'table_name' => $this->table_name(), 'all_columns' => $this->get_columns(), 'visible_column_count' => $this->visible_column_count(), 'number_of_shown_rows' => \count($rows), 'rows' => $rows, 'render_skeleton' => \false, 'page_size' => \IAWPSCOPED\iawp()->pagination_page_size(), 'sort_column' => $sort_column, 'sort_direction' => $sort_direction, 'has_campaigns' => Campaign_Builder::has_campaigns()]);
340 }
341 protected function get_woocommerce_columns() : array
342 {
343 return [new Column(['id' => 'wc_orders', 'name' => \__('Orders', 'independent-analytics'), 'plugin_group' => 'ecommerce', 'type' => 'int']), new Column(['id' => 'wc_gross_sales', 'name' => \__('Gross Sales', 'independent-analytics'), 'plugin_group' => 'ecommerce', 'type' => 'int']), new Column(['id' => 'wc_refunds', 'name' => \__('Refunds', 'independent-analytics'), 'plugin_group' => 'ecommerce', 'type' => 'int']), new Column(['id' => 'wc_refunded_amount', 'name' => \__('Refunded Amount', 'independent-analytics'), 'plugin_group' => 'ecommerce', 'type' => 'int']), new Column(['id' => 'wc_net_sales', 'name' => \__('Total Sales', 'independent-analytics'), 'plugin_group' => 'ecommerce', 'type' => 'int']), new Column(['id' => 'wc_conversion_rate', 'name' => \__('Conversion Rate', 'independent-analytics'), 'plugin_group' => 'ecommerce', 'type' => 'int']), new Column(['id' => 'wc_earnings_per_visitor', 'name' => \__('Earnings Per Visitor', 'independent-analytics'), 'plugin_group' => 'ecommerce', 'type' => 'int']), new Column(['id' => 'wc_average_order_volume', 'name' => \__('Average Order Volume', 'independent-analytics'), 'plugin_group' => 'ecommerce', 'type' => 'int'])];
344 }
345 protected function get_form_columns() : array
346 {
347 $columns = [new Column(['id' => 'form_submissions', 'name' => \__('Submissions', 'independent-analytics'), 'plugin_group' => 'forms', 'type' => 'int']), new Column(['id' => 'form_conversion_rate', 'name' => \__('Conversion Rate', 'independent-analytics'), 'plugin_group' => 'forms', 'type' => 'int'])];
348 foreach (Form::get_forms() as $form) {
349 $columns[] = new Column(['id' => $form->submissions_column(), 'name' => $form->title() . ' ' . \__('Submissions', 'independent-analytics'), 'plugin_group' => 'forms', 'is_subgroup_plugin_active' => $form->is_plugin_active(), 'plugin_group_header' => $form->plugin_name(), 'type' => 'int']);
350 $columns[] = new Column(['id' => $form->conversion_rate_column(), 'name' => $form->title() . ' ' . \__('Conversion Rate', 'independent-analytics'), 'plugin_group' => 'forms', 'is_subgroup_plugin_active' => $form->is_plugin_active(), 'plugin_group_header' => $form->plugin_name(), 'type' => 'int']);
351 }
352 return $columns;
353 }
354 private function include_column_in_csv(Column $column, bool $is_dashboard_export) : bool
355 {
356 if (!$column->is_visible() && $is_dashboard_export) {
357 return \false;
358 }
359 if (!$column->exportable() && !$is_dashboard_export) {
360 return \false;
361 }
362 if (!$column->is_group_plugin_enabled()) {
363 return \false;
364 }
365 return \true;
366 }
367 /**
368 * @return Column[]
369 */
370 private function get_columns($show_disabled_columns = \false) : array
371 {
372 $columns_for_group = \array_filter($this->local_columns(), function (Column $column) {
373 return $column->is_enabled_for_group($this->group) && $column->is_subgroup_plugin_enabled();
374 });
375 if ($show_disabled_columns === \true) {
376 } else {
377 $columns_for_group = \array_filter($columns_for_group, function (Column $column) {
378 return $column->is_group_plugin_enabled();
379 });
380 }
381 if (\is_null($this->visible_columns) || \count($this->visible_columns) === 0) {
382 return $columns_for_group;
383 }
384 if (!$this->is_new_group) {
385 return \array_map(function ($column) {
386 $column->set_visibility(\in_array($column->id(), $this->visible_columns));
387 return $column;
388 }, $columns_for_group);
389 }
390 return \array_map(function ($column) {
391 if ($column->is_group_dependent()) {
392 $column->set_visibility(\true);
393 } elseif (!\in_array($column->id(), $this->visible_columns)) {
394 $column->set_visibility(\false);
395 }
396 return $column;
397 }, $columns_for_group);
398 }
399 /**
400 * Get the number of visible columns
401 *
402 * @return int
403 */
404 private function visible_column_count() : int
405 {
406 $visible_columns = 0;
407 foreach ($this->get_columns() as $column) {
408 if ($column->is_visible()) {
409 $visible_columns++;
410 }
411 }
412 return $visible_columns;
413 }
414 private function filters()
415 {
416 return $this->filters;
417 }
418 /**
419 * @param string $type
420 *
421 * @return ?class-string<Table>
422 */
423 public static function get_table_by_type(string $type) : ?string
424 {
425 switch ($type) {
426 case 'views':
427 return \IAWP\Tables\Table_Pages::class;
428 case 'referrers':
429 return \IAWP\Tables\Table_Referrers::class;
430 case 'geo':
431 return \IAWP\Tables\Table_Geo::class;
432 case 'campaigns':
433 return \IAWP\Tables\Table_Campaigns::class;
434 case 'devices':
435 return \IAWP\Tables\Table_Devices::class;
436 default:
437 return null;
438 }
439 }
440 }
441