PluginProbe
Sessions / 2.3.1
Sessions v2.3.1
2.1.0 2.10.0 2.11.0 2.12.0 2.13.0 2.13.1 2.13.2 2.13.3 2.14.0 2.2.0 2.3.0 2.3.1 2.4.0 2.4.1 2.5.0 2.6.0 2.6.1 2.6.2 2.7.0 2.8.0 2.9.0 2.9.1 3.0.0 3.1.0 3.1.1 All 39 releases
sessions / includes / features / class-analytics.php

class-analytics.php in Sessions 2.3.1, at includes/features/class-analytics.php

1,294 lines 51.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Device detector analytics
4 *
5 * Handles all analytics operations.
6 *
7 * @package Features
8 * @author Pierre Lannoy <https://pierre.lannoy.fr/>.
9 * @since 1.0.0
10 */
11
12 namespace POSessions\Plugin\Feature;
13
14 use POSessions\Plugin\Feature\Schema;
15 use POSessions\System\Blog;
16 use POSessions\System\Cache;
17 use POSessions\System\Date;
18 use POSessions\System\Conversion;
19 use POSessions\System\Role;
20
21 use POSessions\System\L10n;
22 use POSessions\System\Http;
23 use POSessions\System\Favicon;
24 use POSessions\System\Timezone;
25 use POSessions\System\UUID;
26 use Feather;
27 use POSessions\System\Environment;
28
29
30 /**
31 * Define the analytics functionality.
32 *
33 * Handles all analytics operations.
34 *
35 * @package Features
36 * @author Pierre Lannoy <https://pierre.lannoy.fr/>.
37 * @since 1.0.0
38 */
39 class Analytics {
40
41 /**
42 * The dashboard type.
43 *
44 * @since 1.0.0
45 * @var string $title The dashboard type.
46 */
47 public $type = '';
48
49 /**
50 * The start date.
51 *
52 * @since 1.0.0
53 * @var string $start The start date.
54 */
55 private $start = '';
56
57 /**
58 * The end date.
59 *
60 * @since 1.0.0
61 * @var string $end The end date.
62 */
63 private $end = '';
64
65 /**
66 * The period duration in days.
67 *
68 * @since 1.0.0
69 * @var integer $duration The period duration in days.
70 */
71 private $duration = 0;
72
73 /**
74 * The timezone.
75 *
76 * @since 1.0.0
77 * @var string $timezone The timezone.
78 */
79 private $timezone = 'UTC';
80
81 /**
82 * The main query filter.
83 *
84 * @since 1.0.0
85 * @var array $filter The main query filter.
86 */
87 private $filter = [];
88
89 /**
90 * The query filter fro the previous range.
91 *
92 * @since 1.0.0
93 * @var array $previous The query filter fro the previous range.
94 */
95 private $previous = [];
96
97 /**
98 * Is the start date today's date.
99 *
100 * @since 1.0.0
101 * @var boolean $today Is the start date today's date.
102 */
103 private $is_today = false;
104
105 /**
106 * Colors for graphs.
107 *
108 * @since 1.0.0
109 * @var array $colors The colors array.
110 */
111 private $colors = [ '#73879C', '#3398DB', '#9B59B6', '#b2c326', '#BDC3C6' ];
112
113 /**
114 * Initialize the class and set its properties.
115 *
116 * @param string $type The type of analytics ().
117 * @param string $start The start date.
118 * @param string $end The end date.
119 * @param boolean $reload Is it a reload of an already displayed analytics.
120 * @since 1.0.0
121 */
122 public function __construct( $type, $start, $end, $reload ) {
123 if ( $start === $end ) {
124 $this->filter[] = "timestamp='" . $start . "'";
125 } else {
126 $this->filter[] = "timestamp>='" . $start . "' and timestamp<='" . $end . "'";
127 }
128 $this->start = $start;
129 $this->end = $end;
130 $this->type = $type;
131 $this->timezone = Timezone::network_get();
132 $datetime = new \DateTime( 'now', $this->timezone );
133 $this->is_today = ( $this->start === $datetime->format( 'Y-m-d' ) || $this->end === $datetime->format( 'Y-m-d' ) );
134 $start = new \DateTime( $this->start, $this->timezone );
135 $end = new \DateTime( $this->end, $this->timezone );
136 $start->sub( new \DateInterval( 'P1D' ) );
137 $end->sub( new \DateInterval( 'P1D' ) );
138 $delta = $start->diff( $end, true );
139 if ( $delta ) {
140 $start->sub( $delta );
141 $end->sub( $delta );
142 }
143 $this->duration = $delta->days + 1;
144 if ( $start === $end ) {
145 $this->previous[] = "timestamp='" . $start->format( 'Y-m-d' ) . "'";
146 } else {
147 $this->previous[] = "timestamp>='" . $start->format( 'Y-m-d' ) . "' and timestamp<='" . $end->format( 'Y-m-d' ) . "'";
148 }
149 }
150
151 /**
152 * Query statistics table.
153 *
154 * @param string $query The query type.
155 * @param mixed $queried The query params.
156 * @return array The result of the query, ready to encode.
157 * @since 1.0.0
158 */
159 public function query( $query, $queried ) {
160 switch ( $query ) {
161 case 'kpi':
162 return $this->query_kpi( $queried );
163 case 'login':
164 case 'clean':
165 return $this->query_pie( $query, (int) $queried );
166 case 'main-chart':
167 return $this->query_chart();
168 }
169 return [];
170 }
171
172 /**
173 * Query statistics pie.
174 *
175 * @param string $type The type of pie.
176 * @param integer $limit The number to display.
177 * @return array The result of the query, ready to encode.
178 * @since 1.0.0
179 */
180 private function query_pie( $type, $limit ) {
181 $uuid = UUID::generate_unique_id( 5 );
182 $data = Schema::get_grouped_list( $this->filter, '', ! $this->is_today );
183 $names = [
184 'login_success' => esc_html__( 'Successful', 'sessions' ),
185 'login_fail' => esc_html__( 'Failed', 'sessions' ),
186 'login_block' => esc_html__( 'Blocked', 'sessions' ),
187 'expired' => esc_html__( 'Expired', 'sessions' ),
188 'idle' => esc_html__( 'Idle', 'sessions' ),
189 'forced' => esc_html__( 'Overridden', 'sessions' ),
190 ];
191 switch ( $type ) {
192 case 'login':
193 $selectors = [ 'login_success', 'login_fail', 'login_block' ];
194 break;
195 case 'clean':
196 $selectors = [ 'expired', 'idle', 'forced' ];
197 break;
198 }
199 $val = 0;
200 if ( 0 < count( $data ) ) {
201 foreach ( $data as $row ) {
202 foreach ( $selectors as $selector ) {
203 $val += (int) $row[ $selector ];
204 }
205 }
206 }
207 if ( 0 < count( $data ) && 0 !== $val ) {
208 $total = 0;
209 $other = 0;
210 $values = [];
211 foreach ( $selectors as $selector ) {
212 $values[ $selector ] = 0;
213 }
214 foreach ( $data as $row ) {
215 foreach ( $selectors as $selector ) {
216 $total = $total + $row[ $selector ];
217 $values[ $selector ] = $values[ $selector ] + $row[ $selector ];
218 }
219 }
220 $cpt = 0;
221 $labels = [];
222 $series = [];
223 while ( $cpt < $limit ) {
224 if ( 0 < $total ) {
225 $percent = round( 100 * $values[ $selectors[ $cpt ] ] / $total, 1 );
226 } else {
227 $percent = 100;
228 }
229 if ( 0.1 > $percent ) {
230 $percent = 0.1;
231 }
232 $labels[] = $names[ $selectors[ $cpt ] ];
233 $series[] = [
234 'meta' => $names[ $selectors[ $cpt ] ],
235 'value' => (float) $percent,
236 ];
237 ++$cpt;
238 }
239 if ( 0 < $other ) {
240 if ( 0 < $total ) {
241 $percent = round( 100 * $other / $total, 1 );
242 } else {
243 $percent = 100;
244 }
245 if ( 0.1 > $percent ) {
246 $percent = 0.1;
247 }
248 $labels[] = esc_html__( 'Other', 'sessions' );
249 $series[] = [
250 'meta' => esc_html__( 'Other', 'sessions' ),
251 'value' => (float) $percent,
252 ];
253 }
254 $result = '<div class="pose-pie-box">';
255 $result .= '<div class="pose-pie-graph">';
256 $result .= '<div class="pose-pie-graph-handler-120" id="pose-pie-' . $type . '"></div>';
257 $result .= '</div>';
258 $result .= '<div class="pose-pie-legend">';
259 foreach ( $labels as $key => $label ) {
260 $icon = '<img style="width:12px;vertical-align:baseline;" src="' . Feather\Icons::get_base64( 'square', $this->colors[ $key ], $this->colors[ $key ] ) . '" />';
261 $result .= '<div class="pose-pie-legend-item">' . $icon . '&nbsp;&nbsp;' . $label . '</div>';
262 }
263 $result .= '';
264 $result .= '</div>';
265 $result .= '</div>';
266 $result .= '<script>';
267 $result .= 'jQuery(function ($) {';
268 $result .= ' var data' . $uuid . ' = ' . wp_json_encode(
269 [
270 'labels' => $labels,
271 'series' => $series,
272 ]
273 ) . ';';
274 $result .= ' var tooltip' . $uuid . ' = Chartist.plugins.tooltip({percentage: true, appendToBody: true});';
275 $result .= ' var option' . $uuid . ' = {width: 120, height: 120, showLabel: false, donut: true, donutWidth: "40%", startAngle: 270, plugins: [tooltip' . $uuid . ']};';
276 $result .= ' new Chartist.Pie("#pose-pie-' . $type . '", data' . $uuid . ', option' . $uuid . ');';
277 $result .= '});';
278 $result .= '</script>';
279 } else {
280 $result = '<div class="pose-pie-box">';
281 $result .= '<div class="pose-pie-graph" style="margin:0 !important;">';
282 $result .= '<div class="pose-pie-graph-nodata-handler-120" id="pose-pie-' . $type . '"><span style="position: relative; top: 47px;">-&nbsp;' . esc_html__( 'No Data', 'sessions' ) . '&nbsp;-</span></div>';
283 $result .= '</div>';
284 $result .= '';
285 $result .= '</div>';
286 $result .= '</div>';
287 }
288 return [ 'pose-' . $type => $result ];
289 }
290
291 /**
292 * Query statistics chart.
293 *
294 * @return array The result of the query, ready to encode.
295 * @since 1.0.0
296 */
297 private function query_chart() {
298 $uuid = UUID::generate_unique_id( 5 );
299 $query = Schema::get_time_series( $this->filter, ! $this->is_today, '', [], false );
300 $item = [];
301 $item['user'] = [ 'u_ham', 'u_spam' ];
302 $item['session'] = [ 'u_active', 'expired', 'forced', 'idle' ];
303 $item['turnover'] = [ 'registration', 'delete' ];
304 $item['log'] = [ 'logout', 'login_success', 'login_fail', 'login_block' ];
305 $item['password'] = [ 'reset' ];
306 $data = [];
307 $series = [];
308 $labels = [];
309 $boundaries = [];
310 $json = [];
311 foreach ( $item as $selector => $array ) {
312 $boundaries[ $selector ] = [
313 'max' => 0,
314 'factor' => 1,
315 'order' => $item[ $selector ],
316 ];
317 }
318 // Data normalization.
319 if ( 0 !== count( $query ) ) {
320 foreach ( $query as $row ) {
321 $datetime = new \DateTime( $row['timestamp'], new \DateTimeZone( 'UTC' ) );
322 $datetime->setTimezone( $this->timezone );
323 $record = [];
324 foreach ( $row as $k => $v ) {
325 if ( 0 === strpos( $k, 'u_' ) ) {
326 if ( 0 < $row['cnt'] ) {
327 $record[ $k ] = (int) round( $v / $row['cnt'], 0 );
328 } else {
329 $record[ $k ] = 0;
330 }
331 } elseif ( 'cnt' !== $k && 'timestamp' !== $k ) {
332 $record[ $k ] = (int) $v;
333 }
334 }
335 $data[ strtotime( $datetime->format( 'Y-m-d' ) . ' 12:00:00' ) ] = $record;
336 }
337 // Boundaries computation.
338 foreach ( $data as $datum ) {
339 foreach ( array_merge( $item['user'], $item['session'], $item['turnover'], $item['log'], $item['password'] ) as $field ) {
340 foreach ( $item as $selector => $array ) {
341 if ( in_array( $field, $array, true ) ) {
342 if ( $boundaries[ $selector ]['max'] < $datum[ $field ] ) {
343 $boundaries[ $selector ]['max'] = $datum[ $field ];
344 if ( 1100 < $datum[ $field ] ) {
345 $boundaries[ $selector ]['factor'] = 1000;
346 }
347 if ( 1100000 < $datum[ $field ] ) {
348 $boundaries[ $selector ]['factor'] = 1000000;
349 }
350 $boundaries[ $selector ]['order'] = array_diff( $boundaries[ $selector ]['order'], [ $field ] );
351 array_unshift( $boundaries[ $selector ]['order'], $field );
352 }
353 continue 2;
354 }
355 }
356 }
357 }
358 // Series computation.
359 foreach ( $data as $timestamp => $datum ) {
360 // Series.
361 $ts = 'new Date(' . (string) $timestamp . '000)';
362 foreach ( array_merge( $item['user'], $item['session'], $item['turnover'], $item['log'], $item['password'] ) as $key ) {
363 foreach ( $item as $selector => $array ) {
364 if ( in_array( $key, $array, true ) ) {
365 $series[ $key ][] = [
366 'x' => $ts,
367 'y' => round( $datum[ $key ] / $boundaries[ $selector ]['factor'], ( 1 === $boundaries[ $selector ]['factor'] ? 0 : 2 ) ),
368 ];
369 continue 2;
370 }
371 }
372 }
373 // Labels.
374 $labels[] = 'moment(' . $timestamp . '000).format("ll")';
375 }
376 // Result encoding.
377 $shift = 86400;
378 $datetime = new \DateTime( $this->start . ' 00:00:00', $this->timezone );
379 $offset = $this->timezone->getOffset( $datetime );
380 $datetime = $datetime->getTimestamp() + $offset;
381 array_unshift( $labels, 'moment(' . (string) ( $datetime - $shift ) . '000).format("ll")' );
382 $before = [
383 'x' => 'new Date(' . (string) ( $datetime - $shift ) . '000)',
384 'y' => 'null',
385 ];
386 $datetime = new \DateTime( $this->end . ' 23:59:59', $this->timezone );
387 $offset = $this->timezone->getOffset( $datetime );
388 $datetime = $datetime->getTimestamp() + $offset;
389 $after = [
390 'x' => 'new Date(' . (string) ( $datetime + $shift ) . '000)',
391 'y' => 'null',
392 ];
393 foreach ( array_merge( $item['user'], $item['session'], $item['turnover'], $item['log'], $item['password'] ) as $key ) {
394 array_unshift( $series[ $key ], $before );
395 $series[ $key ][] = $after;
396 }
397 // Users.
398 foreach ( $item as $selector => $array ) {
399 $serie = [];
400 foreach ( $boundaries[ $selector ]['order'] as $field ) {
401 switch ( $field ) {
402 case 'u_ham':
403 if ( Environment::is_wordpress_multisite() ) {
404 $name = esc_html__( 'Legit Users', 'sessions' );
405 } else {
406 $name = esc_html__( 'Users', 'sessions' );
407 }
408 break;
409 case 'u_spam':
410 $name = esc_html__( 'Spam Users', 'sessions' );
411 break;
412 case 'u_active':
413 $name = esc_html__( 'Active Sessions', 'sessions' );
414 break;
415 case 'forced':
416 $name = esc_html__( 'Overridden Sessions', 'sessions' );
417 break;
418 case 'expired':
419 $name = esc_html__( 'Expired Sessions', 'sessions' );
420 break;
421 case 'idle':
422 $name = esc_html__( 'Idle Sessions', 'sessions' );
423 break;
424 case 'registration':
425 $name = esc_html__( 'Created Accounts', 'sessions' );
426 break;
427 case 'delete':
428 $name = esc_html__( 'Deleted Accounts', 'sessions' );
429 break;
430 case 'logout':
431 $name = esc_html__( 'Logouts', 'sessions' );
432 break;
433 case 'login_success':
434 $name = esc_html__( 'Successful Logins', 'sessions' );
435 break;
436 case 'login_fail':
437 $name = esc_html__( 'Failed Logins', 'sessions' );
438 break;
439 case 'login_block':
440 $name = esc_html__( 'Blocked Logins', 'sessions' );
441 break;
442 case 'reset':
443 $name = esc_html__( 'Password Resets', 'sessions' );
444 break;
445 default:
446 $name = esc_html__( 'Unknown', 'sessions' );
447 }
448 $serie[] = [
449 'name' => $name,
450 'data' => $series[ $field ],
451 ];
452 }
453 if ( 'turnover' === $selector || 'log' === $selector ) {
454 $json[ $selector ] = wp_json_encode(
455 [
456 'labels' => $labels,
457 'series' => $serie,
458 ]
459 );
460 } else {
461 $json[ $selector ] = wp_json_encode( [ 'series' => $serie ] );
462 }
463 $json[ $selector ] = str_replace( '"x":"new', '"x":new', $json[ $selector ] );
464 $json[ $selector ] = str_replace( ')","y"', '),"y"', $json[ $selector ] );
465 $json[ $selector ] = str_replace( '"null"', 'null', $json[ $selector ] );
466 $json[ $selector ] = str_replace( '"labels":["moment', '"labels":[moment', $json[ $selector ] );
467 $json[ $selector ] = str_replace( '","moment', ',moment', $json[ $selector ] );
468 $json[ $selector ] = str_replace( '"],"series":', '],"series":', $json[ $selector ] );
469 $json[ $selector ] = str_replace( '\\"', '"', $json[ $selector ] );
470 }
471
472 // Rendering.
473 if ( 4 < $this->duration ) {
474 if ( 1 === $this->duration % 2 ) {
475 $divisor = 6;
476 } else {
477 $divisor = 5;
478 }
479 } else {
480 $divisor = $this->duration + 1;
481 }
482 $result = '<div class="pose-multichart-handler">';
483 $result .= '<div class="pose-multichart-item active" id="pose-chart-user">';
484 $result .= '</div>';
485 $result .= '<script>';
486 $result .= 'jQuery(function ($) {';
487 $result .= ' var user_data' . $uuid . ' = ' . $json['user'] . ';';
488 $result .= ' var user_tooltip' . $uuid . ' = Chartist.plugins.tooltip({percentage: false, appendToBody: true});';
489 $result .= ' var user_option' . $uuid . ' = {';
490 $result .= ' height: 300,';
491 $result .= ' fullWidth: true,';
492 $result .= ' showArea: true,';
493 $result .= ' showLine: true,';
494 $result .= ' showPoint: false,';
495 $result .= ' plugins: [user_tooltip' . $uuid . '],';
496 $result .= ' axisX: {labelOffset: {x: 3,y: 0},scaleMinSpace: 100, type: Chartist.FixedScaleAxis, divisor:' . $divisor . ', labelInterpolationFnc: function (value) {return moment(value).format("YYYY-MM-DD");}},';
497 $result .= ' axisY: {type: Chartist.AutoScaleAxis, labelInterpolationFnc: function (value) {return value.toString() + " ' . Conversion::number_shorten( $boundaries['user']['factor'], 0, true )['abbreviation'] . '";}},';
498 $result .= ' };';
499 $result .= ' new Chartist.Line("#pose-chart-user", user_data' . $uuid . ', user_option' . $uuid . ');';
500 $result .= '});';
501 $result .= '</script>';
502 $result .= '<div class="pose-multichart-small-item" id="pose-chart-turnover">';
503 $result .= '<style>';
504 $result .= '.pose-multichart-small-item .ct-bar {stroke-width: 6px !important;stroke-opacity: 0.8 !important;}';
505 $result .= '</style>';
506 $result .= '</div>';
507 $result .= '<script>';
508 $result .= 'jQuery(function ($) {';
509 $result .= ' var turnover_data' . $uuid . ' = ' . $json['turnover'] . ';';
510 $result .= ' var turnover_tooltip' . $uuid . ' = Chartist.plugins.tooltip({justvalue: true, appendToBody: true});';
511 $result .= ' var turnover_option' . $uuid . ' = {';
512 $result .= ' height: 300,';
513 $result .= ' seriesBarDistance: 8,';
514 $result .= ' plugins: [turnover_tooltip' . $uuid . '],';
515 $result .= ' axisX: {showGrid: false, labelOffset: {x: 18,y: 0}},';
516 $result .= ' axisY: {showGrid: true, labelInterpolationFnc: function (value) {return value.toString() + " ' . Conversion::number_shorten( $boundaries['turnover']['factor'], 0, true )['abbreviation'] . '";}},';
517 $result .= ' };';
518 $result .= ' new Chartist.Bar("#pose-chart-turnover", turnover_data' . $uuid . ', turnover_option' . $uuid . ');';
519 $result .= '});';
520 $result .= '</script>';
521 $result .= '<div class="pose-multichart-item" id="pose-chart-session">';
522 $result .= '</div>';
523 $result .= '<script>';
524 $result .= 'jQuery(function ($) {';
525 $result .= ' var session_data' . $uuid . ' = ' . $json['session'] . ';';
526 $result .= ' var session_tooltip' . $uuid . ' = Chartist.plugins.tooltip({percentage: false, appendToBody: true});';
527 $result .= ' var session_option' . $uuid . ' = {';
528 $result .= ' height: 300,';
529 $result .= ' fullWidth: true,';
530 $result .= ' showArea: true,';
531 $result .= ' showLine: true,';
532 $result .= ' showPoint: false,';
533 $result .= ' plugins: [session_tooltip' . $uuid . '],';
534 $result .= ' axisX: {labelOffset: {x: 3,y: 0},scaleMinSpace: 100, type: Chartist.FixedScaleAxis, divisor:' . $divisor . ', labelInterpolationFnc: function (value) {return moment(value).format("YYYY-MM-DD");}},';
535 $result .= ' axisY: {type: Chartist.AutoScaleAxis, labelInterpolationFnc: function (value) {return value.toString() + " ' . Conversion::number_shorten( $boundaries['session']['factor'], 0, true )['abbreviation'] . '";}},';
536 $result .= ' };';
537 $result .= ' new Chartist.Line("#pose-chart-session", session_data' . $uuid . ', session_option' . $uuid . ');';
538 $result .= '});';
539 $result .= '</script>';
540 $result .= '<div class="pose-multichart-large-item large-bar" id="pose-chart-log">';
541 $result .= '<style>';
542 $result .= '.pose-multichart-large-item .ct-bar {stroke-width: 20px !important;stroke-opacity: 0.8 !important;}';
543 $result .= '</style>';
544 $result .= '</div>';
545 $result .= '<script>';
546 $result .= 'jQuery(function ($) {';
547 $result .= ' var log_data' . $uuid . ' = ' . $json['log'] . ';';
548 $result .= ' var log_tooltip' . $uuid . ' = Chartist.plugins.tooltip({justvalue: true, appendToBody: true});';
549 $result .= ' var log_option' . $uuid . ' = {';
550 $result .= ' height: 300,';
551 $result .= ' stackBars: true,';
552 $result .= ' stackMode: "accumulate",';
553 $result .= ' seriesBarDistance: 1,';
554 $result .= ' plugins: [log_tooltip' . $uuid . '],';
555 $result .= ' axisX: {showGrid: false, labelOffset: {x: 18,y: 0}},';
556 $result .= ' axisY: {showGrid: true, labelInterpolationFnc: function (value) {return value.toString() + " ' . Conversion::number_shorten( $boundaries['log']['factor'], 0, true )['abbreviation'] . '";}},';
557 $result .= ' };';
558 $result .= ' new Chartist.Bar("#pose-chart-log", log_data' . $uuid . ', log_option' . $uuid . ');';
559 $result .= '});';
560 $result .= '</script>';
561 $result .= '<div class="pose-multichart-item" id="pose-chart-password">';
562 $result .= '</div>';
563 $result .= '<script>';
564 $result .= 'jQuery(function ($) {';
565 $result .= ' var password_data' . $uuid . ' = ' . $json['password'] . ';';
566 $result .= ' var password_tooltip' . $uuid . ' = Chartist.plugins.tooltip({percentage: false, appendToBody: true});';
567 $result .= ' var password_option' . $uuid . ' = {';
568 $result .= ' height: 300,';
569 $result .= ' fullWidth: true,';
570 $result .= ' showArea: true,';
571 $result .= ' showLine: true,';
572 $result .= ' showPoint: false,';
573 $result .= ' plugins: [password_tooltip' . $uuid . '],';
574 $result .= ' axisX: {labelOffset: {x: 3,y: 0},scaleMinSpace: 100, type: Chartist.FixedScaleAxis, divisor:' . $divisor . ', labelInterpolationFnc: function (value) {return moment(value).format("YYYY-MM-DD");}},';
575 $result .= ' axisY: {type: Chartist.AutoScaleAxis, labelInterpolationFnc: function (value) {return value.toString() + " ' . Conversion::number_shorten( $boundaries['password']['factor'], 0, true )['abbreviation'] . '";}},';
576 $result .= ' };';
577 $result .= ' new Chartist.Line("#pose-chart-password", password_data' . $uuid . ', password_option' . $uuid . ');';
578 $result .= '});';
579 $result .= '</script>';
580 } else {
581 $result = '<div class="pose-multichart-handler">';
582 $result .= '<div class="pose-multichart-item active" id="pose-chart-user">';
583 $result .= $this->get_graph_placeholder_nodata( 274 );
584 $result .= '</div>';
585 $result .= '<div class="pose-multichart-item" id="pose-chart-turnover">';
586 $result .= $this->get_graph_placeholder_nodata( 274 );
587 $result .= '</div>';
588 $result .= '<div class="pose-multichart-item" id="pose-chart-session">';
589 $result .= $this->get_graph_placeholder_nodata( 274 );
590 $result .= '</div>';
591 $result .= '<div class="pose-multichart-item" id="pose-chart-log">';
592 $result .= $this->get_graph_placeholder_nodata( 274 );
593 $result .= '</div>';
594 $result .= '<div class="pose-multichart-item" id="pose-chart-password">';
595 $result .= $this->get_graph_placeholder_nodata( 274 );
596 $result .= '</div>';
597 }
598 return [ 'pose-main-chart' => $result ];
599 }
600
601 /**
602 * Query all kpis in statistics table.
603 *
604 * @param array $args Optional. The needed args.
605 * @return array The KPIs ready to send.
606 * @since 1.0.0
607 */
608 public static function get_status_kpi_collection( $args = [] ) {
609 $result['meta'] = [
610 'plugin' => POSE_PRODUCT_NAME . ' ' . POSE_VERSION,
611 'period' => date( 'Y-m-d' ),
612 ];
613 $result['data'] = [];
614 $kpi = new static( 'summary', date( 'Y-m-d' ), date( 'Y-m-d' ), false );
615 foreach ( [ 'session', 'cleaned', 'login', 'turnover', 'spam', 'user' ] as $query ) {
616 $data = $kpi->query_kpi( $query, false );
617
618 switch ( $query ) {
619 case 'session':
620 $val = Conversion::number_shorten( $data['kpi-main-session'], 0, true );
621 $result['data']['session'] = [
622 'name' => esc_html_x( 'Sessions', 'Noun - Active sessions.', 'sessions' ),
623 'short' => esc_html_x( 'Ses.', 'Noun - Short (max 4 char) - Active sessions.', 'sessions' ),
624 'description' => esc_html__( 'Number of active sessions.', 'sessions' ),
625 'dimension' => 'none',
626 'ratio' => null,
627 'variation' => [
628 'raw' => round( $data['kpi-index-session'] / 100, 6 ),
629 'percent' => round( $data['kpi-index-session'], 2 ),
630 'permille' => round( $data['kpi-index-session'] * 10, 2 ),
631 ],
632 'value' => [
633 'raw' => $data['kpi-main-session'],
634 'human' => $val['value'] . $val['abbreviation'],
635 ],
636 'metrics' => [
637 'name' => 'session_active_today_avg',
638 'desc' => 'Average number of active sessions today - [count]',
639 'value' => (float) $data['kpi-main-session'],
640 'type' => 'gauge',
641 ],
642 ];
643 break;
644 case 'cleaned':
645 $val = Conversion::number_shorten( $data['kpi-main-cleaned'], 0, true );
646 $result['data']['cleaned'] = [
647 'name' => esc_html_x( 'Cleaned', 'Noun - Cleaned sessions.', 'sessions' ),
648 'short' => esc_html_x( 'Cl.', 'Noun - Short (max 4 char) - Cleaned sessions.', 'sessions' ),
649 'description' => esc_html__( 'Number of cleaned sessions (idle, expired or overridden).', 'sessions' ),
650 'dimension' => 'none',
651 'ratio' => null,
652 'variation' => [
653 'raw' => round( $data['kpi-index-cleaned'] / 100, 6 ),
654 'percent' => round( $data['kpi-index-cleaned'], 2 ),
655 'permille' => round( $data['kpi-index-cleaned'] * 10, 2 ),
656 ],
657 'value' => [
658 'raw' => $data['kpi-main-cleaned'],
659 'human' => $val['value'] . $val['abbreviation'],
660 ],
661 'metrics' => [
662 'name' => 'session_cleaned_today',
663 'desc' => 'Number of cleaned sessions today - [count]',
664 'value' => (int) $data['kpi-main-cleaned'],
665 'type' => 'counter',
666 ],
667 ];
668 break;
669 case 'login':
670 $val = Conversion::number_shorten( $data['kpi-bottom-login'], 0, true );
671 $result['data']['login'] = [
672 'name' => esc_html_x( 'Logins', 'Noun - Successful logins.', 'sessions' ),
673 'short' => esc_html_x( 'Log.', 'Noun - Short (max 4 char) - Successful logins.', 'sessions' ),
674 'description' => esc_html__( 'Successful logins.', 'sessions' ),
675 'dimension' => 'none',
676 'ratio' => [
677 'raw' => round( $data['kpi-main-login'] / 100, 6 ),
678 'percent' => round( $data['kpi-main-login'], 2 ),
679 'permille' => round( $data['kpi-main-login'] * 10, 2 ),
680 ],
681 'variation' => [
682 'raw' => round( $data['kpi-index-login'] / 100, 6 ),
683 'percent' => round( $data['kpi-index-login'], 2 ),
684 'permille' => round( $data['kpi-index-login'] * 10, 2 ),
685 ],
686 'value' => [
687 'raw' => $data['kpi-bottom-login'],
688 'human' => $val['value'] . $val['abbreviation'],
689 ],
690 'metrics' => [
691 'name' => 'login_success_today',
692 'desc' => 'Ratio of successful logins today - [percent]',
693 'value' => (float) ( $data['kpi-main-login'] / 100.0 ),
694 'type' => 'gauge',
695 ],
696 ];
697 break;
698 case 'turnover':
699 $val = Conversion::number_shorten( $data['kpi-bottom-turnover'], 0, true );
700 $result['data']['turnover'] = [
701 'name' => esc_html_x( 'Moves', 'Noun - Moving users.', 'sessions' ),
702 'short' => esc_html_x( 'Mov.', 'Noun - Short (max 4 char) - Moving users.', 'sessions' ),
703 'description' => esc_html__( 'Moving users (registered or deleted).', 'sessions' ),
704 'dimension' => 'none',
705 'ratio' => [
706 'raw' => round( $data['kpi-main-turnover'] / 100, 6 ),
707 'percent' => round( $data['kpi-main-turnover'], 2 ),
708 'permille' => round( $data['kpi-main-turnover'] * 10, 2 ),
709 ],
710 'variation' => [
711 'raw' => round( $data['kpi-index-turnover'] / 100, 6 ),
712 'percent' => round( $data['kpi-index-turnover'], 2 ),
713 'permille' => round( $data['kpi-index-turnover'] * 10, 2 ),
714 ],
715 'value' => [
716 'raw' => $data['kpi-bottom-turnover'],
717 'human' => $val['value'] . $val['abbreviation'],
718 ],
719 'metrics' => [
720 'name' => 'user_turnover_today',
721 'desc' => 'Ratio of moving users today - [percent]',
722 'value' => (float) ( $data['kpi-main-turnover'] / 100.0 ),
723 'type' => 'counter',
724 ],
725 ];
726 break;
727 case 'user':
728 $val = Conversion::number_shorten( $data['kpi-bottom-user'], 0, true );
729 $result['data']['user'] = [
730 'name' => esc_html_x( 'Users', 'Noun - Active users.', 'sessions' ),
731 'short' => esc_html_x( 'Usr.', 'Noun - Short (max 4 char) - Active users.', 'sessions' ),
732 'description' => esc_html__( 'Active users.', 'sessions' ),
733 'dimension' => 'none',
734 'ratio' => [
735 'raw' => round( $data['kpi-main-user'] / 100, 6 ),
736 'percent' => round( $data['kpi-main-user'], 2 ),
737 'permille' => round( $data['kpi-main-user'] * 10, 2 ),
738 ],
739 'variation' => [
740 'raw' => round( $data['kpi-index-user'] / 100, 6 ),
741 'percent' => round( $data['kpi-index-user'], 2 ),
742 'permille' => round( $data['kpi-index-user'] * 10, 2 ),
743 ],
744 'value' => [
745 'raw' => $data['kpi-bottom-user'],
746 'human' => $val['value'] . $val['abbreviation'],
747 ],
748 'metrics' => [
749 'name' => 'user_active_today_avg',
750 'desc' => 'Average ratio of active users today - [percent]',
751 'value' => (float) ( $data['kpi-main-user'] / 100.0 ),
752 'type' => 'gauge',
753 ],
754 ];
755 break;
756 case 'spam':
757 $val = Conversion::number_shorten( $data['kpi-bottom-spam'], 0, true );
758 $result['data']['spam'] = [
759 'name' => esc_html_x( 'Spams', 'Noun - Users marked as spam.', 'sessions' ),
760 'short' => esc_html_x( 'Spm.', 'Noun - Short (max 4 char) - Users marked as spam.', 'sessions' ),
761 'description' => esc_html__( 'Users marked as spam.', 'sessions' ),
762 'dimension' => 'none',
763 'ratio' => [
764 'raw' => round( $data['kpi-main-spam'] / 100, 6 ),
765 'percent' => round( $data['kpi-main-spam'], 2 ),
766 'permille' => round( $data['kpi-main-spam'] * 10, 2 ),
767 ],
768 'variation' => [
769 'raw' => round( $data['kpi-index-spam'] / 100, 6 ),
770 'percent' => round( $data['kpi-index-spam'], 2 ),
771 'permille' => round( $data['kpi-index-spam'] * 10, 2 ),
772 ],
773 'value' => [
774 'raw' => $data['kpi-bottom-spam'],
775 'human' => $val['value'] . $val['abbreviation'],
776 ],
777 'metrics' => [
778 'name' => 'user_spam_avg',
779 'desc' => 'Ratio of spam user - [percent]',
780 'value' => (float) ( $data['kpi-main-spam'] / 100.0 ),
781 'type' => 'gauge',
782 ],
783 ];
784 break;
785 }
786 }
787 $result['assets'] = [];
788 return $result;
789 }
790
791 /**
792 * Query statistics table.
793 *
794 * @param mixed $queried The query params.
795 * @param boolean $chart Optional, return the chart if true, only the data if false;
796 * @return array The result of the query, ready to encode.
797 * @since 1.0.0
798 */
799 public function query_kpi( $queried, $chart = true ) {
800 $result = [];
801 $data = Schema::get_grouped_kpi( $this->filter, '', ! $this->is_today );
802 $pdata = Schema::get_grouped_kpi( $this->previous );
803 // COUNTS
804 if ( 'session' === $queried || 'cleaned' === $queried ) {
805 $current = 0.0;
806 $previous = 0.0;
807 switch ( $queried ) {
808 case 'session':
809 foreach ( $data as $row ) {
810 $current = $current + (float) ceil( $row['u_sessions'] / ( 0 < $row['cnt'] ? $row['cnt'] : 1 ) );
811 }
812 foreach ( $pdata as $row ) {
813 $previous = $previous + (float) ceil( $row['u_sessions'] / ( 0 < $row['cnt'] ? $row['cnt'] : 1 ) );
814 }
815 break;
816 case 'cleaned':
817 foreach ( $data as $row ) {
818 $current = $current + (float) $row['expired'] + (float) $row['idle'] + (float) $row['forced'];
819 }
820 foreach ( $pdata as $row ) {
821 $previous = $previous + (float) $row['expired'] + (float) $row['idle'] + (float) $row['forced'];
822 }
823 break;
824 }
825 $current = (int) ceil( $current );
826 $previous = (int) ceil( $previous );
827 if ( ! $chart ) {
828 $result[ 'kpi-main-' . $queried ] = (int) $current;
829 if ( 0 !== $current && 0 !== $previous ) {
830 $result[ 'kpi-index-' . $queried ] = round( 100 * ( $current - $previous ) / $previous, 4 );
831 } else {
832 $result[ 'kpi-index-' . $queried ] = null;
833 }
834 $result[ 'kpi-bottom-' . $queried ] = null;
835 return $result;
836 }
837 $result[ 'kpi-main-' . $queried ] = Conversion::number_shorten( (int) $current, 1, false, '&nbsp;' );
838 if ( 0 !== $current && 0 !== $previous ) {
839 $percent = round( 100 * ( $current - $previous ) / $previous, 1 );
840 if ( 0.1 > abs( $percent ) ) {
841 $percent = 0;
842 }
843 $result[ 'kpi-index-' . $queried ] = '<span style="color:' . ( 0 <= $percent ? '#18BB9C' : '#E74C3C' ) . ';">' . ( 0 < $percent ? '+' : '' ) . $percent . '&nbsp;%</span>';
844 } elseif ( 0 === $previous && 0 !== $current ) {
845 $result[ 'kpi-index-' . $queried ] = '<span style="color:#18BB9C;">+∞</span>';
846 } elseif ( 0 !== $previous && 100 !== $previous && 0 === $current ) {
847 $result[ 'kpi-index-' . $queried ] = '<span style="color:#E74C3C;">-∞</span>';
848 }
849 }
850 // RATIOS
851 if ( 'login' === $queried || 'turnover' === $queried || 'spam' === $queried || 'user' === $queried ) {
852 $base_value = 0.0;
853 $pbase_value = 0.0;
854 $data_value = 0.0;
855 $pdata_value = 0.0;
856 $current = 0.0;
857 $previous = 0.0;
858 $val = null;
859 switch ( $queried ) {
860 case 'login':
861 foreach ( $data as $row ) {
862 $base_value = $base_value + (float) $row['login_success'] + (float) $row['login_fail'] + (float) $row['login_block'];
863 $data_value = $data_value + (float) $row['login_success'];
864 }
865 foreach ( $pdata as $row ) {
866 $pbase_value = $pbase_value + (float) $row['login_success'] + (float) $row['login_fail'] + (float) $row['login_block'];
867 $pdata_value = $pdata_value + (float) $row['login_success'];
868 }
869 $val = (int) $data_value;
870 if ( 0 === $val ) {
871 $txt = esc_html__( 'no successful login', 'sessions' );
872 } else {
873 $txt = sprintf( esc_html( _n( '%s successful login', '%s successful logins', $val, 'sessions' ) ), Conversion::number_shorten( $val, 2, false, '&nbsp;' ) );
874 }
875 $result[ 'kpi-bottom-' . $queried ] = '<span class="pose-kpi-large-bottom-text">' . $txt . '</span>';
876 break;
877 case 'turnover':
878 foreach ( $data as $row ) {
879 $base_value = $base_value + ( 0 !== (int) $row['cnt'] ? (float) $row['u_total'] / (float) $row['cnt'] : 0.0 );
880 $data_value = $data_value + ( (float) $row['registration'] + (float) $row['registration'] ) / 2;
881 }
882 foreach ( $pdata as $row ) {
883 $pbase_value = $pbase_value + ( 0 !== (int) $row['cnt'] ? (float) $row['u_total'] / (float) $row['cnt'] : 0.0 );
884 $pdata_value = $pdata_value + ( (float) $row['registration'] + (float) $row['registration'] ) / 2;
885 }
886 $val = (int) $data_value * 2;
887 if ( 0 === $val ) {
888 $txt = esc_html__( 'no moves', 'sessions' );
889 } else {
890 $txt = sprintf( esc_html( _n( '%s move', '%s moves', $val, 'sessions' ) ), Conversion::number_shorten( $val, 2, false, '&nbsp;' ) );
891 }
892 $result[ 'kpi-bottom-' . $queried ] = '<span class="pose-kpi-large-bottom-text">' . $txt . '</span>';
893 break;
894 case 'spam':
895 foreach ( $data as $row ) {
896 $base_value = $base_value + ( 0 !== (int) $row['cnt'] ? (float) $row['u_total'] / (float) $row['cnt'] : 0.0 );
897 $data_value = $data_value + ( 0 !== (int) $row['cnt'] ? (float) $row['u_spam'] / (float) $row['cnt'] : 0.0 );
898 }
899 foreach ( $pdata as $row ) {
900 $pbase_value = $pbase_value + ( 0 !== (int) $row['cnt'] ? (float) $row['u_total'] / (float) $row['cnt'] : 0.0 );
901 $pdata_value = $pdata_value + ( 0 !== (int) $row['cnt'] ? (float) $row['u_spam'] / (float) $row['cnt'] : 0.0 );
902 }
903 $val = (int) $data_value;
904 if ( 0 === $val ) {
905 $txt = esc_html__( 'no spam users', 'sessions' );
906 } else {
907 $txt = sprintf( esc_html( _n( '%s spam user', '%s spam users', $val, 'sessions' ) ), Conversion::number_shorten( $val, 2, false, '&nbsp;' ) );
908 }
909 $result[ 'kpi-bottom-' . $queried ] = '<span class="pose-kpi-large-bottom-text">' . $txt . '</span>';
910 break;
911 case 'user':
912 foreach ( $data as $row ) {
913 $base_value = $base_value + ( 0 !== (int) $row['cnt'] ? (float) $row['u_total'] / (float) $row['cnt'] : 0.0 );
914 $data_value = $data_value + ( 0 !== (int) $row['cnt'] ? (float) $row['u_active'] / (float) $row['cnt'] : 0.0 );
915 }
916 foreach ( $pdata as $row ) {
917 $pbase_value = $pbase_value + ( 0 !== (int) $row['cnt'] ? (float) $row['u_total'] / (float) $row['cnt'] : 0.0 );
918 $pdata_value = $pdata_value + ( 0 !== (int) $row['cnt'] ? (float) $row['u_active'] / (float) $row['cnt'] : 0.0 );
919 }
920 $val = (int) $data_value;
921 if ( 0 === $val ) {
922 $txt = esc_html__( 'no active users', 'sessions' );
923 } else {
924 $txt = sprintf( esc_html( _n( '%s active user', '%s active users', (int) ( $val / $this->duration ), 'sessions' ) ), Conversion::number_shorten( $val / $this->duration, 2, false, '&nbsp;' ) );
925 }
926 $result[ 'kpi-bottom-' . $queried ] = '<span class="pose-kpi-large-bottom-text">' . $txt . '</span>';
927 break;
928 }
929 if ( 0.0 !== $base_value && 0.0 !== $data_value ) {
930 $current = 100 * $data_value / $base_value;
931 $result[ 'kpi-main-' . $queried ] = round( $current, $chart ? 1 : 4 );
932 } else {
933 if ( 0.0 !== $data_value ) {
934 $result[ 'kpi-main-' . $queried ] = 100;
935 } elseif ( 0.0 !== $base_value ) {
936 $result[ 'kpi-main-' . $queried ] = 0;
937 } else {
938 $result[ 'kpi-main-' . $queried ] = null;
939 }
940 }
941 if ( 0.0 !== $pbase_value && 0.0 !== $pdata_value ) {
942 $previous = 100 * $pdata_value / $pbase_value;
943 } else {
944 if ( 0.0 !== $pdata_value ) {
945 $previous = 100.0;
946 }
947 }
948 if ( 0.0 !== $current && 0.0 !== $previous ) {
949 $result[ 'kpi-index-' . $queried ] = round( 100 * ( $current - $previous ) / $previous, 4 );
950 } else {
951 $result[ 'kpi-index-' . $queried ] = null;
952 }
953 if ( ! $chart ) {
954 $result[ 'kpi-bottom-' . $queried ] = $val;
955 return $result;
956 }
957 if ( isset( $result[ 'kpi-main-' . $queried ] ) ) {
958 $result[ 'kpi-main-' . $queried ] = $result[ 'kpi-main-' . $queried ] . '&nbsp;%';
959 } else {
960 $result[ 'kpi-main-' . $queried ] = '-';
961 }
962 if ( 0.0 !== $current && 0.0 !== $previous ) {
963 $percent = round( 100 * ( $current - $previous ) / $previous, 1 );
964 if ( 0.1 > abs( $percent ) ) {
965 $percent = 0;
966 }
967 $result[ 'kpi-index-' . $queried ] = '<span style="color:' . ( 0 <= $percent ? '#18BB9C' : '#E74C3C' ) . ';">' . ( 0 < $percent ? '+' : '' ) . $percent . '&nbsp;%</span>';
968 } elseif ( 0.0 === $previous && 0.0 !== $current ) {
969 $result[ 'kpi-index-' . $queried ] = '<span style="color:#18BB9C;">+∞</span>';
970 } elseif ( 0.0 !== $previous && 100 !== $previous && 0.0 === $current ) {
971 $result[ 'kpi-index-' . $queried ] = '<span style="color:#E74C3C;">-∞</span>';
972 }
973 }
974 return $result;
975 }
976
977 /**
978 * Get the title bar.
979 *
980 * @return string The bar ready to print.
981 * @since 1.0.0
982 */
983 public function get_title_bar() {
984 $subtitle = '';
985 $title = esc_html__( 'Main Summary', 'sessions' );
986 $result = '<div class="pose-box pose-box-full-line">';
987 $result .= '<span class="pose-title">' . $title . '</span>';
988 $result .= '<span class="pose-subtitle">' . $subtitle . '</span>';
989 $result .= '<span class="pose-datepicker">' . $this->get_date_box() . '</span>';
990 $result .= '</div>';
991 return $result;
992 }
993
994 /**
995 * Get the KPI bar.
996 *
997 * @return string The bar ready to print.
998 * @since 1.0.0
999 */
1000 public function get_kpi_bar() {
1001 $result = '<div class="pose-box pose-box-full-line">';
1002 $result .= '<div class="pose-kpi-bar">';
1003 $result .= '<div class="pose-kpi-large">' . $this->get_large_kpi( 'login' ) . '</div>';
1004 $result .= '<div class="pose-kpi-large">' . $this->get_large_kpi( 'session' ) . '</div>';
1005 $result .= '<div class="pose-kpi-large">' . $this->get_large_kpi( 'cleaned' ) . '</div>';
1006 $result .= '<div class="pose-kpi-large">' . $this->get_large_kpi( 'user' ) . '</div>';
1007 $result .= '<div class="pose-kpi-large">' . $this->get_large_kpi( 'turnover' ) . '</div>';
1008 $result .= '<div class="pose-kpi-large">' . $this->get_large_kpi( 'spam' ) . '</div>';
1009 $result .= '</div>';
1010 $result .= '</div>';
1011 return $result;
1012 }
1013
1014 /**
1015 * Get the main chart.
1016 *
1017 * @return string The main chart ready to print.
1018 * @since 1.0.0
1019 */
1020 public function get_main_chart() {
1021 if ( 1 < $this->duration ) {
1022 $help_user = esc_html__( 'Users variation.', 'sessions' );
1023 $help_session = esc_html__( 'Sessions variation.', 'sessions' );
1024 $help_turnover = esc_html__( 'Moves distribution.', 'sessions' );
1025 $help_log = esc_html__( 'Login / logout breakdown.', 'sessions' );
1026 $help_password = esc_html__( 'Password resets.', 'sessions' );
1027 $detail = '<span class="pose-chart-button not-ready left" id="pose-chart-button-user" data-position="left" data-tooltip="' . $help_user . '"><img style="width:12px;vertical-align:baseline;" src="' . Feather\Icons::get_base64( 'users', 'none', '#73879C' ) . '" /></span>';
1028 $detail .= '&nbsp;&nbsp;&nbsp;<span class="pose-chart-button not-ready left" id="pose-chart-button-turnover" data-position="left" data-tooltip="' . $help_turnover . '"><img style="width:12px;vertical-align:baseline;" src="' . Feather\Icons::get_base64( 'refresh-cw', 'none', '#73879C' ) . '" /></span>';
1029 $detail .= '&nbsp;&nbsp;&nbsp;<span class="pose-chart-button not-ready left" id="pose-chart-button-session" data-position="left" data-tooltip="' . $help_session . '"><img style="width:12px;vertical-align:baseline;" src="' . Feather\Icons::get_base64( 'activity', 'none', '#73879C' ) . '" /></span>';
1030 $detail .= '&nbsp;&nbsp;&nbsp;<span class="pose-chart-button not-ready left" id="pose-chart-button-log" data-position="left" data-tooltip="' . $help_log . '"><img style="width:12px;vertical-align:baseline;" src="' . Feather\Icons::get_base64( 'move', 'none', '#73879C' ) . '" /></span>';
1031 $detail .= '&nbsp;&nbsp;&nbsp;<span class="pose-chart-button not-ready left" id="pose-chart-button-password" data-position="left" data-tooltip="' . $help_password . '"><img style="width:12px;vertical-align:baseline;" src="' . Feather\Icons::get_base64( 'key', 'none', '#73879C' ) . '" /></span>';
1032 $result = '<div class="pose-row">';
1033 $result .= '<div class="pose-box pose-box-full-line">';
1034 $result .= '<div class="pose-module-title-bar"><span class="pose-module-title">' . esc_html__( 'Metrics Variations', 'sessions' ) . '<span class="pose-module-more">' . $detail . '</span></span></div>';
1035 $result .= '<div class="pose-module-content" id="pose-main-chart">' . $this->get_graph_placeholder( 274 ) . '</div>';
1036 $result .= '</div>';
1037 $result .= '</div>';
1038 $result .= $this->get_refresh_script(
1039 [
1040 'query' => 'main-chart',
1041 'queried' => 0,
1042 ]
1043 );
1044 return $result;
1045 } else {
1046 return '';
1047 }
1048 }
1049
1050 /**
1051 * Get a large kpi box.
1052 *
1053 * @param string $kpi The kpi to render.
1054 * @return string The box ready to print.
1055 * @since 1.0.0
1056 */
1057 private function get_large_kpi( $kpi ) {
1058 switch ( $kpi ) {
1059 case 'login':
1060 $icon = Feather\Icons::get_base64( 'log-in', 'none', '#73879C' );
1061 $title = esc_html_x( 'Login Success', 'Noun - Number of successful logins.', 'sessions' );
1062 $help = esc_html__( 'Ratio of successful logins.', 'sessions' );
1063 break;
1064 case 'session':
1065 $icon = Feather\Icons::get_base64( 'activity', 'none', '#73879C' );
1066 $title = esc_html_x( 'Active Sessions', 'Noun - Number of active sessions.', 'sessions' );
1067 $help = esc_html__( 'Number of active sessions.', 'sessions' );
1068 break;
1069 case 'cleaned':
1070 $icon = Feather\Icons::get_base64( 'trash-2', 'none', '#73879C' );
1071 $title = esc_html_x( 'Cleaned Sessions', 'Noun - Number of cleaned sessions.', 'sessions' );
1072 $help = esc_html__( 'Number of cleaned sessions (idle, expired or overridden).', 'sessions' );
1073 break;
1074 case 'user':
1075 $icon = Feather\Icons::get_base64( 'users', 'none', '#73879C' );
1076 $title = esc_html_x( 'Active Users', 'Noun - Percentage of active users.', 'sessions' );
1077 $help = esc_html__( 'Ratio of active users.', 'sessions' );
1078 break;
1079 case 'turnover':
1080 $icon = Feather\Icons::get_base64( 'refresh-cw', 'none', '#73879C' );
1081 $title = esc_html_x( 'Turnover', 'Noun - Users turnover.', 'sessions' );
1082 $help = esc_html__( 'Ratio of moving users (registered or deleted).', 'sessions' );
1083 break;
1084 case 'spam':
1085 $icon = Feather\Icons::get_base64( 'user-x', 'none', '#73879C' );
1086 $title = esc_html_x( 'Spam', 'Noun - Ratio of users marked as spam.', 'sessions' );
1087 $help = esc_html__( 'Ratio of users marked as spam.', 'sessions' );
1088 break;
1089 }
1090 $top = '<img style="width:12px;vertical-align:baseline;" src="' . $icon . '" />&nbsp;&nbsp;<span style="cursor:help;" class="pose-kpi-large-top-text bottom" data-position="bottom" data-tooltip="' . $help . '">' . $title . '</span>';
1091 $indicator = '&nbsp;';
1092 $bottom = '<span class="pose-kpi-large-bottom-text">&nbsp;</span>';
1093 $result = '<div class="pose-kpi-large-top">' . $top . '</div>';
1094 $result .= '<div class="pose-kpi-large-middle"><div class="pose-kpi-large-middle-left" id="kpi-main-' . $kpi . '">' . $this->get_value_placeholder() . '</div><div class="pose-kpi-large-middle-right" id="kpi-index-' . $kpi . '">' . $indicator . '</div></div>';
1095 $result .= '<div class="pose-kpi-large-bottom" id="kpi-bottom-' . $kpi . '">' . $bottom . '</div>';
1096 $result .= $this->get_refresh_script(
1097 [
1098 'query' => 'kpi',
1099 'queried' => $kpi,
1100 ]
1101 );
1102 return $result;
1103 }
1104
1105 /**
1106 * Get the logins pie.
1107 *
1108 * @return string The pie box ready to print.
1109 * @since 1.0.0
1110 */
1111 public function get_login_pie() {
1112 $result = '<div class="pose-50-module-left">';
1113 $result .= '<div class="pose-module-title-bar"><span class="pose-module-title">' . esc_html__( 'Logins', 'sessions' ) . '</span></div>';
1114 $result .= '<div class="pose-module-content" id="pose-login">' . $this->get_graph_placeholder( 90 ) . '</div>';
1115 $result .= '</div>';
1116 $result .= $this->get_refresh_script(
1117 [
1118 'query' => 'login',
1119 'queried' => 3,
1120 ]
1121 );
1122 return $result;
1123 }
1124
1125 /**
1126 * Get the clean pie.
1127 *
1128 * @return string The pie box ready to print.
1129 * @since 1.0.0
1130 */
1131 public function get_clean_pie() {
1132 $result = '<div class="pose-50-module-right">';
1133 $result .= '<div class="pose-module-title-bar"><span class="pose-module-title">' . esc_html__( 'Cleaned Sessions', 'sessions' ) . '</span></div>';
1134 $result .= '<div class="pose-module-content" id="pose-clean">' . $this->get_graph_placeholder( 90 ) . '</div>';
1135 $result .= '</div>';
1136 $result .= $this->get_refresh_script(
1137 [
1138 'query' => 'clean',
1139 'queried' => 3,
1140 ]
1141 );
1142 return $result;
1143 }
1144
1145 /**
1146 * Get a placeholder for graph.
1147 *
1148 * @param integer $height The height of the placeholder.
1149 * @return string The placeholder, ready to print.
1150 * @since 1.0.0
1151 */
1152 private function get_graph_placeholder( $height ) {
1153 return '<p style="text-align:center;line-height:' . $height . 'px;"><img style="width:40px;vertical-align:middle;" src="' . POSE_ADMIN_URL . 'medias/bars.svg" /></p>';
1154 }
1155
1156 /**
1157 * Get a placeholder for graph with no data.
1158 *
1159 * @param integer $height The height of the placeholder.
1160 * @return string The placeholder, ready to print.
1161 * @since 1.0.0
1162 */
1163 private function get_graph_placeholder_nodata( $height ) {
1164 return '<p style="color:#73879C;text-align:center;line-height:' . $height . 'px;">' . esc_html__( 'No Data', 'sessions' ) . '</p>';
1165 }
1166
1167 /**
1168 * Get a placeholder for value.
1169 *
1170 * @return string The placeholder, ready to print.
1171 * @since 1.0.0
1172 */
1173 private function get_value_placeholder() {
1174 return '<img style="width:26px;vertical-align:middle;" src="' . POSE_ADMIN_URL . 'medias/three-dots.svg" />';
1175 }
1176
1177 /**
1178 * Get refresh script.
1179 *
1180 * @param array $args Optional. The args for the ajax call.
1181 * @return string The script, ready to print.
1182 * @since 1.0.0
1183 */
1184 private function get_refresh_script( $args = [] ) {
1185 $result = '<script>';
1186 $result .= 'jQuery(document).ready( function($) {';
1187 $result .= ' var data = {';
1188 $result .= ' action:"pose_get_stats",';
1189 $result .= ' nonce:"' . wp_create_nonce( 'ajax_pose' ) . '",';
1190 foreach ( $args as $key => $val ) {
1191 $s = ' ' . $key . ':';
1192 if ( is_string( $val ) ) {
1193 $s .= '"' . $val . '"';
1194 } elseif ( is_numeric( $val ) ) {
1195 $s .= $val;
1196 } elseif ( is_bool( $val ) ) {
1197 $s .= $val ? 'true' : 'false';
1198 }
1199 $result .= $s . ',';
1200 }
1201 $result .= ' type:"' . $this->type . '",';
1202 $result .= ' start:"' . $this->start . '",';
1203 $result .= ' end:"' . $this->end . '",';
1204 $result .= ' };';
1205 $result .= ' $.post(ajaxurl, data, function(response) {';
1206 $result .= ' var val = JSON.parse(response);';
1207 $result .= ' $.each(val, function(index, value) {$("#" + index).html(value);});';
1208 if ( array_key_exists( 'query', $args ) && 'main-chart' === $args['query'] ) {
1209 $result .= '$(".pose-chart-button").removeClass("not-ready");';
1210 $result .= '$("#pose-chart-button-user").addClass("active");';
1211 }
1212 $result .= ' });';
1213 $result .= '});';
1214 $result .= '</script>';
1215 return $result;
1216 }
1217
1218 /**
1219 * Get the url.
1220 *
1221 * @param array $exclude Optional. The args to exclude.
1222 * @param array $replace Optional. The args to replace or add.
1223 * @param boolean $escape Optional. Forces url escaping.
1224 * @return string The url.
1225 * @since 1.0.0
1226 */
1227 private function get_url( $exclude = [], $replace = [], $escape = true ) {
1228 $params = [];
1229 $params['type'] = $this->type;
1230 $params['start'] = $this->start;
1231 $params['end'] = $this->end;
1232 foreach ( $exclude as $arg ) {
1233 unset( $params[ $arg ] );
1234 }
1235 foreach ( $replace as $key => $arg ) {
1236 $params[ $key ] = $arg;
1237 }
1238 $url = admin_url( 'admin.php?page=pose-viewer' );
1239 foreach ( $params as $key => $arg ) {
1240 if ( '' !== $arg ) {
1241 $url .= '&' . $key . '=' . rawurlencode( $arg );
1242 }
1243 }
1244 $url = str_replace( '"', '\'\'', $url );
1245 if ( $escape ) {
1246 $url = esc_url( $url );
1247 }
1248 return $url;
1249 }
1250
1251 /**
1252 * Get a date picker box.
1253 *
1254 * @return string The box ready to print.
1255 * @since 1.0.0
1256 */
1257 private function get_date_box() {
1258 $result = '<img style="width:13px;vertical-align:middle;" src="' . Feather\Icons::get_base64( 'calendar', 'none', '#5A738E' ) . '" />&nbsp;&nbsp;<span class="pose-datepicker-value"></span>';
1259 $result .= '<script>';
1260 $result .= 'jQuery(function ($) {';
1261 $result .= ' moment.locale("' . L10n::get_display_locale() . '");';
1262 $result .= ' var start = moment("' . $this->start . '");';
1263 $result .= ' var end = moment("' . $this->end . '");';
1264 $result .= ' function changeDate(start, end) {';
1265 $result .= ' $("span.pose-datepicker-value").html(start.format("LL") + " - " + end.format("LL"));';
1266 $result .= ' }';
1267 $result .= ' $(".pose-datepicker").daterangepicker({';
1268 $result .= ' opens: "left",';
1269 $result .= ' startDate: start,';
1270 $result .= ' endDate: end,';
1271 $result .= ' minDate: moment("' . Schema::get_oldest_date() . '"),';
1272 $result .= ' maxDate: moment(),';
1273 $result .= ' showCustomRangeLabel: true,';
1274 $result .= ' alwaysShowCalendars: true,';
1275 $result .= ' locale: {customRangeLabel: "' . esc_html__( 'Custom Range', 'sessions' ) . '",cancelLabel: "' . esc_html__( 'Cancel', 'sessions' ) . '", applyLabel: "' . esc_html__( 'Apply', 'sessions' ) . '"},';
1276 $result .= ' ranges: {';
1277 $result .= ' "' . esc_html__( 'Today', 'sessions' ) . '": [moment(), moment()],';
1278 $result .= ' "' . esc_html__( 'Yesterday', 'sessions' ) . '": [moment().subtract(1, "days"), moment().subtract(1, "days")],';
1279 $result .= ' "' . esc_html__( 'This Month', 'sessions' ) . '": [moment().startOf("month"), moment().endOf("month")],';
1280 $result .= ' "' . esc_html__( 'Last Month', 'sessions' ) . '": [moment().subtract(1, "month").startOf("month"), moment().subtract(1, "month").endOf("month")],';
1281 $result .= ' }';
1282 $result .= ' }, changeDate);';
1283 $result .= ' changeDate(start, end);';
1284 $result .= ' $(".pose-datepicker").on("apply.daterangepicker", function(ev, picker) {';
1285 $result .= ' var url = "' . $this->get_url( [ 'start', 'end' ], [], false ) . '" + "&start=" + picker.startDate.format("YYYY-MM-DD") + "&end=" + picker.endDate.format("YYYY-MM-DD");';
1286 $result .= ' $(location).attr("href", url);';
1287 $result .= ' });';
1288 $result .= '});';
1289 $result .= '</script>';
1290 return $result;
1291 }
1292
1293 }
1294