PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 1.30.0
Independent Analytics – WordPress Analytics Plugin v1.30.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 / Tables / Table.php
independent-analytics / IAWP / Tables Last commit date
Columns 2 years ago Groups 2 years ago Table.php 2 years 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
421 lines
1 <?php
2
3 namespace IAWP_SCOPED\IAWP\Tables;
4
5 use IAWP_SCOPED\IAWP\Campaign_Builder;
6 use IAWP_SCOPED\IAWP\Dashboard_Options;
7 use IAWP_SCOPED\IAWP\Date_Range\Relative_Date_Range;
8 use IAWP_SCOPED\IAWP\Filters;
9 use IAWP_SCOPED\IAWP\Icon_Directory_Factory;
10 use IAWP_SCOPED\IAWP\Sort_Configuration;
11 use IAWP_SCOPED\IAWP\Statistics\Statistics;
12 use IAWP_SCOPED\IAWP\Tables\Columns\Column;
13 use IAWP_SCOPED\IAWP\Tables\Groups\Group;
14 use IAWP_SCOPED\IAWP\Tables\Groups\Groups;
15 use IAWP_SCOPED\IAWP\Utils\Array_To_CSV;
16 use IAWP_SCOPED\IAWP\Utils\Currency;
17 use IAWP_SCOPED\IAWP\Utils\Number_Formatter;
18 use IAWP_SCOPED\IAWP\Utils\Security;
19 use IAWP_SCOPED\IAWP\Utils\WordPress_Site_Date_Format_Pattern;
20 use IAWP_SCOPED\Proper\Timezone;
21 abstract class Table
22 {
23 private $filters;
24 private $visible_columns;
25 private $group;
26 private $is_new_group;
27 private $statistics;
28 /**
29 * @param string[]|null $visible_columns
30 * @param string|null $group_id
31 * @param bool $is_new_group
32 */
33 public function __construct(array $visible_columns = null, ?string $group_id = null, bool $is_new_group = \false)
34 {
35 $this->visible_columns = $visible_columns;
36 $this->group = $this->groups()->find_by_id($group_id);
37 $this->is_new_group = $is_new_group;
38 $this->filters = new Filters($this->group->plural());
39 }
40 public function visible_column_ids()
41 {
42 $visible_columns = [];
43 foreach ($this->get_columns() as $column) {
44 if ($column->visible()) {
45 $visible_columns[] = $column->id();
46 }
47 }
48 return $visible_columns;
49 }
50 public function group() : Group
51 {
52 return $this->group;
53 }
54 public function column_picker_html() : string
55 {
56 return \IAWP_SCOPED\iawp_blade()->run('tables.column-picker', ['columns' => $this->get_columns()]);
57 }
58 public function group_picker_html() : ?string
59 {
60 $has_group_options = \count($this->groups()->buttons()) > 0;
61 if (!$has_group_options) {
62 return null;
63 }
64 return \IAWP_SCOPED\iawp_blade()->run('tables.group-picker', ['buttons' => $this->groups()->buttons(), 'group' => $this->group()]);
65 }
66 public function get_table_markup(string $sort_by, string $sort_direction)
67 {
68 return \IAWP_SCOPED\iawp_blade()->run('tables.table', ['table' => $this, 'table_name' => $this->table_name(), 'all_columns' => $this->get_columns(), 'visible_column_count' => $this->visible_column_count(), 'row_count' => 0, 'rows' => [], 'render_skeleton' => \true, 'page_size' => \IAWP_SCOPED\iawp()->pagination_page_size(), 'sort_by' => $sort_by, 'sort_direction' => $sort_direction, 'has_campaigns' => Campaign_Builder::has_campaigns()]);
69 }
70 public function set_statistics(Statistics $statistics)
71 {
72 $this->statistics = $statistics;
73 }
74 public function get_row_markup($rows, $just_rows = \true, string $sort_by = 'visitors', string $sort_direction = 'desc')
75 {
76 return $this->get_rendered_template($rows, $just_rows, $sort_by, $sort_direction);
77 }
78 public function get_row_data_attributes($row)
79 {
80 $html = '';
81 foreach ($this->get_columns() as $column) {
82 $id = $column->id();
83 $data_val = $row->{$id}();
84 $html .= ' data-' . \esc_attr($column->id()) . '="' . \esc_attr($data_val) . '"';
85 }
86 return $html;
87 }
88 public function get_cell_content($row, Column $column)
89 {
90 $column_id = $column->id();
91 if (\is_null($row->{$column_id}())) {
92 return '-';
93 }
94 if ($column_id == 'title' && $row->is_deleted()) {
95 return Security::string($row->{$column_id}()) . ' <span class="deleted-label">' . \esc_html__('(deleted)', 'independent-analytics') . '</span>';
96 } elseif ($column_id == 'views') {
97 $views = Number_Formatter::decimal($row->views());
98 // Getting a divide by zero error from the line below?
99 // It's likely an issue with $this->views which is an instance of Views. Make sure the queries there are working.
100 $views_percentage = Number_Formatter::percent($row->views() / $this->statistics->views()->value() * 100, 2);
101 return '<span class="no-wrap">' . Security::string($views) . '</span> <span class="percentage">(' . Security::string($views_percentage) . ')</span>';
102 } elseif ($column_id == 'visitors') {
103 $visitors = Number_Formatter::decimal($row->visitors());
104 $visitors_percentage = Number_Formatter::percent($row->visitors() / $this->statistics->visitors()->value() * 100, 2);
105 return '<span class="no-wrap">' . Security::string($visitors) . '</span> <span class="percentage">(' . Security::string($visitors_percentage) . ')</span>';
106 } elseif ($column_id == 'sessions') {
107 $sessions = Number_Formatter::decimal($row->sessions());
108 $sessions_percentage = Number_Formatter::percent($row->sessions() / $this->statistics->sessions()->value() * 100, 2);
109 return '<span class="no-wrap">' . Security::string($sessions) . '</span> <span class="percentage">(' . Security::string($sessions_percentage) . ')</span>';
110 } elseif ($column_id === 'entrances') {
111 $entrances = Number_Formatter::decimal($row->entrances());
112 $entrances_percentage = Number_Formatter::percent($row->entrances() / $this->statistics->sessions()->value() * 100, 2);
113 return '<span class="no-wrap">' . Security::string($entrances) . '</span> <span class="percentage">(' . Security::string($entrances_percentage) . ')</span>';
114 } elseif ($column_id === 'exits') {
115 $exits = Number_Formatter::decimal($row->exits());
116 $exits_percentage = Number_Formatter::percent($row->exits() / $this->statistics->sessions()->value() * 100, 2);
117 return '<span class="no-wrap">' . Security::string($exits) . '</span> <span class="percentage">(' . Security::string($exits_percentage) . ')</span>';
118 } elseif ($column_id === 'bounce_rate') {
119 return Security::string(Number_Formatter::percent($row->bounce_rate()));
120 } elseif ($column_id === 'average_session_duration' || $column_id === 'average_view_duration') {
121 return Number_Formatter::second_to_minute_timestamp($row->{$column_id}());
122 } elseif ($column_id === 'views_growth' || $column_id === 'visitors_growth' || $column_id === 'woocommerce_conversion_rate' || $column_id === 'exit_percent') {
123 return Number_Formatter::percent($row->{$column_id}(), 2);
124 } elseif ($column_id == 'url') {
125 if ($row->is_deleted()) {
126 return Security::string(\esc_url($row->url()));
127 } else {
128 return '<a href="' . Security::string(\esc_url($row->url(\true))) . '" target="_blank" class="external-link">' . Security::string(\esc_url($row->url())) . '<span class="dashicons dashicons-external"></span></a>';
129 }
130 } elseif ($column_id == 'author') {
131 return Security::html($row->avatar()) . ' ' . Security::string($row->author());
132 } elseif ($column_id == 'date') {
133 return Security::string(\date(WordPress_Site_Date_Format_Pattern::for_php(), $row->date()));
134 } elseif ($column_id == 'type' && \method_exists($row, 'icon') && \method_exists($row, 'type')) {
135 return $row->icon(0) . ' ' . Security::string($row->type());
136 } elseif ($column_id == 'referrer' && !$row->is_direct()) {
137 return '<a href="' . \esc_url($row->referrer_url()) . '" target="_blank" class="external-link">' . Security::string($row->referrer()) . '<span class="dashicons dashicons-external"></span></a>';
138 } elseif ($column_id === 'device_type') {
139 return Icon_Directory_Factory::device_types()->find($row->device_type()) . Security::string($row->device_type());
140 } elseif ($column_id === 'browser') {
141 return Icon_Directory_Factory::browsers()->find($row->browser()) . Security::string($row->browser());
142 } elseif ($column_id === 'os') {
143 return Icon_Directory_Factory::operating_systems()->find($row->os()) . Security::string($row->os());
144 } elseif ($column_id === 'country') {
145 return Icon_Directory_Factory::flags()->find($row->country_code()) . Security::string($row->country());
146 } elseif ($column_id === 'wc_gross_sales' || $column_id === 'wc_refunded_amount' || $column_id === 'wc_net_sales' || $column_id === 'woocommerce_earnings_per_visitor' || $column_id === 'woocommerce_average_order_volume') {
147 return Security::string(Currency::format($row->{$column_id}()));
148 } elseif ($column_id === 'views_per_session') {
149 return Number_Formatter::decimal($row->{$column_id}(), 2);
150 } else {
151 return Security::string($row->{$column_id}());
152 }
153 }
154 public function output_toolbar()
155 {
156 $opts = new Dashboard_Options();
157 ?>
158 <div class="toolbar">
159 <div class="buttons primary-buttons">
160 <div class="modal-parent"
161 data-controller="dates"
162 data-dates-relative-range-id-value="<?php
163 \esc_html_e($opts->relative_range_id());
164 ?>"
165 data-dates-exact-start-value="<?php
166 \esc_html_e($opts->get_date_range()->start()->setTimezone(Timezone::site_timezone())->format('Y-m-d'));
167 ?>"
168 data-dates-exact-end-value="<?php
169 \esc_html_e($opts->get_date_range()->end()->setTimezone(Timezone::site_timezone())->format('Y-m-d'));
170 ?>"
171 data-dates-first-day-of-week-value="<?php
172 echo \absint(\IAWP_SCOPED\iawp()->get_option('iawp_dow', 0));
173 ?>"
174 data-dates-css-url-value="<?php
175 echo \esc_url(\IAWP_SCOPED\iawp_url_to('dist/styles/easepick/datepicker.css'));
176 ?>"
177 data-dates-format-value="<?php
178 echo \esc_attr(WordPress_Site_Date_Format_Pattern::for_javascript());
179 ?>"
180 >
181 <button id="dates-button"
182 data-testid="open-calendar"
183 class="iawp-button ghost-white toolbar-button"
184 data-action="dates#toggleModal"
185 data-dates-target="modalButton"
186 >
187 <span class="dashicons dashicons-calendar-alt"></span>
188 <span class="iawp-label"><?php
189 \esc_html_e($opts->get_date_range()->label());
190 ?></span>
191 </button>
192 <div id="modal-dates"
193 class="modal large flex"
194 data-dates-target="modal"
195 >
196 <div class="modal-inner">
197 <div id="easepick-picker"
198 data-dates-target="easepick"
199 style="display: none;"
200 >
201 </div>
202 <div class="relative-dates">
203 <?php
204 foreach (Relative_Date_Range::ranges() as $date_range) {
205 ?>
206 <button class="iawp-button ghost-purple"
207 data-dates-target="relativeRange"
208 data-action="dates#relativeRangeSelected"
209 data-relative-range-id="<?php
210 \esc_html_e($date_range->relative_range_id());
211 ?>"
212 data-relative-range-label="<?php
213 \esc_html_e($date_range->label());
214 ?>"
215 data-relative-range-start="<?php
216 \esc_html_e($date_range->start()->setTimezone(Timezone::site_timezone())->format('Y-m-d'));
217 ?>"
218 data-relative-range-end="<?php
219 \esc_html_e($date_range->end()->setTimezone(Timezone::site_timezone())->format('Y-m-d'));
220 ?>"
221 >
222 <?php
223 \esc_html_e($date_range->label());
224 ?>
225 </button>
226 <?php
227 }
228 ?>
229 </div>
230 <div>
231 <hr/>
232 </div>
233 <div>
234 <button class="iawp-button purple"
235 data-dates-target="apply"
236 data-action="dates#apply"
237 data-testid="apply-dates"
238 >
239 <?php
240 \esc_html_e('Apply', 'independent-analytics');
241 ?>
242 </button>
243 <button class="iawp-button ghost-purple"
244 data-action="dates#closeModal"
245 data-testid="close-calendar"
246 >
247 <?php
248 \esc_html_e('Cancel', 'independent-analytics');
249 ?>
250 </button>
251 </div>
252 </div>
253 </div>
254 </div>
255 <?php
256 echo $this->group_picker_html();
257 echo $this->filters()->get_filters_html($this->get_columns());
258 echo $this->column_picker_html();
259 ?>
260 </div>
261 <div class="buttons secondary-buttons">
262 <button class="iawp-button ghost-white toolbar-button" data-report-target="export" data-action="report#export">
263 <span class="dashicons dashicons-download"></span>
264 <span class="iawp-label">
265 <?php
266 \esc_html_e('Download CSV', 'independent-analytics');
267 ?>
268 </span>
269 </button>
270 <button class="iawp-button ghost-white toolbar-button"
271 data-controller="clipboard"
272 data-action="clipboard#copy"
273 >
274 <span class="dashicons dashicons-admin-page"></span>
275 <span data-clipboard-target="statusTextElement" class="iawp-label">
276 <?php
277 \esc_html_e('Copy URL', 'independent-analytics');
278 ?>
279 </span>
280 </button>
281 <a class="learn-more"
282 href="https://independentwp.com/knowledgebase/dashboard/save-reports-revisit-later/"
283 target="_blank"><span class="dashicons dashicons-info-outline"></span></a>
284 </div>
285 </div><?php
286 }
287 public function filters_template_html() : string
288 {
289 return $this->filters()->get_condition_html($this->get_columns());
290 }
291 public final function csv(array $rows, bool $is_dashboard_export = \false)
292 {
293 $columns = $this->get_columns();
294 $csv_header = [];
295 $csv_rows = [];
296 foreach ($columns as $column) {
297 if (!$this->include_column_in_csv($column, $is_dashboard_export)) {
298 continue;
299 }
300 $csv_header[] = $column->label();
301 }
302 foreach ($rows as $row) {
303 $csv_row = [];
304 foreach ($columns as $column) {
305 if (!$this->include_column_in_csv($column, $is_dashboard_export)) {
306 continue;
307 }
308 $column_id = $column->id();
309 $value = $row->{$column_id}();
310 // Todo - This logic is similar to the rendering logic for table cells. This should
311 // all be handled via the column class itself.
312 if (\is_null($value)) {
313 $csv_row[] = '-';
314 } elseif ($column_id === 'date') {
315 $csv_row[] = \date(WordPress_Site_Date_Format_Pattern::for_php(), $value);
316 } elseif ($column_id === 'average_session_duration' || $column_id === 'average_view_duration') {
317 $csv_row[] = Number_Formatter::second_to_minute_timestamp($value);
318 } elseif ($column_id === 'views_per_session') {
319 $csv_row[] = Number_Formatter::decimal($value, 2);
320 } else {
321 $csv_row[] = $row->{$column_id}();
322 }
323 }
324 $csv_rows[] = $csv_row;
325 }
326 $csv = \array_merge([$csv_header], $csv_rows);
327 return Array_To_CSV::array2csv($csv);
328 }
329 public function sanitize_filters(array $filters) : array
330 {
331 $valid_filters = \array_filter($filters, function ($filter) {
332 return \in_array($filter['column'], $this->get_column_ids());
333 });
334 return \array_values($valid_filters);
335 }
336 public function sanitize_sort_parameters(?string $sort_by, ?string $sort_direction) : Sort_Configuration
337 {
338 return new Sort_Configuration($sort_by, $sort_direction, $this->get_column_ids());
339 }
340 protected abstract function groups() : Groups;
341 /**
342 * @return array<Column>
343 */
344 protected abstract function local_columns() : array;
345 protected abstract function table_name() : string;
346 private function include_column_in_csv(Column $column, bool $is_dashboard_export) : bool
347 {
348 if (!$column->visible() && $is_dashboard_export) {
349 return \false;
350 }
351 if (!$column->exportable() && !$is_dashboard_export) {
352 return \false;
353 }
354 if ($column->requires_woocommerce() && \IAWP_SCOPED\iawp_is_free()) {
355 return \false;
356 }
357 return \true;
358 }
359 /**
360 * @return string[]
361 */
362 private function get_column_ids() : array
363 {
364 return \array_map(function (Column $column) {
365 return $column->id();
366 }, $this->get_columns());
367 }
368 /**
369 * @return Column[]
370 */
371 private function get_columns() : array
372 {
373 $columns_for_group = \array_filter($this->local_columns(), function (Column $column) {
374 return $column->is_enabled_for_group($this->group);
375 });
376 if (\is_null($this->visible_columns) || \count($this->visible_columns) === 0) {
377 return $columns_for_group;
378 }
379 if (!$this->is_new_group) {
380 return \array_map(function ($column) {
381 $column->set_visibility(\in_array($column->id(), $this->visible_columns));
382 return $column;
383 }, $columns_for_group);
384 }
385 return \array_map(function ($column) {
386 if ($column->is_group_dependent()) {
387 $column->set_visibility(\true);
388 } elseif (!\in_array($column->id(), $this->visible_columns)) {
389 $column->set_visibility(\false);
390 }
391 return $column;
392 }, $columns_for_group);
393 }
394 /**
395 * Get the number of visible columns
396 *
397 * @return int
398 */
399 private function visible_column_count() : int
400 {
401 $visible_columns = 0;
402 foreach ($this->get_columns() as $column) {
403 if ($column->visible()) {
404 $visible_columns++;
405 }
406 }
407 return $visible_columns;
408 }
409 private function get_rendered_template($rows, $just_rows = \false, string $sort_by = 'visitors', string $sort_direction = 'desc')
410 {
411 if ($just_rows) {
412 return \IAWP_SCOPED\iawp_blade()->run('tables.rows', ['table' => $this, 'table_name' => $this->table_name(), 'all_columns' => $this->get_columns(), 'visible_column_count' => $this->visible_column_count(), 'row_count' => \count($rows), 'rows' => $rows, 'render_skeleton' => \false, 'page_size' => \IAWP_SCOPED\iawp()->pagination_page_size(), 'sort_by' => $sort_by, 'sort_direction' => $sort_direction, 'has_campaigns' => Campaign_Builder::has_campaigns()]);
413 }
414 return \IAWP_SCOPED\iawp_blade()->run('tables.table', ['table' => $this, 'table_name' => $this->table_name(), 'all_columns' => $this->get_columns(), 'visible_column_count' => $this->visible_column_count(), 'row_count' => \count($rows), 'rows' => $rows, 'render_skeleton' => \false, 'page_size' => \IAWP_SCOPED\iawp()->pagination_page_size(), 'sort_by' => $sort_by, 'sort_direction' => $sort_direction, 'has_campaigns' => Campaign_Builder::has_campaigns()]);
415 }
416 private function filters()
417 {
418 return $this->filters;
419 }
420 }
421