PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 1.27.0
Independent Analytics – WordPress Analytics Plugin v1.27.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_Geo.php 3 years ago Table_Pages.php 3 years ago Table_Referrers.php 3 years ago
Table.php
383 lines
1 <?php
2
3 namespace IAWP_SCOPED\IAWP\Tables;
4
5 use IAWP_SCOPED\IAWP\Dashboard_Options;
6 use IAWP_SCOPED\IAWP\Date_Range\Relative_Date_Range;
7 use IAWP_SCOPED\IAWP\Filters;
8 use IAWP_SCOPED\IAWP\Sort_Configuration;
9 use IAWP_SCOPED\IAWP\Statistics\Statistics;
10 use IAWP_SCOPED\IAWP\Tables\Columns\Column;
11 use IAWP_SCOPED\IAWP\Tables\Groups\Group;
12 use IAWP_SCOPED\IAWP\Tables\Groups\Groups;
13 use IAWP_SCOPED\IAWP\Utils\Array_To_CSV;
14 use IAWP_SCOPED\IAWP\Utils\Currency;
15 use IAWP_SCOPED\IAWP\Utils\Number_Formatter;
16 use IAWP_SCOPED\IAWP\Utils\Security;
17 use IAWP_SCOPED\IAWP\Utils\WordPress_Site_Date_Format_Pattern;
18 use IAWP_SCOPED\Proper\Timezone;
19 abstract class Table
20 {
21 private $filters;
22 private $visible_columns;
23 private $group;
24 private $is_new_group;
25 private $statistics;
26 /**
27 * @param string[]|null $visible_columns
28 * @param string|null $group_id
29 * @param bool $is_new_group
30 */
31 public function __construct(array $visible_columns = null, ?string $group_id = null, bool $is_new_group = \false)
32 {
33 $this->filters = new Filters();
34 $this->visible_columns = $visible_columns;
35 $this->group = $this->groups()->find_by_id($group_id);
36 $this->is_new_group = $is_new_group;
37 }
38 public function visible_column_ids()
39 {
40 $visible_columns = [];
41 foreach ($this->get_columns() as $column) {
42 if ($column->visible()) {
43 $visible_columns[] = $column->id();
44 }
45 }
46 return $visible_columns;
47 }
48 public function group() : Group
49 {
50 return $this->group;
51 }
52 public function column_picker_html() : string
53 {
54 return \IAWP_SCOPED\IAWP()->templates()->render('table/column_picker', ['columns' => $this->get_columns()]);
55 }
56 public function group_picker_html() : ?string
57 {
58 $has_group_options = \count($this->groups()->buttons()) > 0;
59 if (!$has_group_options) {
60 return null;
61 }
62 return \IAWP_SCOPED\IAWP()->templates()->render('table/group_picker', ['buttons' => $this->groups()->buttons(), 'group' => $this->group()]);
63 }
64 public function get_table_markup(string $sort_by, string $sort_direction)
65 {
66 $opts = new Dashboard_Options();
67 $templates = \IAWP_SCOPED\iawp()->templates();
68 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]);
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 Security::string($views) . ' <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 Security::string($visitors) . ' <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 Security::string($sessions) . ' <span class="percentage">(' . Security::string($sessions_percentage) . ')</span>';
110 } elseif ($column_id === 'bounce_rate') {
111 return Security::string(Number_Formatter::percent($row->bounce_rate()));
112 } elseif ($column_id === 'average_session_duration' || $column_id === 'average_view_duration') {
113 return Number_Formatter::second_to_minute_timestamp($row->{$column_id}());
114 } elseif ($column_id === 'views_growth' || $column_id === 'visitors_growth' || $column_id === 'woocommerce_conversion_rate') {
115 return Number_Formatter::percent($row->{$column_id}(), 2);
116 } elseif ($column_id == 'url') {
117 if ($row->is_deleted()) {
118 return Security::string(\esc_url($row->url()));
119 } else {
120 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>';
121 }
122 } elseif ($column_id == 'author') {
123 return Security::html($row->avatar()) . ' ' . Security::string($row->author());
124 } elseif ($column_id == 'date') {
125 return Security::string(\date(WordPress_Site_Date_Format_Pattern::for_php(), $row->date()));
126 } elseif ($column_id == 'type' && \method_exists($row, 'icon') && \method_exists($row, 'type')) {
127 return $row->icon(0) . ' ' . Security::string($row->type());
128 } elseif ($column_id == 'referrer' && !$row->is_direct()) {
129 return '<a href="' . \esc_url($row->referrer_url()) . '" target="_blank" class="external-link">' . Security::string($row->referrer()) . '<span class="dashicons dashicons-external"></span></a>';
130 } elseif ($column_id === 'country') {
131 return '<img class="flag" alt="Country flag" src="' . Security::string($row->flag()) . '"/>' . Security::string($row->country());
132 } 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') {
133 return Security::string(Currency::format($row->{$column_id}()));
134 } elseif ($column_id === 'views_per_session') {
135 return Number_Formatter::decimal($row->{$column_id}(), 2);
136 } else {
137 return Security::string($row->{$column_id}());
138 }
139 }
140 public function output_toolbar()
141 {
142 $opts = new Dashboard_Options();
143 ?>
144 <div class="toolbar">
145 <div class="buttons">
146 <div class="modal-parent"
147 data-controller="dates"
148 data-dates-relative-range-id-value="<?php
149 \esc_html_e($opts->relative_range_id());
150 ?>"
151 data-dates-exact-start-value="<?php
152 \esc_html_e($opts->get_date_range()->start()->setTimezone(Timezone::site_timezone())->format('Y-m-d'));
153 ?>"
154 data-dates-exact-end-value="<?php
155 \esc_html_e($opts->get_date_range()->end()->setTimezone(Timezone::site_timezone())->format('Y-m-d'));
156 ?>"
157 data-dates-first-day-of-week-value="<?php
158 echo \absint(\IAWP_SCOPED\iawp()->get_option('iawp_dow', 0));
159 ?>"
160 data-dates-css-url-value="<?php
161 echo \esc_url(\IAWP_SCOPED\iawp_url_to('dist/styles/easepick/datepicker.css'));
162 ?>"
163 data-dates-format-value="<?php
164 echo \esc_attr(WordPress_Site_Date_Format_Pattern::for_javascript());
165 ?>"
166 >
167 <button id="dates-button"
168 data-testid="open-calendar"
169 class="iawp-button ghost-white toolbar-button"
170 data-action="dates#toggleModal"
171 data-dates-target="modalButton"
172 >
173 <span class="dashicons dashicons-calendar-alt"></span>
174 <span class="iawp-label"><?php
175 \esc_html_e($opts->get_date_range()->label());
176 ?></span>
177 </button>
178 <div id="modal-dates"
179 class="modal large flex"
180 data-dates-target="modal"
181 >
182 <div class="modal-inner">
183 <div id="easepick-picker"
184 data-dates-target="easepick"
185 style="display: none;"
186 >
187 </div>
188 <div class="relative-dates">
189 <?php
190 foreach (Relative_Date_Range::ranges() as $date_range) {
191 ?>
192 <button class="iawp-button ghost-purple"
193 data-dates-target="relativeRange"
194 data-action="dates#relativeRangeSelected"
195 data-relative-range-id="<?php
196 \esc_html_e($date_range->relative_range_id());
197 ?>"
198 data-relative-range-label="<?php
199 \esc_html_e($date_range->label());
200 ?>"
201 data-relative-range-start="<?php
202 \esc_html_e($date_range->start()->setTimezone(Timezone::site_timezone())->format('Y-m-d'));
203 ?>"
204 data-relative-range-end="<?php
205 \esc_html_e($date_range->end()->setTimezone(Timezone::site_timezone())->format('Y-m-d'));
206 ?>"
207 >
208 <?php
209 \esc_html_e($date_range->label());
210 ?>
211 </button>
212 <?php
213 }
214 ?>
215 </div>
216 <div>
217 <hr/>
218 </div>
219 <div>
220 <button class="iawp-button purple"
221 data-dates-target="apply"
222 data-action="dates#apply"
223 data-testid="apply-dates"
224 >
225 <?php
226 \esc_html_e('Apply', 'independent-analytics');
227 ?>
228 </button>
229 <button class="iawp-button ghost-purple"
230 data-action="dates#closeModal"
231 data-testid="close-calendar"
232 >
233 <?php
234 \esc_html_e('Cancel', 'independent-analytics');
235 ?>
236 </button>
237 </div>
238 </div>
239 </div>
240 </div>
241 <?php
242 echo $this->group_picker_html();
243 echo $this->filters()->get_filters_html($this->get_columns());
244 echo $this->column_picker_html();
245 ?>
246 </div>
247 <div class="buttons">
248 <button class="iawp-button ghost-white toolbar-button"
249 data-controller="clipboard"
250 data-action="clipboard#copy"
251 >
252 <span class="dashicons dashicons-admin-page"></span>
253 <span data-clipboard-target="statusTextElement" class="iawp-label">
254 <?php
255 \esc_html_e('Copy Dashboard URL', 'independent-analytics');
256 ?>
257 </span>
258 </button>
259 <a class="learn-more"
260 href="https://independentwp.com/knowledgebase/dashboard/save-reports-revisit-later/"
261 target="_blank"><span class="dashicons dashicons-info-outline"></span></a>
262 </div>
263 </div><?php
264 }
265 public function filters_template_html() : string
266 {
267 return $this->filters()->get_condition_html($this->get_columns());
268 }
269 public final function csv($rows)
270 {
271 $columns = $this->get_columns();
272 $csv_header = [];
273 $csv_rows = [];
274 foreach ($columns as $column) {
275 if (!$column->exportable() || $column->requires_woocommerce() && \IAWP_SCOPED\iawp_is_free()) {
276 continue;
277 }
278 $csv_header[] = $column->label();
279 }
280 foreach ($rows as $row) {
281 $csv_row = [];
282 foreach ($columns as $column) {
283 if (!$column->exportable() || $column->requires_woocommerce() && \IAWP_SCOPED\iawp_is_free()) {
284 continue;
285 }
286 $column_id = $column->id();
287 $value = $row->{$column_id}();
288 // Todo - This logic is similar to the rendering logic for table cells. This should
289 // all be handled via the column class itself.
290 if (\is_null($value)) {
291 $csv_row[] = '-';
292 } elseif ($column_id === 'date') {
293 $csv_row[] = \date(WordPress_Site_Date_Format_Pattern::for_php(), $value);
294 } elseif ($column_id === 'average_session_duration' || $column_id === 'average_view_duration') {
295 $csv_row[] = Number_Formatter::second_to_minute_timestamp($value);
296 } elseif ($column_id === 'bounce_rate') {
297 $csv_row[] = Security::string(Number_Formatter::percent($value));
298 } elseif ($column_id === 'views_per_session') {
299 $csv_row[] = Number_Formatter::decimal($value, 2);
300 } else {
301 $csv_row[] = $row->{$column_id}();
302 }
303 }
304 $csv_rows[] = $csv_row;
305 }
306 $csv = \array_merge([$csv_header], $csv_rows);
307 return Array_To_CSV::array2csv($csv);
308 }
309 public function sanitize_filters(array $filters) : array
310 {
311 $valid_filters = \array_filter($filters, function ($filter) {
312 return \in_array($filter['column'], $this->get_column_ids());
313 });
314 return \array_values($valid_filters);
315 }
316 public function sanitize_sort_parameters(?string $sort_by, ?string $sort_direction) : Sort_Configuration
317 {
318 return new Sort_Configuration($sort_by, $sort_direction, $this->get_column_ids());
319 }
320 protected abstract function groups() : Groups;
321 /**
322 * @return array<Column>
323 */
324 protected abstract function local_columns() : array;
325 protected abstract function table_name() : string;
326 /**
327 * @return string[]
328 */
329 private function get_column_ids() : array
330 {
331 return \array_map(function (Column $column) {
332 return $column->id();
333 }, $this->get_columns());
334 }
335 /**
336 * @return Column[]
337 */
338 private function get_columns() : array
339 {
340 $columns_for_group = \array_filter($this->local_columns(), function (Column $column) {
341 return $column->is_enabled_for_group($this->group);
342 });
343 if ($this->visible_columns === null) {
344 return $columns_for_group;
345 }
346 return \array_map(function ($column) {
347 if (\in_array($column->id(), $this->visible_columns) || $this->is_new_group && $column->is_exclusive_to($this->group)) {
348 $column->set_visibility(\true);
349 } else {
350 $column->set_visibility(\false);
351 }
352 return $column;
353 }, $columns_for_group);
354 }
355 /**
356 * Get the number of visible columns
357 *
358 * @return int
359 */
360 private function visible_column_count() : int
361 {
362 $visible_columns = 0;
363 foreach ($this->get_columns() as $column) {
364 if ($column->visible()) {
365 $visible_columns++;
366 }
367 }
368 return $visible_columns;
369 }
370 private function get_rendered_template($rows, $just_rows = \false, string $sort_by = 'visitors', string $sort_direction = 'desc')
371 {
372 $opts = new Dashboard_Options();
373 $templates = \IAWP_SCOPED\iawp()->templates();
374 $templates->registerFunction('row_data_attributes', [$this, 'get_row_data_attributes']);
375 $templates->registerFunction('cell_content', [$this, 'get_cell_content']);
376 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]);
377 }
378 private function filters()
379 {
380 return $this->filters;
381 }
382 }
383