From 2909baa00f222da7eb5876acd1885aad9b9fe426 Mon Sep 17 00:00:00 2001 From: Stefan Kalscheuer Date: Thu, 25 Aug 2022 17:55:54 +0200 Subject: [PATCH 1/8] add hits column to statify table This column will be used for aggregation and is filled with 1 initially. The number of hits for a specific date/target/referrer is thus no longer the number of entries, but the sum of all hits. --- inc/class-statify-dashboard.php | 14 +++++++------- inc/class-statify-table.php | 2 +- inc/class-statify.php | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/inc/class-statify-dashboard.php b/inc/class-statify-dashboard.php index 455654f5..7265b49b 100755 --- a/inc/class-statify-dashboard.php +++ b/inc/class-statify-dashboard.php @@ -322,7 +322,7 @@ private static function _select_data() { $data = array( 'visits' => $wpdb->get_results( $wpdb->prepare( - "SELECT `created` as `date`, COUNT(`created`) as `count` FROM `$wpdb->statify` GROUP BY `created` ORDER BY `created` DESC LIMIT %d", + "SELECT `created` as `date`, SUM(`hits`) as `count` FROM `$wpdb->statify` GROUP BY `created` ORDER BY `created` DESC LIMIT %d", $days_show ), ARRAY_A @@ -332,7 +332,7 @@ private static function _select_data() { if ( $today ) { $data['target'] = $wpdb->get_results( $wpdb->prepare( - "SELECT COUNT(`target`) as `count`, `target` as `url` FROM `$wpdb->statify` WHERE created = %s GROUP BY `target` ORDER BY `count` DESC, `url` ASC LIMIT %d", + "SELECT SUM(`hits`) as `count`, `target` as `url` FROM `$wpdb->statify` WHERE created = %s GROUP BY `target` ORDER BY `count` DESC, `url` ASC LIMIT %d", $current_date, $limit ), @@ -340,7 +340,7 @@ private static function _select_data() { ); $data['referrer'] = $wpdb->get_results( $wpdb->prepare( - "SELECT COUNT(`referrer`) as `count`, `referrer` as `url`, SUBSTRING_INDEX(SUBSTRING_INDEX(TRIM(LEADING 'www.' FROM(TRIM(LEADING 'https://' FROM TRIM(LEADING 'http://' FROM TRIM(`referrer`))))), '/', 1), ':', 1) as `host` FROM `$wpdb->statify` WHERE `referrer` != '' AND created = %s GROUP BY `host` ORDER BY `count` DESC, `url` ASC LIMIT %d", + "SELECT SUM(`hits`) as `count`, `referrer` as `url`, SUBSTRING_INDEX(SUBSTRING_INDEX(TRIM(LEADING 'www.' FROM(TRIM(LEADING 'https://' FROM TRIM(LEADING 'http://' FROM TRIM(`referrer`))))), '/', 1), ':', 1) as `host` FROM `$wpdb->statify` WHERE `referrer` != '' AND created = %s GROUP BY `host` ORDER BY `count` DESC, `url` ASC LIMIT %d", $current_date, $limit ), @@ -349,7 +349,7 @@ private static function _select_data() { } else { $data['target'] = $wpdb->get_results( $wpdb->prepare( - "SELECT COUNT(`target`) as `count`, `target` as `url` FROM `$wpdb->statify` WHERE created > DATE_SUB(%s, INTERVAL %d DAY) GROUP BY `target` ORDER BY `count` DESC, `url` ASC LIMIT %d", + "SELECT SUM(`hits`) as `count`, `target` as `url` FROM `$wpdb->statify` WHERE created > DATE_SUB(%s, INTERVAL %d DAY) GROUP BY `target` ORDER BY `count` DESC, `url` ASC LIMIT %d", $current_date, $days_show, $limit @@ -358,7 +358,7 @@ private static function _select_data() { ); $data['referrer'] = $wpdb->get_results( $wpdb->prepare( - "SELECT COUNT(`referrer`) as `count`, `referrer` as `url`, SUBSTRING_INDEX(SUBSTRING_INDEX(TRIM(LEADING 'www.' FROM(TRIM(LEADING 'https://' FROM TRIM(LEADING 'http://' FROM TRIM(`referrer`))))), '/', 1), ':', 1) as `host` FROM `$wpdb->statify` WHERE `referrer` != '' AND created > DATE_SUB(%s, INTERVAL %d DAY) GROUP BY `host` ORDER BY `count` DESC, `url` ASC LIMIT %d", + "SELECT SUM(`hits`) as `count`, `referrer` as `url`, SUBSTRING_INDEX(SUBSTRING_INDEX(TRIM(LEADING 'www.' FROM(TRIM(LEADING 'https://' FROM TRIM(LEADING 'http://' FROM TRIM(`referrer`))))), '/', 1), ':', 1) as `host` FROM `$wpdb->statify` WHERE `referrer` != '' AND created > DATE_SUB(%s, INTERVAL %d DAY) GROUP BY `host` ORDER BY `count` DESC, `url` ASC LIMIT %d", $current_date, $days_show, $limit @@ -371,12 +371,12 @@ private static function _select_data() { $data['visit_totals'] = array( 'today' => $wpdb->get_var( $wpdb->prepare( - "SELECT COUNT(`created`) FROM `$wpdb->statify` WHERE created = %s", + "SELECT SUM(`hits`) FROM `$wpdb->statify` WHERE created = %s", $current_date ) ), 'since_beginning' => $wpdb->get_row( - "SELECT COUNT(`created`) AS `count`, MIN(`created`) AS `date` FROM `$wpdb->statify`", + "SELECT SUM(`hits`) AS `count`, MIN(`created`) AS `date` FROM `$wpdb->statify`", ARRAY_A ), ); diff --git a/inc/class-statify-table.php b/inc/class-statify-table.php index 05b375c4..3af90046 100644 --- a/inc/class-statify-table.php +++ b/inc/class-statify-table.php @@ -47,7 +47,6 @@ public static function init() { * @version 1.2.4 */ public static function create() { - global $wpdb; // If existent. @@ -65,6 +64,7 @@ public static function create() { `created` date NOT NULL default '0000-00-00', `referrer` varchar(255) NOT NULL default '', `target` varchar(255) NOT NULL default '', + `hits` integer NOT NULL default 1, PRIMARY KEY (`id`), KEY `referrer` (`referrer`), KEY `target` (`target`), diff --git a/inc/class-statify.php b/inc/class-statify.php index 398d1385..f01a2261 100755 --- a/inc/class-statify.php +++ b/inc/class-statify.php @@ -147,6 +147,7 @@ protected static function track( $referrer, $target ) { 'created' => current_time( 'Y-m-d' ), 'referrer' => $referrer, 'target' => $target, + 'hits' => 1, ); // Insert. From 79ce9a94368b6d57df9e4010aeb1a967f89375e9 Mon Sep 17 00:00:00 2001 From: Stefan Kalscheuer Date: Thu, 25 Aug 2022 18:12:49 +0200 Subject: [PATCH 2/8] introduce data aggregation routine We now aggregate statistics data in the cleanup routine preserving only distinct entries for each date/referrer/target combination with the sum of all hits. --- inc/class-statify-cron.php | 66 ++++++++++++++++++++++++++++++++++++++ tests/test-cron.php | 47 +++++++++++++++++++++++++++ tests/test-tracking.php | 3 +- 3 files changed, 115 insertions(+), 1 deletion(-) diff --git a/inc/class-statify-cron.php b/inc/class-statify-cron.php index 969c7704..31848699 100644 --- a/inc/class-statify-cron.php +++ b/inc/class-statify-cron.php @@ -38,9 +38,75 @@ public static function cleanup_data() { ) ); + // Aggregate. + self::aggregate_data(); + // Optimize DB. $wpdb->query( "OPTIMIZE TABLE `$wpdb->statify`" ); } + + /** + * Aggregate data in database. + * + * @since 1.9 + */ + public static function aggregate_data() { + global $wpdb; + + // Get date of last aggregation. + if ( isset( self::$_options['last_aggregation'] ) ) { + // Value saved, use it. + $start = self::$_options['last_aggregation']; + } else { + // No? We need to clean up all data. Let's determine the oldest data in the database. + $start = $wpdb->get_col( "SELECT MIN(`created`) FROM `$wpdb->statify`" ); + $start = $start[0]; + } + + if ( is_null( $start ) ) { + // No data available, i.e not cleaned up yet and no data in database. + return; + } + + $now = new DateTime(); + $date = new DateTime( $start ); + + // Iterate over every day from start (inclusive) til now. + while ( $date < $now ) { + $agg = $wpdb->get_results( + $wpdb->prepare( + "SELECT `created`, `referrer`, `target`, SUM(`hits`) as `hits` FROM `$wpdb->statify` WHERE `created` = %s GROUP BY `created`, `referrer`, `target`", + $date->format( 'Y-m-d' ) + ), + ARRAY_A + ); + + // Remove non-aggregated data and insert aggregates within one transaction. + $wpdb->query( 'START TRANSACTION' ); + $res = $wpdb->query( + $wpdb->prepare( + "DELETE FROM `$wpdb->statify` WHERE `created` = %s", + $date->format( 'Y-m-d' ) + ) + ); + if ( false !== $res ) { + foreach ( $agg as $a ) { + if ( false === $wpdb->insert( $wpdb->statify, $a ) ) { + $wpdb->query( 'ROLLBACK' ); + break; + } + } + } + $wpdb->query( 'COMMIT' ); + + // Continue with next day. + $date->modify( '+1 day' ); + } + + // Remember last aggregation date. + self::$_options['last_aggregation'] = $now->format( 'Y-m-d' ); + update_option( 'statify', self::$_options ); + } } diff --git a/tests/test-cron.php b/tests/test-cron.php index 9d8d689e..2e125a2d 100644 --- a/tests/test-cron.php +++ b/tests/test-cron.php @@ -73,4 +73,51 @@ public function test_cronjob() { $this->assertEquals( 2, $v['count'], 'Unexpected visit count' ); } } + + /** + * Test Statify Cron Job execution. + * + * @runInSeparateProcess Must not preserve global constant. + * @preserveGlobalState disabled + */ + public function test_aggregation() { + global $wpdb; + + // Insert test data: 2 days with 3 and 4 distinct combinations of referrer and target. + $date = new DateTime(); + $this->insert_test_data( $date->format( 'Y-m-d' ), '', '', 2 ); + $this->insert_test_data( $date->format( 'Y-m-d' ), 'https://statify.pluginkollektiv.org/', '/', 3 ); + $this->insert_test_data( $date->format( 'Y-m-d' ), 'https://statify.pluginkollektiv.org/', '/test/', 4 ); + $this->insert_test_data( $date->format( 'Y-m-d' ), 'https://pluginkollektiv.org/', '/', 5 ); + $date->modify( '-1 days' ); + $this->insert_test_data( $date->format( 'Y-m-d' ), 'https://statify.pluginkollektiv.org/', '/', 4 ); + $this->insert_test_data( $date->format( 'Y-m-d' ), 'https://statify.pluginkollektiv.org/', '/test/', 3 ); + $this->insert_test_data( $date->format( 'Y-m-d' ), 'https://pluginkollektiv.org/', '/', 2 ); + + // Get baseline. + $this->assertEquals( 23, $wpdb->get_var( "SELECT COUNT(*) FROM `$wpdb->statify`" ), 'Unexpected number of entries before aggregation' ); + $stats = $this->get_stats(); + + // Trigger aggregation. + Statify_Cron::aggregate_data(); + + // Verify results. + $this->assertEquals( 7, $wpdb->get_var( "SELECT COUNT(*) FROM `$wpdb->statify`" ), 'Unexpected number of entries after aggregation' ); + $stats2 = $this->get_stats(); + $this->assertEquals( $stats, $stats2, 'Statistics data should be the same after aggregation' ); + // Check one single row explicitly. + $this->assertEquals( + 3, + $wpdb->get_var( + $wpdb->prepare( + "SELECT hits FROM `$wpdb->statify` WHERE created = %s AND referrer = %s AND target = %s", + $date->format( 'Y-m-d' ), + 'https://statify.pluginkollektiv.org/', + '/test/' + ) + ), + 'Unexpected hit count after aggregation' + ); + + } } diff --git a/tests/test-tracking.php b/tests/test-tracking.php index 66dad2bb..07d8433e 100644 --- a/tests/test-tracking.php +++ b/tests/test-tracking.php @@ -361,10 +361,11 @@ function ( $data, $id ) use ( &$capture ) { $this->assertNotNull( $stats['visits'][0]['count'], 'Request not tracked' ); $this->assertNotEmpty( $capture, 'Hook stativy__visit_saved has not fired' ); $this->assertTrue( is_numeric( $capture['id'] ) && $capture['id'] > 0, 'unexpected entry ID' ); - $this->assertCount( 3, $capture['data'], 'unexpected number of data fields' ); + $this->assertCount( 4, $capture['data'], 'unexpected number of data fields' ); $this->assertEquals( ( new DateTime() )->format( 'Y-m-d' ), $capture['data']['created'], 'unexpected creation date' ); $this->assertEquals( 'https://statify.pluginkollektiv.org/', $capture['data']['referrer'], 'unexpected referrer' ); $this->assertEquals( '/page', $capture['data']['target'], 'unexpected target' ); + $this->assertEquals( 1, $capture['data']['hits'], 'unexpected hits' ); } /** From e789310a562b47dc88247b04fce296798e4a01ec Mon Sep 17 00:00:00 2001 From: Stefan Kalscheuer Date: Thu, 8 Sep 2022 16:57:02 +0200 Subject: [PATCH 3/8] add "statify__skip_aggregation" hook to disable aggregation If Statify is extended by custom columns the aggregation routine will fail. To support such scenarios, we introduce a boolean hook, so the previous behavior can be preserved without breaking compatibility. --- inc/class-statify-cron.php | 15 +++++++-------- tests/test-cron.php | 19 ++++++++++++++++++- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/inc/class-statify-cron.php b/inc/class-statify-cron.php index 31848699..7af0b93a 100644 --- a/inc/class-statify-cron.php +++ b/inc/class-statify-cron.php @@ -23,13 +23,12 @@ class Statify_Cron extends Statify { * * @since 0.3.0 * @version 1.4.0 + * @wp-hook boolean statify__skip_aggregation */ public static function cleanup_data() { - - // Global. global $wpdb; - // Remove items. + // Remove old items. $wpdb->query( $wpdb->prepare( "DELETE FROM `$wpdb->statify` WHERE created <= SUBDATE(%s, %d)", @@ -38,13 +37,13 @@ public static function cleanup_data() { ) ); - // Aggregate. - self::aggregate_data(); + // Aggregate data. + if ( ! apply_filters( 'statify__skip_aggregation', false ) ) { + self::aggregate_data(); + } // Optimize DB. - $wpdb->query( - "OPTIMIZE TABLE `$wpdb->statify`" - ); + $wpdb->query( "OPTIMIZE TABLE `$wpdb->statify`" ); } /** diff --git a/tests/test-cron.php b/tests/test-cron.php index 2e125a2d..57f5037f 100644 --- a/tests/test-cron.php +++ b/tests/test-cron.php @@ -29,6 +29,8 @@ public function set_up() { * @preserveGlobalState disabled */ public function test_cronjob() { + global $wpdb; + // Initialize normal cycle, configure storage period of 3 days. $this->init_statify_widget( 3 ); $this->assertNotFalse( @@ -61,7 +63,8 @@ public function test_cronjob() { $this->assertEquals( 2, $v['count'], 'Unexpected visit count' ); } - // Run the cron job. + // Run the cron job without aggregation. + add_filter( 'statify__skip_aggregation', '__return_true' ); Statify_Cron::cleanup_data(); // Verify that 2 days have been deleted. @@ -72,6 +75,20 @@ public function test_cronjob() { $this->assertContains( $v['date'], $remaining_dates, 'Unexpected remaining date in stats' ); $this->assertEquals( 2, $v['count'], 'Unexpected visit count' ); } + $this->assertEquals( + 6, + $wpdb->get_var( "SELECT COUNT(*) FROM `$wpdb->statify`" ), + 'Unexpected number of entries after cleanup without aggregation' + ); + + // Run the cron job with aggregation (default). + remove_filter( 'statify__skip_aggregation', '__return_true' ); + Statify_Cron::cleanup_data(); + $this->assertEquals( + 3, + $wpdb->get_var( "SELECT COUNT(*) FROM `$wpdb->statify`" ), + 'Unexpected number of entries after cleanup with aggregation' + ); } /** From e3d991af3afeed16145fbdf0ec0b07c88cda582b Mon Sep 17 00:00:00 2001 From: Justin Kostka <128225253+jujoko7CF@users.noreply.github.com> Date: Sun, 19 Mar 2023 21:44:54 +0100 Subject: [PATCH 4/8] add new statifymeta table --- inc/class-statify-install.php | 5 +- inc/class-statify-schema.php | 127 ++++++++++++++++++++++++++++++++ inc/class-statify-table.php | 90 ---------------------- inc/class-statify-uninstall.php | 8 +- inc/class-statify.php | 4 +- statify.php | 2 +- 6 files changed, 136 insertions(+), 100 deletions(-) create mode 100644 inc/class-statify-schema.php delete mode 100644 inc/class-statify-table.php diff --git a/inc/class-statify-install.php b/inc/class-statify-install.php index ee7ecf44..c685cddc 100644 --- a/inc/class-statify-install.php +++ b/inc/class-statify-install.php @@ -82,8 +82,7 @@ private static function _apply() { ); } - // Create the actual tables. - Statify_Table::init(); - Statify_Table::create(); + // Initialize the database schema. + Statify_Schema::init(); } } diff --git a/inc/class-statify-schema.php b/inc/class-statify-schema.php new file mode 100644 index 00000000..505400f9 --- /dev/null +++ b/inc/class-statify-schema.php @@ -0,0 +1,127 @@ +tables[] = $table; + $wpdb->$table = $wpdb->get_blog_prefix() . $table; + } + + self::maybe_create_tables(); + } + + /** + * Create the tables. + * + * @since 2.0.0 + * @version 2.0.0 + */ + public static function maybe_create_tables() { + $current_db_version = get_option( 'statify_db_version', '1.8.4' ); + if ( $current_db_version === self::$db_version ) { + return; + } + + // Global. + global $wpdb, $charset_collate; + + /** + * Use same index length like the WordPress core + * + * @see wp_get_db_schema() + */ + $max_index_length = 191; + + // Include. + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + + // Create statify table. + dbDelta( + "CREATE TABLE {$wpdb->statify} ( + id bigint(20) unsigned NOT NULL auto_increment, + created date NOT NULL default '0000-00-00', + referrer varchar(255) NOT NULL default '', + target varchar(255) NOT NULL default '', + hits integer NOT NULL default 1, + PRIMARY KEY (id), + KEY referrer (referrer), + KEY target (target), + KEY created (created) + ) {$charset_collate};" + ); + + // Create statifymeta table. + dbDelta( + "CREATE TABLE {$wpdb->statifymeta} ( + meta_id bigint(20) unsigned NOT NULL auto_increment, + statify_id bigint(20) unsigned NOT NULL default 0, + meta_key varchar(255) default NULL, + meta_value longtext, + PRIMARY KEY (meta_id), + KEY statify_id (statify_id), + KEY meta_key (meta_key({$max_index_length})) + ) {$charset_collate};" + ); + + update_option( 'statify_db_version', self::$db_version ); + } + + /** + * Remove the custom tables. + * + * @since 2.0.0 + * @version 2.0.0 + */ + public static function drop_tables() { + global $wpdb; + + // Remove. + foreach ( static::$tables as $table ) { + $wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->$table}`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + } + + delete_option( 'statify_db_version' ); + } +} diff --git a/inc/class-statify-table.php b/inc/class-statify-table.php deleted file mode 100644 index 3af90046..00000000 --- a/inc/class-statify-table.php +++ /dev/null @@ -1,90 +0,0 @@ -tables[] = $table; - - // With prefix. - $wpdb->$table = $wpdb->get_blog_prefix() . $table; - } - - - /** - * Create the table. - * - * @since 0.6.0 - * @version 1.2.4 - */ - public static function create() { - global $wpdb; - - // If existent. - if ( $wpdb->get_var( "SHOW TABLES LIKE '$wpdb->statify'" ) === $wpdb->statify ) { - return; - } - - // Include. - require_once ABSPATH . 'wp-admin/includes/upgrade.php'; - - // Create. - dbDelta( - "CREATE TABLE `$wpdb->statify` ( - `id` bigint(20) unsigned NOT NULL auto_increment, - `created` date NOT NULL default '0000-00-00', - `referrer` varchar(255) NOT NULL default '', - `target` varchar(255) NOT NULL default '', - `hits` integer NOT NULL default 1, - PRIMARY KEY (`id`), - KEY `referrer` (`referrer`), - KEY `target` (`target`), - KEY `created` (`created`) - );" - ); - } - - - /** - * Remove the custom table. - * - * @since 0.6.0 - * @version 1.2.4 - */ - public static function drop() { - - global $wpdb; - - // Remove. - $wpdb->query( "DROP TABLE IF EXISTS `$wpdb->statify`" ); - } -} diff --git a/inc/class-statify-uninstall.php b/inc/class-statify-uninstall.php index f9f1cb3f..493dd6f5 100644 --- a/inc/class-statify-uninstall.php +++ b/inc/class-statify-uninstall.php @@ -70,10 +70,10 @@ private static function _apply() { // Delete options. delete_option( 'statify' ); - // Init table. - Statify_Table::init(); + // Initialize the database schema. + Statify_Schema::init(); - // Delete table. - Statify_Table::drop(); + // Delete tables. + Statify_Schema::drop_tables(); } } diff --git a/inc/class-statify.php b/inc/class-statify.php index f01a2261..3c4f5793 100755 --- a/inc/class-statify.php +++ b/inc/class-statify.php @@ -44,8 +44,8 @@ public static function init() { return; } - // Table init. - Statify_Table::init(); + // Initialize the database schema. + Statify_Schema::init(); // Plugin options. self::$_options = wp_parse_args( diff --git a/statify.php b/statify.php index d98aed50..203c1b1f 100644 --- a/statify.php +++ b/statify.php @@ -73,7 +73,7 @@ function statify_autoload( $class ) { 'Statify_Uninstall', 'Statify_Deactivate', 'Statify_Settings', - 'Statify_Table', + 'Statify_Schema', 'Statify_XMLRPC', 'Statify_Cron', ); From 1535ea5f15c1df840395dc2d4a0e73002de06ab7 Mon Sep 17 00:00:00 2001 From: Justin Kostka <128225253+jujoko7CF@users.noreply.github.com> Date: Wed, 18 Oct 2023 12:18:07 +0200 Subject: [PATCH 5/8] Add Statify:get_meta() functions --- inc/class-statify.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/inc/class-statify.php b/inc/class-statify.php index 3c4f5793..1f01115f 100755 --- a/inc/class-statify.php +++ b/inc/class-statify.php @@ -396,4 +396,23 @@ private static function strposa( $haystack, array $needle, $offset = 0 ) { return false; } + + /** + * Retrieves statify metadata for the given statify ID. + * + * @param int $statify_id Statify ID. + * @param string $meta_key Optional. The meta key to retrieve. By default, + * returns data for all keys. Default empty. + * @param bool $single Optional. Whether to return a single value. + * This parameter has no effect if `$key` is not specified. + * Default false. + * + * @return mixed An array of values if `$single` is false. + * The value of the meta field if `$single` is true. + * False for an invalid `$statify_id` (non-numeric, zero, or negative value). + * An empty string if a valid but non-existing statify ID is passed. + */ + public static function get_meta( $statify_id, $meta_key = '', $single = false ) { + return get_metadata( 'statify', $statify_id, $meta_key, $single ); + } } From 0ac2d7bbb21d2f0b9aa6035df0c220bd9f2158af Mon Sep 17 00:00:00 2001 From: Justin Kostka <128225253+jujoko7CF@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:19:00 +0200 Subject: [PATCH 6/8] Implement meta fields in tracking functions --- inc/class-statify-api.php | 4 ++- inc/class-statify-frontend.php | 28 +++++++++++++++++-- inc/class-statify.php | 50 +++++++++++++++++++++++++++++++++- js/snippet.js | 1 + 4 files changed, 79 insertions(+), 4 deletions(-) diff --git a/inc/class-statify-api.php b/inc/class-statify-api.php index 99e69f95..c11afc0e 100644 --- a/inc/class-statify-api.php +++ b/inc/class-statify-api.php @@ -108,7 +108,9 @@ public static function track_visit( $request ) { $target = filter_var( $target, FILTER_SANITIZE_URL ); } - Statify::track( $referrer, $target ); + $meta = $request->get_param( 'meta' ); + + Statify::track( $referrer, $target, $meta ); } return new WP_REST_Response( null, 204 ); diff --git a/inc/class-statify-frontend.php b/inc/class-statify-frontend.php index 7153e8c7..9a9575d0 100644 --- a/inc/class-statify-frontend.php +++ b/inc/class-statify-frontend.php @@ -18,6 +18,29 @@ */ class Statify_Frontend extends Statify { + /** + * Returns key value pair array with tracking metadata. + * + * @return array + */ + private static function load_metadata() { + $meta = array(); + + foreach ( Statify::get_metafields() as $field ) { + // Get values. + if ( + isset( $field['key'] ) && + is_string( $field['key'] ) && + isset( $field['callback'] ) && + is_callable( $field['callback'] ) + ) { + $meta[ $field['key'] ] = call_user_func( $field['callback'] ); + } + } + + return $meta; + } + /** * Track the page view * @@ -44,7 +67,7 @@ public static function track_visit( $is_snippet = false ) { $referrer = filter_var( wp_unslash( $_SERVER['HTTP_REFERER'] ), FILTER_SANITIZE_URL ); } - Statify::track( $referrer, $target ); + Statify::track( $referrer, $target, self::load_metadata() ); } /** @@ -94,9 +117,10 @@ public static function wp_footer() { true ); - // Add endpoint to script. + // Add endpoint and tracking meta data to script. $script_data = array( 'url' => esc_url_raw( rest_url( Statify_Api::REST_NAMESPACE . '/' . Statify_Api::REST_ROUTE_TRACK ) ), + 'tracking_meta' => self::load_metadata(), ); if ( Statify::TRACKING_METHOD_JAVASCRIPT_WITH_NONCE_CHECK === self::$_options['snippet'] ) { $script_data['nonce'] = wp_create_nonce( 'statify_track' ); diff --git a/inc/class-statify.php b/inc/class-statify.php index 1f01115f..b96a3f95 100755 --- a/inc/class-statify.php +++ b/inc/class-statify.php @@ -93,11 +93,31 @@ public static function init() { } } + /** + * Returns meta fields which should be tracked. + * + * @return array + */ + public static function get_metafields() { + $meta = array( + array( + 'key' => 'title', + 'callback' => 'wp_get_document_title', + 'sanitize_callback' => 'sanitize_text_field', + ), + ); + + $meta = apply_filters( 'statify__get_metafields', $meta ); + + return is_array( $meta ) ? $meta : array(); + } + /** * Track the page view. * * @param string|null $referrer Referrer URL. * @param string|null $target Target URL. + * @param array $meta Meta field data. * * @return void * @@ -105,7 +125,7 @@ public static function init() { * @since 1.7.0 $is_snippet parameter added. * @since 2.0.0 Migration from Statify_Frontend::track_visit to Statify::track with multiple parameters. */ - protected static function track( $referrer, $target ) { + protected static function track( $referrer, $target, $meta = array() ) { // Fallbacks for uninitialized or omitted target and referrer values. if ( is_null( $target ) ) { $target = '/'; @@ -154,6 +174,34 @@ protected static function track( $referrer, $target ) { global $wpdb; $wpdb->insert( $wpdb->statify, $data ); + // Meta fields. + if ( is_array( $meta ) ) { + $statify_id = $wpdb->insert_id; + + foreach ( self::get_metafields() as $field ) { + if ( isset( $field['key'] ) && array_key_exists( $field['key'], $meta ) ) { + $value = $meta[ $field['key'] ]; + + // Sanitizing. + $sanitize_function = isset( $field['sanitize_callback'] ) && is_callable( $field['sanitize_callback'] ) + ? $field['sanitize_callback'] + : 'sanitize_text_field'; + + $value = call_user_func( $sanitize_function, $value ); + + // Init rows. + $data = array( + 'statify_id' => $statify_id, + 'meta_key' => $field['meta_key'], + 'meta_value' => $value, + ); + + // Insert. + $wpdb->insert( $wpdb->statifymeta, $data ); + } + } + } + /** * Fires after a visit was stored in the database * diff --git a/js/snippet.js b/js/snippet.js index 23f58f1c..0911991a 100644 --- a/js/snippet.js +++ b/js/snippet.js @@ -8,6 +8,7 @@ data = { referrer: document.referrer, target: location.pathname + location.search, + meta: statifyAjax.tracking_meta, }; if ( 'nonce' in statifyAjax ) { data.nonce = statifyAjax.nonce; From a1003647d296a880c1f40a79aa3d9770a19f74c8 Mon Sep 17 00:00:00 2001 From: Justin Kostka <128225253+jujoko7CF@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:22:38 +0200 Subject: [PATCH 7/8] Fix single line comment --- inc/class-statify.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/class-statify.php b/inc/class-statify.php index b96a3f95..d8c2d0f4 100755 --- a/inc/class-statify.php +++ b/inc/class-statify.php @@ -154,7 +154,7 @@ protected static function track( $referrer, $target, $meta = array() ) { // Relative target URL. $target = user_trailingslashit( str_replace( home_url( '/', 'relative' ), '/', $target ) ); - /* Global vars */ + // Global vars. global $wp_rewrite; // Trim target URL. From 01f3dd449a9f436ce47bf90c36c963496e124be4 Mon Sep 17 00:00:00 2001 From: Justin Kostka <128225253+jujoko7CF@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:42:30 +0200 Subject: [PATCH 8/8] Fix comment code style --- inc/class-statify.php | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/inc/class-statify.php b/inc/class-statify.php index d8c2d0f4..144e8156 100755 --- a/inc/class-statify.php +++ b/inc/class-statify.php @@ -445,22 +445,22 @@ private static function strposa( $haystack, array $needle, $offset = 0 ) { return false; } - /** - * Retrieves statify metadata for the given statify ID. - * - * @param int $statify_id Statify ID. - * @param string $meta_key Optional. The meta key to retrieve. By default, - * returns data for all keys. Default empty. - * @param bool $single Optional. Whether to return a single value. - * This parameter has no effect if `$key` is not specified. - * Default false. - * - * @return mixed An array of values if `$single` is false. - * The value of the meta field if `$single` is true. - * False for an invalid `$statify_id` (non-numeric, zero, or negative value). - * An empty string if a valid but non-existing statify ID is passed. - */ - public static function get_meta( $statify_id, $meta_key = '', $single = false ) { - return get_metadata( 'statify', $statify_id, $meta_key, $single ); - } + /** + * Retrieves statify metadata for the given statify ID. + * + * @param int $statify_id Statify ID. + * @param string $meta_key Optional. The meta key to retrieve. By default, + * returns data for all keys. Default empty. + * @param bool $single Optional. Whether to return a single value. + * This parameter has no effect if `$key` is not specified. + * Default false. + * + * @return mixed An array of values if `$single` is false. + * The value of the meta field if `$single` is true. + * False for an invalid `$statify_id` (non-numeric, zero, or negative value). + * An empty string if a valid but non-existing statify ID is passed. + */ + public static function get_meta( $statify_id, $meta_key = '', $single = false ) { + return get_metadata( 'statify', $statify_id, $meta_key, $single ); + } }