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-schema.php

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

400 lines 13.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * POSessions schema
4 *
5 * Handles all schema 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\System\Option;
15 use POSessions\System\Database;
16 use POSessions\System\Environment;
17 use POSessions\System\Favicon;
18
19 use POSessions\System\Cache;
20 use POSessions\System\Timezone;
21 use POSessions\Plugin\Feature\Capture;
22
23 /**
24 * Define the schema functionality.
25 *
26 * Handles all schema operations.
27 *
28 * @package Features
29 * @author Pierre Lannoy <https://pierre.lannoy.fr/>.
30 * @since 1.0.0
31 */
32 class Schema {
33
34 /**
35 * Statistics table name.
36 *
37 * @since 1.0.0
38 * @var string $statistics The statistics table name.
39 */
40 private static $statistics = 'sessions_statistics';
41
42 /**
43 * Initialize the class and set its properties.
44 *
45 * @since 1.0.0
46 */
47 public function __construct() {
48 }
49
50 /**
51 * Initialize static properties and hooks.
52 *
53 * @since 1.0.0
54 */
55 public static function init() {
56 add_action( 'shutdown', [ self::class, 'write' ], 11, 0 );
57 }
58
59 /**
60 * Write all buffers to database.
61 *
62 * @param boolean $purge Optional. Purge od dates too.
63 * @since 1.0.0
64 */
65 public static function write( $purge = true ) {
66 if ( Option::network_get( 'analytics' ) ) {
67 self::write_current_to_database( Capture::get_stats() );
68 }
69 if ( $purge ) {
70 self::purge();
71 }
72 }
73
74 /**
75 * Effectively write a buffer element in the database.
76 *
77 * @param array $record The buffer to write.
78 * @since 1.0.0
79 */
80 private static function write_current_to_database( $record ) {
81 $record = self::maybe_add_stats( $record );
82 if ( 0 === count( $record ) ) {
83 return;
84 }
85 $datetime = new \DateTime( 'now', Timezone::network_get() );
86 $record['timestamp'] = $datetime->format( 'Y-m-d' );
87 $field_insert = [];
88 $value_insert = [];
89 $value_update = [];
90 foreach ( $record as $k => $v ) {
91 $field_insert[] = '`' . $k . '`';
92 if ( 'timestamp' === $k ) {
93 $value_insert[] = "'" . $v . "'";
94 } else {
95 $value_insert[] = (int) $v;
96 $value_update[] = '`' . $k . '`=`' . $k . '` + ' . (int) $v;
97 }
98 }
99 if ( count( $field_insert ) > 1 ) {
100 global $wpdb;
101 $sql = 'INSERT INTO `' . $wpdb->base_prefix . self::$statistics . '` ';
102 $sql .= '(' . implode( ',', $field_insert ) . ') ';
103 $sql .= 'VALUES (' . implode( ',', $value_insert ) . ') ';
104 $sql .= 'ON DUPLICATE KEY UPDATE ' . implode( ',', $value_update ) . ';';
105 // phpcs:ignore
106 $wpdb->query( $sql );
107 }
108 }
109
110 /**
111 * Adds misc stats to a buffer, if needed.
112 *
113 * @param array $record The buffer to write.
114 * @return array The completed buffer if needed.
115 * @since 1.0.0
116 */
117 private static function maybe_add_stats( $record ) {
118 $check = Cache::get_global( 'data/statcheck' );
119 if ( isset( $check ) && $check && (int) $check + 6 * HOUR_IN_SECONDS > time() ) {
120 return $record;
121 }
122 $record['cnt'] = 1;
123 $record['u_ham'] = 0;
124 $record['u_total'] = 0;
125 $record['u_spam'] = 0;
126 $record['u_active'] = 0;
127 $record['u_sessions'] = 0;
128 global $wpdb;
129 $sql = 'SELECT COUNT(*) as u_cnt, user_status FROM ' . $wpdb->users . ' GROUP BY user_status';
130 // phpcs:ignore
131 $query = $wpdb->get_results( $sql, ARRAY_A );
132 if ( is_array( $query ) && 0 < count( $query ) ) {
133 $record['u_total'] = 0;
134 foreach ( $query as $row ) {
135 if ( 0 === (int) $row['user_status'] ) {
136 $record['u_ham'] = $row['u_cnt'];
137 $record['u_total'] += $row['u_cnt'];
138 }
139 if ( 1 === (int) $row['user_status'] ) {
140 $record['u_spam'] = $row['u_cnt'];
141 $record['u_total'] += $row['u_cnt'];
142 }
143 }
144 }
145 $sql = "SELECT COUNT(*) AS users, SUM( CAST( SUBSTRING(`meta_value`,3,POSITION('{' IN `meta_value`) - 4) AS UNSIGNED)) AS sessions FROM " . $wpdb->usermeta . " WHERE `meta_key`='session_tokens' and `meta_value`<>'' and `meta_value`<>'a:0:{}'";
146 // phpcs:ignore
147 $query = $wpdb->get_results( $sql, ARRAY_A );
148 if ( is_array( $query ) && 0 < count( $query ) ) {
149 $record['u_active'] = $query[0]['users'];
150 $record['u_sessions'] = $query[0]['sessions'];
151 }
152 \DecaLog\Engine::eventsLogger( POSE_SLUG )->debug( 'Misc stats added.' );
153 Cache::set_global( 'data/statcheck', time(), 'infinite' );
154 return $record;
155 }
156
157 /**
158 * Initialize the schema.
159 *
160 * @since 1.1.0
161 */
162 public function initialize() {
163 global $wpdb;
164 try {
165 $this->create_table();
166 \DecaLog\Engine::eventsLogger( POSE_SLUG )->debug( sprintf( 'Table "%s" created.', $wpdb->base_prefix . self::$statistics ) );
167 \DecaLog\Engine::eventsLogger( POSE_SLUG )->info( 'Schema installed.' );
168 } catch ( \Throwable $e ) {
169 \DecaLog\Engine::eventsLogger( POSE_SLUG )->alert( sprintf( 'Unable to create "%s" table: %s', $wpdb->base_prefix . self::$statistics, $e->getMessage() ), [ 'code' => $e->getCode() ] );
170 \DecaLog\Engine::eventsLogger( POSE_SLUG )->alert( 'Schema not installed.', $e->getCode() );
171 }
172 }
173
174 /**
175 * Update the schema.
176 *
177 * @since 1.1.0
178 */
179 public function update() {
180 global $wpdb;
181 try {
182 $this->create_table();
183 \DecaLog\Engine::eventsLogger( POSE_SLUG )->debug( sprintf( 'Table "%s" updated.', $wpdb->base_prefix . self::$statistics ) );
184 \DecaLog\Engine::eventsLogger( POSE_SLUG )->info( 'Schema updated.' );
185 } catch ( \Throwable $e ) {
186 \DecaLog\Engine::eventsLogger( POSE_SLUG )->alert( sprintf( 'Unable to update "%s" table: %s', $wpdb->base_prefix . self::$statistics, $e->getMessage() ), [ 'code' => $e->getCode() ] );
187 }
188 }
189
190 /**
191 * Purge old records.
192 *
193 * @since 1.0.0
194 */
195 private static function purge() {
196 $days = (int) Option::network_get( 'history' );
197 if ( ! is_numeric( $days ) || 30 > $days ) {
198 $days = 30;
199 Option::network_set( 'history', $days );
200 }
201 $database = new Database();
202 $count = $database->purge( self::$statistics, 'timestamp', 24 * $days );
203 if ( 0 === $count ) {
204 \DecaLog\Engine::eventsLogger( POSE_SLUG )->debug( 'No old records to delete.' );
205 } elseif ( 1 === $count ) {
206 \DecaLog\Engine::eventsLogger( POSE_SLUG )->debug( '1 old record deleted.' );
207 Cache::delete_global( 'data/oldestdate' );
208 } else {
209 \DecaLog\Engine::eventsLogger( POSE_SLUG )->debug( sprintf( '%1$s old records deleted.', $count ) );
210 Cache::delete_global( 'data/oldestdate' );
211 }
212 }
213
214 /**
215 * Create the table.
216 *
217 * @since 1.0.0
218 */
219 private function create_table() {
220 global $wpdb;
221 $charset_collate = 'DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci';
222 $sql = 'CREATE TABLE IF NOT EXISTS ' . $wpdb->base_prefix . self::$statistics;
223 $sql .= " (`timestamp` date NOT NULL DEFAULT '0000-00-00',";
224 $sql .= " `cnt` int(11) UNSIGNED NOT NULL DEFAULT '0',";
225 $sql .= " `u_total` bigint UNSIGNED NOT NULL DEFAULT '0',";
226 $sql .= " `u_ham` bigint UNSIGNED NOT NULL DEFAULT '0',";
227 $sql .= " `u_spam` bigint UNSIGNED NOT NULL DEFAULT '0',";
228 $sql .= " `u_active` bigint UNSIGNED NOT NULL DEFAULT '0',";
229 $sql .= " `u_suspended` bigint UNSIGNED NOT NULL DEFAULT '0',";
230 $sql .= " `u_banned` bigint UNSIGNED NOT NULL DEFAULT '0',";
231 $sql .= " `u_sessions` bigint UNSIGNED NOT NULL DEFAULT '0',";
232 $sql .= " `expired` int(11) UNSIGNED NOT NULL DEFAULT '0',";
233 $sql .= " `idle` int(11) UNSIGNED NOT NULL DEFAULT '0',";
234 $sql .= " `forced` int(11) UNSIGNED NOT NULL DEFAULT '0',";
235 $sql .= " `registration` int(11) UNSIGNED NOT NULL DEFAULT '0',";
236 $sql .= " `delete` int(11) UNSIGNED NOT NULL DEFAULT '0',";
237 $sql .= " `reset` int(11) UNSIGNED NOT NULL DEFAULT '0',";
238 $sql .= " `logout` int(11) UNSIGNED NOT NULL DEFAULT '0',";
239 $sql .= " `login_success` int(11) UNSIGNED NOT NULL DEFAULT '0',";
240 $sql .= " `login_fail` int(11) UNSIGNED NOT NULL DEFAULT '0',";
241 $sql .= " `login_block` int(11) UNSIGNED NOT NULL DEFAULT '0',";
242 $sql .= ' UNIQUE KEY u_stat (timestamp)';
243 $sql .= ") $charset_collate;";
244 // phpcs:ignore
245 $wpdb->query( $sql );
246 }
247
248 /**
249 * Finalize the schema.
250 *
251 * @since 1.0.0
252 */
253 public function finalize() {
254 global $wpdb;
255 $sql = 'DROP TABLE IF EXISTS ' . $wpdb->base_prefix . self::$statistics;
256 // phpcs:ignore
257 $wpdb->query( $sql );
258 \DecaLog\Engine::eventsLogger( POSE_SLUG )->debug( sprintf( 'Table "%s" removed.', $wpdb->base_prefix . self::$statistics ) );
259 \DecaLog\Engine::eventsLogger( POSE_SLUG )->debug( 'Schema destroyed.' );
260 }
261
262 /**
263 * Get "where" clause of a query.
264 *
265 * @param array $filters Optional. An array of filters.
266 * @return string The "where" clause.
267 * @since 1.0.0
268 */
269 private static function get_where_clause( $filters = [] ) {
270 $result = '';
271 if ( 0 < count( $filters ) ) {
272 $w = [];
273 foreach ( $filters as $key => $filter ) {
274 if ( is_array( $filter ) ) {
275 $w[] = '`' . $key . '` IN (' . implode( ',', $filter ) . ')';
276 } else {
277 $w[] = '`' . $key . '`="' . $filter . '"';
278 }
279 }
280 $result = 'WHERE (' . implode( ' AND ', $w ) . ')';
281 }
282 return $result;
283 }
284
285 /**
286 * Get the oldest date.
287 *
288 * @return string The oldest timestamp in the statistics table.
289 * @since 1.0.0
290 */
291 public static function get_oldest_date() {
292 $result = Cache::get_global( 'data/oldestdate' );
293 if ( $result ) {
294 return $result;
295 }
296 global $wpdb;
297 $sql = 'SELECT * FROM ' . $wpdb->base_prefix . self::$statistics . ' ORDER BY `timestamp` ASC LIMIT 1';
298 // phpcs:ignore
299 $result = $wpdb->get_results( $sql, ARRAY_A );
300 if ( is_array( $result ) && 0 < count( $result ) && array_key_exists( 'timestamp', $result[0] ) ) {
301 Cache::set_global( 'data/oldestdate', $result[0]['timestamp'], 'infinite' );
302 return $result[0]['timestamp'];
303 }
304 return '';
305 }
306
307 /**
308 * Get the standard KPIs.
309 *
310 * @param array $filter The filter of the query.
311 * @param string $group Optional. The group of the query.
312 * @param boolean $cache Optional. Has the query to be cached.
313 * @return array The grouped KPIs.
314 * @since 1.0.0
315 */
316 public static function get_grouped_kpi( $filter, $group = '', $cache = true ) {
317 // phpcs:ignore
318 $id = Cache::id( __FUNCTION__ . serialize( $filter ) . $group );
319 if ( $cache ) {
320 $result = Cache::get_global( $id );
321 if ( $result ) {
322 return $result;
323 }
324 }
325 if ( '' !== $group ) {
326 $group = ' GROUP BY ' . $group;
327 }
328 global $wpdb;
329 $sql = 'SELECT * FROM ' . $wpdb->base_prefix . self::$statistics . ' WHERE (' . implode( ' AND ', $filter ) . ')' . $group;
330 // phpcs:ignore
331 $result = $wpdb->get_results( $sql, ARRAY_A );
332 if ( is_array( $result ) ) {
333 if ( $cache ) {
334 Cache::set_global( $id, $result, 'infinite' );
335 }
336 return $result;
337 }
338 return [];
339 }
340
341 /**
342 * Get a time series.
343 *
344 * @param array $filter The filter of the query.
345 * @param boolean $cache Has the query to be cached.
346 * @param string $extra_field Optional. The extra field to filter.
347 * @param array $extras Optional. The extra values to match.
348 * @param boolean $not Optional. Exclude extra filter.
349 * @param integer $limit Optional. The number of results to return.
350 * @return array The time series.
351 * @since 1.0.0
352 */
353 public static function get_time_series( $filter, $cache = true, $extra_field = '', $extras = [], $not = false, $limit = 0 ) {
354 return self::get_grouped_list( $filter, '', $cache, $extra_field, $extras, $not, 'ORDER BY timestamp ASC', $limit );
355 }
356
357 /**
358 * Get the a grouped list.
359 *
360 * @param array $filter The filter of the query.
361 * @param string $group Optional. The group of the query.
362 * @param boolean $cache Optional. Has the query to be cached.
363 * @param string $extra_field Optional. The extra field to filter.
364 * @param array $extras Optional. The extra values to match.
365 * @param boolean $not Optional. Exclude extra filter.
366 * @param string $order Optional. The sort order of results.
367 * @param integer $limit Optional. The number of results to return.
368 * @return array The grouped list.
369 * @since 1.0.0
370 */
371 public static function get_grouped_list( $filter, $group = '', $cache = true, $extra_field = '', $extras = [], $not = false, $order = '', $limit = 0 ) {
372 // phpcs:ignore
373 $id = Cache::id( __FUNCTION__ . serialize( $filter ) . $group . $extra_field . serialize( $extras ) . ( $not ? 'no' : 'yes') . $order . (string) $limit);
374 if ( $cache ) {
375 $result = Cache::get_global( $id );
376 if ( $result ) {
377 return $result;
378 }
379 }
380 if ( '' !== $group ) {
381 $group = ' GROUP BY ' . $group;
382 }
383 $where_extra = '';
384 if ( 0 < count( $extras ) && '' !== $extra_field ) {
385 $where_extra = ' AND ' . $extra_field . ( $not ? ' NOT' : '' ) . " IN ( '" . implode( "', '", $extras ) . "' )";
386 }
387 global $wpdb;
388 $sql = 'SELECT * FROM ' . $wpdb->base_prefix . self::$statistics . ' WHERE (' . implode( ' AND ', $filter ) . ')' . $where_extra . ' ' . $group . ' ' . $order . ( $limit > 0 ? ' LIMIT ' . $limit : '') .';';
389 // phpcs:ignore
390 $result = $wpdb->get_results( $sql, ARRAY_A );
391 if ( is_array( $result ) ) {
392 if ( $cache ) {
393 Cache::set_global( $id, $result, 'infinite' );
394 }
395 return $result;
396 }
397 return [];
398 }
399 }
400