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