From 87a9f47d8266a6218b44aac08c2aa86db71b9991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sat, 18 Nov 2023 21:51:05 +0100 Subject: [PATCH 01/59] Tests: Add npm scripts to test sql query on different sql servers --- package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 0f79c2ff..92b18da1 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,11 @@ "test:functional": "docker-compose run --rm php-cli vendor/bin/codecept run functional", "test:acceptance": "docker-compose run --rm php-cli vendor/bin/codecept run acceptance", "spell-check": "typos", - "spell-check:write-changes": "typos --write-changes" + "spell-check:write-changes": "typos --write-changes", + "test:log-query-mysq55": "PHP_CLI_VERSION=81 PHP_VERSION=8.1 DB_IMAGE=biarms/mysql:5.5 DB_DATA_DIR=./data/mysql-5.5 npm run test:wpunit-logquery", + "test:log-query-mysq57": "PHP_CLI_VERSION=81 PHP_VERSION=8.1 DB_IMAGE=biarms/mysql:5.7 DB_DATA_DIR=./data/mysql-5.7 npm run test:wpunit-logquery", + "test:log-query-mariadb105": "PHP_CLI_VERSION=81 PHP_VERSION=8.1 DB_IMAGE=mariadb:10.5 DB_DATA_DIR=./data/mysql npm run test:wpunit-logquery", + "test:wpunit-logquery": "docker-compose run --rm php-cli vendor/bin/codecept run wpunit:LogQueryTest" }, "lint-staged": { "*.php": [ From 379b1b986113d80295d5b6810a42ee051d2e694e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sat, 18 Nov 2023 21:51:35 +0100 Subject: [PATCH 02/59] Tests: Remove commented out docker compose things --- docker-compose.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 06373894..dbd7fdd3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,8 +32,6 @@ services: db: image: "${DB_IMAGE:-mariadb:10.5}" - #image: "${DB_IMAGE:-biarms/mysql:5.5}" - #image: "${DB_IMAGE:-biarms/mysql:5.7}" container_name: simple-history-database restart: "${DOCKER_RESTART_POLICY:-unless-stopped}" environment: @@ -46,8 +44,6 @@ services: - "${DB_EXPOSE_PORT:-127.0.0.1:}:3306" volumes: - "${DB_DATA_DIR:-./data/mysql}:/var/lib/mysql" - #- "${DB_DATA_DIR:-./data/mysql-5.5}:/var/lib/mysql" - #- "${DB_DATA_DIR:-./data/mysql-5.7}:/var/lib/mysql" chrome: image: seleniarm/standalone-chromium:117.0 From c2af0520566ccb8dc6a60f6710b678c236322406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Mon, 20 Nov 2023 21:31:34 +0100 Subject: [PATCH 03/59] Add some comments to log query class --- inc/class-log-query.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 121174dd..d5680023 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -40,8 +40,9 @@ public function query( $args ) { $sql_user = null; $sql_tmpl = null; $defaults = array( - - // overview | occasions. + // overview | occasions | single. + // When type is occasions then logRowID, occasionsID, occasionsCount, occasionsCountMaxReturn are required. + // TODO: Add default for above required args. 'type' => 'overview', // Number of posts to show per page. 0 to show all. @@ -110,6 +111,7 @@ public function query( $args ) { 'users' => null, // Can also contain: + // logRowID // occasionsCount // occasionsCountMaxReturn // occasionsID. @@ -226,7 +228,10 @@ public function query( $args ) { %2$s '; + // Get rows with id lower than logRowID, i.e. previous rows. $where .= ' AND h.id < ' . (int) $args['logRowID']; + + // Get rows with occasionsID equal to occasionsID. $where .= " AND h.occasionsID = '" . esc_sql( $args['occasionsID'] ) . "'"; if ( isset( $args['occasionsCountMaxReturn'] ) && (int) $args['occasionsCountMaxReturn'] < (int) $args['occasionsCount'] ) { @@ -741,7 +746,7 @@ public function query( $args ) { 'log_rows_count' => $log_rows_count, 'log_rows' => $log_rows, // Add sql query to debug. - // 'sql' => $sql, + // 'sql' => $sql, // . ); wp_cache_set( $cache_key, $arr_return, $cache_group ); From 95401d60ca62ed8fda058aa3c254fa1e4a2e8f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Mon, 20 Nov 2023 21:32:33 +0100 Subject: [PATCH 04/59] Tests: Add more log query tests --- tests/wpunit/LogQueryTest.php | 149 ++++++++++++++++++++++++++++++---- 1 file changed, 132 insertions(+), 17 deletions(-) diff --git a/tests/wpunit/LogQueryTest.php b/tests/wpunit/LogQueryTest.php index 08c7a07c..8d5ec31c 100644 --- a/tests/wpunit/LogQueryTest.php +++ b/tests/wpunit/LogQueryTest.php @@ -7,7 +7,6 @@ use Simple_History\Log_Query; class LogQueryTest extends \Codeception\TestCase\WPTestCase { - /** * Add n log entries and then query for them. * @@ -43,7 +42,7 @@ function test_query() { "num_new_rows": 1, "num_mysql_queries": 50, (what? why so many??) */ - $added_rows_id = []; + $added_rows_ids = []; $num_rows_to_add = 10; for ($i = 0; $i < $num_rows_to_add; $i++) { $logger = SimpleLogger()->info( @@ -53,31 +52,147 @@ function test_query() { 'message_num' => $i, ] ); - // sh_d('$logger', $logger->last_insert_context, $logger->last_insert_data, $logger->last_insert_id); - $added_rows_id[] = $logger->last_insert_id; + $added_rows_ids[] = $logger->last_insert_id; } // Now query the log and see what id we get as the latest. - $log_query_args = array( - 'posts_per_page' => 1, - ); - - $log_query = new Log_Query(); - $query_results = $log_query->query( $log_query_args ); - $first_log_row = $query_results['log_rows'][0]; + $query_results = (new Log_Query())->query( ['posts_per_page' => 1] ); + $first_log_row_from_query = $query_results['log_rows'][0]; // On MariaDB $first_log_row->id is the same as the value in $added_rows_id[0] (the first added row) // but it should be the id from the last added row, i.e. the value in $added_rows_id[9]. - sh_d('$first_log_row id', $first_log_row->id); - sh_d('$added_rows_id[0]', $added_rows_id[0]); - sh_d('$added_rows_id[max]', $added_rows_id[$num_rows_to_add-1]); + // sh_d('$first_log_row id', $first_log_row_from_query->id); + // sh_d('$added_rows_id[0]', $added_rows_ids[0]); + // sh_d('$added_rows_id[max]', $added_rows_ids[$num_rows_to_add-1]); // $this->markTestIncomplete('This test will fail in MariaDB until bug is fixed.'); $this->assertEquals( - $added_rows_id[$num_rows_to_add-1], - $first_log_row->id, - 'The id of the first row should be the same as the id of the last added row.' + $added_rows_ids[$num_rows_to_add-1], + $first_log_row_from_query->id, + 'The id of the first row in query result should be the same as the id of the last added row.' + ); + + // Add more. + for ($i = 0; $i < 4; $i++) { + $logger = SimpleLogger()->info( + 'Another test info message ' . $i, + [ + '_occasionsID' => 'my_occasion_id_2', + 'message_num' => $i, + ] + ); + } + + $logger = SimpleLogger()->info( + 'Single message ' . 0, + [ + '_occasionsID' => 'my_occasion_id_3', + 'message_num' => 0, + ] ); + + + $hello_some_messages_message_count = 7; + for ($i = 0; $i < $hello_some_messages_message_count; $i++) { + $logger = SimpleLogger()->info( + 'Hello some messages ' . $i, + [ + '_occasionsID' => 'my_occasion_id_5', + 'message_num' => $i, + ] + ); + } + + for ($i = 0; $i < 3; $i++) { + $logger = SimpleLogger()->info( + 'Oh such logging things ' . $i, + [ + '_occasionsID' => 'my_occasion_id_6', + 'message_num' => $i, + ] + ); + $last_insert_id = $logger->last_insert_id; + } + + // Get first result and check that it has 3 subsequentOccasions + // and that the message is + // "Oh such logging things {$i-1}" + // and that context contains message_num = {$i-1}. + $results = (new Log_Query())->query([ + 'posts_per_page' => 3 + ]); + + $first_log_row_from_query = $results['log_rows'][0]; + $second_log_row_from_query = $results['log_rows'][1]; + $third_log_row_from_query = $results['log_rows'][2]; + + $this->assertEquals( + 3, + $first_log_row_from_query->subsequentOccasions, + 'The first log row should have 3 subsequentOccasions.' + ); + + $this->assertIsNumeric($first_log_row_from_query->subsequentOccasions); + + $this->assertEquals( + 'Oh such logging things ' . ($i-1), + $first_log_row_from_query->message, + 'The first log row should have the message "Oh such logging things" ' . $i-1 + ); + + $this->assertEquals( + $i-1, + $first_log_row_from_query->context['message_num'], + 'The first log row should have the context message_num = ' . ($i-1) + ); + + // Test second message. + $this->assertEquals( + $hello_some_messages_message_count, + $second_log_row_from_query->subsequentOccasions, + "The second log row should have $hello_some_messages_message_count subsequentOccasions." + ); + + $this->assertIsNumeric($second_log_row_from_query->subsequentOccasions); + + $this->assertEquals( + 'Hello some messages 6', + $second_log_row_from_query->message, + 'The first log row should have the message "Hello some messages 6"' + ); + + // Test third message. + $this->assertEquals( + 1, + $third_log_row_from_query->subsequentOccasions, + 'The third log row should have 1 subsequentOccasions.' + ); + + $this->assertIsNumeric($third_log_row_from_query->subsequentOccasions); + + $this->assertEquals( + 'Single message 0', + $third_log_row_from_query->message, + 'The third log row should have the message "Single message 0"' + ); + + + // Test occassions query arg. + // Based on first, second, third, rows. + // When type is occasions then logRowID, occasionsID, occasionsCount, occasionsCountMaxReturn are required. + $query_results = (new Log_Query())->query([ + 'type' => 'occasions', + 'logRowID' => $last_insert_id, + 'occasionsID' => 'my_occasion_id_6', // The occassions id is md5:ed so we need to use log query to get the last row, and then get ocassions id.. + 'occasionsCount' => 3, + ]); + + // sh_d($last_insert_id); + // sh_d($query_results); + + // sh_d('$second_log_row_from_query', $second_log_row_from_query);exit; + + // exit; } /** From 2f10689c9555a9987836b98a357ec84ac7383ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Tue, 21 Nov 2023 18:43:44 +0100 Subject: [PATCH 05/59] Tests: Add more log query occasions tests --- tests/wpunit/LogQueryTest.php | 59 +++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/tests/wpunit/LogQueryTest.php b/tests/wpunit/LogQueryTest.php index 8d5ec31c..3bf8f7c8 100644 --- a/tests/wpunit/LogQueryTest.php +++ b/tests/wpunit/LogQueryTest.php @@ -103,7 +103,8 @@ function test_query() { ); } - for ($i = 0; $i < 3; $i++) { + $oh_such_logging_rows_num_to_add = 3; + for ($i = 0; $i < $oh_such_logging_rows_num_to_add; $i++) { $logger = SimpleLogger()->info( 'Oh such logging things ' . $i, [ @@ -111,7 +112,6 @@ function test_query() { 'message_num' => $i, ] ); - $last_insert_id = $logger->last_insert_id; } // Get first result and check that it has 3 subsequentOccasions @@ -177,22 +177,55 @@ function test_query() { ); - // Test occassions query arg. - // Based on first, second, third, rows. - // When type is occasions then logRowID, occasionsID, occasionsCount, occasionsCountMaxReturn are required. + // Test occassions query arg. for first returned row. $query_results = (new Log_Query())->query([ 'type' => 'occasions', - 'logRowID' => $last_insert_id, - 'occasionsID' => 'my_occasion_id_6', // The occassions id is md5:ed so we need to use log query to get the last row, and then get ocassions id.. - 'occasionsCount' => 3, + // Get history rows with id:s less than this, i.e. get earlier/previous rows. + 'logRowID' => $first_log_row_from_query->id, + 'occasionsID' => $first_log_row_from_query->occasionsID, // The occassions id is md5:ed so we need to use log query to get the last row, and then get ocassions id.. + 'occasionsCount' => $first_log_row_from_query->subsequentOccasions - 1, ]); - // sh_d($last_insert_id); - // sh_d($query_results); - - // sh_d('$second_log_row_from_query', $second_log_row_from_query);exit; + $this->assertCount( + $oh_such_logging_rows_num_to_add - 1, + $query_results['log_rows'], + 'The number of rows returned when getting occassions should be ' . ($oh_such_logging_rows_num_to_add - 1) + ); + + // Test occassions query arg. for second returned row. + $query_results = (new Log_Query())->query([ + 'type' => 'occasions', + 'logRowID' => $second_log_row_from_query->id, + 'occasionsID' => $second_log_row_from_query->occasionsID, // The occassions id is md5:ed so we need to use log query to get the last row, and then get ocassions id.. + 'occasionsCount' => $second_log_row_from_query->subsequentOccasions - 1, + ]); + + $this->assertCount( + $hello_some_messages_message_count - 1, + $query_results['log_rows'], + 'The number of rows returned when getting occassions should be ' . ($hello_some_messages_message_count - 1) + ); - // exit; + // Test occassions query arg. for third returned row. + $query_results = (new Log_Query())->query([ + 'type' => 'occasions', + 'logRowID' => $third_log_row_from_query->id, + 'occasionsID' => $third_log_row_from_query->occasionsID, // The occassions id is md5:ed so we need to use log query to get the last row, and then get ocassions id.. + 'occasionsCount' => $third_log_row_from_query->subsequentOccasions - 1, + ]); + + // No further occasions for this row. + $this->assertSame( + "1", + $third_log_row_from_query->subsequentOccasions, + 'The number of rows returned when getting occassions should be 0' + ); + + $this->assertCount( + 0, + $query_results['log_rows'], + 'The number of rows returned when getting occassions should be 0' + ); } /** From 4d8598de152bf472114137ef5813442f007631fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Wed, 22 Nov 2023 18:58:27 +0100 Subject: [PATCH 06/59] Remove mysql_get_server_info() because unknown function says editor --- tests/wpunit/APHPVersionTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/wpunit/APHPVersionTest.php b/tests/wpunit/APHPVersionTest.php index 22af4385..51ba1e49 100644 --- a/tests/wpunit/APHPVersionTest.php +++ b/tests/wpunit/APHPVersionTest.php @@ -11,7 +11,7 @@ public function test_a_php_version() { // Output MySQL/MariaDB version. global $wpdb; if ( empty( $wpdb->use_mysqli ) ) { - $mysqlVersion = mysql_get_server_info(); + // $mysqlVersion = mysql_get_server_info(); } else { $mysqlVersion = mysqli_get_server_info( $wpdb->dbh ); } From 9b3529d34010ff412f3b6439d780ed1da6f1e268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sat, 9 Dec 2023 18:45:36 +0100 Subject: [PATCH 07/59] Misc log query cleanup --- inc/class-log-query.php | 169 +++++++++++++++++++++------------------- 1 file changed, 89 insertions(+), 80 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index d5680023..f361a8c2 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -8,7 +8,6 @@ * Queries the Simple History Log. */ class Log_Query { - /** * Query the log. * @@ -36,102 +35,112 @@ class Log_Query { * @return array */ public function query( $args ) { - $users_in = null; - $sql_user = null; + global $wpdb; + + /** @var Simple_History Simple History instance. */ + $simple_history = Simple_History::get_instance(); + + /** @var string SQL Template to use. Template used depends on $args['type']. */ $sql_tmpl = null; - $defaults = array( - // overview | occasions | single. - // When type is occasions then logRowID, occasionsID, occasionsCount, occasionsCountMaxReturn are required. - // TODO: Add default for above required args. - 'type' => 'overview', - // Number of posts to show per page. 0 to show all. - 'posts_per_page' => 0, + $args = wp_parse_args( + $args, + [ + // overview | occasions | single. + // When type is occasions then logRowID, occasionsID, occasionsCount, occasionsCountMaxReturn are required. + // TODO: Add default for above required args. + 'type' => 'overview', + + // Number of posts to show per page. 0 to show all. + 'posts_per_page' => 0, - // Page to show. 1 = first page. - 'paged' => 1, + // Page to show. 1 = first page. + 'paged' => 1, - // Array. Only get posts that are in array. - 'post__in' => null, + // Array. Only get posts that are in array. + 'post__in' => [], - // array or html. - 'format' => 'array', + // array or html. + 'format' => 'array', - // If max_id_first_page is set then only get rows - // that have id equal or lower than this, to make. - 'max_id_first_page' => null, + // If max_id_first_page is set then only get rows + // that have id equal or lower than this, to make. + 'max_id_first_page' => null, - // if since_id is set the rows returned will only be rows with an ID greater than (i.e. more recent than) since_id. - 'since_id' => null, + // if since_id is set the rows returned will only be rows with an ID greater than (i.e. more recent than) since_id. + 'since_id' => null, - /** - * From date, as unix timestamp integer or as a format compatible with strtotime, for example 'Y-m-d H:i:s'. - * - * @var int|string - */ - 'date_from' => null, + /** + * From date, as unix timestamp integer or as a format compatible with strtotime, for example 'Y-m-d H:i:s'. + * + * @var int|string + */ + 'date_from' => null, - /** - * To date, as unix timestamp integer or as a format compatible with strtotime, for example 'Y-m-d H:i:s'. - * - * @var int|string - */ - 'date_to' => null, + /** + * To date, as unix timestamp integer or as a format compatible with strtotime, for example 'Y-m-d H:i:s'. + * + * @var int|string + */ + 'date_to' => null, - // months in format "Y-m" - // array or comma separated. - 'months' => null, + // months in format "Y-m" + // array or comma separated. + 'months' => null, - // dates in format - // "month:2015-06" for june 2015 - // "lastdays:7" for the last 7 days. - 'dates' => null, + // dates in format + // "month:2015-06" for june 2015 + // "lastdays:7" for the last 7 days. + 'dates' => null, - /** - * Text to search for. - * Message, logger and level are searched for in main table. - * Values are searched for in context table. - * - * @var string - */ - 'search' => null, + /** + * Text to search for. + * Message, logger and level are searched for in main table. + * Values are searched for in context table. + * + * @var string + */ + 'search' => null, - // log levels to include. comma separated or as array. defaults to all. - 'loglevels' => null, + // log levels to include. comma separated or as array. defaults to all. + 'loglevels' => null, - // loggers to include. comma separated. defaults to all the user can read. - 'loggers' => null, + // loggers to include. comma separated. defaults to all the user can read. + 'loggers' => null, - 'messages' => null, + 'messages' => null, - // userID as number. - 'user' => null, + // userID as number. + 'user' => null, - // user ids, comma separated. - 'users' => null, + // User ids, comma separated or array. + 'users' => [], // Can also contain: // logRowID // occasionsCount // occasionsCountMaxReturn // occasionsID. + ] ); - $args = wp_parse_args( $args, $defaults ); - // Create cache key based on args and request and current user. $cache_key = 'SimpleHistoryLogQuery_' . md5( serialize( $args ) ) . '_get_' . md5( serialize( $_GET ) ) . '_userid_' . get_current_user_id(); $cache_group = 'simple-history-' . Helpers::get_cache_incrementor(); + + /** @var array Return value. */ $arr_return = wp_cache_get( $cache_key, $cache_group ); + // Return cached value if it exists. if ( false !== $arr_return ) { $arr_return['cached_result'] = true; return $arr_return; } /* - Subequent occasions query thanks to this Stack Overflow thread: + Subequent occasions query thanks to the answer Stack Overflow thread: http://stackoverflow.com/questions/13566303/how-to-group-subsequent-rows-based-on-a-criteria-and-then-count-them-mysql/13567320#13567320 + Similar questions that I didn't manage to understand, work, or did try: - http://stackoverflow.com/questions/23651176/mysql-query-if-dates-are-subsequent - http://stackoverflow.com/questions/17651868/mysql-group-by-subsequent @@ -141,15 +150,18 @@ public function query( $args ) { - http://stackoverflow.com/questions/6602006/complicated-query-with-group-by-and-range-of-prices-in-mysql */ - global $wpdb; - $simple_history = Simple_History::get_instance(); $table_name = $simple_history->get_events_table_name(); $table_name_contexts = $simple_history->get_contexts_table_name(); + /** @var string Where clause for outer query. */ $where = '1 = 1'; - $limit = ''; + + /** @var string Where clause for inner query. */ $inner_where = '1 = 1'; + /** @var string Limit clause. */ + $limit = ''; + if ( 'overview' === $args['type'] || 'single' === $args['type'] ) { // Set variables used by query. $sql_set_var = 'SET @a:=NULL, @counter:=1, @groupby:=0'; @@ -207,11 +219,9 @@ public function query( $args ) { %2$s '; - $sh = Simple_History::get_instance(); - // Only include loggers that the current user can view // @TODO: this causes error if user has no access to any logger at all. - $sql_loggers_user_can_view = $sh->get_loggers_that_user_can_read( get_current_user_id(), 'sql' ); + $sql_loggers_user_can_view = $simple_history->get_loggers_that_user_can_read( get_current_user_id(), 'sql' ); $inner_where .= " AND logger IN {$sql_loggers_user_can_view}"; } elseif ( 'occasions' === $args['type'] ) { // Query template @@ -253,11 +263,14 @@ public function query( $args ) { $limit .= sprintf( 'LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page'] ); } - // Determine where. - if ( $args['post__in'] && is_array( $args['post__in'] ) ) { - // make sure all vals are integers. + // Add post__in where. + if ( sizeof( $args['post__in'] ) > 0 ) { + // Make sure all vals are integers. $args['post__in'] = array_map( 'intval', $args['post__in'] ); + // Remove empty values. + $args['post__in'] = array_filter( $args['post__in'] ); + $inner_where .= sprintf( ' AND id IN (%1$s)', implode( ',', $args['post__in'] ) ); } @@ -568,18 +581,15 @@ public function query( $args ) { $inner_where .= $sql_loggers; } - // user, a single userID. + // Add where for a single user ID. if ( ! empty( $args['user'] ) && is_numeric( $args['user'] ) ) { - $userID = (int) $args['user']; - $sql_user = sprintf( + $inner_where .= sprintf( ' AND id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value = %2$s ) ', $table_name_contexts, // 1 - $userID // 2 + (int) $args['user'], // 2 ); - - $inner_where .= $sql_user; } // If users is array, make it comma separated. @@ -589,17 +599,16 @@ public function query( $args ) { // Users, comma separated. if ( ! empty( $args['users'] ) && is_string( $args['users'] ) ) { + /** @var array User ids. */ $users = explode( ',', $args['users'] ); $users = array_map( 'intval', $users ); - $users_in = implode( ',', $users ); - $sql_user = sprintf( + $inner_where .= sprintf( ' AND id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value IN (%2$s) ) - ', + ', $table_name_contexts, // 1 - $users_in // 2 + implode( ',', $users ), // 2 ); - $inner_where .= $sql_user; } /** From 44351a5ecf5e0f59aecd6b3b634ee5dfeef5659e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sun, 10 Dec 2023 09:46:23 +0100 Subject: [PATCH 08/59] Log query: Use arrays to store where clauses + misc cleanup --- inc/class-log-query.php | 165 ++++++++++++++++++++++------------------ 1 file changed, 93 insertions(+), 72 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index f361a8c2..3808d329 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -6,6 +6,9 @@ /** * Queries the Simple History Log. + * + * Todo + * - [ ] Convert $inner_where and $where to arrays, instead of concating. */ class Log_Query { /** @@ -43,6 +46,7 @@ public function query( $args ) { /** @var string SQL Template to use. Template used depends on $args['type']. */ $sql_tmpl = null; + /** @var array Query arguments. */ $args = wp_parse_args( $args, [ @@ -137,43 +141,50 @@ public function query( $args ) { return $arr_return; } - /* - Subequent occasions query thanks to the answer Stack Overflow thread: - http://stackoverflow.com/questions/13566303/how-to-group-subsequent-rows-based-on-a-criteria-and-then-count-them-mysql/13567320#13567320 + /** @var string Table name for events. */ + $table_history = $simple_history->get_events_table_name(); - Similar questions that I didn't manage to understand, work, or did try: - - http://stackoverflow.com/questions/23651176/mysql-query-if-dates-are-subsequent - - http://stackoverflow.com/questions/17651868/mysql-group-by-subsequent - - http://stackoverflow.com/questions/4495242/mysql-number-of-subsequent-occurrences - - http://stackoverflow.com/questions/20446242/postgresql-group-subsequent-rows - - http://stackoverflow.com/questions/17061156/mysql-group-by-range - - http://stackoverflow.com/questions/6602006/complicated-query-with-group-by-and-range-of-prices-in-mysql - */ + /** @var string Table name for contexts. */ + $table_contexts = $simple_history->get_contexts_table_name(); - $table_name = $simple_history->get_events_table_name(); - $table_name_contexts = $simple_history->get_contexts_table_name(); + /** @var array Where clauses for outer query. */ + $outer_where = []; - /** @var string Where clause for outer query. */ - $where = '1 = 1'; - - /** @var string Where clause for inner query. */ - $inner_where = '1 = 1'; + /** @var array Where clause for inner query. */ + $inner_where = []; /** @var string Limit clause. */ $limit = ''; if ( 'overview' === $args['type'] || 'single' === $args['type'] ) { + /* + Subequent occasions query thanks to the answer Stack Overflow thread: + http://stackoverflow.com/questions/13566303/how-to-group-subsequent-rows-based-on-a-criteria-and-then-count-them-mysql/13567320#13567320 + + Similar questions that I didn't manage to understand, work, or did try: + - http://stackoverflow.com/questions/23651176/mysql-query-if-dates-are-subsequent + - http://stackoverflow.com/questions/17651868/mysql-group-by-subsequent + - http://stackoverflow.com/questions/4495242/mysql-number-of-subsequent-occurrences + - http://stackoverflow.com/questions/20446242/postgresql-group-subsequent-rows + - http://stackoverflow.com/questions/17061156/mysql-group-by-range + - http://stackoverflow.com/questions/6602006/complicated-query-with-group-by-and-range-of-prices-in-mysql + */ // Set variables used by query. $sql_set_var = 'SET @a:=NULL, @counter:=1, @groupby:=0'; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $wpdb->query( $sql_set_var ); - // Main query - // 1 = where. - // 2 = limit. - // 3 = db name. - // 4 = where for inner calc sql query thingie. - // 5 = db name contexts. + /** + * @var string $sql_tmpl SQL template for overview or single event query. + * + * Template uses number argument to sprintf to insert values. + * Arguments: + * 1 = where clause. + * 2 = limit clause. + * 3 = table name for events. + * 4 = where clause for inner query. + * 5 = table name for contexts. + */ $sql_tmpl = ' /*NO_SELECT_FOUND_ROWS*/ SELECT @@ -203,16 +214,14 @@ public function query( $args ) { @a:=occasionsID occasionsIDType FROM %3$s AS h2 - # First/inner where - WHERE - %4$s + # Inner where + %4$s ORDER BY id DESC, date DESC ) AS t ON t.id = h.id - WHERE - # Outer/Second where - %1$s + # Outer where + %1$s GROUP BY repeated ORDER BY id DESC, date DESC @@ -222,7 +231,7 @@ public function query( $args ) { // Only include loggers that the current user can view // @TODO: this causes error if user has no access to any logger at all. $sql_loggers_user_can_view = $simple_history->get_loggers_that_user_can_read( get_current_user_id(), 'sql' ); - $inner_where .= " AND logger IN {$sql_loggers_user_can_view}"; + $inner_where[] = "logger IN {$sql_loggers_user_can_view}"; } elseif ( 'occasions' === $args['type'] ) { // Query template // 1 = where. @@ -239,10 +248,10 @@ public function query( $args ) { '; // Get rows with id lower than logRowID, i.e. previous rows. - $where .= ' AND h.id < ' . (int) $args['logRowID']; + $outer_where[] = 'h.id < ' . (int) $args['logRowID']; // Get rows with occasionsID equal to occasionsID. - $where .= " AND h.occasionsID = '" . esc_sql( $args['occasionsID'] ) . "'"; + $outer_where[] = "h.occasionsID = '" . esc_sql( $args['occasionsID'] ) . "'"; if ( isset( $args['occasionsCountMaxReturn'] ) && (int) $args['occasionsCountMaxReturn'] < (int) $args['occasionsCount'] ) { // Limit to max nn events if occasionsCountMaxReturn is set. @@ -252,7 +261,7 @@ public function query( $args ) { // Regular limit that gets all occasions. $limit = 'LIMIT ' . (int) $args['occasionsCount']; } - }// End if(). + } // End if(). // Determine limit // Both posts_per_page and paged must be set. @@ -271,25 +280,23 @@ public function query( $args ) { // Remove empty values. $args['post__in'] = array_filter( $args['post__in'] ); - $inner_where .= sprintf( ' AND id IN (%1$s)', implode( ',', $args['post__in'] ) ); + $inner_where[] = sprintf( 'id IN (%1$s)', implode( ',', $args['post__in'] ) ); } // If max_id_first_page is then then only include rows // with id equal to or earlier. if ( isset( $args['max_id_first_page'] ) && is_numeric( $args['max_id_first_page'] ) ) { - $max_id_first_page = (int) $args['max_id_first_page']; - $inner_where .= sprintf( - ' AND id <= %1$d', - $max_id_first_page + $inner_where[] = sprintf( + 'id <= %1$d', + (int) $args['max_id_first_page'] ); } if ( isset( $args['since_id'] ) && is_numeric( $args['since_id'] ) ) { - $since_id = (int) $args['since_id']; // Add where to inner because that's faster. - $inner_where .= sprintf( - ' AND id > %1$d', - $since_id + $inner_where[] = sprintf( + 'id > %1$d', + (int) $args['since_id'], ); } @@ -302,7 +309,7 @@ public function query( $args ) { $date_from = strtotime( $date_from ); } - $inner_where .= "\n" . sprintf( ' AND date >= "%1$s"', gmdate( 'Y-m-d H:i:s', $date_from ) ); + $inner_where[] = sprintf( 'date >= "%1$s"', gmdate( 'Y-m-d H:i:s', $date_from ) ); } if ( ! empty( $args['date_to'] ) ) { @@ -313,7 +320,7 @@ public function query( $args ) { $date_to = strtotime( $date_to ); } - $inner_where .= "\n" . sprintf( ' AND date <= "%1$s"', gmdate( 'Y-m-d H:i:s', $date_to ) ); + $inner_where[] = sprintf( 'date <= "%1$s"', gmdate( 'Y-m-d H:i:s', $date_to ) ); } // If months they translate to $args["months"] because we already have support for that @@ -363,10 +370,10 @@ public function query( $args ) { // lastdays, as int. if ( ! empty( $args['lastdays'] ) ) { - $inner_where .= "\n" . sprintf( + $inner_where[] = sprintf( ' # lastdays - AND date >= DATE(NOW()) - INTERVAL %d DAY + date >= DATE(NOW()) - INTERVAL %d DAY ', $args['lastdays'] ); @@ -382,7 +389,7 @@ public function query( $args ) { $sql_months = "\n" . ' # sql_months - AND ( + ( '; foreach ( $arr_months as $one_month ) { @@ -418,7 +425,7 @@ public function query( $args ) { ) '; - $inner_where .= $sql_months; + $inner_where[] = $sql_months; } // End if(). // Search. @@ -461,7 +468,7 @@ public function query( $args ) { $str_search_conditions .= "\n" . sprintf( ' id IN ( SELECT history_id FROM %1$s AS c WHERE c.value LIKE "%2$s" ) AND ', - $table_name_contexts, // 1 + $table_contexts, // 1 '%' . $str_like . '%' // 2 ); } @@ -469,7 +476,7 @@ public function query( $args ) { $str_search_conditions .= "\n ) "; // end OR for contexts. - $inner_where .= "\n AND \n(\n {$str_search_conditions} \n ) "; + $inner_where[] = "\n(\n {$str_search_conditions} \n ) "; }// End if(). // log levels @@ -489,9 +496,9 @@ public function query( $args ) { } $sql_loglevels = rtrim( $sql_loglevels, ' ,' ); - $sql_loglevels = "\n AND level IN ({$sql_loglevels}) "; + $sql_loglevels = "level IN ({$sql_loglevels}) "; - $inner_where .= $sql_loglevels; + $inner_where[] = $sql_loglevels; } // messages. @@ -532,7 +539,7 @@ public function query( $args ) { } // Create sql where based on loggers and messages. - $sql_messages_where = ' AND ('; + $sql_messages_where = '('; foreach ( $arr_loggers_and_messages as $logger_slug => $logger_messages ) { @@ -558,7 +565,7 @@ public function query( $args ) { $sql_messages_where = preg_replace( '/OR $/', '', $sql_messages_where ); $sql_messages_where .= "\n )"; - $where .= $sql_messages_where; + $outer_where[] = $sql_messages_where; } // End if(). // loggers @@ -576,18 +583,16 @@ public function query( $args ) { $sql_loggers .= sprintf( ' "%s", ', esc_sql( $one_logger ) ); } $sql_loggers = rtrim( $sql_loggers, ' ,' ); - $sql_loggers = "\n AND logger IN ({$sql_loggers}) "; + $sql_loggers = "logger IN ({$sql_loggers}) "; - $inner_where .= $sql_loggers; + $inner_where[] = $sql_loggers; } // Add where for a single user ID. if ( ! empty( $args['user'] ) && is_numeric( $args['user'] ) ) { - $inner_where .= sprintf( - ' - AND id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value = %2$s ) - ', - $table_name_contexts, // 1 + $inner_where[] = sprintf( + 'id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value = %2$s )', + $table_contexts, // 1 (int) $args['user'], // 2 ); } @@ -602,11 +607,9 @@ public function query( $args ) { /** @var array User ids. */ $users = explode( ',', $args['users'] ); $users = array_map( 'intval', $users ); - $inner_where .= sprintf( - ' - AND id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value IN (%2$s) ) - ', - $table_name_contexts, // 1 + $inner_where[] = sprintf( + 'id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value IN (%2$s) )', + $table_contexts, // 1 implode( ',', $users ), // 2 ); } @@ -620,6 +623,14 @@ public function query( $args ) { */ $sql_tmpl = apply_filters( 'simple_history/log_query_sql_template', $sql_tmpl ); + // Create where string. + $outer_where = implode( "\nAND ", $outer_where ); + + // Append where to sql template. + if ( ! empty( $outer_where ) ) { + $outer_where = "\nWHERE {$outer_where}"; + } + /** * Filter the sql template where clause * @@ -627,7 +638,7 @@ public function query( $args ) { * * @param string $where */ - $where = apply_filters( 'simple_history/log_query_sql_where', $where ); + $outer_where = apply_filters( 'simple_history/log_query_sql_where', $outer_where ); /** * Filter the sql template limit @@ -638,6 +649,14 @@ public function query( $args ) { */ $limit = apply_filters( 'simple_history/log_query_limit', $limit ); + // Create where string. + $inner_where = implode( "\nAND ", $inner_where ); + + // Append where to sql template. + if ( ! empty( $inner_where ) ) { + $inner_where = "\nWHERE {$inner_where}"; + } + /** * Filter the sql template limit * @@ -649,13 +668,15 @@ public function query( $args ) { $sql = sprintf( $sql_tmpl, // sprintf template. - $where, // 1 + $outer_where, // 1 $limit, // 2 - $table_name, // 3 + $table_history, // 3 $inner_where, // 4 - $table_name_contexts // 5 + $table_contexts // 5 ); + // echo $sql;exit; + /** * Filter the final sql query * @@ -679,7 +700,7 @@ public function query( $args ) { if ( empty( $post_ids ) ) { $context_results = array(); } else { - $sql_context = sprintf( 'SELECT * FROM %2$s WHERE history_id IN (%1$s)', join( ',', $post_ids ), $table_name_contexts ); + $sql_context = sprintf( 'SELECT * FROM %2$s WHERE history_id IN (%1$s)', join( ',', $post_ids ), $table_contexts ); $context_results = $wpdb->get_results( $sql_context ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared } From 80eef6f6458b6e62d269465437e580c458589ca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Mon, 11 Dec 2023 20:03:47 +0100 Subject: [PATCH 09/59] Log query: Add more arguments to prepare function and misc cleanup --- inc/class-log-query.php | 223 +++++++++++++++++++++++++++------------- 1 file changed, 153 insertions(+), 70 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 3808d329..aa8c7f40 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -8,7 +8,11 @@ * Queries the Simple History Log. * * Todo - * - [ ] Convert $inner_where and $where to arrays, instead of concating. + * - [x] Convert $inner_where and $where to arrays, instead of concating. + * - [ ] Should array be array of arrays where each AND clause contains a description? Could be useful for debugging + be a little bit prepared for future support for both "AND" and "OR" clauses. + * - Add "prepare args" function. + * - Add "get where clause" function that returns the where clause. + * - Add "get sql_query" function that returns the sql query. */ class Log_Query { /** @@ -17,7 +21,7 @@ class Log_Query { * @param string|array|object $args { * Optional. Array or string of arguments for querying the log. * @type string $type Type of query. Accepts 'overview', 'occasions', or 'single'. Default 'overview'. - * @type int $posts_per_page Number of posts to show per page. 0 to show all. Default 0. + * @type int $posts_per_page Number of posts to show per page. Default is 10. * @type int $paged Page to show. 1 = first page. Default 1. * @type array $post__in Array. Only get posts that are in array. Default null. * @type string $format Array or html. Default 'array'. @@ -56,7 +60,7 @@ public function query( $args ) { 'type' => 'overview', // Number of posts to show per page. 0 to show all. - 'posts_per_page' => 0, + 'posts_per_page' => 10, // Page to show. 1 = first page. 'paged' => 1, @@ -141,6 +145,9 @@ public function query( $args ) { return $arr_return; } + // Parse and prepare args. + $args = $this->prepare_args( $args ); + /** @var string Table name for events. */ $table_history = $simple_history->get_events_table_name(); @@ -169,10 +176,10 @@ public function query( $args ) { - http://stackoverflow.com/questions/17061156/mysql-group-by-range - http://stackoverflow.com/questions/6602006/complicated-query-with-group-by-and-range-of-prices-in-mysql */ + // Set variables used by query. - $sql_set_var = 'SET @a:=NULL, @counter:=1, @groupby:=0'; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - $wpdb->query( $sql_set_var ); + $wpdb->query( 'SET @a:=NULL, @counter:=1, @groupby:=0' ); /** * @var string $sql_tmpl SQL template for overview or single event query. @@ -214,17 +221,19 @@ public function query( $args ) { @a:=occasionsID occasionsIDType FROM %3$s AS h2 - # Inner where + # Inner where. %4$s ORDER BY id DESC, date DESC ) AS t ON t.id = h.id - # Outer where + # Outer where. %1$s GROUP BY repeated ORDER BY id DESC, date DESC + + # Limit clause. %2$s '; @@ -253,77 +262,55 @@ public function query( $args ) { // Get rows with occasionsID equal to occasionsID. $outer_where[] = "h.occasionsID = '" . esc_sql( $args['occasionsID'] ) . "'"; - if ( isset( $args['occasionsCountMaxReturn'] ) && (int) $args['occasionsCountMaxReturn'] < (int) $args['occasionsCount'] ) { + if ( isset( $args['occasionsCountMaxReturn'] ) && $args['occasionsCountMaxReturn'] < $args['occasionsCount'] ) { // Limit to max nn events if occasionsCountMaxReturn is set. // Used in gui to prevent to many events returned, that can stall the browser. - $limit = 'LIMIT ' . (int) $args['occasionsCountMaxReturn']; + $limit = 'LIMIT ' . $args['occasionsCountMaxReturn']; } else { // Regular limit that gets all occasions. - $limit = 'LIMIT ' . (int) $args['occasionsCount']; + $limit = 'LIMIT ' . $args['occasionsCount']; } } // End if(). - // Determine limit - // Both posts_per_page and paged must be set. - $is_limit_query = ( is_numeric( $args['posts_per_page'] ) && $args['posts_per_page'] > 0 ); - $is_limit_query = $is_limit_query && ( is_numeric( $args['paged'] ) && $args['paged'] > 0 ); - if ( $is_limit_query ) { - $limit_offset = ( $args['paged'] - 1 ) * $args['posts_per_page']; - $limit .= sprintf( 'LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page'] ); - } + // Determine limit. + $limit_offset = ( $args['paged'] - 1 ) * $args['posts_per_page']; + $limit = sprintf( 'LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page'] ); // Add post__in where. if ( sizeof( $args['post__in'] ) > 0 ) { - // Make sure all vals are integers. - $args['post__in'] = array_map( 'intval', $args['post__in'] ); - - // Remove empty values. - $args['post__in'] = array_filter( $args['post__in'] ); - $inner_where[] = sprintf( 'id IN (%1$s)', implode( ',', $args['post__in'] ) ); } // If max_id_first_page is then then only include rows // with id equal to or earlier. - if ( isset( $args['max_id_first_page'] ) && is_numeric( $args['max_id_first_page'] ) ) { + if ( isset( $args['max_id_first_page'] ) ) { $inner_where[] = sprintf( 'id <= %1$d', - (int) $args['max_id_first_page'] + $args['max_id_first_page'] ); } - if ( isset( $args['since_id'] ) && is_numeric( $args['since_id'] ) ) { - // Add where to inner because that's faster. + if ( isset( $args['since_id'] ) ) { + // Add clause to inner where because that's faster. $inner_where[] = sprintf( 'id > %1$d', (int) $args['since_id'], ); } - // Append date where. + // Append date where clause. + // If date_from is set it is a timestamp. if ( ! empty( $args['date_from'] ) ) { - // date_from=2014-08-01 - // if date is not numeric assume Y-m-d H:i-format. - $date_from = $args['date_from']; - if ( ! is_numeric( $date_from ) ) { - $date_from = strtotime( $date_from ); - } - - $inner_where[] = sprintf( 'date >= "%1$s"', gmdate( 'Y-m-d H:i:s', $date_from ) ); + $inner_where[] = sprintf( 'date >= "%1$s"', gmdate( 'Y-m-d H:i:s', $args['date_from'] ) ); } + // Date to. + // If date_to is set it is a timestamp. if ( ! empty( $args['date_to'] ) ) { - // date_to=2014-08-01 - // if date is not numeric assume Y-m-d H:i-format. - $date_to = $args['date_to']; - if ( ! is_numeric( $date_to ) ) { - $date_to = strtotime( $date_to ); - } - - $inner_where[] = sprintf( 'date <= "%1$s"', gmdate( 'Y-m-d H:i:s', $date_to ) ); + $inner_where[] = sprintf( 'date <= "%1$s"', gmdate( 'Y-m-d H:i:s', $args['date_to'] ) ); } - // If months they translate to $args["months"] because we already have support for that + // If "months" they translate to $args["months"] because we already have support for that // can't use months and dates and the same time. if ( ! empty( $args['dates'] ) ) { if ( is_array( $args['dates'] ) ) { @@ -368,7 +355,7 @@ public function query( $args ) { } } - // lastdays, as int. + // Add where clause for "lastdays", as int. if ( ! empty( $args['lastdays'] ) ) { $inner_where[] = sprintf( ' @@ -430,9 +417,8 @@ public function query( $args ) { // Search. if ( ! empty( $args['search'] ) ) { - $search_words = $args['search']; $str_search_conditions = ''; - $arr_search_words = preg_split( '/[\s,]+/', $search_words ); + $arr_search_words = preg_split( '/[\s,]+/', $args['search'] ); // create array of all searched words // split both spaces and commas and such. @@ -479,26 +465,20 @@ public function query( $args ) { $inner_where[] = "\n(\n {$str_search_conditions} \n ) "; }// End if(). - // log levels - // comma separated - // http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&type=overview&format=&posts_per_page=10&paged=1&max_id_first_page=27273&SimpleHistoryLogQuery-showDebug=0&loglevel=error,warn. + // "loglevels", array with loglevels. + // e.g. info, debug, and so on. if ( ! empty( $args['loglevels'] ) ) { $sql_loglevels = ''; - if ( is_array( $args['loglevels'] ) ) { - $arr_loglevels = $args['loglevels']; - } else { - $arr_loglevels = explode( ',', $args['loglevels'] ); - } - - foreach ( $arr_loglevels as $one_loglevel ) { + foreach ( $args['loglevels'] as $one_loglevel ) { $sql_loglevels .= sprintf( ' "%s", ', esc_sql( $one_loglevel ) ); } + // Remove last comma. $sql_loglevels = rtrim( $sql_loglevels, ' ,' ); - $sql_loglevels = "level IN ({$sql_loglevels}) "; - $inner_where[] = $sql_loglevels; + // Add to where in clause. + $inner_where[] = "level IN ({$sql_loglevels})"; } // messages. @@ -675,8 +655,6 @@ public function query( $args ) { $table_contexts // 5 ); - // echo $sql;exit; - /** * Filter the final sql query * @@ -714,7 +692,11 @@ public function query( $args ) { // Remove id from keys, because they are cumbersome when working with JSON. $log_rows = array_values( $log_rows ); + + /** @var null|int */ $min_id = null; + + /** @var null|int */ $max_id = null; if ( count( $log_rows ) ) { @@ -754,21 +736,17 @@ public function query( $args ) { } // End if(). // Calc pages. - if ( $args['posts_per_page'] ) { - $pages_count = Ceil( $total_found_rows / (int) $args['posts_per_page'] ); - } else { - $pages_count = 1; - } + $pages_count = Ceil( $total_found_rows / $args['posts_per_page'] ); // Create array to return. // Make all rows a sub key because we want to add some meta info too. $log_rows_count = count( $log_rows ); - $page_rows_from = ( (int) $args['paged'] * (int) $args['posts_per_page'] ) - (int) $args['posts_per_page'] + 1; + $page_rows_from = ( $args['paged'] * $args['posts_per_page'] ) - $args['posts_per_page'] + 1; $page_rows_to = $page_rows_from + $log_rows_count - 1; $arr_return = array( 'total_row_count' => $total_found_rows, 'pages_count' => $pages_count, - 'page_current' => (int) $args['paged'], + 'page_current' => $args['paged'], 'page_rows_from' => $page_rows_from, 'page_rows_to' => $page_rows_to, 'max_id' => (int) $max_id, @@ -783,4 +761,109 @@ public function query( $args ) { return $arr_return; } + + /** + * Prepare arguments, i.e. checking that they are valid, + * of the correct type, etc. + * + * @param array $args Argument. + * @return array + * @throws \InvalidArgumentException If invalid type. + */ + protected function prepare_args( $args ) { + // Type must be string and any of "overview", "occasions", "single". + if ( ! is_string( $args['type'] ) && ! in_array( $args['type'], [ 'overview', 'occasions', 'single' ], true ) ) { + throw new \InvalidArgumentException( 'Invalid type' ); + } + + // If occasionsCountMaxReturn is set then it must be an integer. + if ( isset( $args['occasionsCountMaxReturn'] ) && ! is_numeric( $args['occasionsCountMaxReturn'] ) ) { + throw new \InvalidArgumentException( 'Invalid occasionsCountMaxReturn' ); + } elseif ( isset( $args['occasionsCountMaxReturn'] ) ) { + $args['occasionsCountMaxReturn'] = (int) $args['occasionsCountMaxReturn']; + } + + // If occasionsCount is set then it must be an integer. + if ( isset( $args['occasionsCount'] ) && ! is_numeric( $args['occasionsCount'] ) ) { + throw new \InvalidArgumentException( 'Invalid occasionsCount' ); + } elseif ( isset( $args['occasionsCount'] ) ) { + $args['occasionsCount'] = (int) $args['occasionsCount']; + } + + // posts_per_page must be set and must be a positive integer. + if ( ! isset( $args['posts_per_page'] ) || ! is_numeric( $args['posts_per_page'] ) && $args['posts_per_page'] < 1 ) { + throw new \InvalidArgumentException( 'Invalid posts_per_page' ); + } else { + $args['posts_per_page'] = (int) $args['posts_per_page']; + } + + // paged must be set and must be a positive integer. + if ( ! isset( $args['paged'] ) || ! is_numeric( $args['paged'] ) || $args['paged'] < 1 ) { + throw new \InvalidArgumentException( 'Invalid paged' ); + } else { + $args['paged'] = (int) $args['paged']; + } + + // "post__in" must be array and must only contain integers. + if ( isset( $args['post__in'] ) && ! is_array( $args['post__in'] ) ) { + throw new \InvalidArgumentException( 'Invalid post__in' ); + } elseif ( isset( $args['post__in'] ) ) { + $args['post__in'] = array_map( 'intval', $args['post__in'] ); + $args['post__in'] = array_filter( $args['post__in'] ); + } + + // "max_id_first_page" must be integer. + if ( isset( $args['max_id_first_page'] ) && ! is_numeric( $args['max_id_first_page'] ) ) { + throw new \InvalidArgumentException( 'Invalid max_id_first_page' ); + } elseif ( isset( $args['max_id_first_page'] ) ) { + $args['max_id_first_page'] = (int) $args['max_id_first_page']; + } + + // "since_id" must be integer. + if ( isset( $args['since_id'] ) && ! is_numeric( $args['since_id'] ) ) { + throw new \InvalidArgumentException( 'Invalid since_id' ); + } elseif ( isset( $args['since_id'] ) ) { + $args['since_id'] = (int) $args['since_id']; + } + + // "date_from" must be timestamp or string. If string then convert to timestamp. + if ( isset( $args['date_from'] ) && ! is_numeric( $args['date_from'] ) ) { + $args['date_from'] = strtotime( $args['date_from'] ); + } elseif ( isset( $args['date_from'] ) && is_string( $args['date_from'] ) ) { + $args['date_from'] = (int) $args['date_from']; + } elseif ( isset( $args['date_from'] ) ) { + throw new \InvalidArgumentException( 'Invalid date_from' ); + } + + // "date_to" must be timestamp or string. If string then convert to timestamp. + if ( isset( $args['date_to'] ) && ! is_numeric( $args['date_to'] ) ) { + $args['date_to'] = strtotime( $args['date_to'] ); + } elseif ( isset( $args['date_to'] ) && is_string( $args['date_to'] ) ) { + $args['date_to'] = (int) $args['date_to']; + } elseif ( isset( $args['date_to'] ) ) { + throw new \InvalidArgumentException( 'Invalid date_to' ); + } + + // "search" must be string. + if ( isset( $args['search'] ) && ! is_string( $args['search'] ) ) { + throw new \InvalidArgumentException( 'Invalid search' ); + } + + // "loglevels" must be comma separeated string "info,debug" + // or array of log level strings. + if ( isset( $args['loglevels'] ) && ! is_string( $args['loglevels'] ) && ! is_array( $args['loglevels'] ) ) { + throw new \InvalidArgumentException( 'Invalid loglevels' ); + } elseif ( isset( $args['loglevels'] ) && is_string( $args['loglevels'] ) ) { + $args['loglevels'] = explode( ',', $args['loglevels'] ); + } + + // Make sure loglevels are trimed, strings, and empty vals removed. + if ( isset( $args['loglevels'] ) ) { + $args['loglevels'] = array_map( 'trim', $args['loglevels'] ); + $args['loglevels'] = array_map( 'strval', $args['loglevels'] ); + $args['loglevels'] = array_filter( $args['loglevels'] ); + } + + return $args; + } } From 64144e3aa0ae8a60e0f0aa140a9275fa02c7c9dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Tue, 12 Dec 2023 20:27:31 +0100 Subject: [PATCH 10/59] Log query: Add arguments users, user, loggers, messages, and more, to prepare function --- inc/class-log-query.php | 171 ++++++++++++++++++++++++---------------- 1 file changed, 103 insertions(+), 68 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index aa8c7f40..a4948b4f 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -34,10 +34,10 @@ class Log_Query { * @type array|string $dates Dates in format "month:2015-06" for june 2015 or "lastdays:7" for the last 7 days. Default null. * @type string $search Text to search for. Message, logger and level are searched for in main table. Values are searched for in context table. Default null. * @type string $loglevels Log levels to include. Comma separated or as array. Defaults to all. Default null. - * @type string $loggers Loggers to include. Comma separated. Defaults to all the user can read. Default null. - * @type string $messages Messages to include. Comma separated. Defaults to all. Default null. - * @type int $user User ID as number. Default null. - * @type string $users User IDs, comma separated. Default null. + * @type string $loggers Loggers to include. Comma separated or array. Default null = all the user can read. + * @type string $messages Messages to include. Array or string with commaa separated in format "LoggerSlug:Message", e.g. "SimplePluginLogger:plugin_activated,SimplePluginLogger:plugin_deactivated". Default null = show all messages. + * @type int $user Single user ID as number. Default null. + * @type string $users User IDs, comma separated or array. Default null. * } * @return array */ @@ -122,7 +122,7 @@ public function query( $args ) { 'user' => null, // User ids, comma separated or array. - 'users' => [], + 'users' => null, // Can also contain: // logRowID @@ -483,50 +483,16 @@ public function query( $args ) { // messages. if ( ! empty( $args['messages'] ) ) { - /* - $args['messages']: - Array - ( - [0] => SimpleCommentsLogger:anon_comment_added,SimpleCommentsLogger:user_comment_added,SimpleCommentsLogger:anon_trackback_added,SimpleCommentsLogger:user_trackback_added,SimpleCommentsLogger:anon_pingback_added,SimpleCommentsLogger:user_pingback_added,SimpleCommentsLogger:comment_edited,SimpleCommentsLogger:trackback_edited,SimpleCommentsLogger:pingback_edited,SimpleCommentsLogger:comment_status_approve,SimpleCommentsLogger:trackback_status_approve,SimpleCommentsLogger:pingback_status_approve,SimpleCommentsLogger:comment_status_hold,SimpleCommentsLogger:trackback_status_hold,SimpleCommentsLogger:pingback_status_hold,SimpleCommentsLogger:comment_status_spam,SimpleCommentsLogger:trackback_status_spam,SimpleCommentsLogger:pingback_status_spam,SimpleCommentsLogger:comment_status_trash,SimpleCommentsLogger:trackback_status_trash,SimpleCommentsLogger:pingback_status_trash,SimpleCommentsLogger:comment_untrashed,SimpleCommentsLogger:trackback_untrashed,SimpleCommentsLogger:pingback_untrashed,SimpleCommentsLogger:comment_deleted,SimpleCommentsLogger:trackback_deleted,SimpleCommentsLogger:pingback_deleted - [1] => SimpleCommentsLogger:SimpleCommentsLogger:comment_status_spam,SimpleCommentsLogger:trackback_status_spam,SimpleCommentsLogger:pingback_status_spam - ) - */ - - // Array with loggers and messages. - $arr_loggers_and_messages = array(); - - // Transform from received format to our own internal format. - foreach ( (array) $args['messages'] as $one_arr_messages_row ) { - $arr_row_messages = explode( ',', $one_arr_messages_row ); - - /* - $one_arr_messages_row: - Array - ( - [0] => SimpleCommentsLogger:anon_comment_added - [1] => SimpleCommentsLogger:user_comment_added - [2] => SimpleCommentsLogger:anon_trackback_added - */ - foreach ( $arr_row_messages as $one_row_logger_and_message ) { - $arr_one_logger_and_message = explode( ':', $one_row_logger_and_message ); - - if ( ! isset( $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ] ) ) { - $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ] = array(); - } - - $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ][] = $arr_one_logger_and_message[1]; - } - } - // Create sql where based on loggers and messages. $sql_messages_where = '('; - foreach ( $arr_loggers_and_messages as $logger_slug => $logger_messages ) { - + foreach ( $args['messages'] as $logger_slug => $logger_messages ) { $sql_logger_messages_in = ''; + foreach ( $logger_messages as $one_logger_message ) { $sql_logger_messages_in .= sprintf( '"%s",', esc_sql( $one_logger_message ) ); } + $sql_logger_messages_in = rtrim( $sql_logger_messages_in, ' ,' ); $sql_logger_messages_in = "\n AND c1.value IN ({$sql_logger_messages_in}) "; @@ -541,56 +507,45 @@ public function query( $args ) { $sql_logger_messages_in ); } - // remove last or. + + // Remove last 'OR '. $sql_messages_where = preg_replace( '/OR $/', '', $sql_messages_where ); $sql_messages_where .= "\n )"; $outer_where[] = $sql_messages_where; } // End if(). - // loggers - // comma separated + // loggers, comma separated or array. // http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&type=overview&format=&posts_per_page=10&paged=1&max_id_first_page=27273&SimpleHistoryLogQuery-showDebug=0&loggers=SimpleCommentsLogger,SimpleCoreUpdatesLogger. if ( ! empty( $args['loggers'] ) ) { $sql_loggers = ''; - if ( is_array( $args['loggers'] ) ) { - $arr_loggers = $args['loggers']; - } else { - $arr_loggers = explode( ',', $args['loggers'] ); - } - foreach ( $arr_loggers as $one_logger ) { + foreach ( $args['loggers'] as $one_logger ) { $sql_loggers .= sprintf( ' "%s", ', esc_sql( $one_logger ) ); } + + // Remove last comma. $sql_loggers = rtrim( $sql_loggers, ' ,' ); - $sql_loggers = "logger IN ({$sql_loggers}) "; - $inner_where[] = $sql_loggers; + // Add to where in clause. + $inner_where[] = "logger IN ({$sql_loggers}) "; } // Add where for a single user ID. - if ( ! empty( $args['user'] ) && is_numeric( $args['user'] ) ) { + if ( isset( $args['user'] ) ) { $inner_where[] = sprintf( 'id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value = %2$s )', $table_contexts, // 1 - (int) $args['user'], // 2 + $args['user'], // 2 ); } - // If users is array, make it comma separated. - if ( isset( $args['users'] ) && is_array( $args['users'] ) ) { - $args['users'] = implode( ',', $args['users'] ); - } - - // Users, comma separated. - if ( ! empty( $args['users'] ) && is_string( $args['users'] ) ) { - /** @var array User ids. */ - $users = explode( ',', $args['users'] ); - $users = array_map( 'intval', $users ); + // Users, array with user ids. + if ( isset( $args['users'] ) ) { $inner_where[] = sprintf( 'id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value IN (%2$s) )', $table_contexts, // 1 - implode( ',', $users ), // 2 + implode( ',', $args['users'] ), // 2 ); } @@ -604,6 +559,7 @@ public function query( $args ) { $sql_tmpl = apply_filters( 'simple_history/log_query_sql_template', $sql_tmpl ); // Create where string. + $outer_where_array = $outer_where; $outer_where = implode( "\nAND ", $outer_where ); // Append where to sql template. @@ -630,6 +586,7 @@ public function query( $args ) { $limit = apply_filters( 'simple_history/log_query_limit', $limit ); // Create where string. + $inner_where_array = $inner_where; $inner_where = implode( "\nAND ", $inner_where ); // Append where to sql template. @@ -743,7 +700,7 @@ public function query( $args ) { $log_rows_count = count( $log_rows ); $page_rows_from = ( $args['paged'] * $args['posts_per_page'] ) - $args['posts_per_page'] + 1; $page_rows_to = $page_rows_from + $log_rows_count - 1; - $arr_return = array( + $arr_return = [ 'total_row_count' => $total_found_rows, 'pages_count' => $pages_count, 'page_current' => $args['paged'], @@ -755,7 +712,9 @@ public function query( $args ) { 'log_rows' => $log_rows, // Add sql query to debug. // 'sql' => $sql, // . - ); + 'outer_where_array' => $outer_where_array, + 'inner_where_array' => $inner_where_array, + ]; wp_cache_set( $cache_key, $arr_return, $cache_group ); @@ -829,6 +788,8 @@ protected function prepare_args( $args ) { // "date_from" must be timestamp or string. If string then convert to timestamp. if ( isset( $args['date_from'] ) && ! is_numeric( $args['date_from'] ) ) { $args['date_from'] = strtotime( $args['date_from'] ); + } elseif ( isset( $args['date_from'] ) && is_numeric( $args['date_from'] ) ) { + $args['date_from'] = (int) $args['date_from']; } elseif ( isset( $args['date_from'] ) && is_string( $args['date_from'] ) ) { $args['date_from'] = (int) $args['date_from']; } elseif ( isset( $args['date_from'] ) ) { @@ -864,6 +825,80 @@ protected function prepare_args( $args ) { $args['loglevels'] = array_filter( $args['loglevels'] ); } + // "messages" is string with comma separated loggers and messages, + // or array with comma separated loggers and messages. + // Array example: + // Array + // ( + // [0] => SimpleCommentsLogger:anon_comment_added,SimpleCommentsLogger:user_comment_added,SimpleCommentsLogger:anon_trackback_added,SimpleCommentsLogger:user_trackback_added,SimpleCommentsLogger:anon_pingback_added,SimpleCommentsLogger:user_pingback_added,SimpleCommentsLogger:comment_edited,SimpleCommentsLogger:trackback_edited,SimpleCommentsLogger:pingback_edited,SimpleCommentsLogger:comment_status_approve,SimpleCommentsLogger:trackback_status_approve,SimpleCommentsLogger:pingback_status_approve,SimpleCommentsLogger:comment_status_hold,SimpleCommentsLogger:trackback_status_hold,SimpleCommentsLogger:pingback_status_hold,SimpleCommentsLogger:comment_status_spam,SimpleCommentsLogger:trackback_status_spam,SimpleCommentsLogger:pingback_status_spam,SimpleCommentsLogger:comment_status_trash,SimpleCommentsLogger:trackback_status_trash,SimpleCommentsLogger:pingback_status_trash,SimpleCommentsLogger:comment_untrashed,SimpleCommentsLogger:trackback_untrashed,SimpleCommentsLogger:pingback_untrashed,SimpleCommentsLogger:comment_deleted,SimpleCommentsLogger:trackback_deleted,SimpleCommentsLogger:pingback_deleted + // [1] => SimpleCommentsLogger:SimpleCommentsLogger:comment_status_spam,SimpleCommentsLogger:trackback_status_spam,SimpleCommentsLogger:pingback_status_spam + // ) + if ( isset( $args['messages'] ) && ! is_string( $args['messages'] ) && ! is_array( $args['messages'] ) ) { + throw new \InvalidArgumentException( 'Invalid messages' ); + } elseif ( isset( $args['messages'] ) && is_string( $args['messages'] ) ) { + $args['messages'] = explode( ',', $args['messages'] ); + } elseif ( isset( $args['messages'] ) && is_array( $args['messages'] ) ) { + // Turn multi dimensional array into single array with strings. + $arr_messages = []; + foreach ( $args['messages'] as $one_arr_messages_row ) { + $arr_messages = array_merge( $arr_messages, explode( ',', $one_arr_messages_row ) ); + } + + $args['messages'] = $arr_messages; + } + + // Make sure messages are trimed, strings, and empty vals removed. + if ( isset( $args['messages'] ) ) { + $args['messages'] = array_map( 'trim', $args['messages'] ); + $args['messages'] = array_map( 'strval', $args['messages'] ); + $args['messages'] = array_filter( $args['messages'] ); + + $arr_loggers_and_messages = []; + + // Transform to format where + // - key = logger slug. + // - value = array of logger messages.. + foreach ( $args['messages'] as $one_row_logger_and_message ) { + $arr_one_logger_and_message = explode( ':', $one_row_logger_and_message ); + + if ( ! isset( $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ] ) ) { + $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ] = array(); + } + + $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ][] = $arr_one_logger_and_message[1]; + } + + $args['messages'] = $arr_loggers_and_messages; + } + + // "loggers", comma separated string or array with strings. + // Example format: "AvailableUpdatesLogger,SimpleuserLogger". + if ( isset( $args['loggers'] ) && ! is_string( $args['loggers'] ) && ! is_array( $args['loggers'] ) ) { + throw new \InvalidArgumentException( 'Invalid loggers' ); + } elseif ( isset( $args['loggers'] ) && is_string( $args['loggers'] ) ) { + $args['loggers'] = explode( ',', $args['loggers'] ); + } + + // "user" must be integer. + if ( isset( $args['user'] ) && ! is_numeric( $args['user'] ) ) { + throw new \InvalidArgumentException( 'Invalid user' ); + } elseif ( isset( $args['user'] ) ) { + $args['user'] = (int) $args['user']; + } + + // "users" must be comma separated string or array with integers. + if ( isset( $args['users'] ) && ! is_string( $args['users'] ) && ! is_array( $args['users'] ) ) { + throw new \InvalidArgumentException( 'Invalid users' ); + } elseif ( isset( $args['users'] ) && is_string( $args['users'] ) ) { + $args['users'] = explode( ',', $args['users'] ); + } + + // Make sure users are integers and remove empty vals. + if ( isset( $args['users'] ) ) { + $args['users'] = array_map( 'intval', $args['users'] ); + $args['users'] = array_filter( $args['users'] ); + } + return $args; } } From e19d390e42b649e7937eb3e0cd9f87b80322c9fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Wed, 13 Dec 2023 20:23:52 +0100 Subject: [PATCH 11/59] Log query: Limit fix for occassions --- inc/class-log-query.php | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index a4948b4f..3aebecee 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -241,17 +241,33 @@ public function query( $args ) { // @TODO: this causes error if user has no access to any logger at all. $sql_loggers_user_can_view = $simple_history->get_loggers_that_user_can_read( get_current_user_id(), 'sql' ); $inner_where[] = "logger IN {$sql_loggers_user_can_view}"; + + // Add limit clause. + $limit_offset = ( $args['paged'] - 1 ) * $args['posts_per_page']; + $limit = sprintf( 'LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page'] ); } elseif ( 'occasions' === $args['type'] ) { - // Query template - // 1 = where. - // 2 = limit. - // 3 = db name. + // Get occasions for a single event. + // Args must contain: + // - occasionsID: The id to get occassions for + // - occasionsCount: The number of occasions to get. + // - occasionsCountMaxReturn: The max number of occasions to return, + // if occassionsCount is very large and we do not want to get all occassions. + + /** + * @var string $sql_tmpl SQL template for occasions query. + * Template uses number argument to sprintf to insert values. + * Arguments: + * 1 = where clause. + * 2 = limit clause. + * 3 = table name for events. + */ $sql_tmpl = ' SELECT h.*, - # fake columns that exist in overview query + # Fake columns that exist in overview query 1 as subsequentOccasions FROM %3$s AS h - WHERE %1$s + # Where + %1$s ORDER BY id DESC %2$s '; @@ -272,10 +288,6 @@ public function query( $args ) { } } // End if(). - // Determine limit. - $limit_offset = ( $args['paged'] - 1 ) * $args['posts_per_page']; - $limit = sprintf( 'LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page'] ); - // Add post__in where. if ( sizeof( $args['post__in'] ) > 0 ) { $inner_where[] = sprintf( 'id IN (%1$s)', implode( ',', $args['post__in'] ) ); From 85ae317c08eb530b1941d10a20a35eb76a8eca0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Wed, 13 Dec 2023 20:24:15 +0100 Subject: [PATCH 12/59] Log query: use %d for integers --- inc/class-log-query.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 3aebecee..12311f7e 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -688,9 +688,9 @@ public function query( $args ) { ' SELECT id, date, occasionsID FROM %1$s - WHERE id <= %2$s + WHERE id <= %2$d ORDER BY id DESC - LIMIT %3$s + LIMIT %3$d ', $db_table, $last_row->id, From cfb166bb3bcfd3cae1d75bd3a023cb1adffe61bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Wed, 13 Dec 2023 20:35:12 +0100 Subject: [PATCH 13/59] Log query: Specify columns to get when adding context instead of getting all --- inc/class-log-query.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 12311f7e..f7b64453 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -641,13 +641,18 @@ public function query( $args ) { $sql_found_rows = 'SELECT FOUND_ROWS()'; $total_found_rows = (int) $wpdb->get_var( $sql_found_rows ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - // Add context. + // Add context to log rows. $post_ids = wp_list_pluck( $log_rows, 'id' ); if ( empty( $post_ids ) ) { - $context_results = array(); + $context_results = []; } else { - $sql_context = sprintf( 'SELECT * FROM %2$s WHERE history_id IN (%1$s)', join( ',', $post_ids ), $table_contexts ); + $sql_context = sprintf( + 'SELECT history_id, `key`, value FROM %2$s WHERE history_id IN (%1$s)', + join( ',', $post_ids ), + $table_contexts + ); + $context_results = $wpdb->get_results( $sql_context ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared } From 539a074c79a5e74e51966329fa055f91257e8018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Wed, 13 Dec 2023 20:35:31 +0100 Subject: [PATCH 14/59] Log query: Misc cleanup --- inc/class-log-query.php | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index f7b64453..d0f5ed56 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -10,7 +10,7 @@ * Todo * - [x] Convert $inner_where and $where to arrays, instead of concating. * - [ ] Should array be array of arrays where each AND clause contains a description? Could be useful for debugging + be a little bit prepared for future support for both "AND" and "OR" clauses. - * - Add "prepare args" function. + * - [x] Add "prepare args" function, that checks that args are valid, so that we don't have to do that in the query function. * - Add "get where clause" function that returns the where clause. * - Add "get sql_query" function that returns the sql query. */ @@ -250,8 +250,8 @@ public function query( $args ) { // Args must contain: // - occasionsID: The id to get occassions for // - occasionsCount: The number of occasions to get. - // - occasionsCountMaxReturn: The max number of occasions to return, - // if occassionsCount is very large and we do not want to get all occassions. + // - occasionsCountMaxReturn: The max number of occasions to return, + // if occassionsCount is very large and we do not want to get all occassions. /** * @var string $sql_tmpl SQL template for occasions query. @@ -370,10 +370,7 @@ public function query( $args ) { // Add where clause for "lastdays", as int. if ( ! empty( $args['lastdays'] ) ) { $inner_where[] = sprintf( - ' - # lastdays - date >= DATE(NOW()) - INTERVAL %d DAY - ', + 'date >= DATE(NOW()) - INTERVAL %d DAY', $args['lastdays'] ); } @@ -387,7 +384,6 @@ public function query( $args ) { } $sql_months = "\n" . ' - # sql_months ( '; @@ -420,7 +416,6 @@ public function query( $args ) { $sql_months = rtrim( $sql_months, ' OR ' ); $sql_months .= ' - # end sql_months and wrap ) '; @@ -428,13 +423,13 @@ public function query( $args ) { } // End if(). // Search. - if ( ! empty( $args['search'] ) ) { + if ( isset( $args['search'] ) ) { $str_search_conditions = ''; $arr_search_words = preg_split( '/[\s,]+/', $args['search'] ); // create array of all searched words // split both spaces and commas and such. - $arr_sql_like_cols = array( 'message', 'logger', 'level' ); + $arr_sql_like_cols = [ 'message', 'logger', 'level' ]; foreach ( $arr_sql_like_cols as $one_col ) { $str_sql_search_words = ''; @@ -452,7 +447,7 @@ public function query( $args ) { $str_sql_search_words = ltrim( $str_sql_search_words, ' AND ' ); $str_search_conditions .= "\n" . sprintf( - ' OR ( %1$s ) ', + ' OR ( %1$s ) ', $str_sql_search_words ); } @@ -465,7 +460,7 @@ public function query( $args ) { $str_like = esc_sql( $wpdb->esc_like( $one_search_word ) ); $str_search_conditions .= "\n" . sprintf( - ' id IN ( SELECT history_id FROM %1$s AS c WHERE c.value LIKE "%2$s" ) AND ', + ' id IN ( SELECT history_id FROM %1$s AS c WHERE c.value LIKE "%2$s" ) AND ', $table_contexts, // 1 '%' . $str_like . '%' // 2 ); @@ -633,7 +628,7 @@ public function query( $args ) { */ $sql = apply_filters( 'simple_history/log_query_sql', $sql ); - /** @var array */ + /** @var array Log rows matching where queries. */ $log_rows = $wpdb->get_results( $sql, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared // Find total number of rows that we would have gotten without pagination @@ -658,7 +653,7 @@ public function query( $args ) { foreach ( $context_results as $context_row ) { if ( ! isset( $log_rows[ $context_row->history_id ]->context ) ) { - $log_rows[ $context_row->history_id ]->context = array(); + $log_rows[ $context_row->history_id ]->context = []; } $log_rows[ $context_row->history_id ]->context[ $context_row->key ] = $context_row->value; @@ -673,6 +668,7 @@ public function query( $args ) { /** @var null|int */ $max_id = null; + // Calculate min and max id. if ( count( $log_rows ) ) { // Max id is simply the id of the first row. $max_id = reset( $log_rows )->id; @@ -728,9 +724,11 @@ public function query( $args ) { 'log_rows_count' => $log_rows_count, 'log_rows' => $log_rows, // Add sql query to debug. - // 'sql' => $sql, // . 'outer_where_array' => $outer_where_array, 'inner_where_array' => $inner_where_array, + 'sql' => $sql, + 'sql_context' => $sql_context ?? null, + 'context_results' => $context_results ?? null, ]; wp_cache_set( $cache_key, $arr_return, $cache_group ); From df451b6cb828ea2142f32d6937af7f18263dbb8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Fri, 15 Dec 2023 20:27:30 +0100 Subject: [PATCH 15/59] Log query: Add query_occasions(), query_overview(), add_contexts_to_log_rows(), get_max_min_ids(), and cleanup --- inc/class-log-query.php | 521 ++++++++++++++++++++++++---------------- 1 file changed, 315 insertions(+), 206 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index d0f5ed56..ec460bbf 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -11,6 +11,7 @@ * - [x] Convert $inner_where and $where to arrays, instead of concating. * - [ ] Should array be array of arrays where each AND clause contains a description? Could be useful for debugging + be a little bit prepared for future support for both "AND" and "OR" clauses. * - [x] Add "prepare args" function, that checks that args are valid, so that we don't have to do that in the query function. + * - [ ] Create functions get_occasions(), get_single_event(), get_overview() that calls query() with correct args. * - Add "get where clause" function that returns the where clause. * - Add "get sql_query" function that returns the sql query. */ @@ -24,7 +25,6 @@ class Log_Query { * @type int $posts_per_page Number of posts to show per page. Default is 10. * @type int $paged Page to show. 1 = first page. Default 1. * @type array $post__in Array. Only get posts that are in array. Default null. - * @type string $format Array or html. Default 'array'. * @type int $max_id_first_page If max_id_first_page is set then only get rows that have id equal or lower than this, to make * sure that the first page of results is not too large. Default null. * @type int $since_id If since_id is set the rows returned will only be rows with an ID greater than (i.e. more recent than) since_id. Default null. @@ -40,16 +40,30 @@ class Log_Query { * @type string $users User IDs, comma separated or array. Default null. * } * @return array + * @throws \InvalidArgumentException If invalid query type. */ public function query( $args ) { - global $wpdb; + $args = wp_parse_args( $args ); - /** @var Simple_History Simple History instance. */ - $simple_history = Simple_History::get_instance(); + // Determine kind of query. + $type = $args['type'] ?? 'overview'; - /** @var string SQL Template to use. Template used depends on $args['type']. */ - $sql_tmpl = null; + if ( $type === 'overview' || $type === 'single' ) { + return $this->query_overview( $args ); + } elseif ( $type === 'occasions' ) { + return $this->query_occasions( $args ); + } else { + throw new \InvalidArgumentException( 'Invalid query type' ); + } + } + /** + * Query overview. + * + * @param string|array|object $args Arguments. + * @return array + */ + protected function query_overview( $args ) { /** @var array Query arguments. */ $args = wp_parse_args( $args, @@ -68,9 +82,6 @@ public function query( $args ) { // Array. Only get posts that are in array. 'post__in' => [], - // array or html. - 'format' => 'array', - // If max_id_first_page is set then only get rows // that have id equal or lower than this, to make. 'max_id_first_page' => null, @@ -132,6 +143,14 @@ public function query( $args ) { ] ); + global $wpdb; + + /** @var Simple_History Simple History instance. */ + $simple_history = Simple_History::get_instance(); + + /** @var string SQL Template to use. Template used depends on $args['type']. */ + $sql_tmpl = null; + // Create cache key based on args and request and current user. $cache_key = 'SimpleHistoryLogQuery_' . md5( serialize( $args ) ) . '_get_' . md5( serialize( $_GET ) ) . '_userid_' . get_current_user_id(); $cache_group = 'simple-history-' . Helpers::get_cache_incrementor(); @@ -149,10 +168,10 @@ public function query( $args ) { $args = $this->prepare_args( $args ); /** @var string Table name for events. */ - $table_history = $simple_history->get_events_table_name(); + $events_table_name = $simple_history->get_events_table_name(); /** @var string Table name for contexts. */ - $table_contexts = $simple_history->get_contexts_table_name(); + $contexts_table_name = $simple_history->get_contexts_table_name(); /** @var array Where clauses for outer query. */ $outer_where = []; @@ -163,130 +182,87 @@ public function query( $args ) { /** @var string Limit clause. */ $limit = ''; - if ( 'overview' === $args['type'] || 'single' === $args['type'] ) { - /* - Subequent occasions query thanks to the answer Stack Overflow thread: - http://stackoverflow.com/questions/13566303/how-to-group-subsequent-rows-based-on-a-criteria-and-then-count-them-mysql/13567320#13567320 - - Similar questions that I didn't manage to understand, work, or did try: - - http://stackoverflow.com/questions/23651176/mysql-query-if-dates-are-subsequent - - http://stackoverflow.com/questions/17651868/mysql-group-by-subsequent - - http://stackoverflow.com/questions/4495242/mysql-number-of-subsequent-occurrences - - http://stackoverflow.com/questions/20446242/postgresql-group-subsequent-rows - - http://stackoverflow.com/questions/17061156/mysql-group-by-range - - http://stackoverflow.com/questions/6602006/complicated-query-with-group-by-and-range-of-prices-in-mysql - */ + /* + Subequent occasions query thanks to the answer Stack Overflow thread: + http://stackoverflow.com/questions/13566303/how-to-group-subsequent-rows-based-on-a-criteria-and-then-count-them-mysql/13567320#13567320 + + Similar questions that I didn't manage to understand, work, or did try: + - http://stackoverflow.com/questions/23651176/mysql-query-if-dates-are-subsequent + - http://stackoverflow.com/questions/17651868/mysql-group-by-subsequent + - http://stackoverflow.com/questions/4495242/mysql-number-of-subsequent-occurrences + - http://stackoverflow.com/questions/20446242/postgresql-group-subsequent-rows + - http://stackoverflow.com/questions/17061156/mysql-group-by-range + - http://stackoverflow.com/questions/6602006/complicated-query-with-group-by-and-range-of-prices-in-mysql + */ - // Set variables used by query. - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - $wpdb->query( 'SET @a:=NULL, @counter:=1, @groupby:=0' ); - - /** - * @var string $sql_tmpl SQL template for overview or single event query. - * - * Template uses number argument to sprintf to insert values. - * Arguments: - * 1 = where clause. - * 2 = limit clause. - * 3 = table name for events. - * 4 = where clause for inner query. - * 5 = table name for contexts. - */ - $sql_tmpl = ' - /*NO_SELECT_FOUND_ROWS*/ + // Set variables used by query. + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + $wpdb->query( 'SET @a:=NULL, @counter:=1, @groupby:=0' ); + + /** + * @var string $sql_tmpl SQL template for overview or single event query. + * + * Template uses number argument to sprintf to insert values. + * Arguments: + * 1 = where clause. + * 2 = limit clause. + * 3 = table name for events. + * 4 = where clause for inner query. + * 5 = table name for contexts. + */ + $sql_tmpl = ' + /*NO_SELECT_FOUND_ROWS*/ + SELECT + SQL_CALC_FOUND_ROWS + h.id, + h.logger, + h.level, + h.date, + h.message, + h.initiator, + h.occasionsID, + count(t.repeated) AS subsequentOccasions, + t.rep, + t.repeated, + t.occasionsIDType, + c1.value AS context_message_key + + FROM %3$s AS h + + LEFT OUTER JOIN %5$s AS c1 ON (c1.history_id = h.id AND c1.key = "_message_key") + + INNER JOIN ( SELECT - SQL_CALC_FOUND_ROWS - h.id, - h.logger, - h.level, - h.date, - h.message, - h.initiator, - h.occasionsID, - count(t.repeated) AS subsequentOccasions, - t.rep, - t.repeated, - t.occasionsIDType, - c1.value AS context_message_key - - FROM %3$s AS h - - LEFT OUTER JOIN %5$s AS c1 ON (c1.history_id = h.id AND c1.key = "_message_key") - - INNER JOIN ( - SELECT - id, - IF(@a=occasionsID,@counter:=@counter+1,@counter:=1) AS rep, - IF(@counter=1,@groupby:=@groupby+1,@groupby) AS repeated, - @a:=occasionsID occasionsIDType - FROM %3$s AS h2 - - # Inner where. - %4$s - - ORDER BY id DESC, date DESC - ) AS t ON t.id = h.id - - # Outer where. - %1$s - - GROUP BY repeated + id, + IF(@a=occasionsID,@counter:=@counter+1,@counter:=1) AS rep, + IF(@counter=1,@groupby:=@groupby+1,@groupby) AS repeated, + @a:=occasionsID occasionsIDType + FROM %3$s AS h2 + + # Inner where. + %4$s + ORDER BY id DESC, date DESC + ) AS t ON t.id = h.id - # Limit clause. - %2$s - '; + # Outer where. + %1$s - // Only include loggers that the current user can view - // @TODO: this causes error if user has no access to any logger at all. - $sql_loggers_user_can_view = $simple_history->get_loggers_that_user_can_read( get_current_user_id(), 'sql' ); - $inner_where[] = "logger IN {$sql_loggers_user_can_view}"; - - // Add limit clause. - $limit_offset = ( $args['paged'] - 1 ) * $args['posts_per_page']; - $limit = sprintf( 'LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page'] ); - } elseif ( 'occasions' === $args['type'] ) { - // Get occasions for a single event. - // Args must contain: - // - occasionsID: The id to get occassions for - // - occasionsCount: The number of occasions to get. - // - occasionsCountMaxReturn: The max number of occasions to return, - // if occassionsCount is very large and we do not want to get all occassions. - - /** - * @var string $sql_tmpl SQL template for occasions query. - * Template uses number argument to sprintf to insert values. - * Arguments: - * 1 = where clause. - * 2 = limit clause. - * 3 = table name for events. - */ - $sql_tmpl = ' - SELECT h.*, - # Fake columns that exist in overview query - 1 as subsequentOccasions - FROM %3$s AS h - # Where - %1$s - ORDER BY id DESC - %2$s - '; + GROUP BY repeated + ORDER BY id DESC, date DESC - // Get rows with id lower than logRowID, i.e. previous rows. - $outer_where[] = 'h.id < ' . (int) $args['logRowID']; + # Limit clause. + %2$s + '; - // Get rows with occasionsID equal to occasionsID. - $outer_where[] = "h.occasionsID = '" . esc_sql( $args['occasionsID'] ) . "'"; + // Only include loggers that the current user can view + // @TODO: this causes error if user has no access to any logger at all. + $sql_loggers_user_can_view = $simple_history->get_loggers_that_user_can_read( get_current_user_id(), 'sql' ); + $inner_where[] = "logger IN {$sql_loggers_user_can_view}"; - if ( isset( $args['occasionsCountMaxReturn'] ) && $args['occasionsCountMaxReturn'] < $args['occasionsCount'] ) { - // Limit to max nn events if occasionsCountMaxReturn is set. - // Used in gui to prevent to many events returned, that can stall the browser. - $limit = 'LIMIT ' . $args['occasionsCountMaxReturn']; - } else { - // Regular limit that gets all occasions. - $limit = 'LIMIT ' . $args['occasionsCount']; - } - } // End if(). + // Add limit clause. + $limit_offset = ( $args['paged'] - 1 ) * $args['posts_per_page']; + $limit = sprintf( 'LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page'] ); // Add post__in where. if ( sizeof( $args['post__in'] ) > 0 ) { @@ -294,7 +270,7 @@ public function query( $args ) { } // If max_id_first_page is then then only include rows - // with id equal to or earlier. + // with id equal to or earlier than this, i.e. older than this. if ( isset( $args['max_id_first_page'] ) ) { $inner_where[] = sprintf( 'id <= %1$d', @@ -302,8 +278,9 @@ public function query( $args ) { ); } + // Add where clause for since_id, + // to include rows with id greater than since_id, i.e. more recent than since_id. if ( isset( $args['since_id'] ) ) { - // Add clause to inner where because that's faster. $inner_where[] = sprintf( 'id > %1$d', (int) $args['since_id'], @@ -352,7 +329,7 @@ public function query( $args ) { ) */ - $args['months'] = array(); + $args['months'] = []; $args['lastdays'] = 0; foreach ( $arr_dates as $one_date ) { @@ -461,7 +438,7 @@ public function query( $args ) { $str_search_conditions .= "\n" . sprintf( ' id IN ( SELECT history_id FROM %1$s AS c WHERE c.value LIKE "%2$s" ) AND ', - $table_contexts, // 1 + $contexts_table_name, // 1 '%' . $str_like . '%' // 2 ); } @@ -542,7 +519,7 @@ public function query( $args ) { if ( isset( $args['user'] ) ) { $inner_where[] = sprintf( 'id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value = %2$s )', - $table_contexts, // 1 + $contexts_table_name, // 1 $args['user'], // 2 ); } @@ -551,7 +528,7 @@ public function query( $args ) { if ( isset( $args['users'] ) ) { $inner_where[] = sprintf( 'id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value IN (%2$s) )', - $table_contexts, // 1 + $contexts_table_name, // 1 implode( ',', $args['users'] ), // 2 ); } @@ -610,13 +587,14 @@ public function query( $args ) { */ $inner_where = apply_filters( 'simple_history/log_query_inner_where', $inner_where ); + /** @var string SQL generated from template. */ $sql = sprintf( $sql_tmpl, // sprintf template. $outer_where, // 1 $limit, // 2 - $table_history, // 3 + $events_table_name, // 3 $inner_where, // 4 - $table_contexts // 5 + $contexts_table_name // 5 ); /** @@ -633,86 +611,28 @@ public function query( $args ) { // Find total number of rows that we would have gotten without pagination // This is the number of rows with occasions taken into consideration. + // TODO: Remove this and run same query with count(*) before. $sql_found_rows = 'SELECT FOUND_ROWS()'; $total_found_rows = (int) $wpdb->get_var( $sql_found_rows ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared // Add context to log rows. - $post_ids = wp_list_pluck( $log_rows, 'id' ); - - if ( empty( $post_ids ) ) { - $context_results = []; - } else { - $sql_context = sprintf( - 'SELECT history_id, `key`, value FROM %2$s WHERE history_id IN (%1$s)', - join( ',', $post_ids ), - $table_contexts - ); - - $context_results = $wpdb->get_results( $sql_context ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - } - - foreach ( $context_results as $context_row ) { - if ( ! isset( $log_rows[ $context_row->history_id ]->context ) ) { - $log_rows[ $context_row->history_id ]->context = []; - } - - $log_rows[ $context_row->history_id ]->context[ $context_row->key ] = $context_row->value; - } + $log_rows = $this->add_contexts_to_log_rows( $log_rows ); // Remove id from keys, because they are cumbersome when working with JSON. $log_rows = array_values( $log_rows ); - /** @var null|int */ - $min_id = null; - - /** @var null|int */ - $max_id = null; - - // Calculate min and max id. - if ( count( $log_rows ) ) { - // Max id is simply the id of the first row. - $max_id = reset( $log_rows )->id; - - // Min id = to find the lowest id we must take occasions into consideration. - $last_row = end( $log_rows ); - - // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase - $last_row_occasions_count = (int) $last_row->subsequentOccasions - 1; - - if ( $last_row_occasions_count === 0 ) { - // Last row did not have any more occasions, so get min_id directly from the row. - $min_id = $last_row->id; - } else { - // Last row did have occasions, so fetch all occasions, and find id of last one. - $db_table = $simple_history->get_events_table_name(); - $sql = sprintf( - ' - SELECT id, date, occasionsID - FROM %1$s - WHERE id <= %2$d - ORDER BY id DESC - LIMIT %3$d - ', - $db_table, - $last_row->id, - $last_row_occasions_count + 1 - ); - - $results = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - - // the last occasion has the id we consider last in this paged result. - $min_id = end( $results )->id; - } - } // End if(). + [$max_id, $min_id] = $this->get_max_min_ids( $log_rows ); // Calc pages. $pages_count = Ceil( $total_found_rows / $args['posts_per_page'] ); - // Create array to return. - // Make all rows a sub key because we want to add some meta info too. + // Calc pagination info. $log_rows_count = count( $log_rows ); $page_rows_from = ( $args['paged'] * $args['posts_per_page'] ) - $args['posts_per_page'] + 1; $page_rows_to = $page_rows_from + $log_rows_count - 1; + + // Create array to return. + // Add log rows to sub key 'log_rows' because meta info is also added. $arr_return = [ 'total_row_count' => $total_found_rows, 'pages_count' => $pages_count, @@ -736,6 +656,93 @@ public function query( $args ) { return $arr_return; } + /** + * Get occasions for a single event. + * + * Required args are: + * - occasionsID: The id to get occassions for. + * - occasionsCount: The number of occasions to get. + * - occasionsCountMaxReturn: The max number of occasions to return. + * + * Does not take filters/where into consideration. + * + * @param string|array|object $args Arguments. + * @return array + */ + protected function query_occasions( $args ) { + $args = wp_parse_args( + $args, + [ + 'type' => 'occasions', + 'logRowID' => null, + 'occasionsID' => null, + 'occasionsCount' => null, + 'occasionsCountMaxReturn' => null, + ] + ); + + $args = $this->prepare_args( $args ); + + // Get occasions for a single event. + // Args must contain: + // - occasionsID: The id to get occassions for + // - occasionsCount: The number of occasions to get. + // - occasionsCountMaxReturn: The max number of occasions to return, + // if occassionsCount is very large and we do not want to get all occassions. + + /** + * @var string $sql_statement_template SQL template for occasions query. + * Template uses number argument to sprintf to insert values. + * Arguments: + * 1 = where clause. + * 2 = limit clause. + * 3 = table name for events. + */ + $sql_statement_template = ' + SELECT h.*, + # Fake columns that exist in overview query + 1 as subsequentOccasions + FROM %3$s AS h + # Where + %1$s + ORDER BY id DESC + %2$s + '; + + /** @var array Where clauses for outer query. */ + $outer_where = []; + + // Get rows with id lower than logRowID, i.e. previous rows. + $outer_where[] = 'h.id < ' . (int) $args['logRowID']; + + // Get rows with occasionsID equal to occasionsID. + $outer_where[] = "h.occasionsID = '" . esc_sql( $args['occasionsID'] ) . "'"; + + if ( isset( $args['occasionsCountMaxReturn'] ) && $args['occasionsCountMaxReturn'] < $args['occasionsCount'] ) { + // Limit to max nn events if occasionsCountMaxReturn is set. + // Used in gui to prevent to many events returned, that can stall the browser. + $limit = 'LIMIT ' . $args['occasionsCountMaxReturn']; + } else { + // Regular limit that gets all occasions. + $limit = 'LIMIT ' . $args['occasionsCount']; + } + + // Create where string. + $outer_where = implode( "\nAND ", $outer_where ); + + // Append where to sql template. + if ( ! empty( $outer_where ) ) { + $outer_where = "\nWHERE {$outer_where}"; + } + + /** @var string SQL generated from template. */ + $sql_query = sprintf( + $sql_statement_template, // sprintf template. + $outer_where, // 1 + $limit, // 2 + ); + } + /** * Prepare arguments, i.e. checking that they are valid, * of the correct type, etc. @@ -764,17 +771,17 @@ protected function prepare_args( $args ) { $args['occasionsCount'] = (int) $args['occasionsCount']; } - // posts_per_page must be set and must be a positive integer. - if ( ! isset( $args['posts_per_page'] ) || ! is_numeric( $args['posts_per_page'] ) && $args['posts_per_page'] < 1 ) { + // If posts_per_page is set then it must be a positive integer. + if ( isset( $args['posts_per_page'] ) && ( ! is_numeric( $args['posts_per_page'] ) || $args['posts_per_page'] < 1 ) ) { throw new \InvalidArgumentException( 'Invalid posts_per_page' ); - } else { + } elseif ( isset( $args['posts_per_page'] ) ) { $args['posts_per_page'] = (int) $args['posts_per_page']; } - // paged must be set and must be a positive integer. - if ( ! isset( $args['paged'] ) || ! is_numeric( $args['paged'] ) || $args['paged'] < 1 ) { + // paged must be must be a positive integer. + if ( isset( $args['paged'] ) && ( ! is_numeric( $args['paged'] ) || $args['paged'] < 1 ) ) { throw new \InvalidArgumentException( 'Invalid paged' ); - } else { + } elseif ( isset( $args['paged'] ) ) { $args['paged'] = (int) $args['paged']; } @@ -916,4 +923,106 @@ protected function prepare_args( $args ) { return $args; } + + /** + * Add context to log rows. + * + * @param array $log_rows Log rows to append context to. + * @return array Log rows with context added. + */ + protected function add_contexts_to_log_rows( $log_rows ) { + // Bail if no log rows. + if ( sizeof( $log_rows ) === 0 ) { + return $log_rows; + } + + global $wpdb; + + $simple_history = Simple_History::get_instance(); + $table_contexts = $simple_history->get_contexts_table_name(); + + $post_ids = wp_list_pluck( $log_rows, 'id' ); + + $sql_context_query = sprintf( + 'SELECT history_id, `key`, value FROM %2$s WHERE history_id IN (%1$s)', + join( ',', $post_ids ), + $table_contexts + ); + + $context_results = $wpdb->get_results( $sql_context_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + + foreach ( $context_results as $context_row ) { + if ( ! isset( $log_rows[ $context_row->history_id ]->context ) ) { + $log_rows[ $context_row->history_id ]->context = []; + } + + $log_rows[ $context_row->history_id ]->context[ $context_row->key ] = $context_row->value; + } + + return $log_rows; + } + + /** + * Get max and min ids for a set of log rows. + * + * @param array $log_rows Log rows. + * @return array Array with max and min id. + */ + protected function get_max_min_ids( $log_rows ) { + /** @var null|int */ + $min_id = null; + + /** @var null|int */ + $max_id = null; + + // Bail of no log rows. + if ( sizeof( $log_rows ) === 0 ) { + return [ + $max_id, + $min_id, + ]; + } + + global $wpdb; + + $events_table_name = Simple_History::get_instance()->get_events_table_name(); + + // Max id is simply the id of the first/most recent row. + $max_id = reset( $log_rows )->id; + + // Min id = to find the lowest id we must take occasions into consideration. + $last_row = end( $log_rows ); + + // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + $last_row_occasions_count = (int) $last_row->subsequentOccasions - 1; + + if ( $last_row_occasions_count === 0 ) { + // Last row did not have any more occasions, so get min_id directly from the row. + $min_id = (int) $last_row->id; + } else { + // Last row did have occasions, so fetch all occasions, and find id of last one. + $sql = sprintf( + ' + SELECT id, date, occasionsID + FROM %1$s + WHERE id <= %2$d + ORDER BY id DESC + LIMIT %3$d + ', + $events_table_name, + $last_row->id, + $last_row_occasions_count + 1 + ); + + $results = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + + // the last occasion has the id we consider last in this paged result. + $min_id = (int) end( $results )->id; + } + + return [ + $max_id, + $min_id, + ]; + } } From be45ae137975ca7e340eeb0fd56f3d244c2aafa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Fri, 15 Dec 2023 21:02:31 +0100 Subject: [PATCH 16/59] IP addresses are now shown on occasions --- inc/class-log-query.php | 49 ++++++++++++++++++++++++++++++++++++----- readme.txt | 4 ++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index ec460bbf..3c9acf7e 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -618,9 +618,6 @@ protected function query_overview( $args ) { // Add context to log rows. $log_rows = $this->add_contexts_to_log_rows( $log_rows ); - // Remove id from keys, because they are cumbersome when working with JSON. - $log_rows = array_values( $log_rows ); - [$max_id, $min_id] = $this->get_max_min_ids( $log_rows ); // Calc pages. @@ -642,7 +639,8 @@ protected function query_overview( $args ) { 'max_id' => (int) $max_id, 'min_id' => (int) $min_id, 'log_rows_count' => $log_rows_count, - 'log_rows' => $log_rows, + // Remove id from keys, because they are cumbersome when working with JSON. + 'log_rows' => array_values( $log_rows ), // Add sql query to debug. 'outer_where_array' => $outer_where_array, 'inner_where_array' => $inner_where_array, @@ -670,6 +668,10 @@ protected function query_overview( $args ) { * @return array */ protected function query_occasions( $args ) { + $simpe_history = Simple_History::get_instance(); + $events_table_name = $simpe_history->get_events_table_name(); + $contexts_table_name = $simpe_history->get_contexts_table_name(); + $args = wp_parse_args( $args, [ @@ -701,14 +703,23 @@ protected function query_occasions( $args ) { $sql_statement_template = ' SELECT h.*, # Fake columns that exist in overview query - 1 as subsequentOccasions + 1 as subsequentOccasions, + c1.value AS context_message_key + FROM %3$s AS h + + # Add context message key + LEFT OUTER JOIN %4$s AS c1 ON (c1.history_id = h.id AND c1.key = "_message_key") + # Where %1$s + ORDER BY id DESC %2$s '; + // HERE: context_message_key is not added to query. + /** @var array Where clauses for outer query. */ $outer_where = []; @@ -740,7 +751,35 @@ protected function query_occasions( $args ) { $sql_statement_template, // sprintf template. $outer_where, // 1 $limit, // 2 + $events_table_name, // 3 + $contexts_table_name // 4 ); + + global $wpdb; + + /** @var array Log rows matching where queries. */ + $log_rows = $wpdb->get_results( $sql_query, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + + $log_rows = $this->add_contexts_to_log_rows( $log_rows ); + + return [ + // 'total_row_count' => $total_found_rows, + // 'pages_count' => $pages_count, + // 'page_current' => $args['paged'], + // 'page_rows_from' => $page_rows_from, + // 'page_rows_to' => $page_rows_to, + // 'max_id' => (int) $max_id, + // 'min_id' => (int) $min_id, + // 'log_rows_count' => sizeof( $log_rows ), + // Remove id from keys, because they are cumbersome when working with JSON. + 'log_rows' => array_values( $log_rows ), + // Add sql query to debug. + // 'outer_where_array' => $outer_where_array, + // 'inner_where_array' => $inner_where_array, + 'sql' => $sql_query, + 'sql_context' => $sql_context ?? null, + 'context_results' => $context_results ?? null, + ]; } /** diff --git a/readme.txt b/readme.txt index 5424af92..dccee98b 100644 --- a/readme.txt +++ b/readme.txt @@ -209,6 +209,10 @@ This can be modified using the filter [`simple_history/db_purge_days_interval`]( ## Changelog +Unreleased + +- IP addresses are now shown on occasions. + ### 4.8.0 (December 2023) đŸ§© This release contains minor fixes, some code cleanup, and adds [support for add-ons](https://simple-history.com/2023/simple-history-4-8-0-introducing-add-ons/)! From 0100bf5595fe1aa927792d119aca164cad4bccd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sat, 16 Dec 2023 09:07:44 +0100 Subject: [PATCH 17/59] Remove unused "log_rows_raw" key from occassions api response --- inc/services/class-api.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/inc/services/class-api.php b/inc/services/class-api.php index eff9c738..0f014774 100644 --- a/inc/services/class-api.php +++ b/inc/services/class-api.php @@ -68,8 +68,6 @@ public function api() { // Output can be array or HTML. if ( isset( $args['format'] ) && 'html' === $args['format'] ) { - $data['log_rows_raw'] = []; - foreach ( $data['log_rows'] as $key => $oneLogRow ) { $args = []; if ( $type == 'single' ) { From 7baef32297b9afd7b0d25125ed6e8eea9b235719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sat, 16 Dec 2023 09:14:42 +0100 Subject: [PATCH 18/59] Log query: Remove $_GET from cache key --- inc/class-log-query.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 3c9acf7e..26672186 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -143,16 +143,8 @@ protected function query_overview( $args ) { ] ); - global $wpdb; - - /** @var Simple_History Simple History instance. */ - $simple_history = Simple_History::get_instance(); - - /** @var string SQL Template to use. Template used depends on $args['type']. */ - $sql_tmpl = null; - - // Create cache key based on args and request and current user. - $cache_key = 'SimpleHistoryLogQuery_' . md5( serialize( $args ) ) . '_get_' . md5( serialize( $_GET ) ) . '_userid_' . get_current_user_id(); + // Create cache key based on args and current user. + $cache_key = 'SimpleHistoryLogQuery_' . md5( serialize( $args ) ) . '_userid_' . get_current_user_id(); $cache_group = 'simple-history-' . Helpers::get_cache_incrementor(); /** @var array Return value. */ @@ -164,6 +156,14 @@ protected function query_overview( $args ) { return $arr_return; } + global $wpdb; + + /** @var Simple_History Simple History instance. */ + $simple_history = Simple_History::get_instance(); + + /** @var string SQL Template to use. Template used depends on $args['type']. */ + $sql_tmpl = null; + // Parse and prepare args. $args = $this->prepare_args( $args ); From 6bc6c35f2db0fe9e4feb2ed43e4de2a51169dc1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sat, 16 Dec 2023 09:14:52 +0100 Subject: [PATCH 19/59] Log query: Occassions cleanup --- inc/class-log-query.php | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 26672186..05b0f62e 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -701,11 +701,17 @@ protected function query_occasions( $args ) { * 3 = table name for events. */ $sql_statement_template = ' - SELECT h.*, - # Fake columns that exist in overview query - 1 as subsequentOccasions, - c1.value AS context_message_key - + SELECT + h.id, + h.logger, + h.level, + h.date, + h.message, + h.initiator, + h.occasionsID, + c1.value AS context_message_key, + # Hard code subsequentOccasions column that exist in overview query + 1 as subsequentOccasions FROM %3$s AS h # Add context message key @@ -718,8 +724,6 @@ protected function query_occasions( $args ) { %2$s '; - // HERE: context_message_key is not added to query. - /** @var array Where clauses for outer query. */ $outer_where = []; @@ -731,7 +735,7 @@ protected function query_occasions( $args ) { if ( isset( $args['occasionsCountMaxReturn'] ) && $args['occasionsCountMaxReturn'] < $args['occasionsCount'] ) { // Limit to max nn events if occasionsCountMaxReturn is set. - // Used in gui to prevent to many events returned, that can stall the browser. + // Used for example in GUI to prevent to many events returned, that can stall the browser. $limit = 'LIMIT ' . $args['occasionsCountMaxReturn']; } else { // Regular limit that gets all occasions. @@ -763,22 +767,9 @@ protected function query_occasions( $args ) { $log_rows = $this->add_contexts_to_log_rows( $log_rows ); return [ - // 'total_row_count' => $total_found_rows, - // 'pages_count' => $pages_count, - // 'page_current' => $args['paged'], - // 'page_rows_from' => $page_rows_from, - // 'page_rows_to' => $page_rows_to, - // 'max_id' => (int) $max_id, - // 'min_id' => (int) $min_id, - // 'log_rows_count' => sizeof( $log_rows ), // Remove id from keys, because they are cumbersome when working with JSON. 'log_rows' => array_values( $log_rows ), - // Add sql query to debug. - // 'outer_where_array' => $outer_where_array, - // 'inner_where_array' => $inner_where_array, 'sql' => $sql_query, - 'sql_context' => $sql_context ?? null, - 'context_results' => $context_results ?? null, ]; } From 5cc562e3786e2e410735cd92d8403859ac3577ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sat, 16 Dec 2023 10:43:03 +0100 Subject: [PATCH 20/59] Log query: Add functions get_inner_where() and get_outer_where() --- inc/class-log-query.php | 823 +++++++++++++++++++++------------------- 1 file changed, 433 insertions(+), 390 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 05b0f62e..74a759f3 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -14,6 +14,7 @@ * - [ ] Create functions get_occasions(), get_single_event(), get_overview() that calls query() with correct args. * - Add "get where clause" function that returns the where clause. * - Add "get sql_query" function that returns the sql query. + * - Occasions should check user permissions, or otherwise it can add any id and the user will have access to it. */ class Log_Query { /** @@ -70,7 +71,6 @@ protected function query_overview( $args ) { [ // overview | occasions | single. // When type is occasions then logRowID, occasionsID, occasionsCount, occasionsCountMaxReturn are required. - // TODO: Add default for above required args. 'type' => 'overview', // Number of posts to show per page. 0 to show all. @@ -173,12 +173,6 @@ protected function query_overview( $args ) { /** @var string Table name for contexts. */ $contexts_table_name = $simple_history->get_contexts_table_name(); - /** @var array Where clauses for outer query. */ - $outer_where = []; - - /** @var array Where clause for inner query. */ - $inner_where = []; - /** @var string Limit clause. */ $limit = ''; @@ -255,419 +249,162 @@ protected function query_overview( $args ) { %2$s '; - // Only include loggers that the current user can view - // @TODO: this causes error if user has no access to any logger at all. - $sql_loggers_user_can_view = $simple_history->get_loggers_that_user_can_read( get_current_user_id(), 'sql' ); - $inner_where[] = "logger IN {$sql_loggers_user_can_view}"; - // Add limit clause. $limit_offset = ( $args['paged'] - 1 ) * $args['posts_per_page']; $limit = sprintf( 'LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page'] ); - // Add post__in where. - if ( sizeof( $args['post__in'] ) > 0 ) { - $inner_where[] = sprintf( 'id IN (%1$s)', implode( ',', $args['post__in'] ) ); - } - - // If max_id_first_page is then then only include rows - // with id equal to or earlier than this, i.e. older than this. - if ( isset( $args['max_id_first_page'] ) ) { - $inner_where[] = sprintf( - 'id <= %1$d', - $args['max_id_first_page'] - ); - } + // Get inner and outer where depending on type of query. + $inner_where = $this->get_inner_where( $args ); + $outer_where = $this->get_outer_where( $args ); - // Add where clause for since_id, - // to include rows with id greater than since_id, i.e. more recent than since_id. - if ( isset( $args['since_id'] ) ) { - $inner_where[] = sprintf( - 'id > %1$d', - (int) $args['since_id'], - ); - } + /** + * Filter the sql template + * + * @since 2.0 + * + * @param string $sql_tmpl + */ + $sql_tmpl = apply_filters( 'simple_history/log_query_sql_template', $sql_tmpl ); - // Append date where clause. - // If date_from is set it is a timestamp. - if ( ! empty( $args['date_from'] ) ) { - $inner_where[] = sprintf( 'date >= "%1$s"', gmdate( 'Y-m-d H:i:s', $args['date_from'] ) ); - } + // Create where string. + $outer_where_array = $outer_where; + $outer_where = implode( "\nAND ", $outer_where ); - // Date to. - // If date_to is set it is a timestamp. - if ( ! empty( $args['date_to'] ) ) { - $inner_where[] = sprintf( 'date <= "%1$s"', gmdate( 'Y-m-d H:i:s', $args['date_to'] ) ); + // Append where to sql template. + if ( ! empty( $outer_where ) ) { + $outer_where = "\nWHERE {$outer_where}"; } - // If "months" they translate to $args["months"] because we already have support for that - // can't use months and dates and the same time. - if ( ! empty( $args['dates'] ) ) { - if ( is_array( $args['dates'] ) ) { - $arr_dates = $args['dates']; - } else { - $arr_dates = explode( ',', $args['dates'] ); - } - - /* - $arr_dates can be a month: - - Array - ( - [0] => month:2021-11 - ) - - $arr_dates can be a number of days: - Array - ( - [0] => lastdays:7 - ) + /** + * Filter the sql template where clause + * + * @since 2.0 + * + * @param string $where + */ + $outer_where = apply_filters( 'simple_history/log_query_sql_where', $outer_where ); - $arr_dates can be allDates - Array - ( - [0] => allDates - ) - */ + /** + * Filter the sql template limit + * + * @since 2.0 + * + * @param string $limit + */ + $limit = apply_filters( 'simple_history/log_query_limit', $limit ); - $args['months'] = []; - $args['lastdays'] = 0; + // Create where string. + $inner_where_array = $inner_where; + $inner_where = implode( "\nAND ", $inner_where ); - foreach ( $arr_dates as $one_date ) { - if ( strpos( $one_date, 'month:' ) === 0 ) { - // If begins with "month:" then strip string and keep only month numbers. - $args['months'][] = substr( $one_date, strlen( 'month:' ) ); - // If begins with "lastdays:" then strip string and keep only number of days. - } elseif ( strpos( $one_date, 'lastdays:' ) === 0 ) { - // Only keep largest lastdays value. - $args['lastdays'] = max( $args['lastdays'], substr( $one_date, strlen( 'lastdays:' ) ) ); - } - } + // Append where to sql template. + if ( ! empty( $inner_where ) ) { + $inner_where = "\nWHERE {$inner_where}"; } - // Add where clause for "lastdays", as int. - if ( ! empty( $args['lastdays'] ) ) { - $inner_where[] = sprintf( - 'date >= DATE(NOW()) - INTERVAL %d DAY', - $args['lastdays'] - ); - } + /** + * Filter the sql template limit + * + * @since 2.0 + * + * @param string $limit + */ + $inner_where = apply_filters( 'simple_history/log_query_inner_where', $inner_where ); - // months, in format "Y-m". - if ( ! empty( $args['months'] ) ) { - if ( is_array( $args['months'] ) ) { - $arr_months = $args['months']; - } else { - $arr_months = explode( ',', $args['months'] ); - } + /** @var string SQL generated from template. */ + $sql = sprintf( + $sql_tmpl, // sprintf template. + $outer_where, // 1 + $limit, // 2 + $events_table_name, // 3 + $inner_where, // 4 + $contexts_table_name // 5 + ); - $sql_months = "\n" . ' - ( - '; + /** + * Filter the final sql query + * + * @since 2.0 + * + * @param string $sql + */ + $sql = apply_filters( 'simple_history/log_query_sql', $sql ); - foreach ( $arr_months as $one_month ) { - // beginning of month - // $ php -r ' echo date("Y-m-d H:i", strtotime("2014-08") ) . "\n"; - // >> 2014-08-01 00:00. - $date_month_beginning = strtotime( $one_month ); + /** @var array Log rows matching where queries. */ + $log_rows = $wpdb->get_results( $sql, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - // end of month - // $ php -r ' echo date("Y-m-d H:i", strtotime("2014-08 + 1 month") ) . "\n";' - // >> 2014-09-01 00:00. - $date_month_end = strtotime( "{$one_month} + 1 month" ); + // Find total number of rows that we would have gotten without pagination + // This is the number of rows with occasions taken into consideration. + // TODO: Remove this and run same query with count(*) before. + $sql_found_rows = 'SELECT FOUND_ROWS()'; + $total_found_rows = (int) $wpdb->get_var( $sql_found_rows ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - $sql_months .= sprintf( - ' - ( - date >= "%1$s" - AND date <= "%2$s" - ) + // Add context to log rows. + $log_rows = $this->add_contexts_to_log_rows( $log_rows ); - OR - ', - gmdate( 'Y-m-d H:i:s', $date_month_beginning ), // 1 - gmdate( 'Y-m-d H:i:s', $date_month_end ) // 2 - ); - } + [$max_id, $min_id] = $this->get_max_min_ids( $log_rows ); - $sql_months = trim( $sql_months ); - $sql_months = rtrim( $sql_months, ' OR ' ); + // Calc pages. + $pages_count = Ceil( $total_found_rows / $args['posts_per_page'] ); - $sql_months .= ' - ) - '; + // Calc pagination info. + $log_rows_count = count( $log_rows ); + $page_rows_from = ( $args['paged'] * $args['posts_per_page'] ) - $args['posts_per_page'] + 1; + $page_rows_to = $page_rows_from + $log_rows_count - 1; - $inner_where[] = $sql_months; - } // End if(). + // Create array to return. + // Add log rows to sub key 'log_rows' because meta info is also added. + $arr_return = [ + 'total_row_count' => $total_found_rows, + 'pages_count' => $pages_count, + 'page_current' => $args['paged'], + 'page_rows_from' => $page_rows_from, + 'page_rows_to' => $page_rows_to, + 'max_id' => (int) $max_id, + 'min_id' => (int) $min_id, + 'log_rows_count' => $log_rows_count, + // Remove id from keys, because they are cumbersome when working with JSON. + 'log_rows' => array_values( $log_rows ), + // Add sql query to debug. + 'outer_where_array' => $outer_where_array, + 'inner_where_array' => $inner_where_array, + 'sql' => $sql, + 'sql_context' => $sql_context ?? null, + 'context_results' => $context_results ?? null, + ]; - // Search. - if ( isset( $args['search'] ) ) { - $str_search_conditions = ''; - $arr_search_words = preg_split( '/[\s,]+/', $args['search'] ); + wp_cache_set( $cache_key, $arr_return, $cache_group ); - // create array of all searched words - // split both spaces and commas and such. - $arr_sql_like_cols = [ 'message', 'logger', 'level' ]; + return $arr_return; + } - foreach ( $arr_sql_like_cols as $one_col ) { - $str_sql_search_words = ''; + /** + * Get occasions for a single event. + * + * Required args are: + * - occasionsID: The id to get occassions for. + * - occasionsCount: The number of occasions to get. + * - occasionsCountMaxReturn: The max number of occasions to return. + * + * Does not take filters/where into consideration. + * + * @param string|array|object $args Arguments. + * @return array + */ + protected function query_occasions( $args ) { + // Create cache key based on args and current user. + $cache_key = 'SimpleHistoryLogQuery_' . md5( serialize( $args ) ) . '_userid_' . get_current_user_id(); + $cache_group = 'simple-history-' . Helpers::get_cache_incrementor(); - foreach ( $arr_search_words as $one_search_word ) { - $str_like = esc_sql( $wpdb->esc_like( $one_search_word ) ); + /** @var array Return value. */ + $arr_return = wp_cache_get( $cache_key, $cache_group ); - $str_sql_search_words .= sprintf( - ' AND %1$s LIKE "%2$s" ', - $one_col, - "%{$str_like}%" - ); - } - - $str_sql_search_words = ltrim( $str_sql_search_words, ' AND ' ); - - $str_search_conditions .= "\n" . sprintf( - ' OR ( %1$s ) ', - $str_sql_search_words - ); - } - - $str_search_conditions = preg_replace( '/^OR /', ' ', trim( $str_search_conditions ) ); - - // Also search contexts. - $str_search_conditions .= "\n OR ( "; - foreach ( $arr_search_words as $one_search_word ) { - $str_like = esc_sql( $wpdb->esc_like( $one_search_word ) ); - - $str_search_conditions .= "\n" . sprintf( - ' id IN ( SELECT history_id FROM %1$s AS c WHERE c.value LIKE "%2$s" ) AND ', - $contexts_table_name, // 1 - '%' . $str_like . '%' // 2 - ); - } - $str_search_conditions = preg_replace( '/ AND $/', '', $str_search_conditions ); - - $str_search_conditions .= "\n ) "; // end OR for contexts. - - $inner_where[] = "\n(\n {$str_search_conditions} \n ) "; - }// End if(). - - // "loglevels", array with loglevels. - // e.g. info, debug, and so on. - if ( ! empty( $args['loglevels'] ) ) { - $sql_loglevels = ''; - - foreach ( $args['loglevels'] as $one_loglevel ) { - $sql_loglevels .= sprintf( ' "%s", ', esc_sql( $one_loglevel ) ); - } - - // Remove last comma. - $sql_loglevels = rtrim( $sql_loglevels, ' ,' ); - - // Add to where in clause. - $inner_where[] = "level IN ({$sql_loglevels})"; - } - - // messages. - if ( ! empty( $args['messages'] ) ) { - // Create sql where based on loggers and messages. - $sql_messages_where = '('; - - foreach ( $args['messages'] as $logger_slug => $logger_messages ) { - $sql_logger_messages_in = ''; - - foreach ( $logger_messages as $one_logger_message ) { - $sql_logger_messages_in .= sprintf( '"%s",', esc_sql( $one_logger_message ) ); - } - - $sql_logger_messages_in = rtrim( $sql_logger_messages_in, ' ,' ); - $sql_logger_messages_in = "\n AND c1.value IN ({$sql_logger_messages_in}) "; - - $sql_messages_where .= sprintf( - ' - ( - h.logger = "%1$s" - %2$s - ) - OR ', - esc_sql( $logger_slug ), - $sql_logger_messages_in - ); - } - - // Remove last 'OR '. - $sql_messages_where = preg_replace( '/OR $/', '', $sql_messages_where ); - - $sql_messages_where .= "\n )"; - $outer_where[] = $sql_messages_where; - } // End if(). - - // loggers, comma separated or array. - // http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&type=overview&format=&posts_per_page=10&paged=1&max_id_first_page=27273&SimpleHistoryLogQuery-showDebug=0&loggers=SimpleCommentsLogger,SimpleCoreUpdatesLogger. - if ( ! empty( $args['loggers'] ) ) { - $sql_loggers = ''; - - foreach ( $args['loggers'] as $one_logger ) { - $sql_loggers .= sprintf( ' "%s", ', esc_sql( $one_logger ) ); - } - - // Remove last comma. - $sql_loggers = rtrim( $sql_loggers, ' ,' ); - - // Add to where in clause. - $inner_where[] = "logger IN ({$sql_loggers}) "; - } - - // Add where for a single user ID. - if ( isset( $args['user'] ) ) { - $inner_where[] = sprintf( - 'id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value = %2$s )', - $contexts_table_name, // 1 - $args['user'], // 2 - ); - } - - // Users, array with user ids. - if ( isset( $args['users'] ) ) { - $inner_where[] = sprintf( - 'id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value IN (%2$s) )', - $contexts_table_name, // 1 - implode( ',', $args['users'] ), // 2 - ); - } - - /** - * Filter the sql template - * - * @since 2.0 - * - * @param string $sql_tmpl - */ - $sql_tmpl = apply_filters( 'simple_history/log_query_sql_template', $sql_tmpl ); - - // Create where string. - $outer_where_array = $outer_where; - $outer_where = implode( "\nAND ", $outer_where ); - - // Append where to sql template. - if ( ! empty( $outer_where ) ) { - $outer_where = "\nWHERE {$outer_where}"; - } - - /** - * Filter the sql template where clause - * - * @since 2.0 - * - * @param string $where - */ - $outer_where = apply_filters( 'simple_history/log_query_sql_where', $outer_where ); - - /** - * Filter the sql template limit - * - * @since 2.0 - * - * @param string $limit - */ - $limit = apply_filters( 'simple_history/log_query_limit', $limit ); - - // Create where string. - $inner_where_array = $inner_where; - $inner_where = implode( "\nAND ", $inner_where ); - - // Append where to sql template. - if ( ! empty( $inner_where ) ) { - $inner_where = "\nWHERE {$inner_where}"; + // Return cached value if it exists. + if ( false !== $arr_return ) { + $arr_return['cached_result'] = true; + return $arr_return; } - /** - * Filter the sql template limit - * - * @since 2.0 - * - * @param string $limit - */ - $inner_where = apply_filters( 'simple_history/log_query_inner_where', $inner_where ); - - /** @var string SQL generated from template. */ - $sql = sprintf( - $sql_tmpl, // sprintf template. - $outer_where, // 1 - $limit, // 2 - $events_table_name, // 3 - $inner_where, // 4 - $contexts_table_name // 5 - ); - - /** - * Filter the final sql query - * - * @since 2.0 - * - * @param string $sql - */ - $sql = apply_filters( 'simple_history/log_query_sql', $sql ); - - /** @var array Log rows matching where queries. */ - $log_rows = $wpdb->get_results( $sql, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - - // Find total number of rows that we would have gotten without pagination - // This is the number of rows with occasions taken into consideration. - // TODO: Remove this and run same query with count(*) before. - $sql_found_rows = 'SELECT FOUND_ROWS()'; - $total_found_rows = (int) $wpdb->get_var( $sql_found_rows ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - - // Add context to log rows. - $log_rows = $this->add_contexts_to_log_rows( $log_rows ); - - [$max_id, $min_id] = $this->get_max_min_ids( $log_rows ); - - // Calc pages. - $pages_count = Ceil( $total_found_rows / $args['posts_per_page'] ); - - // Calc pagination info. - $log_rows_count = count( $log_rows ); - $page_rows_from = ( $args['paged'] * $args['posts_per_page'] ) - $args['posts_per_page'] + 1; - $page_rows_to = $page_rows_from + $log_rows_count - 1; - - // Create array to return. - // Add log rows to sub key 'log_rows' because meta info is also added. - $arr_return = [ - 'total_row_count' => $total_found_rows, - 'pages_count' => $pages_count, - 'page_current' => $args['paged'], - 'page_rows_from' => $page_rows_from, - 'page_rows_to' => $page_rows_to, - 'max_id' => (int) $max_id, - 'min_id' => (int) $min_id, - 'log_rows_count' => $log_rows_count, - // Remove id from keys, because they are cumbersome when working with JSON. - 'log_rows' => array_values( $log_rows ), - // Add sql query to debug. - 'outer_where_array' => $outer_where_array, - 'inner_where_array' => $inner_where_array, - 'sql' => $sql, - 'sql_context' => $sql_context ?? null, - 'context_results' => $context_results ?? null, - ]; - - wp_cache_set( $cache_key, $arr_return, $cache_group ); - - return $arr_return; - } - - /** - * Get occasions for a single event. - * - * Required args are: - * - occasionsID: The id to get occassions for. - * - occasionsCount: The number of occasions to get. - * - occasionsCountMaxReturn: The max number of occasions to return. - * - * Does not take filters/where into consideration. - * - * @param string|array|object $args Arguments. - * @return array - */ - protected function query_occasions( $args ) { $simpe_history = Simple_History::get_instance(); $events_table_name = $simpe_history->get_events_table_name(); $contexts_table_name = $simpe_history->get_contexts_table_name(); @@ -1055,4 +792,310 @@ protected function get_max_min_ids( $log_rows ) { $min_id, ]; } + + /** + * Get inner where clause. + * + * @param array $args Arguments. + * @return array Where clauses. + */ + protected function get_inner_where( $args ) { + global $wpdb; + + $simple_history = Simple_History::get_instance(); + + $contexts_table_name = $simple_history->get_contexts_table_name(); + + /** @var array Where clause for inner query. */ + $inner_where = []; + + // Only include loggers that the current user can view + // @TODO: this causes error if user has no access to any logger at all. + $sql_loggers_user_can_view = $simple_history->get_loggers_that_user_can_read( get_current_user_id(), 'sql' ); + $inner_where[] = "logger IN {$sql_loggers_user_can_view}"; + + // Add post__in where. + if ( sizeof( $args['post__in'] ) > 0 ) { + $inner_where[] = sprintf( 'id IN (%1$s)', implode( ',', $args['post__in'] ) ); + } + + // If max_id_first_page is then then only include rows + // with id equal to or earlier than this, i.e. older than this. + if ( isset( $args['max_id_first_page'] ) ) { + $inner_where[] = sprintf( + 'id <= %1$d', + $args['max_id_first_page'] + ); + } + + // Add where clause for since_id, + // to include rows with id greater than since_id, i.e. more recent than since_id. + if ( isset( $args['since_id'] ) ) { + $inner_where[] = sprintf( + 'id > %1$d', + (int) $args['since_id'], + ); + } + + // Append date where clause. + // If date_from is set it is a timestamp. + if ( ! empty( $args['date_from'] ) ) { + $inner_where[] = sprintf( 'date >= "%1$s"', gmdate( 'Y-m-d H:i:s', $args['date_from'] ) ); + } + + // Date to. + // If date_to is set it is a timestamp. + if ( ! empty( $args['date_to'] ) ) { + $inner_where[] = sprintf( 'date <= "%1$s"', gmdate( 'Y-m-d H:i:s', $args['date_to'] ) ); + } + + // If "months" they translate to $args["months"] because we already have support for that + // can't use months and dates and the same time. + if ( ! empty( $args['dates'] ) ) { + if ( is_array( $args['dates'] ) ) { + $arr_dates = $args['dates']; + } else { + $arr_dates = explode( ',', $args['dates'] ); + } + + /* + $arr_dates can be a month: + + Array + ( + [0] => month:2021-11 + ) + + $arr_dates can be a number of days: + Array + ( + [0] => lastdays:7 + ) + + $arr_dates can be allDates + Array + ( + [0] => allDates + ) + */ + + $args['months'] = []; + $args['lastdays'] = 0; + + foreach ( $arr_dates as $one_date ) { + if ( strpos( $one_date, 'month:' ) === 0 ) { + // If begins with "month:" then strip string and keep only month numbers. + $args['months'][] = substr( $one_date, strlen( 'month:' ) ); + // If begins with "lastdays:" then strip string and keep only number of days. + } elseif ( strpos( $one_date, 'lastdays:' ) === 0 ) { + // Only keep largest lastdays value. + $args['lastdays'] = max( $args['lastdays'], substr( $one_date, strlen( 'lastdays:' ) ) ); + } + } + } + + // Add where clause for "lastdays", as int. + if ( ! empty( $args['lastdays'] ) ) { + $inner_where[] = sprintf( + 'date >= DATE(NOW()) - INTERVAL %d DAY', + $args['lastdays'] + ); + } + + // months, in format "Y-m". + if ( ! empty( $args['months'] ) ) { + if ( is_array( $args['months'] ) ) { + $arr_months = $args['months']; + } else { + $arr_months = explode( ',', $args['months'] ); + } + + $sql_months = "\n" . ' + ( + '; + + foreach ( $arr_months as $one_month ) { + // beginning of month + // $ php -r ' echo date("Y-m-d H:i", strtotime("2014-08") ) . "\n"; + // >> 2014-08-01 00:00. + $date_month_beginning = strtotime( $one_month ); + + // end of month + // $ php -r ' echo date("Y-m-d H:i", strtotime("2014-08 + 1 month") ) . "\n";' + // >> 2014-09-01 00:00. + $date_month_end = strtotime( "{$one_month} + 1 month" ); + + $sql_months .= sprintf( + ' + ( + date >= "%1$s" + AND date <= "%2$s" + ) + + OR + ', + gmdate( 'Y-m-d H:i:s', $date_month_beginning ), // 1 + gmdate( 'Y-m-d H:i:s', $date_month_end ) // 2 + ); + } + + $sql_months = trim( $sql_months ); + $sql_months = rtrim( $sql_months, ' OR ' ); + + $sql_months .= ' + ) + '; + + $inner_where[] = $sql_months; + } // End if(). + + // Search. + if ( isset( $args['search'] ) ) { + $str_search_conditions = ''; + $arr_search_words = preg_split( '/[\s,]+/', $args['search'] ); + + // create array of all searched words + // split both spaces and commas and such. + $arr_sql_like_cols = [ 'message', 'logger', 'level' ]; + + foreach ( $arr_sql_like_cols as $one_col ) { + $str_sql_search_words = ''; + + foreach ( $arr_search_words as $one_search_word ) { + $str_like = esc_sql( $wpdb->esc_like( $one_search_word ) ); + + $str_sql_search_words .= sprintf( + ' AND %1$s LIKE "%2$s" ', + $one_col, + "%{$str_like}%" + ); + } + + $str_sql_search_words = ltrim( $str_sql_search_words, ' AND ' ); + + $str_search_conditions .= "\n" . sprintf( + ' OR ( %1$s ) ', + $str_sql_search_words + ); + } + + $str_search_conditions = preg_replace( '/^OR /', ' ', trim( $str_search_conditions ) ); + + // Also search contexts. + $str_search_conditions .= "\n OR ( "; + foreach ( $arr_search_words as $one_search_word ) { + $str_like = esc_sql( $wpdb->esc_like( $one_search_word ) ); + + $str_search_conditions .= "\n" . sprintf( + ' id IN ( SELECT history_id FROM %1$s AS c WHERE c.value LIKE "%2$s" ) AND ', + $contexts_table_name, // 1 + '%' . $str_like . '%' // 2 + ); + } + $str_search_conditions = preg_replace( '/ AND $/', '', $str_search_conditions ); + + $str_search_conditions .= "\n ) "; // end OR for contexts. + + $inner_where[] = "\n(\n {$str_search_conditions} \n ) "; + }// End if(). + + // "loglevels", array with loglevels. + // e.g. info, debug, and so on. + if ( ! empty( $args['loglevels'] ) ) { + $sql_loglevels = ''; + + foreach ( $args['loglevels'] as $one_loglevel ) { + $sql_loglevels .= sprintf( ' "%s", ', esc_sql( $one_loglevel ) ); + } + + // Remove last comma. + $sql_loglevels = rtrim( $sql_loglevels, ' ,' ); + + // Add to where in clause. + $inner_where[] = "level IN ({$sql_loglevels})"; + } + + // loggers, comma separated or array. + // http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&type=overview&format=&posts_per_page=10&paged=1&max_id_first_page=27273&SimpleHistoryLogQuery-showDebug=0&loggers=SimpleCommentsLogger,SimpleCoreUpdatesLogger. + if ( ! empty( $args['loggers'] ) ) { + $sql_loggers = ''; + + foreach ( $args['loggers'] as $one_logger ) { + $sql_loggers .= sprintf( ' "%s", ', esc_sql( $one_logger ) ); + } + + // Remove last comma. + $sql_loggers = rtrim( $sql_loggers, ' ,' ); + + // Add to where in clause. + $inner_where[] = "logger IN ({$sql_loggers}) "; + } + + // Add where for a single user ID. + if ( isset( $args['user'] ) ) { + $inner_where[] = sprintf( + 'id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value = %2$s )', + $contexts_table_name, // 1 + $args['user'], // 2 + ); + } + + // Users, array with user ids. + if ( isset( $args['users'] ) ) { + $inner_where[] = sprintf( + 'id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value IN (%2$s) )', + $contexts_table_name, // 1 + implode( ',', $args['users'] ), // 2 + ); + } + + return $inner_where; + } + + /** + * Get outer where clause. + * + * @param array $args Arguments. + * @return array Where clauses. + */ + protected function get_outer_where( $args ) { + /** @var array Where clauses for outer query. */ + $outer_where = []; + + // messages. + if ( ! empty( $args['messages'] ) ) { + // Create sql where based on loggers and messages. + $sql_messages_where = '('; + + foreach ( $args['messages'] as $logger_slug => $logger_messages ) { + $sql_logger_messages_in = ''; + + foreach ( $logger_messages as $one_logger_message ) { + $sql_logger_messages_in .= sprintf( '"%s",', esc_sql( $one_logger_message ) ); + } + + $sql_logger_messages_in = rtrim( $sql_logger_messages_in, ' ,' ); + $sql_logger_messages_in = "\n AND c1.value IN ({$sql_logger_messages_in}) "; + + $sql_messages_where .= sprintf( + ' + ( + h.logger = "%1$s" + %2$s + ) + OR ', + esc_sql( $logger_slug ), + $sql_logger_messages_in + ); + } + + // Remove last 'OR '. + $sql_messages_where = preg_replace( '/OR $/', '', $sql_messages_where ); + + $sql_messages_where .= "\n )"; + $outer_where[] = $sql_messages_where; + } // End if(). + + return $outer_where; + } } From 0224b5275cc5132fe44c4ff7cac2ae84b0b7fb83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sat, 16 Dec 2023 10:49:44 +0100 Subject: [PATCH 21/59] Remove "rep" column from Log_Query response. --- inc/class-log-query.php | 1 - inc/class-simple-history.php | 1 - readme.txt | 2 ++ tests/wpunit/LogQueryTest.php | 1 - 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 74a759f3..3f514d89 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -216,7 +216,6 @@ protected function query_overview( $args ) { h.initiator, h.occasionsID, count(t.repeated) AS subsequentOccasions, - t.rep, t.repeated, t.occasionsIDType, c1.value AS context_message_key diff --git a/inc/class-simple-history.php b/inc/class-simple-history.php index 6192a4a3..9ee0daa6 100644 --- a/inc/class-simple-history.php +++ b/inc/class-simple-history.php @@ -934,7 +934,6 @@ public function get_log_row_html_output( $one_log_row, $args ) { unset( $logRowKeysToShow['occasionsID'], $logRowKeysToShow['subsequentOccasions'], - $logRowKeysToShow['rep'], $logRowKeysToShow['repeated'], $logRowKeysToShow['occasionsIDType'], $logRowKeysToShow['context'], diff --git a/readme.txt b/readme.txt index dccee98b..a76af35c 100644 --- a/readme.txt +++ b/readme.txt @@ -212,6 +212,8 @@ This can be modified using the filter [`simple_history/db_purge_days_interval`]( Unreleased - IP addresses are now shown on occasions. +- Remove "rep" column from Log_Query response. +- ### 4.8.0 (December 2023) diff --git a/tests/wpunit/LogQueryTest.php b/tests/wpunit/LogQueryTest.php index bae79cb3..eb17643f 100644 --- a/tests/wpunit/LogQueryTest.php +++ b/tests/wpunit/LogQueryTest.php @@ -278,7 +278,6 @@ function test_log_query() { $this->assertTrue( property_exists( $first_log_row, 'initiator' ) ); $this->assertTrue( property_exists( $first_log_row, 'occasionsID' ) ); $this->assertTrue( property_exists( $first_log_row, 'subsequentOccasions' ) ); - $this->assertTrue( property_exists( $first_log_row, 'rep' ) ); $this->assertTrue( property_exists( $first_log_row, 'repeated' ) ); $this->assertTrue( property_exists( $first_log_row, 'occasionsIDType' ) ); $this->assertTrue( property_exists( $first_log_row, 'context' ) ); From 8f4a4cb74e7605112cfb15654e3fb42eff2d30e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sat, 16 Dec 2023 10:51:24 +0100 Subject: [PATCH 22/59] Remove column "occasionsIDType" from Log Query --- inc/class-log-query.php | 1 - inc/class-simple-history.php | 1 - tests/wpunit/LogQueryTest.php | 1 - tests/wpunit/OldLoggerTest.php | 2 +- 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 3f514d89..2e8cf3e7 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -217,7 +217,6 @@ protected function query_overview( $args ) { h.occasionsID, count(t.repeated) AS subsequentOccasions, t.repeated, - t.occasionsIDType, c1.value AS context_message_key FROM %3$s AS h diff --git a/inc/class-simple-history.php b/inc/class-simple-history.php index 9ee0daa6..41724c44 100644 --- a/inc/class-simple-history.php +++ b/inc/class-simple-history.php @@ -935,7 +935,6 @@ public function get_log_row_html_output( $one_log_row, $args ) { $logRowKeysToShow['occasionsID'], $logRowKeysToShow['subsequentOccasions'], $logRowKeysToShow['repeated'], - $logRowKeysToShow['occasionsIDType'], $logRowKeysToShow['context'], $logRowKeysToShow['type'] ); diff --git a/tests/wpunit/LogQueryTest.php b/tests/wpunit/LogQueryTest.php index eb17643f..7557deba 100644 --- a/tests/wpunit/LogQueryTest.php +++ b/tests/wpunit/LogQueryTest.php @@ -279,7 +279,6 @@ function test_log_query() { $this->assertTrue( property_exists( $first_log_row, 'occasionsID' ) ); $this->assertTrue( property_exists( $first_log_row, 'subsequentOccasions' ) ); $this->assertTrue( property_exists( $first_log_row, 'repeated' ) ); - $this->assertTrue( property_exists( $first_log_row, 'occasionsIDType' ) ); $this->assertTrue( property_exists( $first_log_row, 'context' ) ); } } diff --git a/tests/wpunit/OldLoggerTest.php b/tests/wpunit/OldLoggerTest.php index 267b472c..b131c216 100644 --- a/tests/wpunit/OldLoggerTest.php +++ b/tests/wpunit/OldLoggerTest.php @@ -65,7 +65,7 @@ public function test_old_log_query_class() { $expected_object->initiator = 'wp_user'; $actual = $query_results['log_rows'][0]; - unset($actual->id, $actual->date, $actual->occasionsID, $actual->subsequentOccasions, $actual->rep, $actual->repeated, $actual->occasionsIDType, $actual->context); + unset($actual->id, $actual->date, $actual->occasionsID, $actual->subsequentOccasions, $actual->rep, $actual->repeated, $actual->context); $this->assertEquals($expected_object, $actual); } From 43db5dc2753d31a6e1c9d5f83c23ebb56ef8ce26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sat, 16 Dec 2023 11:18:52 +0100 Subject: [PATCH 23/59] Log query: Misc cleanup --- inc/class-log-query.php | 10 +--------- readme.txt | 3 +-- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 2e8cf3e7..3443a9e6 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -8,13 +8,8 @@ * Queries the Simple History Log. * * Todo - * - [x] Convert $inner_where and $where to arrays, instead of concating. - * - [ ] Should array be array of arrays where each AND clause contains a description? Could be useful for debugging + be a little bit prepared for future support for both "AND" and "OR" clauses. - * - [x] Add "prepare args" function, that checks that args are valid, so that we don't have to do that in the query function. - * - [ ] Create functions get_occasions(), get_single_event(), get_overview() that calls query() with correct args. - * - Add "get where clause" function that returns the where clause. - * - Add "get sql_query" function that returns the sql query. * - Occasions should check user permissions, or otherwise it can add any id and the user will have access to it. + * - Fix for full group by. */ class Log_Query { /** @@ -161,9 +156,6 @@ protected function query_overview( $args ) { /** @var Simple_History Simple History instance. */ $simple_history = Simple_History::get_instance(); - /** @var string SQL Template to use. Template used depends on $args['type']. */ - $sql_tmpl = null; - // Parse and prepare args. $args = $this->prepare_args( $args ); diff --git a/readme.txt b/readme.txt index a76af35c..eff2936e 100644 --- a/readme.txt +++ b/readme.txt @@ -212,8 +212,7 @@ This can be modified using the filter [`simple_history/db_purge_days_interval`]( Unreleased - IP addresses are now shown on occasions. -- Remove "rep" column from Log_Query response. -- +- Remove columns "rep" and "occasionsIDType" from Log_Query response. ### 4.8.0 (December 2023) From cb9f85c46d4f764a681a46f7c0b1d186b37ead3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Mon, 18 Dec 2023 10:05:27 +0100 Subject: [PATCH 24/59] Log query: New full group by query seems to work (lot's of debug!) --- inc/class-log-query.php | 215 ++++++++++++++++++++++++++++++++++++- inc/services/class-api.php | 5 + 2 files changed, 218 insertions(+), 2 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 3443a9e6..9a50cf77 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -9,7 +9,11 @@ * * Todo * - Occasions should check user permissions, or otherwise it can add any id and the user will have access to it. - * - Fix for full group by. + * - Test if fix for full group is working. + * - Finish query_overview_full_group_by() to return same data as query_overview(), and then compare and verify that it returns same data. + * - Also print SQL query and do some EXPLAIN on it in a regular editor. If this works it would be nice to blog about the findings, + * and print benchmarks etc. + * */ class Log_Query { /** @@ -53,6 +57,211 @@ public function query( $args ) { } } + /** + * Query history using a query that uses full group by. + * + * @param string|array|object $args Arguments. + * @return array Log rows. + */ + public function query_overview_full_group_by( $args ) { + global $wpdb; + + // Parse and prepare args. + $args = $this->prepare_args( $args ); + + $Simple_History = Simple_History::get_instance(); + + $wpdb->query( 'SET @a:=NULL, @counter:=1, @groupby:=0' ); + + /** + * @var string SQL statement that will be used for inner join. + * + * Template uses number argument to sprintf to insert values. + * Arguments: + * 1 = table name for events. + * 2 = table name for contexts. + * 2 = where clause. + * + * TODO: Add where for messages. Check that both logger and key are correct. + */ + $inner_sql_statement_template = ' + + + ## START INNER_SQL_QUERY_STATEMENT + SELECT + id, + #message, + IF(@a=occasionsID,@counter:=@counter+1,@counter:=1) AS repeatCount, + IF(@counter=1,@groupby:=@groupby+1,@groupby) AS repeated, + @a:=occasionsId, + contexts.value as context_message_key + FROM %1$s AS h2 + + # Join column with message key so its searchable/filterable. + LEFT OUTER JOIN %2$s AS contexts ON (contexts.history_id = h2.id AND contexts.key = "_message_key") + + # Where statement. + %3$s + + ORDER BY id DESC + ## END INNER_SQL_QUERY_STATEMENT + + + '; + + $inner_where = $this->get_inner_where( $args ); + $inner_where_string = empty( $inner_where ) ? '' : "\nWHERE " . implode( "\nAND ", $inner_where ); + + $inner_sql_query_statement = sprintf( + $inner_sql_statement_template, + $Simple_History->get_events_table_name(), // 1 + $Simple_History->get_contexts_table_name(), // 2 + $inner_where_string // 3 + ); + + // $inner_query_results = $wpdb->get_results( $inner_sql_query, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + // sh_d( '$inner_sql_query', $inner_sql_query ); + // sh_d( '$inner_query_results', $inner_query_results ); + // if ( ! empty( $wpdb->last_error ) ) { + // sh_d( '$wpdb->last_error', $wpdb->last_error ); + // exit; + // } + // exit; + + /** + * @var string SQL statement template used to get IDs of all events. + * + * Template uses number argument to sprintf to insert values. + * Arguments: + * 1 = table name for events. + * 2 = table name for contexts. + * 3 = Inner join SQL query. + * 4 = where clause for outer query. + * 5 = limit clause. + */ + $sql_statement_max_ids_and_count_template = ' + + + ## START SQL_STATEMENT_MAX_IDS_AND_COUNT_TEMPLATE + SELECT + max(h.id) as maxId, + max(historyWithRepeated.repeatCount) as repeatCount + FROM %1$s AS h + + INNER JOIN ( + %3$s + ) as historyWithRepeated ON historyWithRepeated.id = h.id + + # Outer where + %4$s + + GROUP BY historyWithRepeated.repeated + ORDER by maxId DESC + + # Limit + %5$s + + + ## END SQL_STATEMENT_MAX_IDS_AND_COUNT_TEMPLATE + '; + + $inner_where = $this->get_inner_where( $args ); + if ( ! empty( $inner_where ) ) { + $inner_where_string = "\nWHERE\n" . implode( "\nAND ", $inner_where ); + } + + $outer_where = $this->get_outer_where( $args ); + if ( ! empty( $outer_where ) ) { + $outer_where_string = "\nWHERE " . implode( "\nAND ", $outer_where ); + } + + $max_ids_and_count_sql_statement = sprintf( + $sql_statement_max_ids_and_count_template, + $Simple_History->get_events_table_name(), // 1 + $Simple_History->get_contexts_table_name(), // 2 + $inner_sql_query_statement, // 3 + $outer_where_string, // 4 + '' // 5 + ); + + /** + * @var string SQL template used to get all events from the ones + * found in statement sql_statement_max_ids_and_count_template. + * This final statement gets all columns we finally need. + */ + $sql_statement_log_rows = ' + + + ## START SQL_STATEMENT_LOG_ROWS + SELECT + simple_history_1.id, + simple_history_1.logger, + simple_history_1.level, + simple_history_1.date, + simple_history_1.message, + simple_history_1.initiator, + simple_history_1.occasionsID, + repeatCount + + FROM %1$s AS simple_history_1 + + INNER JOIN ( + %2$s + ) AS max_ids_and_count ON simple_history_1.id = max_ids_and_count.maxId + + ORDER BY simple_history_1.id DESC + ## END SQL_STATEMENT_LOG_ROWS + + + '; + + $sql_query_log_rows = sprintf( + $sql_statement_log_rows, + $Simple_History->get_events_table_name(), // 1 + $max_ids_and_count_sql_statement // 2 + ); + + $result_log_rows = $wpdb->get_results( $sql_query_log_rows, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + + sh_d('DB_HOST', DB_HOST, 'DB_NAME', DB_NAME); + sh_d( '$sql_query_log_rows', $sql_query_log_rows ); + if ( ! empty( $wpdb->last_error ) ) { + sh_d( '$wpdb->last_error', $wpdb->last_error ); + exit; + } + + // Get all ids from $result_ids_and_count. + // $max_ids = wp_list_pluck( $result_max_ids_and_count, 'maxId' ); + + // Values must be id's. + // $max_ids = array_map( 'intval', $max_ids ); + + // $sql_statement_max_ids_and_count_template = ' + // SELECT + // * + // FROM wp_simple_history + // WHERE id IN (%s) + // ORDER BY id DESC + // '; + + // $sql_query = sprintf( + // $sql_statement_max_ids_and_count_template, + // implode( ',', $max_ids ) + // ); + + // $result_log_rows = $wpdb->get_results( $sql_query, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + + // // sh_d( '$max_ids', $max_ids ); + // sh_d( '$sql_query', $sql_query ); + + $result_log_rows = $this->add_contexts_to_log_rows( $result_log_rows ); + + // sh_d( '$result_log_rows', $result_log_rows ); + // exit; + + return $result_log_rows; + } + /** * Query overview. * @@ -683,6 +892,8 @@ protected function prepare_args( $args ) { /** * Add context to log rows. + * Gets all ids from log rows and then fetches context for those ids + * in a single query. * * @param array $log_rows Log rows to append context to. * @return array Log rows with context added. @@ -805,7 +1016,7 @@ protected function get_inner_where( $args ) { $inner_where[] = "logger IN {$sql_loggers_user_can_view}"; // Add post__in where. - if ( sizeof( $args['post__in'] ) > 0 ) { + if ( isset( $args['post__in'] ) && sizeof( $args['post__in'] ) > 0 ) { $inner_where[] = sprintf( 'id IN (%1$s)', implode( ',', $args['post__in'] ) ); } diff --git a/inc/services/class-api.php b/inc/services/class-api.php index 0f014774..950d94f6 100644 --- a/inc/services/class-api.php +++ b/inc/services/class-api.php @@ -63,9 +63,14 @@ public function api() { $logQuery = new Log_Query(); $data = $logQuery->query( $args ); + $data_full_group_by = $logQuery->query_overview_full_group_by( $args ); + + sh_d('$data', $data, '$data_full_group_by', $data_full_group_by);exit; $data['api_args'] = $args; + $data['log_rows_full_group_by'] = array_values( $data_full_group_by ); + // Output can be array or HTML. if ( isset( $args['format'] ) && 'html' === $args['format'] ) { foreach ( $data['log_rows'] as $key => $oneLogRow ) { From 596eefbcf58cbe7b4fea1b2bfe9ce70d70ea5bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Mon, 18 Dec 2023 10:12:03 +0100 Subject: [PATCH 25/59] Remove debug --- inc/class-log-query.php | 3 +-- inc/services/class-api.php | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 9a50cf77..45482fa0 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -223,10 +223,9 @@ public function query_overview_full_group_by( $args ) { $result_log_rows = $wpdb->get_results( $sql_query_log_rows, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - sh_d('DB_HOST', DB_HOST, 'DB_NAME', DB_NAME); - sh_d( '$sql_query_log_rows', $sql_query_log_rows ); if ( ! empty( $wpdb->last_error ) ) { sh_d( '$wpdb->last_error', $wpdb->last_error ); + sh_d( '$sql_query_log_rows', $sql_query_log_rows ); exit; } diff --git a/inc/services/class-api.php b/inc/services/class-api.php index 950d94f6..b9db4939 100644 --- a/inc/services/class-api.php +++ b/inc/services/class-api.php @@ -65,7 +65,7 @@ public function api() { $data = $logQuery->query( $args ); $data_full_group_by = $logQuery->query_overview_full_group_by( $args ); - sh_d('$data', $data, '$data_full_group_by', $data_full_group_by);exit; + // sh_d('$data', $data, '$data_full_group_by', $data_full_group_by);exit; $data['api_args'] = $args; From c281c51d6298109c4137403e1c4bcee40b38c83f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Mon, 18 Dec 2023 21:04:28 +0100 Subject: [PATCH 26/59] Log query fixes --- inc/class-log-query.php | 117 +++++++++++++++++++++++++------------ inc/services/class-api.php | 20 +++++-- 2 files changed, 95 insertions(+), 42 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 45482fa0..1a458875 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -13,7 +13,7 @@ * - Finish query_overview_full_group_by() to return same data as query_overview(), and then compare and verify that it returns same data. * - Also print SQL query and do some EXPLAIN on it in a regular editor. If this works it would be nice to blog about the findings, * and print benchmarks etc. - * + * - Get num rows using second query with count(*) */ class Log_Query { /** @@ -109,8 +109,8 @@ public function query_overview_full_group_by( $args ) { '; - $inner_where = $this->get_inner_where( $args ); - $inner_where_string = empty( $inner_where ) ? '' : "\nWHERE " . implode( "\nAND ", $inner_where ); + $inner_where_array = $this->get_inner_where( $args ); + $inner_where_string = empty( $inner_where_array ) ? '' : "\nWHERE " . implode( "\nAND ", $inner_where_array ); $inner_sql_query_statement = sprintf( $inner_sql_statement_template, @@ -145,6 +145,7 @@ public function query_overview_full_group_by( $args ) { ## START SQL_STATEMENT_MAX_IDS_AND_COUNT_TEMPLATE SELECT max(h.id) as maxId, + min(h.id) as minId, max(historyWithRepeated.repeatCount) as repeatCount FROM %1$s AS h @@ -165,14 +166,16 @@ public function query_overview_full_group_by( $args ) { ## END SQL_STATEMENT_MAX_IDS_AND_COUNT_TEMPLATE '; - $inner_where = $this->get_inner_where( $args ); - if ( ! empty( $inner_where ) ) { - $inner_where_string = "\nWHERE\n" . implode( "\nAND ", $inner_where ); + $inner_where_string = ''; + $inner_where_array = $this->get_inner_where( $args ); + if ( ! empty( $inner_where_array ) ) { + $inner_where_string = "\nWHERE\n" . implode( "\nAND ", $inner_where_array ); } - $outer_where = $this->get_outer_where( $args ); - if ( ! empty( $outer_where ) ) { - $outer_where_string = "\nWHERE " . implode( "\nAND ", $outer_where ); + $outer_where_string = ''; + $outer_where_array = $this->get_outer_where( $args ); + if ( ! empty( $outer_where_array ) ) { + $outer_where_string = "\nWHERE " . implode( "\nAND ", $outer_where_array ); } $max_ids_and_count_sql_statement = sprintf( @@ -181,7 +184,7 @@ public function query_overview_full_group_by( $args ) { $Simple_History->get_contexts_table_name(), // 2 $inner_sql_query_statement, // 3 $outer_where_string, // 4 - '' // 5 + '' // 5 Limit clause. ); /** @@ -195,13 +198,16 @@ public function query_overview_full_group_by( $args ) { ## START SQL_STATEMENT_LOG_ROWS SELECT simple_history_1.id, + maxId, + minId, simple_history_1.logger, simple_history_1.level, simple_history_1.date, simple_history_1.message, simple_history_1.initiator, simple_history_1.occasionsID, - repeatCount + repeatCount, + repeatCount AS subsequentOccasions FROM %1$s AS simple_history_1 @@ -223,42 +229,68 @@ public function query_overview_full_group_by( $args ) { $result_log_rows = $wpdb->get_results( $sql_query_log_rows, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + // sh_d( '$sql_query_log_rows', $sql_query_log_rows );exit; if ( ! empty( $wpdb->last_error ) ) { sh_d( '$wpdb->last_error', $wpdb->last_error ); sh_d( '$sql_query_log_rows', $sql_query_log_rows ); exit; } - // Get all ids from $result_ids_and_count. - // $max_ids = wp_list_pluck( $result_max_ids_and_count, 'maxId' ); + // Append context to log rows. + $result_log_rows = $this->add_contexts_to_log_rows( $result_log_rows ); - // Values must be id's. - // $max_ids = array_map( 'intval', $max_ids ); + // Re-index array. + $result_log_rows = array_values( $result_log_rows ); - // $sql_statement_max_ids_and_count_template = ' - // SELECT - // * - // FROM wp_simple_history - // WHERE id IN (%s) - // ORDER BY id DESC - // '; + #sh_d('$result_log_rows', $result_log_rows);exit; - // $sql_query = sprintf( - // $sql_statement_max_ids_and_count_template, - // implode( ',', $max_ids ) - // ); + // Get max id and min id. + // Max id is the id of the first row in the result (i.e. the latest entry). + // Min id is the minId value of the last row in the result (i.e. the oldest entry). + $min_id = null; + $max_id = null; + if ( sizeof( $result_log_rows ) > 0 ) { + $max_id = $result_log_rows[0]->id; + $min_id = $result_log_rows[ count( $result_log_rows ) - 1 ]->minId; + } - // $result_log_rows = $wpdb->get_results( $sql_query, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + // sh_d( '$result_log_rows', $result_log_rows );exit; - // // sh_d( '$max_ids', $max_ids ); - // sh_d( '$sql_query', $sql_query ); + // TODO: This must be calculated 4 realz. + $total_found_rows = 999; - $result_log_rows = $this->add_contexts_to_log_rows( $result_log_rows ); + // Calc pages. + $pages_count = Ceil( $total_found_rows / $args['posts_per_page'] ); - // sh_d( '$result_log_rows', $result_log_rows ); - // exit; + // Calc pagination info. + $log_rows_count = count( $result_log_rows ); + $page_rows_from = ( $args['paged'] * $args['posts_per_page'] ) - $args['posts_per_page'] + 1; + $page_rows_to = $page_rows_from + $log_rows_count - 1; + + // Create array to return. + // Add log rows to sub key 'log_rows' because meta info is also added. + $arr_return = [ + 'total_row_count' => $total_found_rows, + 'pages_count' => $pages_count, + 'page_current' => $args['paged'], + 'page_rows_from' => $page_rows_from, + 'page_rows_to' => $page_rows_to, + 'max_id' => (int) $max_id, + 'min_id' => (int) $min_id, + 'log_rows_count' => $log_rows_count, + // Remove id from keys, because they are cumbersome when working with JSON. + 'log_rows' => $result_log_rows, + // Add sql query to debug. + #'outer_where_array' => $outer_where_array, + #'inner_where_array' => $inner_where_array, + #'sql' => $sql, + #'sql_context' => $sql_context ?? null, + #'context_results' => $context_results ?? null, + ]; + + wp_cache_set( $cache_key, $arr_return, $cache_group ); - return $result_log_rows; + return $arr_return; } /** @@ -564,11 +596,11 @@ protected function query_overview( $args ) { // Remove id from keys, because they are cumbersome when working with JSON. 'log_rows' => array_values( $log_rows ), // Add sql query to debug. - 'outer_where_array' => $outer_where_array, - 'inner_where_array' => $inner_where_array, - 'sql' => $sql, - 'sql_context' => $sql_context ?? null, - 'context_results' => $context_results ?? null, + #'outer_where_array' => $outer_where_array, + #'inner_where_array' => $inner_where_array, + #'sql' => $sql, + #'sql_context' => $sql_context ?? null, + #'context_results' => $context_results ?? null, ]; wp_cache_set( $cache_key, $arr_return, $cache_group ); @@ -926,6 +958,15 @@ protected function add_contexts_to_log_rows( $log_rows ) { $log_rows[ $context_row->history_id ]->context[ $context_row->key ] = $context_row->value; } + // Move up _message_key from context row to main row as context_message_key. + // This is beacuse that's the way it was before SQL was rewritten + // to support FULL_GROUP_BY in December 2023. + foreach ( $log_rows as $log_row ) { + if ( isset( $log_row->context['_message_key'] ) ) { + $log_row->context_message_key = $log_row->context['_message_key']; + } + } + return $log_rows; } diff --git a/inc/services/class-api.php b/inc/services/class-api.php index b9db4939..5466c90a 100644 --- a/inc/services/class-api.php +++ b/inc/services/class-api.php @@ -69,21 +69,33 @@ public function api() { $data['api_args'] = $args; - $data['log_rows_full_group_by'] = array_values( $data_full_group_by ); + // $data['log_rows_full_group_by'] = array_values( $data_full_group_by ); // Output can be array or HTML. if ( isset( $args['format'] ) && 'html' === $args['format'] ) { foreach ( $data['log_rows'] as $key => $oneLogRow ) { - $args = []; + $format_args = []; if ( $type == 'single' ) { - $args['type'] = 'single'; + $format_args['type'] = 'single'; } - $data['log_rows'][ $key ] = $this->simple_history->get_log_row_html_output( $oneLogRow, $args ); + $data['log_rows'][ $key ] = $this->simple_history->get_log_row_html_output( $oneLogRow, $format_args ); } $data['num_queries'] = get_num_queries(); $data['cached_result'] = $data['cached_result'] ?? false; + + // Add full group by result. + #sh_d( 'data_full_group_by', $data_full_group_by ); + #exit; + foreach ( $data_full_group_by['log_rows'] as $key => $one_log_row ) { + $format_args = []; + if ( $type == 'single' ) { + $format_args['type'] = 'single'; + } + + $data['log_rows_full'][ $key ] = $this->simple_history->get_log_row_html_output( $one_log_row, $format_args ); + } } break; From 188afa02ac048eac30e868f72863817aab838f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Tue, 19 Dec 2023 09:24:08 +0100 Subject: [PATCH 27/59] Log query: Count using count(*) --- inc/class-log-query.php | 53 +++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 1a458875..0b8ecfa8 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -10,10 +10,12 @@ * Todo * - Occasions should check user permissions, or otherwise it can add any id and the user will have access to it. * - Test if fix for full group is working. - * - Finish query_overview_full_group_by() to return same data as query_overview(), and then compare and verify that it returns same data. + * - [x] Finish query_overview_full_group_by() to return same data as query_overview(), and then compare and verify that it returns same data. * - Also print SQL query and do some EXPLAIN on it in a regular editor. If this works it would be nice to blog about the findings, * and print benchmarks etc. - * - Get num rows using second query with count(*) + * - [x] Get num rows using second query with count(*) + * - [ ] Merge together all git commits to one commit with close-##-messages. + * - [ ] Test in MySQL 5.5, 5.7, MariaDB 10.4. Tests fail, bad tests or something that isn't working? */ class Log_Query { /** @@ -242,7 +244,7 @@ public function query_overview_full_group_by( $args ) { // Re-index array. $result_log_rows = array_values( $result_log_rows ); - #sh_d('$result_log_rows', $result_log_rows);exit; + // sh_d('$result_log_rows', $result_log_rows);exit; // Get max id and min id. // Max id is the id of the first row in the result (i.e. the latest entry). @@ -256,8 +258,29 @@ public function query_overview_full_group_by( $args ) { // sh_d( '$result_log_rows', $result_log_rows );exit; - // TODO: This must be calculated 4 realz. - $total_found_rows = 999; + // Like $sql_statement_log_rows but all columns is replaced by a single COUNT(*). + $sql_statement_log_rows_count = ' + ## START SQL_STATEMENT_LOG_ROWS + SELECT + count(*) as count + + FROM %1$s AS simple_history_1 + + INNER JOIN ( + %2$s + ) AS max_ids_and_count ON simple_history_1.id = max_ids_and_count.maxId + + ORDER BY simple_history_1.id DESC + ## END SQL_STATEMENT_LOG_ROWS + '; + + $sql_query_log_rows_count = sprintf( + $sql_statement_log_rows_count, + $Simple_History->get_events_table_name(), // 1 + $max_ids_and_count_sql_statement // 2 + ); + + $total_found_rows = $wpdb->get_var( $sql_query_log_rows_count ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared // Calc pages. $pages_count = Ceil( $total_found_rows / $args['posts_per_page'] ); @@ -281,11 +304,11 @@ public function query_overview_full_group_by( $args ) { // Remove id from keys, because they are cumbersome when working with JSON. 'log_rows' => $result_log_rows, // Add sql query to debug. - #'outer_where_array' => $outer_where_array, - #'inner_where_array' => $inner_where_array, - #'sql' => $sql, - #'sql_context' => $sql_context ?? null, - #'context_results' => $context_results ?? null, + // 'outer_where_array' => $outer_where_array, + // 'inner_where_array' => $inner_where_array, + // 'sql' => $sql, + // 'sql_context' => $sql_context ?? null, + // 'context_results' => $context_results ?? null, ]; wp_cache_set( $cache_key, $arr_return, $cache_group ); @@ -596,11 +619,11 @@ protected function query_overview( $args ) { // Remove id from keys, because they are cumbersome when working with JSON. 'log_rows' => array_values( $log_rows ), // Add sql query to debug. - #'outer_where_array' => $outer_where_array, - #'inner_where_array' => $inner_where_array, - #'sql' => $sql, - #'sql_context' => $sql_context ?? null, - #'context_results' => $context_results ?? null, + // 'outer_where_array' => $outer_where_array, + // 'inner_where_array' => $inner_where_array, + // 'sql' => $sql, + // 'sql_context' => $sql_context ?? null, + // 'context_results' => $context_results ?? null, ]; wp_cache_set( $cache_key, $arr_return, $cache_group ); From 4636a231829deeda83f6daa592d6cba3e1b94a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Tue, 19 Dec 2023 17:12:50 +0100 Subject: [PATCH 28/59] Log query test: Disable mark as incomplete --- tests/wpunit/LogQueryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/wpunit/LogQueryTest.php b/tests/wpunit/LogQueryTest.php index 7557deba..d1a38ab1 100644 --- a/tests/wpunit/LogQueryTest.php +++ b/tests/wpunit/LogQueryTest.php @@ -27,7 +27,7 @@ class LogQueryTest extends \Codeception\TestCase\WPTestCase { */ function test_query() { // I know this fails. - $this->markTestIncomplete('This test will fail in Mysql >5.5 and MariaDB until SQL bug is fixed.'); + // $this->markTestIncomplete('This test will fail in Mysql >5.5 and MariaDB until SQL bug is fixed.'); // Add and set current user to admin user, so user can read all logs. $user_id = $this->factory->user->create( From 739eab005da467ebc677c55953b437a8cffb6b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Tue, 19 Dec 2023 17:13:01 +0100 Subject: [PATCH 29/59] Log query: Move default args to prepare_args function --- inc/class-log-query.php | 160 ++++++++++++++++++++-------------------- 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 0b8ecfa8..29beb8ca 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -323,83 +323,8 @@ public function query_overview_full_group_by( $args ) { * @return array */ protected function query_overview( $args ) { - /** @var array Query arguments. */ - $args = wp_parse_args( - $args, - [ - // overview | occasions | single. - // When type is occasions then logRowID, occasionsID, occasionsCount, occasionsCountMaxReturn are required. - 'type' => 'overview', - - // Number of posts to show per page. 0 to show all. - 'posts_per_page' => 10, - - // Page to show. 1 = first page. - 'paged' => 1, - - // Array. Only get posts that are in array. - 'post__in' => [], - - // If max_id_first_page is set then only get rows - // that have id equal or lower than this, to make. - 'max_id_first_page' => null, - - // if since_id is set the rows returned will only be rows with an ID greater than (i.e. more recent than) since_id. - 'since_id' => null, - - /** - * From date, as unix timestamp integer or as a format compatible with strtotime, for example 'Y-m-d H:i:s'. - * - * @var int|string - */ - 'date_from' => null, - - /** - * To date, as unix timestamp integer or as a format compatible with strtotime, for example 'Y-m-d H:i:s'. - * - * @var int|string - */ - 'date_to' => null, - - // months in format "Y-m" - // array or comma separated. - 'months' => null, - - // dates in format - // "month:2015-06" for june 2015 - // "lastdays:7" for the last 7 days. - 'dates' => null, - - /** - * Text to search for. - * Message, logger and level are searched for in main table. - * Values are searched for in context table. - * - * @var string - */ - 'search' => null, - - // log levels to include. comma separated or as array. defaults to all. - 'loglevels' => null, - - // loggers to include. comma separated. defaults to all the user can read. - 'loggers' => null, - - 'messages' => null, - - // userID as number. - 'user' => null, - - // User ids, comma separated or array. - 'users' => null, - - // Can also contain: - // logRowID - // occasionsCount - // occasionsCountMaxReturn - // occasionsID. - ] - ); + // Parse and prepare args. + $args = $this->prepare_args( $args ); // Create cache key based on args and current user. $cache_key = 'SimpleHistoryLogQuery_' . md5( serialize( $args ) ) . '_userid_' . get_current_user_id(); @@ -419,9 +344,6 @@ protected function query_overview( $args ) { /** @var Simple_History Simple History instance. */ $simple_history = Simple_History::get_instance(); - // Parse and prepare args. - $args = $this->prepare_args( $args ); - /** @var string Table name for events. */ $events_table_name = $simple_history->get_events_table_name(); @@ -772,6 +694,84 @@ protected function query_occasions( $args ) { * @throws \InvalidArgumentException If invalid type. */ protected function prepare_args( $args ) { + /** @var array Query arguments. */ + $args = wp_parse_args( + $args, + [ + // overview | occasions | single. + // When type is occasions then logRowID, occasionsID, occasionsCount, occasionsCountMaxReturn are required. + 'type' => 'overview', + + // Number of posts to show per page. 0 to show all. + 'posts_per_page' => 10, + + // Page to show. 1 = first page. + 'paged' => 1, + + // Array. Only get posts that are in array. + 'post__in' => [], + + // If max_id_first_page is set then only get rows + // that have id equal or lower than this, to make. + 'max_id_first_page' => null, + + // if since_id is set the rows returned will only be rows with an ID greater than (i.e. more recent than) since_id. + 'since_id' => null, + + /** + * From date, as unix timestamp integer or as a format compatible with strtotime, for example 'Y-m-d H:i:s'. + * + * @var int|string + */ + 'date_from' => null, + + /** + * To date, as unix timestamp integer or as a format compatible with strtotime, for example 'Y-m-d H:i:s'. + * + * @var int|string + */ + 'date_to' => null, + + // months in format "Y-m" + // array or comma separated. + 'months' => null, + + // dates in format + // "month:2015-06" for june 2015 + // "lastdays:7" for the last 7 days. + 'dates' => null, + + /** + * Text to search for. + * Message, logger and level are searched for in main table. + * Values are searched for in context table. + * + * @var string + */ + 'search' => null, + + // log levels to include. comma separated or as array. defaults to all. + 'loglevels' => null, + + // loggers to include. comma separated. defaults to all the user can read. + 'loggers' => null, + + 'messages' => null, + + // userID as number. + 'user' => null, + + // User ids, comma separated or array. + 'users' => null, + + // Can also contain: + // logRowID + // occasionsCount + // occasionsCountMaxReturn + // occasionsID. + ] + ); + // Type must be string and any of "overview", "occasions", "single". if ( ! is_string( $args['type'] ) && ! in_array( $args['type'], [ 'overview', 'occasions', 'single' ], true ) ) { throw new \InvalidArgumentException( 'Invalid type' ); From 276a9736a262bd11a6c2a2805473dc336c6d56ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Tue, 19 Dec 2023 18:43:04 +0100 Subject: [PATCH 30/59] Log query: Add cache and limit to full group function, and rename to old name --- inc/class-log-query.php | 61 ++++++++++++++++++++++++++++---------- inc/services/class-api.php | 18 +++++------ 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 29beb8ca..f2c37bb0 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -14,8 +14,10 @@ * - Also print SQL query and do some EXPLAIN on it in a regular editor. If this works it would be nice to blog about the findings, * and print benchmarks etc. * - [x] Get num rows using second query with count(*) + * - [ ] Add limit. * - [ ] Merge together all git commits to one commit with close-##-messages. * - [ ] Test in MySQL 5.5, 5.7, MariaDB 10.4. Tests fail, bad tests or something that isn't working? + * - [ ] Run PHPStan and Rector */ class Log_Query { /** @@ -65,14 +67,27 @@ public function query( $args ) { * @param string|array|object $args Arguments. * @return array Log rows. */ - public function query_overview_full_group_by( $args ) { - global $wpdb; - + public function query_overview( $args ) { // Parse and prepare args. $args = $this->prepare_args( $args ); + // Create cache key based on args and current user. + $cache_key = md5( __METHOD__ . serialize( $args ) ) . '_userid_' . get_current_user_id(); + $cache_group = 'simple-history-' . Helpers::get_cache_incrementor(); + + /** @var array Return value. */ + $arr_return = wp_cache_get( $cache_key, $cache_group ); + + // Return cached value if it exists. + if ( false !== $arr_return ) { + $arr_return['cached_result'] = true; + return $arr_return; + } + $Simple_History = Simple_History::get_instance(); + global $wpdb; + $wpdb->query( 'SET @a:=NULL, @counter:=1, @groupby:=0' ); /** @@ -121,15 +136,6 @@ public function query_overview_full_group_by( $args ) { $inner_where_string // 3 ); - // $inner_query_results = $wpdb->get_results( $inner_sql_query, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - // sh_d( '$inner_sql_query', $inner_sql_query ); - // sh_d( '$inner_query_results', $inner_query_results ); - // if ( ! empty( $wpdb->last_error ) ) { - // sh_d( '$wpdb->last_error', $wpdb->last_error ); - // exit; - // } - // exit; - /** * @var string SQL statement template used to get IDs of all events. * @@ -180,13 +186,20 @@ public function query_overview_full_group_by( $args ) { $outer_where_string = "\nWHERE " . implode( "\nAND ", $outer_where_array ); } + // Add limit clause. + /** @var string Limit clause. */ + $limit_clause = ''; + + $limit_offset = ( $args['paged'] - 1 ) * $args['posts_per_page']; + $limit_clause = sprintf( 'LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page'] ); + $max_ids_and_count_sql_statement = sprintf( $sql_statement_max_ids_and_count_template, $Simple_History->get_events_table_name(), // 1 $Simple_History->get_contexts_table_name(), // 2 $inner_sql_query_statement, // 3 $outer_where_string, // 4 - '' // 5 Limit clause. + $limit_clause // 5 Limit clause. ); /** @@ -274,10 +287,22 @@ public function query_overview_full_group_by( $args ) { ## END SQL_STATEMENT_LOG_ROWS '; + // TODO: + // create $max_ids_and_count_sql_statement without limit. + // Then use that to get count(*). + $max_ids_and_count_without_limit_sql_statement = sprintf( + $sql_statement_max_ids_and_count_template, + $Simple_History->get_events_table_name(), // 1 + $Simple_History->get_contexts_table_name(), // 2 + $inner_sql_query_statement, // 3 + $outer_where_string, // 4 + '', // 5 Limit clause. + ); + $sql_query_log_rows_count = sprintf( $sql_statement_log_rows_count, $Simple_History->get_events_table_name(), // 1 - $max_ids_and_count_sql_statement // 2 + $max_ids_and_count_without_limit_sql_statement // 2 ); $total_found_rows = $wpdb->get_var( $sql_query_log_rows_count ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared @@ -293,7 +318,7 @@ public function query_overview_full_group_by( $args ) { // Create array to return. // Add log rows to sub key 'log_rows' because meta info is also added. $arr_return = [ - 'total_row_count' => $total_found_rows, + 'total_row_count' => (int) $total_found_rows, 'pages_count' => $pages_count, 'page_current' => $args['paged'], 'page_rows_from' => $page_rows_from, @@ -319,10 +344,14 @@ public function query_overview_full_group_by( $args ) { /** * Query overview. * + * @deprecated 4.9.0 Use query_overview() instead. * @param string|array|object $args Arguments. * @return array */ - protected function query_overview( $args ) { + protected function query_overview_not_full_group_by( $args ) { + // Mark as deprecated. + wp_deprecated_function( __METHOD__, '4.9.0', 'query_overview()' ); + // Parse and prepare args. $args = $this->prepare_args( $args ); diff --git a/inc/services/class-api.php b/inc/services/class-api.php index 5466c90a..68cf5c0f 100644 --- a/inc/services/class-api.php +++ b/inc/services/class-api.php @@ -63,7 +63,7 @@ public function api() { $logQuery = new Log_Query(); $data = $logQuery->query( $args ); - $data_full_group_by = $logQuery->query_overview_full_group_by( $args ); + // $data_full_group_by = $logQuery->query_overview_full_group_by( $args ); // sh_d('$data', $data, '$data_full_group_by', $data_full_group_by);exit; @@ -88,14 +88,14 @@ public function api() { // Add full group by result. #sh_d( 'data_full_group_by', $data_full_group_by ); #exit; - foreach ( $data_full_group_by['log_rows'] as $key => $one_log_row ) { - $format_args = []; - if ( $type == 'single' ) { - $format_args['type'] = 'single'; - } - - $data['log_rows_full'][ $key ] = $this->simple_history->get_log_row_html_output( $one_log_row, $format_args ); - } + // foreach ( $data_full_group_by['log_rows'] as $key => $one_log_row ) { + // $format_args = []; + // if ( $type == 'single' ) { + // $format_args['type'] = 'single'; + // } + + // $data['log_rows_full'][ $key ] = $this->simple_history->get_log_row_html_output( $one_log_row, $format_args ); + // } } break; From b31fdd4917e7220e1cd1b1f6932738d9895c6537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Tue, 19 Dec 2023 19:49:41 +0100 Subject: [PATCH 31/59] Log query: Remove "repeated" column --- inc/class-simple-history.php | 1 - readme.txt | 2 +- tests/wpunit/LogQueryTest.php | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/inc/class-simple-history.php b/inc/class-simple-history.php index 41724c44..dd6a2657 100644 --- a/inc/class-simple-history.php +++ b/inc/class-simple-history.php @@ -934,7 +934,6 @@ public function get_log_row_html_output( $one_log_row, $args ) { unset( $logRowKeysToShow['occasionsID'], $logRowKeysToShow['subsequentOccasions'], - $logRowKeysToShow['repeated'], $logRowKeysToShow['context'], $logRowKeysToShow['type'] ); diff --git a/readme.txt b/readme.txt index eff2936e..ececae4c 100644 --- a/readme.txt +++ b/readme.txt @@ -212,7 +212,7 @@ This can be modified using the filter [`simple_history/db_purge_days_interval`]( Unreleased - IP addresses are now shown on occasions. -- Remove columns "rep" and "occasionsIDType" from Log_Query response. +- Remove columns "rep", "repeated" and "occasionsIDType" from Log_Query response. ### 4.8.0 (December 2023) diff --git a/tests/wpunit/LogQueryTest.php b/tests/wpunit/LogQueryTest.php index d1a38ab1..e85b1508 100644 --- a/tests/wpunit/LogQueryTest.php +++ b/tests/wpunit/LogQueryTest.php @@ -278,7 +278,6 @@ function test_log_query() { $this->assertTrue( property_exists( $first_log_row, 'initiator' ) ); $this->assertTrue( property_exists( $first_log_row, 'occasionsID' ) ); $this->assertTrue( property_exists( $first_log_row, 'subsequentOccasions' ) ); - $this->assertTrue( property_exists( $first_log_row, 'repeated' ) ); $this->assertTrue( property_exists( $first_log_row, 'context' ) ); } } From 6a233b9ca168610890a5170b6d0a54c1e4e546aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Tue, 19 Dec 2023 19:53:39 +0100 Subject: [PATCH 32/59] =?UTF-8?q?Log=20Query:=20=F0=9F=91=8B=20Remove=20ol?= =?UTF-8?q?d=20query=20that=20did=20not=20use=20full=20group=20by?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- inc/class-log-query.php | 245 +--------------------------------------- 1 file changed, 5 insertions(+), 240 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index f2c37bb0..abe6f09c 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -62,7 +62,11 @@ public function query( $args ) { } /** - * Query history using a query that uses full group by. + * Query history using a query that uses full group by, + * making it compatible with both MySQL 5.5, 5.7 and MariaDB. + * + * Subequent occasions query thanks to the answer Stack Overflow thread: + * http://stackoverflow.com/questions/13566303/how-to-group-subsequent-rows-based-on-a-criteria-and-then-count-them-mysql/13567320#13567320 * * @param string|array|object $args Arguments. * @return array Log rows. @@ -341,246 +345,7 @@ public function query_overview( $args ) { return $arr_return; } - /** - * Query overview. - * - * @deprecated 4.9.0 Use query_overview() instead. - * @param string|array|object $args Arguments. - * @return array - */ - protected function query_overview_not_full_group_by( $args ) { - // Mark as deprecated. - wp_deprecated_function( __METHOD__, '4.9.0', 'query_overview()' ); - - // Parse and prepare args. - $args = $this->prepare_args( $args ); - - // Create cache key based on args and current user. - $cache_key = 'SimpleHistoryLogQuery_' . md5( serialize( $args ) ) . '_userid_' . get_current_user_id(); - $cache_group = 'simple-history-' . Helpers::get_cache_incrementor(); - - /** @var array Return value. */ - $arr_return = wp_cache_get( $cache_key, $cache_group ); - - // Return cached value if it exists. - if ( false !== $arr_return ) { - $arr_return['cached_result'] = true; - return $arr_return; - } - - global $wpdb; - - /** @var Simple_History Simple History instance. */ - $simple_history = Simple_History::get_instance(); - - /** @var string Table name for events. */ - $events_table_name = $simple_history->get_events_table_name(); - - /** @var string Table name for contexts. */ - $contexts_table_name = $simple_history->get_contexts_table_name(); - - /** @var string Limit clause. */ - $limit = ''; - - /* - Subequent occasions query thanks to the answer Stack Overflow thread: - http://stackoverflow.com/questions/13566303/how-to-group-subsequent-rows-based-on-a-criteria-and-then-count-them-mysql/13567320#13567320 - - Similar questions that I didn't manage to understand, work, or did try: - - http://stackoverflow.com/questions/23651176/mysql-query-if-dates-are-subsequent - - http://stackoverflow.com/questions/17651868/mysql-group-by-subsequent - - http://stackoverflow.com/questions/4495242/mysql-number-of-subsequent-occurrences - - http://stackoverflow.com/questions/20446242/postgresql-group-subsequent-rows - - http://stackoverflow.com/questions/17061156/mysql-group-by-range - - http://stackoverflow.com/questions/6602006/complicated-query-with-group-by-and-range-of-prices-in-mysql - */ - - // Set variables used by query. - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - $wpdb->query( 'SET @a:=NULL, @counter:=1, @groupby:=0' ); - - /** - * @var string $sql_tmpl SQL template for overview or single event query. - * - * Template uses number argument to sprintf to insert values. - * Arguments: - * 1 = where clause. - * 2 = limit clause. - * 3 = table name for events. - * 4 = where clause for inner query. - * 5 = table name for contexts. - */ - $sql_tmpl = ' - /*NO_SELECT_FOUND_ROWS*/ - SELECT - SQL_CALC_FOUND_ROWS - h.id, - h.logger, - h.level, - h.date, - h.message, - h.initiator, - h.occasionsID, - count(t.repeated) AS subsequentOccasions, - t.repeated, - c1.value AS context_message_key - - FROM %3$s AS h - - LEFT OUTER JOIN %5$s AS c1 ON (c1.history_id = h.id AND c1.key = "_message_key") - - INNER JOIN ( - SELECT - id, - IF(@a=occasionsID,@counter:=@counter+1,@counter:=1) AS rep, - IF(@counter=1,@groupby:=@groupby+1,@groupby) AS repeated, - @a:=occasionsID occasionsIDType - FROM %3$s AS h2 - - # Inner where. - %4$s - - ORDER BY id DESC, date DESC - ) AS t ON t.id = h.id - - # Outer where. - %1$s - - GROUP BY repeated - ORDER BY id DESC, date DESC - - # Limit clause. - %2$s - '; - - // Add limit clause. - $limit_offset = ( $args['paged'] - 1 ) * $args['posts_per_page']; - $limit = sprintf( 'LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page'] ); - - // Get inner and outer where depending on type of query. - $inner_where = $this->get_inner_where( $args ); - $outer_where = $this->get_outer_where( $args ); - - /** - * Filter the sql template - * - * @since 2.0 - * - * @param string $sql_tmpl - */ - $sql_tmpl = apply_filters( 'simple_history/log_query_sql_template', $sql_tmpl ); - - // Create where string. - $outer_where_array = $outer_where; - $outer_where = implode( "\nAND ", $outer_where ); - // Append where to sql template. - if ( ! empty( $outer_where ) ) { - $outer_where = "\nWHERE {$outer_where}"; - } - - /** - * Filter the sql template where clause - * - * @since 2.0 - * - * @param string $where - */ - $outer_where = apply_filters( 'simple_history/log_query_sql_where', $outer_where ); - - /** - * Filter the sql template limit - * - * @since 2.0 - * - * @param string $limit - */ - $limit = apply_filters( 'simple_history/log_query_limit', $limit ); - - // Create where string. - $inner_where_array = $inner_where; - $inner_where = implode( "\nAND ", $inner_where ); - - // Append where to sql template. - if ( ! empty( $inner_where ) ) { - $inner_where = "\nWHERE {$inner_where}"; - } - - /** - * Filter the sql template limit - * - * @since 2.0 - * - * @param string $limit - */ - $inner_where = apply_filters( 'simple_history/log_query_inner_where', $inner_where ); - - /** @var string SQL generated from template. */ - $sql = sprintf( - $sql_tmpl, // sprintf template. - $outer_where, // 1 - $limit, // 2 - $events_table_name, // 3 - $inner_where, // 4 - $contexts_table_name // 5 - ); - - /** - * Filter the final sql query - * - * @since 2.0 - * - * @param string $sql - */ - $sql = apply_filters( 'simple_history/log_query_sql', $sql ); - - /** @var array Log rows matching where queries. */ - $log_rows = $wpdb->get_results( $sql, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - - // Find total number of rows that we would have gotten without pagination - // This is the number of rows with occasions taken into consideration. - // TODO: Remove this and run same query with count(*) before. - $sql_found_rows = 'SELECT FOUND_ROWS()'; - $total_found_rows = (int) $wpdb->get_var( $sql_found_rows ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - - // Add context to log rows. - $log_rows = $this->add_contexts_to_log_rows( $log_rows ); - - [$max_id, $min_id] = $this->get_max_min_ids( $log_rows ); - - // Calc pages. - $pages_count = Ceil( $total_found_rows / $args['posts_per_page'] ); - - // Calc pagination info. - $log_rows_count = count( $log_rows ); - $page_rows_from = ( $args['paged'] * $args['posts_per_page'] ) - $args['posts_per_page'] + 1; - $page_rows_to = $page_rows_from + $log_rows_count - 1; - - // Create array to return. - // Add log rows to sub key 'log_rows' because meta info is also added. - $arr_return = [ - 'total_row_count' => $total_found_rows, - 'pages_count' => $pages_count, - 'page_current' => $args['paged'], - 'page_rows_from' => $page_rows_from, - 'page_rows_to' => $page_rows_to, - 'max_id' => (int) $max_id, - 'min_id' => (int) $min_id, - 'log_rows_count' => $log_rows_count, - // Remove id from keys, because they are cumbersome when working with JSON. - 'log_rows' => array_values( $log_rows ), - // Add sql query to debug. - // 'outer_where_array' => $outer_where_array, - // 'inner_where_array' => $inner_where_array, - // 'sql' => $sql, - // 'sql_context' => $sql_context ?? null, - // 'context_results' => $context_results ?? null, - ]; - - wp_cache_set( $cache_key, $arr_return, $cache_group ); - - return $arr_return; - } /** * Get occasions for a single event. From da360f872067bf3c000ee6596edcb6c1ca4724fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Tue, 19 Dec 2023 21:18:21 +0100 Subject: [PATCH 33/59] Remove debug code --- inc/class-log-query.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index abe6f09c..47705b6b 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -261,8 +261,6 @@ public function query_overview( $args ) { // Re-index array. $result_log_rows = array_values( $result_log_rows ); - // sh_d('$result_log_rows', $result_log_rows);exit; - // Get max id and min id. // Max id is the id of the first row in the result (i.e. the latest entry). // Min id is the minId value of the last row in the result (i.e. the oldest entry). @@ -273,8 +271,6 @@ public function query_overview( $args ) { $min_id = $result_log_rows[ count( $result_log_rows ) - 1 ]->minId; } - // sh_d( '$result_log_rows', $result_log_rows );exit; - // Like $sql_statement_log_rows but all columns is replaced by a single COUNT(*). $sql_statement_log_rows_count = ' ## START SQL_STATEMENT_LOG_ROWS @@ -345,8 +341,6 @@ public function query_overview( $args ) { return $arr_return; } - - /** * Get occasions for a single event. * From 06b79232fde34f857fa0923b2ad74f5e5bf384f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Fri, 22 Dec 2023 21:11:44 +0100 Subject: [PATCH 34/59] =?UTF-8?q?Log=20Query:=20SQLite=20almost=20working?= =?UTF-8?q?=20=F0=9F=98=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- inc/class-log-query.php | 170 ++++++++++++++++++++-- templates/template-settings-tab-debug.php | 4 + 2 files changed, 165 insertions(+), 9 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 47705b6b..aef4fd7a 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -14,10 +14,13 @@ * - Also print SQL query and do some EXPLAIN on it in a regular editor. If this works it would be nice to blog about the findings, * and print benchmarks etc. * - [x] Get num rows using second query with count(*) - * - [ ] Add limit. + * - [x] Add limit. * - [ ] Merge together all git commits to one commit with close-##-messages. - * - [ ] Test in MySQL 5.5, 5.7, MariaDB 10.4. Tests fail, bad tests or something that isn't working? - * - [ ] Run PHPStan and Rector + * - [x] Test in MySQL 5.5, 5.7, MariaDB 10.4. + * - [ ] Add tests for single event occasions. + * - [ ] Add tests for log row notifier. + * - [ ] Run PHPStan and Rector. + * - [ ] Add support for SQLite. */ class Log_Query { /** @@ -72,6 +75,139 @@ public function query( $args ) { * @return array Log rows. */ public function query_overview( $args ) { + $db_engine = $this->get_db_engine(); + + if ( $db_engine === 'mysql' ) { + // Call usual method. + return $this->query_overview_mysql( $args ); + } else if ( $db_engine === 'sqlite' ) { + // Call sqlite method. + return $this->query_overview_sqlite( $args ); + } else { + throw new \ErrorException( 'Invalid DB engine' ); + } + } + + /** + * SQLite compatiable version of query_overview_mysql(). + * Main difference is that the SQL query is much simplier, + * because it does not support occasions. + * + * @param string|array|object $args Arguments. + * @return array Log rows. + */ + protected function query_overview_sqlite( $args ) { + + $Simple_History = Simple_History::get_instance(); + + global $wpdb; + + $args = $this->prepare_args( $args ); + + /** + * @var string SQL template used to get all events from the ones + * found in statement sql_statement_max_ids_and_count_template. + * This final statement gets all columns we finally need. + */ + $sql_statement_log_rows = ' + SELECT + simple_history_1.id, + /* maxId, */ + /* minId, */ + simple_history_1.logger, + simple_history_1.level, + simple_history_1.date, + simple_history_1.message, + simple_history_1.initiator, + simple_history_1.occasionsID, + 1 AS repeatCount, + 1 AS subsequentOccasions + FROM %1$s AS simple_history_1 + %2$s + ORDER BY simple_history_1.id DESC + %3$s + '; + + $inner_where_array = $this->get_inner_where( $args ); + $inner_where_string = empty( $inner_where_array ) ? '' : "\nWHERE " . implode( "\nAND ", $inner_where_array ); + + /** @var int $limit_offset */ + $limit_offset = ( $args['paged'] - 1 ) * $args['posts_per_page']; + + /** @var string Limit clause. */ + $limit_clause = sprintf( 'LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page'] ); + + $sql_query_log_rows = sprintf( + $sql_statement_log_rows, + $Simple_History->get_events_table_name(), // 1 + $inner_where_string, // 2 + $limit_clause // 3 + ); + + $result_log_rows = $wpdb->get_results( $sql_query_log_rows, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + + if ( ! empty( $wpdb->last_error ) ) { + sh_d( '$wpdb->last_error', $wpdb->last_error ); + sh_d( '$sql_query_log_rows', $sql_query_log_rows ); + exit; + } + + // Like $sql_statement_log_rows but all columns is replaced by a single COUNT(*). + $sql_statement_log_rows_count = ' + SELECT count(*) as count + FROM %1$s AS simple_history_1 + %2$s + ORDER BY simple_history_1.id DESC + '; + + $sql_query_log_rows_count = sprintf( + $sql_statement_log_rows_count, + $Simple_History->get_events_table_name(), // 1 + $inner_where_string, // 2 + ); + + + $total_found_rows = $wpdb->get_var( $sql_query_log_rows_count ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + + // Calc pages. + $pages_count = Ceil( $total_found_rows / $args['posts_per_page'] ); + + // Calc pagination info. + $log_rows_count = count( $result_log_rows ); + $page_rows_from = ( $args['paged'] * $args['posts_per_page'] ) - $args['posts_per_page'] + 1; + $page_rows_to = $page_rows_from + $log_rows_count - 1; + + // Create array to return. + // Add log rows to sub key 'log_rows' because meta info is also added. + $arr_return = [ + 'total_row_count' => (int) $total_found_rows, + 'pages_count' => $pages_count, + 'page_current' => $args['paged'], + 'page_rows_from' => $page_rows_from, + 'page_rows_to' => $page_rows_to, + 'max_id' => (int) $max_id, + 'min_id' => (int) $min_id, + 'log_rows_count' => $log_rows_count, + // Remove id from keys, because they are cumbersome when working with JSON. + 'log_rows' => $result_log_rows, + // Add sql query to debug. + // 'outer_where_array' => $outer_where_array, + // 'inner_where_array' => $inner_where_array, + // 'sql' => $sql, + // 'sql_context' => $sql_context ?? null, + // 'context_results' => $context_results ?? null, + ]; + + #sh_d( '$arr_return', $arr_return);exit; + + return $arr_return; + } + + /** + * @param string|array|object $args Arguments. + * @return array Log rows. + */ + protected function query_overview_mysql( $args ) { // Parse and prepare args. $args = $this->prepare_args( $args ); @@ -178,23 +314,26 @@ public function query_overview( $args ) { ## END SQL_STATEMENT_MAX_IDS_AND_COUNT_TEMPLATE '; + /** @var string Inner where clause, including "where" if has values. */ $inner_where_string = ''; + $inner_where_array = $this->get_inner_where( $args ); if ( ! empty( $inner_where_array ) ) { $inner_where_string = "\nWHERE\n" . implode( "\nAND ", $inner_where_array ); } + /** @var string Outer where clause, including "where" if has values. */ $outer_where_string = ''; + $outer_where_array = $this->get_outer_where( $args ); if ( ! empty( $outer_where_array ) ) { $outer_where_string = "\nWHERE " . implode( "\nAND ", $outer_where_array ); } - // Add limit clause. - /** @var string Limit clause. */ - $limit_clause = ''; - + /** @var int $limit_offset */ $limit_offset = ( $args['paged'] - 1 ) * $args['posts_per_page']; + + /** @var string Limit clause. */ $limit_clause = sprintf( 'LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page'] ); $max_ids_and_count_sql_statement = sprintf( @@ -248,7 +387,6 @@ public function query_overview( $args ) { $result_log_rows = $wpdb->get_results( $sql_query_log_rows, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - // sh_d( '$sql_query_log_rows', $sql_query_log_rows );exit; if ( ! empty( $wpdb->last_error ) ) { sh_d( '$wpdb->last_error', $wpdb->last_error ); sh_d( '$sql_query_log_rows', $sql_query_log_rows ); @@ -858,7 +996,7 @@ protected function get_inner_where( $args ) { $contexts_table_name = $simple_history->get_contexts_table_name(); - /** @var array Where clause for inner query. */ + /** @var array Where clauses for inner query. */ $inner_where = []; // Only include loggers that the current user can view @@ -1150,4 +1288,18 @@ protected function get_outer_where( $args ) { return $outer_where; } + + /** + * Get db engine in use. + * Default is "mysql", which supports both MySQL and MariaDB. + * Can also return "sqlite", which means that plugin + * https://wordpress.org/plugins/sqlite-database-integration/ is in use, + * and we need to use SQLite specific SQL at some places. + * + * @return string + */ + public static function get_db_engine() { + $db_engine = defined( 'DB_ENGINE' ) && 'sqlite' === DB_ENGINE ? 'sqlite' : 'mysql'; + return $db_engine; + } } diff --git a/templates/template-settings-tab-debug.php b/templates/template-settings-tab-debug.php index fece243a..ca399001 100644 --- a/templates/template-settings-tab-debug.php +++ b/templates/template-settings-tab-debug.php @@ -97,7 +97,11 @@ echo ''; +/** + * Number of rows in database + */ $logQuery = new Log_Query(); + $rows = $logQuery->query( array( 'posts_per_page' => 1, From 8f4bd78f1f5242336faa57602376066fe9855fa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sat, 23 Dec 2023 15:59:23 +0100 Subject: [PATCH 35/59] Log query: SQLite support for maxId and minId --- inc/class-log-query.php | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index aef4fd7a..27b40b9b 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -15,12 +15,12 @@ * and print benchmarks etc. * - [x] Get num rows using second query with count(*) * - [x] Add limit. - * - [ ] Merge together all git commits to one commit with close-##-messages. * - [x] Test in MySQL 5.5, 5.7, MariaDB 10.4. * - [ ] Add tests for single event occasions. * - [ ] Add tests for log row notifier. * - [ ] Run PHPStan and Rector. * - [ ] Add support for SQLite. + * - [ ] Merge together all git commits to one commit with close-##-messages. */ class Log_Query { /** @@ -145,13 +145,19 @@ protected function query_overview_sqlite( $args ) { ); $result_log_rows = $wpdb->get_results( $sql_query_log_rows, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - +#sh_d('$result_log_rows', $result_log_rows);exit; if ( ! empty( $wpdb->last_error ) ) { sh_d( '$wpdb->last_error', $wpdb->last_error ); sh_d( '$sql_query_log_rows', $sql_query_log_rows ); exit; } + // Append context to log rows. + $result_log_rows = $this->add_contexts_to_log_rows( $result_log_rows ); + + // Re-index array. + $result_log_rows = array_values( $result_log_rows ); + // Like $sql_statement_log_rows but all columns is replaced by a single COUNT(*). $sql_statement_log_rows_count = ' SELECT count(*) as count @@ -166,7 +172,6 @@ protected function query_overview_sqlite( $args ) { $inner_where_string, // 2 ); - $total_found_rows = $wpdb->get_var( $sql_query_log_rows_count ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared // Calc pages. @@ -177,6 +182,16 @@ protected function query_overview_sqlite( $args ) { $page_rows_from = ( $args['paged'] * $args['posts_per_page'] ) - $args['posts_per_page'] + 1; $page_rows_to = $page_rows_from + $log_rows_count - 1; + // Get maxId and minId. + // MaxId is the id of the first row in the result (i.e. the latest entry). + // MinId is the id of the last row in the result (i.e. the oldest entry). + $min_id = null; + $max_id = null; + if ( sizeof( $result_log_rows ) > 0 ) { + $max_id = $result_log_rows[0]->id; + $min_id = $result_log_rows[ count( $result_log_rows ) - 1 ]->id; + } + // Create array to return. // Add log rows to sub key 'log_rows' because meta info is also added. $arr_return = [ @@ -198,7 +213,7 @@ protected function query_overview_sqlite( $args ) { // 'context_results' => $context_results ?? null, ]; - #sh_d( '$arr_return', $arr_return);exit; + // sh_d( '$arr_return', $arr_return);exit; return $arr_return; } From ba06515483935eaefcfb9d6c322ab4ccca38fd8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sat, 23 Dec 2023 20:02:10 +0100 Subject: [PATCH 36/59] Add helper functions `get_cache_group()`, `clear_cache()` --- inc/class-helpers.php | 19 +++++++++++++++++++ inc/class-log-query.php | 6 +++--- readme.txt | 1 + 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/inc/class-helpers.php b/inc/class-helpers.php index 410b45d3..74fa3a45 100644 --- a/inc/class-helpers.php +++ b/inc/class-helpers.php @@ -297,6 +297,25 @@ public static function get_cache_incrementor( $refresh = false ) { return $incrementor_value; } + /** + * Get the cache group for the cache. + * Used by all function that use cache, so they use the + * same cache group, meaning if we invalidate the cache group + * all caches will be cleared/flushed. + * + * @return string + */ + public static function get_cache_group() { + return 'simple-history-' . self::get_cache_incrementor(); + } + + /** + * Clears the cache. + */ + public static function clear_cache() { + self::get_cache_incrementor( true ); + } + /** * Return a name for a callable. * diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 27b40b9b..61520766 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -16,10 +16,11 @@ * - [x] Get num rows using second query with count(*) * - [x] Add limit. * - [x] Test in MySQL 5.5, 5.7, MariaDB 10.4. + * - [x] Add support for SQLite. + * - [ ] Add caching to SQLite * - [ ] Add tests for single event occasions. * - [ ] Add tests for log row notifier. * - [ ] Run PHPStan and Rector. - * - [ ] Add support for SQLite. * - [ ] Merge together all git commits to one commit with close-##-messages. */ class Log_Query { @@ -97,7 +98,6 @@ public function query_overview( $args ) { * @return array Log rows. */ protected function query_overview_sqlite( $args ) { - $Simple_History = Simple_History::get_instance(); global $wpdb; @@ -145,7 +145,7 @@ protected function query_overview_sqlite( $args ) { ); $result_log_rows = $wpdb->get_results( $sql_query_log_rows, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -#sh_d('$result_log_rows', $result_log_rows);exit; + // sh_d('$result_log_rows', $result_log_rows);exit; if ( ! empty( $wpdb->last_error ) ) { sh_d( '$wpdb->last_error', $wpdb->last_error ); sh_d( '$sql_query_log_rows', $sql_query_log_rows ); diff --git a/readme.txt b/readme.txt index ececae4c..c57fac3f 100644 --- a/readme.txt +++ b/readme.txt @@ -213,6 +213,7 @@ Unreleased - IP addresses are now shown on occasions. - Remove columns "rep", "repeated" and "occasionsIDType" from Log_Query response. +- Add helper functions `get_cache_group()`, `clear_cache()`. ### 4.8.0 (December 2023) From ed57b4403fcef2f8863543f7bcf29b5f8dc680a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Wed, 27 Dec 2023 08:12:33 +0100 Subject: [PATCH 37/59] Use Helpers::get_cache_group() --- dropins/class-quick-stats.php | 2 +- inc/class-log-query.php | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dropins/class-quick-stats.php b/dropins/class-quick-stats.php index 8b9744b4..958ab131 100644 --- a/dropins/class-quick-stats.php +++ b/dropins/class-quick-stats.php @@ -57,7 +57,7 @@ public function output_quick_stats() { ); $cache_key = 'quick_stats_users_today_' . md5( serialize( $sql_loggers_in ) ); - $cache_group = 'simple-history-' . Helpers::get_cache_incrementor(); + $cache_group = Helpers::get_cache_group(); $results_users_today = wp_cache_get( $cache_key, $cache_group ); if ( false === $results_users_today ) { diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 61520766..821cbd8c 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -17,6 +17,7 @@ * - [x] Add limit. * - [x] Test in MySQL 5.5, 5.7, MariaDB 10.4. * - [x] Add support for SQLite. + * - [ ] Use get_cache_group * - [ ] Add caching to SQLite * - [ ] Add tests for single event occasions. * - [ ] Add tests for log row notifier. @@ -228,7 +229,7 @@ protected function query_overview_mysql( $args ) { // Create cache key based on args and current user. $cache_key = md5( __METHOD__ . serialize( $args ) ) . '_userid_' . get_current_user_id(); - $cache_group = 'simple-history-' . Helpers::get_cache_incrementor(); + $cache_group = Helpers::get_cache_group(); /** @var array Return value. */ $arr_return = wp_cache_get( $cache_key, $cache_group ); @@ -510,7 +511,7 @@ protected function query_overview_mysql( $args ) { protected function query_occasions( $args ) { // Create cache key based on args and current user. $cache_key = 'SimpleHistoryLogQuery_' . md5( serialize( $args ) ) . '_userid_' . get_current_user_id(); - $cache_group = 'simple-history-' . Helpers::get_cache_incrementor(); + $cache_group = Helpers::get_cache_group(); /** @var array Return value. */ $arr_return = wp_cache_get( $cache_key, $cache_group ); From 809b05b8d3852bcdde1afff93d2f776f2244c3f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Wed, 27 Dec 2023 08:19:03 +0100 Subject: [PATCH 38/59] Use Helpers::clear_cache(); --- inc/class-helpers.php | 2 +- inc/class-log-query.php | 21 ++++----------------- inc/services/class-setup-purge-db-cron.php | 2 +- loggers/class-logger.php | 2 +- 4 files changed, 7 insertions(+), 20 deletions(-) diff --git a/inc/class-helpers.php b/inc/class-helpers.php index 74fa3a45..48fe92b3 100644 --- a/inc/class-helpers.php +++ b/inc/class-helpers.php @@ -900,7 +900,7 @@ public static function clear_log() { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $wpdb->query( $sql ); - self::get_cache_incrementor( true ); + self::clear_cache(); return $num_rows; } diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 821cbd8c..d6001a23 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -17,7 +17,8 @@ * - [x] Add limit. * - [x] Test in MySQL 5.5, 5.7, MariaDB 10.4. * - [x] Add support for SQLite. - * - [ ] Use get_cache_group + * - [x] Use get_cache_group + * - [ ] Use clear_cache instead of (true) * - [ ] Add caching to SQLite * - [ ] Add tests for single event occasions. * - [ ] Add tests for log row notifier. @@ -75,6 +76,7 @@ public function query( $args ) { * * @param string|array|object $args Arguments. * @return array Log rows. + * @throws \ErrorException If invalid DB engine. */ public function query_overview( $args ) { $db_engine = $this->get_db_engine(); @@ -146,7 +148,7 @@ protected function query_overview_sqlite( $args ) { ); $result_log_rows = $wpdb->get_results( $sql_query_log_rows, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - // sh_d('$result_log_rows', $result_log_rows);exit; + if ( ! empty( $wpdb->last_error ) ) { sh_d( '$wpdb->last_error', $wpdb->last_error ); sh_d( '$sql_query_log_rows', $sql_query_log_rows ); @@ -204,18 +206,9 @@ protected function query_overview_sqlite( $args ) { 'max_id' => (int) $max_id, 'min_id' => (int) $min_id, 'log_rows_count' => $log_rows_count, - // Remove id from keys, because they are cumbersome when working with JSON. 'log_rows' => $result_log_rows, - // Add sql query to debug. - // 'outer_where_array' => $outer_where_array, - // 'inner_where_array' => $inner_where_array, - // 'sql' => $sql, - // 'sql_context' => $sql_context ?? null, - // 'context_results' => $context_results ?? null, ]; - // sh_d( '$arr_return', $arr_return);exit; - return $arr_return; } @@ -482,12 +475,6 @@ protected function query_overview_mysql( $args ) { 'log_rows_count' => $log_rows_count, // Remove id from keys, because they are cumbersome when working with JSON. 'log_rows' => $result_log_rows, - // Add sql query to debug. - // 'outer_where_array' => $outer_where_array, - // 'inner_where_array' => $inner_where_array, - // 'sql' => $sql, - // 'sql_context' => $sql_context ?? null, - // 'context_results' => $context_results ?? null, ]; wp_cache_set( $cache_key, $arr_return, $cache_group ); diff --git a/inc/services/class-setup-purge-db-cron.php b/inc/services/class-setup-purge-db-cron.php index 26eee102..57a1745b 100644 --- a/inc/services/class-setup-purge-db-cron.php +++ b/inc/services/class-setup-purge-db-cron.php @@ -132,7 +132,7 @@ public function purge_db() { */ do_action( 'simple_history/db/events_purged', $days, $num_rows_purged ); - Helpers::get_cache_incrementor( true ); + Helpers::clear_cache(); } } } diff --git a/loggers/class-logger.php b/loggers/class-logger.php index cc760ed2..f291f2a9 100644 --- a/loggers/class-logger.php +++ b/loggers/class-logger.php @@ -1306,7 +1306,7 @@ public function log( $level = 'info', $message = '', $context = array() ) { $this->last_insert_context = $context; $this->last_insert_data = $data; - Helpers::get_cache_incrementor( true ); + Helpers::clear_cache(); /** * Fired after an event has been logged. From 13a2bd003a12bccc6297561b4716150b9ba1b4e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Wed, 27 Dec 2023 15:59:24 +0100 Subject: [PATCH 39/59] Use constant() to fix warning in editor --- inc/class-log-query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index d6001a23..27c3f270 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -1302,7 +1302,7 @@ protected function get_outer_where( $args ) { * @return string */ public static function get_db_engine() { - $db_engine = defined( 'DB_ENGINE' ) && 'sqlite' === DB_ENGINE ? 'sqlite' : 'mysql'; + $db_engine = defined( 'DB_ENGINE' ) && constant( 'DB_ENGINE' ) === 'sqlite' ? 'sqlite' : 'mysql'; return $db_engine; } } From bee4f3a98e3c48bd1f3346c05a36674d21972c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Wed, 27 Dec 2023 16:29:41 +0100 Subject: [PATCH 40/59] Add support for sqlite in get_num_events_per_day_last_n_days()x --- inc/class-helpers.php | 60 ++++++++++++++++++++++++++++------------- inc/class-log-query.php | 2 +- readme.txt | 1 + 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/inc/class-helpers.php b/inc/class-helpers.php index 48fe92b3..e48eed45 100644 --- a/inc/class-helpers.php +++ b/inc/class-helpers.php @@ -1146,31 +1146,55 @@ public static function get_num_events_last_n_days( $period_days = 28 ) { */ public static function get_num_events_per_day_last_n_days( $period_days = 28 ) { $simple_history = Simple_History::get_instance(); - $transient_key = 'sh_' . md5( __METHOD__ . $period_days . '_2' ); + $transient_key = 'sh_' . md5( __METHOD__ . $period_days . '_3' ); $dates = get_transient( $transient_key ); if ( false === $dates ) { + /** @var \wpdb $wpdb */ global $wpdb; $sqlStringLoggersUserCanRead = $simple_history->get_loggers_that_user_can_read( null, 'sql' ); - $sql = sprintf( - ' - SELECT - date_format(date, "%%Y-%%m-%%d") AS yearDate, - count(date) AS count - FROM - %1$s - WHERE - UNIX_TIMESTAMP(date) >= %2$d - AND logger IN (%3$d) - GROUP BY yearDate - ORDER BY yearDate ASC - ', - $simple_history->get_events_table_name(), - strtotime( "-$period_days days" ), - $sqlStringLoggersUserCanRead - ); + $db_engine = Log_Query::get_db_engine(); + + if ( $db_engine === 'mysql' ) { + $sql = sprintf( + ' + SELECT + date_format(date, "%%Y-%%m-%%d") AS yearDate, + count(date) AS count + FROM + %1$s + WHERE + UNIX_TIMESTAMP(date) >= %2$d + AND logger IN %3$s + GROUP BY yearDate + ORDER BY yearDate ASC + ', + $simple_history->get_events_table_name(), + strtotime( "-$period_days days" ), + $sqlStringLoggersUserCanRead + ); + } elseif ( $db_engine === 'sqlite' ) { + // SQLite does not support date_format() or UNIX_TIMESTAMP so we need to use strftime(). + $sql = sprintf( + ' + SELECT + strftime("%%Y-%%m-%%d", date) AS yearDate, + count(date) AS count + FROM + %1$s + WHERE + unixepoch(date) >= %2$d + AND logger IN %3$s + GROUP BY yearDate + ORDER BY yearDate ASC + ', + $simple_history->get_events_table_name(), + strtotime( "-$period_days days" ), + $sqlStringLoggersUserCanRead + ); + } $dates = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 27c3f270..57e4b5d8 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -1299,7 +1299,7 @@ protected function get_outer_where( $args ) { * https://wordpress.org/plugins/sqlite-database-integration/ is in use, * and we need to use SQLite specific SQL at some places. * - * @return string + * @return string "mysql" or "sqlite" */ public static function get_db_engine() { $db_engine = defined( 'DB_ENGINE' ) && constant( 'DB_ENGINE' ) === 'sqlite' ? 'sqlite' : 'mysql'; diff --git a/readme.txt b/readme.txt index c57fac3f..4eb418ac 100644 --- a/readme.txt +++ b/readme.txt @@ -214,6 +214,7 @@ Unreleased - IP addresses are now shown on occasions. - Remove columns "rep", "repeated" and "occasionsIDType" from Log_Query response. - Add helper functions `get_cache_group()`, `clear_cache()`. +- Fix stats widget counting due to incorrect loggers included in stats query. ### 4.8.0 (December 2023) From 88e91a99c72b4e8ee62e6f202393d63858e2343d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Fri, 29 Dec 2023 12:58:16 +0100 Subject: [PATCH 41/59] Log query: Use different queries for lastdays depending on db engine --- inc/class-log-query.php | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 57e4b5d8..99b40310 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -18,7 +18,8 @@ * - [x] Test in MySQL 5.5, 5.7, MariaDB 10.4. * - [x] Add support for SQLite. * - [x] Use get_cache_group - * - [ ] Use clear_cache instead of (true) + * - [x] Use clear_cache instead of (true) + * - [ ] Date filtering is broken (sql where clause missing/not added) * - [ ] Add caching to SQLite * - [ ] Add tests for single event occasions. * - [ ] Add tests for log row notifier. @@ -115,8 +116,6 @@ protected function query_overview_sqlite( $args ) { $sql_statement_log_rows = ' SELECT simple_history_1.id, - /* maxId, */ - /* minId, */ simple_history_1.logger, simple_history_1.level, simple_history_1.date, @@ -996,8 +995,8 @@ protected function get_inner_where( $args ) { global $wpdb; $simple_history = Simple_History::get_instance(); - $contexts_table_name = $simple_history->get_contexts_table_name(); + $db_engine = $this->get_db_engine(); /** @var array Where clauses for inner query. */ $inner_where = []; @@ -1089,10 +1088,17 @@ protected function get_inner_where( $args ) { // Add where clause for "lastdays", as int. if ( ! empty( $args['lastdays'] ) ) { - $inner_where[] = sprintf( - 'date >= DATE(NOW()) - INTERVAL %d DAY', - $args['lastdays'] - ); + if ( $db_engine === 'mysql' ) { + $inner_where[] = sprintf( + 'date >= DATE(NOW() - INTERVAL %d DAY)', + $args['lastdays'] + ); + } elseif ( $db_engine === 'sqlite' ) { + $inner_where[] = sprintf( + 'date >= datetime("now", "-%d days")', + $args['lastdays'] + ); + } } // months, in format "Y-m". From 4ab73b49dbe832ea43cea8e619667f2df183bca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sun, 31 Dec 2023 20:32:35 +0100 Subject: [PATCH 42/59] Typos --- inc/class-log-query.php | 6 +++--- tests/wpunit/LogQueryTest.php | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 99b40310..9918328b 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -485,7 +485,7 @@ protected function query_overview_mysql( $args ) { * Get occasions for a single event. * * Required args are: - * - occasionsID: The id to get occassions for. + * - occasionsID: The id to get occasions for. * - occasionsCount: The number of occasions to get. * - occasionsCountMaxReturn: The max number of occasions to return. * @@ -527,10 +527,10 @@ protected function query_occasions( $args ) { // Get occasions for a single event. // Args must contain: - // - occasionsID: The id to get occassions for + // - occasionsID: The id to get occasions for // - occasionsCount: The number of occasions to get. // - occasionsCountMaxReturn: The max number of occasions to return, - // if occassionsCount is very large and we do not want to get all occassions. + // if occasionsCount is very large and we do not want to get all occasions. /** * @var string $sql_statement_template SQL template for occasions query. diff --git a/tests/wpunit/LogQueryTest.php b/tests/wpunit/LogQueryTest.php index e85b1508..6a7ada7e 100644 --- a/tests/wpunit/LogQueryTest.php +++ b/tests/wpunit/LogQueryTest.php @@ -180,40 +180,40 @@ function test_query() { ); - // Test occassions query arg. for first returned row. + // Test occasions query arg. for first returned row. $query_results = (new Log_Query())->query([ 'type' => 'occasions', // Get history rows with id:s less than this, i.e. get earlier/previous rows. 'logRowID' => $first_log_row_from_query->id, - 'occasionsID' => $first_log_row_from_query->occasionsID, // The occassions id is md5:ed so we need to use log query to get the last row, and then get ocassions id.. + 'occasionsID' => $first_log_row_from_query->occasionsID, // The occasions id is md5:ed so we need to use log query to get the last row, and then get ocassions id.. 'occasionsCount' => $first_log_row_from_query->subsequentOccasions - 1, ]); $this->assertCount( $oh_such_logging_rows_num_to_add - 1, $query_results['log_rows'], - 'The number of rows returned when getting occassions should be ' . ($oh_such_logging_rows_num_to_add - 1) + 'The number of rows returned when getting occasions should be ' . ($oh_such_logging_rows_num_to_add - 1) ); - // Test occassions query arg. for second returned row. + // Test occasions query arg. for second returned row. $query_results = (new Log_Query())->query([ 'type' => 'occasions', 'logRowID' => $second_log_row_from_query->id, - 'occasionsID' => $second_log_row_from_query->occasionsID, // The occassions id is md5:ed so we need to use log query to get the last row, and then get ocassions id.. + 'occasionsID' => $second_log_row_from_query->occasionsID, // The occasions id is md5:ed so we need to use log query to get the last row, and then get ocassions id.. 'occasionsCount' => $second_log_row_from_query->subsequentOccasions - 1, ]); $this->assertCount( $hello_some_messages_message_count - 1, $query_results['log_rows'], - 'The number of rows returned when getting occassions should be ' . ($hello_some_messages_message_count - 1) + 'The number of rows returned when getting occasions should be ' . ($hello_some_messages_message_count - 1) ); - // Test occassions query arg. for third returned row. + // Test occasions query arg. for third returned row. $query_results = (new Log_Query())->query([ 'type' => 'occasions', 'logRowID' => $third_log_row_from_query->id, - 'occasionsID' => $third_log_row_from_query->occasionsID, // The occassions id is md5:ed so we need to use log query to get the last row, and then get ocassions id.. + 'occasionsID' => $third_log_row_from_query->occasionsID, // The occasions id is md5:ed so we need to use log query to get the last row, and then get ocassions id.. 'occasionsCount' => $third_log_row_from_query->subsequentOccasions - 1, ]); @@ -221,13 +221,13 @@ function test_query() { $this->assertSame( "1", $third_log_row_from_query->subsequentOccasions, - 'The number of rows returned when getting occassions should be 0' + 'The number of rows returned when getting occasions should be 0' ); $this->assertCount( 0, $query_results['log_rows'], - 'The number of rows returned when getting occassions should be 0' + 'The number of rows returned when getting occasions should be 0' ); } From 670b1ea88bee134318317bf8dd359d86d47a4b30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sun, 31 Dec 2023 20:32:57 +0100 Subject: [PATCH 43/59] Add dropin that adds information about add-ons in the sidebar --- dropins/class-sidebar-add-ons-dropin.php | 65 +++++++++++++++++++++++ dropins/class-sidebar-settings-dropin.php | 2 +- inc/class-simple-history.php | 1 + 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 dropins/class-sidebar-add-ons-dropin.php diff --git a/dropins/class-sidebar-add-ons-dropin.php b/dropins/class-sidebar-add-ons-dropin.php new file mode 100644 index 00000000..75fa852f --- /dev/null +++ b/dropins/class-sidebar-add-ons-dropin.php @@ -0,0 +1,65 @@ + +
+ +

+ + New +

+ +
+

+ +

+ + +
  • + + +
  • + +
  • + + +
  • + +
  • + + +
  • + + */ + ?> + +

    + + + +

    +
    +
    + Date: Sun, 31 Dec 2023 20:35:36 +0100 Subject: [PATCH 44/59] Typos --- inc/class-log-query.php | 10 +++++----- tests/wpunit/LogQueryTest.php | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 9918328b..28fc2c30 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -94,8 +94,8 @@ public function query_overview( $args ) { } /** - * SQLite compatiable version of query_overview_mysql(). - * Main difference is that the SQL query is much simplier, + * SQLite compatible version of query_overview_mysql(). + * Main difference is that the SQL query is simpler, * because it does not support occasions. * * @param string|array|object $args Arguments. @@ -788,7 +788,7 @@ protected function prepare_args( $args ) { $args['loglevels'] = explode( ',', $args['loglevels'] ); } - // Make sure loglevels are trimed, strings, and empty vals removed. + // Make sure loglevels are trimmed, strings, and empty vals removed. if ( isset( $args['loglevels'] ) ) { $args['loglevels'] = array_map( 'trim', $args['loglevels'] ); $args['loglevels'] = array_map( 'strval', $args['loglevels'] ); @@ -817,7 +817,7 @@ protected function prepare_args( $args ) { $args['messages'] = $arr_messages; } - // Make sure messages are trimed, strings, and empty vals removed. + // Make sure messages are trimmed, strings, and empty vals removed. if ( isset( $args['messages'] ) ) { $args['messages'] = array_map( 'trim', $args['messages'] ); $args['messages'] = array_map( 'strval', $args['messages'] ); @@ -910,7 +910,7 @@ protected function add_contexts_to_log_rows( $log_rows ) { } // Move up _message_key from context row to main row as context_message_key. - // This is beacuse that's the way it was before SQL was rewritten + // This is because that's the way it was before SQL was rewritten // to support FULL_GROUP_BY in December 2023. foreach ( $log_rows as $log_row ) { if ( isset( $log_row->context['_message_key'] ) ) { diff --git a/tests/wpunit/LogQueryTest.php b/tests/wpunit/LogQueryTest.php index 6a7ada7e..0712fef3 100644 --- a/tests/wpunit/LogQueryTest.php +++ b/tests/wpunit/LogQueryTest.php @@ -185,7 +185,7 @@ function test_query() { 'type' => 'occasions', // Get history rows with id:s less than this, i.e. get earlier/previous rows. 'logRowID' => $first_log_row_from_query->id, - 'occasionsID' => $first_log_row_from_query->occasionsID, // The occasions id is md5:ed so we need to use log query to get the last row, and then get ocassions id.. + 'occasionsID' => $first_log_row_from_query->occasionsID, // The occasions id is md5:ed so we need to use log query to get the last row, and then get occasions id.. 'occasionsCount' => $first_log_row_from_query->subsequentOccasions - 1, ]); @@ -199,7 +199,7 @@ function test_query() { $query_results = (new Log_Query())->query([ 'type' => 'occasions', 'logRowID' => $second_log_row_from_query->id, - 'occasionsID' => $second_log_row_from_query->occasionsID, // The occasions id is md5:ed so we need to use log query to get the last row, and then get ocassions id.. + 'occasionsID' => $second_log_row_from_query->occasionsID, // The occasions id is md5:ed so we need to use log query to get the last row, and then get occasions id.. 'occasionsCount' => $second_log_row_from_query->subsequentOccasions - 1, ]); @@ -213,7 +213,7 @@ function test_query() { $query_results = (new Log_Query())->query([ 'type' => 'occasions', 'logRowID' => $third_log_row_from_query->id, - 'occasionsID' => $third_log_row_from_query->occasionsID, // The occasions id is md5:ed so we need to use log query to get the last row, and then get ocassions id.. + 'occasionsID' => $third_log_row_from_query->occasionsID, // The occasions id is md5:ed so we need to use log query to get the last row, and then get occasions id. 'occasionsCount' => $third_log_row_from_query->subsequentOccasions - 1, ]); From 4109fd0ef0276f1c8368fe48437555ac61e334a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Mon, 1 Jan 2024 21:25:53 +0100 Subject: [PATCH 45/59] Add text to "purged_events" message with information that the number of days to keep the log can be changed --- inc/services/class-setup-purge-db-cron.php | 4 ++++ loggers/class-simple-history-logger.php | 26 +++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/inc/services/class-setup-purge-db-cron.php b/inc/services/class-setup-purge-db-cron.php index 57a1745b..dd180dcf 100644 --- a/inc/services/class-setup-purge-db-cron.php +++ b/inc/services/class-setup-purge-db-cron.php @@ -40,6 +40,7 @@ public function setup_cron() { public function maybe_purge_db() { /** * Day of week today. + * * @int $current_day_of_week */ $current_day_of_week = (int) gmdate( 'N' ); @@ -67,6 +68,9 @@ public function maybe_purge_db() { /** * Removes old entries from the db. + * + * Removes in batches of 100 000 rows. + * */ public function purge_db() { $do_purge_history = true; diff --git a/loggers/class-simple-history-logger.php b/loggers/class-simple-history-logger.php index ef9b05be..d7e91a4b 100644 --- a/loggers/class-simple-history-logger.php +++ b/loggers/class-simple-history-logger.php @@ -160,10 +160,34 @@ public function on_updated_option( $option, $old_value, $new_value ) { /** * Get the log row details for this logger. * - * @param array $row Log row. + * @param object $row Log row. * @return Event_Details_Group */ public function get_log_row_details_output( $row ) { + + $message_key = $row->context_message_key; + + if ( $message_key === 'purged_events' ) { + // For message "Removed 24318 events that were older than 60 days" + // add a text with a link with information on how to modify this. + $message = sprintf( + /* translators: 1 is a link to webpage with info about how to modify number of days to keep the log */ + __( 'The number of days the log is kept can be changed using a filter or an add-on. More info.', 'simple-history' ), + esc_url( 'https://simple-history.com/support/change-number-of-days-to-keep-log/?utm_source=wpadmin' ) + ); + + return '

    ' . wp_kses( + $message, + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'class' => [], + ], + ] + ) . '

    '; + } + $event_details_group = ( new Event_Details_Group() ) ->add_items( [ From 01d1ddc288d4a50a0ed793ce3703f6fa4f68311d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Mon, 1 Jan 2024 21:35:36 +0100 Subject: [PATCH 46/59] Add text to the clear log settings text with info how to modify number of days to keep the log --- inc/services/class-setup-settings-page.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/inc/services/class-setup-settings-page.php b/inc/services/class-setup-settings-page.php index 414630a5..4c8ad1d3 100644 --- a/inc/services/class-setup-settings-page.php +++ b/inc/services/class-setup-settings-page.php @@ -341,6 +341,25 @@ public function settings_field_clear_log() { esc_html__( 'Items in the database are automatically removed after %1$s days.', 'simple-history' ), esc_html( $clear_days ) ); + echo '
    '; + + $message = sprintf( + /* translators: 1 is a link to webpage with info about how to modify number of days to keep the log */ + __( 'The number of days can be changed using a filter or with an add-on. More info.', 'simple-history' ), + esc_url( 'https://simple-history.com/support/change-number-of-days-to-keep-log/?utm_source=wpadmin' ) + ); + + echo '

    ' . wp_kses( + $message, + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'class' => [], + ], + ] + ) . '

    '; + } else { esc_html_e( 'Items in the database are kept forever.', 'simple-history' ); } From e2e51869ac6f2199ebbe81a86128550c2492c323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Fri, 5 Jan 2024 14:09:21 +0100 Subject: [PATCH 47/59] Check date filtering works --- inc/class-log-query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 28fc2c30..55cd7383 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -19,7 +19,7 @@ * - [x] Add support for SQLite. * - [x] Use get_cache_group * - [x] Use clear_cache instead of (true) - * - [ ] Date filtering is broken (sql where clause missing/not added) + * - [x] Date filtering is broken (sql where clause missing/not added) * - [ ] Add caching to SQLite * - [ ] Add tests for single event occasions. * - [ ] Add tests for log row notifier. From 5461eebd0b5d6de9ef8421902f1edcf2ec7381ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sun, 7 Jan 2024 15:52:01 +0100 Subject: [PATCH 48/59] Readme: Add note about add-ons to sponsor headline --- readme.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index 4eb418ac..e0a0eb1d 100644 --- a/readme.txt +++ b/readme.txt @@ -164,12 +164,14 @@ If you want to translate Simple History to your language then read about how thi Development of this plugin takes place at GitHub. Please join in with feature requests, bug reports, or even pull requests! https://github.com/bonny/WordPress-Simple-History -### Donation +### Sponsor this project If you like this plugin please consider donating to support the development. You can [donate using PayPal](https://www.paypal.me/eskapism) or you can [become a GitHub sponsor](https://github.com/sponsors/bonny). +There is also some [add-ons](https://simple-history.com/add-ons/) that you can buy to support the development of this plugin and get some extra features. + ## Frequently Asked Questions = Can I add my own events to the log? = From 8c42b61a80a9d8087385c85dc7242a3ebe3d4069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sun, 7 Jan 2024 15:52:15 +0100 Subject: [PATCH 49/59] Log query: Add caching to query_overview_sqlite() --- inc/class-log-query.php | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 55cd7383..cea435e6 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -102,11 +102,25 @@ public function query_overview( $args ) { * @return array Log rows. */ protected function query_overview_sqlite( $args ) { - $Simple_History = Simple_History::get_instance(); + + $args = $this->prepare_args( $args ); + + // Create cache key based on args and current user. + $cache_key = md5( __METHOD__ . serialize( $args ) ) . '_userid_' . get_current_user_id(); + $cache_group = Helpers::get_cache_group(); + + /** @var array|false Return value. */ + $arr_return = wp_cache_get( $cache_key, $cache_group ); + + // Return cached value if it exists. + if ( false !== $arr_return ) { + $arr_return['cached_result'] = true; + return $arr_return; + } global $wpdb; - $args = $this->prepare_args( $args ); + $Simple_History = Simple_History::get_instance(); /** * @var string SQL template used to get all events from the ones @@ -208,6 +222,8 @@ protected function query_overview_sqlite( $args ) { 'log_rows' => $result_log_rows, ]; + wp_cache_set( $cache_key, $arr_return, $cache_group ); + return $arr_return; } @@ -223,7 +239,7 @@ protected function query_overview_mysql( $args ) { $cache_key = md5( __METHOD__ . serialize( $args ) ) . '_userid_' . get_current_user_id(); $cache_group = Helpers::get_cache_group(); - /** @var array Return value. */ + /** @var array|false Return value. */ $arr_return = wp_cache_get( $cache_key, $cache_group ); // Return cached value if it exists. @@ -232,10 +248,10 @@ protected function query_overview_mysql( $args ) { return $arr_return; } - $Simple_History = Simple_History::get_instance(); - global $wpdb; + $Simple_History = Simple_History::get_instance(); + $wpdb->query( 'SET @a:=NULL, @counter:=1, @groupby:=0' ); /** @@ -433,9 +449,8 @@ protected function query_overview_mysql( $args ) { ## END SQL_STATEMENT_LOG_ROWS '; - // TODO: - // create $max_ids_and_count_sql_statement without limit. - // Then use that to get count(*). + // Create $max_ids_and_count_sql_statement without limit, + // to get count(*). $max_ids_and_count_without_limit_sql_statement = sprintf( $sql_statement_max_ids_and_count_template, $Simple_History->get_events_table_name(), // 1 From 924c6bd5355b44855e0d1f307e8d339eafb8c5df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sun, 7 Jan 2024 16:14:48 +0100 Subject: [PATCH 50/59] Tests: Add tests for single event occasions --- dropins/class-new-rows-notifier-dropin.php | 1 - inc/class-log-query.php | 5 +- tests/wpunit/LogQueryTest.php | 81 ++++++++++++++-------- 3 files changed, 56 insertions(+), 31 deletions(-) diff --git a/dropins/class-new-rows-notifier-dropin.php b/dropins/class-new-rows-notifier-dropin.php index ac65c7c5..5e8e91cb 100644 --- a/dropins/class-new-rows-notifier-dropin.php +++ b/dropins/class-new-rows-notifier-dropin.php @@ -90,7 +90,6 @@ public function ajax() { ); } - // $since_id = isset( $_GET["since_id"] ) ? absint($_GET["since_id"]) : null; $logQueryArgs = $apiArgs; $logQuery = new Log_Query(); diff --git a/inc/class-log-query.php b/inc/class-log-query.php index cea435e6..1ebcfaa2 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -20,8 +20,8 @@ * - [x] Use get_cache_group * - [x] Use clear_cache instead of (true) * - [x] Date filtering is broken (sql where clause missing/not added) - * - [ ] Add caching to SQLite - * - [ ] Add tests for single event occasions. + * - [x] Add caching to SQLite + * - [x] Add tests for single event occasions. * - [ ] Add tests for log row notifier. * - [ ] Run PHPStan and Rector. * - [ ] Merge together all git commits to one commit with close-##-messages. @@ -102,7 +102,6 @@ public function query_overview( $args ) { * @return array Log rows. */ protected function query_overview_sqlite( $args ) { - $args = $this->prepare_args( $args ); // Create cache key based on args and current user. diff --git a/tests/wpunit/LogQueryTest.php b/tests/wpunit/LogQueryTest.php index 0712fef3..dfb6f81e 100644 --- a/tests/wpunit/LogQueryTest.php +++ b/tests/wpunit/LogQueryTest.php @@ -1,34 +1,27 @@ markTestIncomplete('This test will fail in Mysql >5.5 and MariaDB until SQL bug is fixed.'); - // Add and set current user to admin user, so user can read all logs. $user_id = $this->factory->user->create( array( @@ -62,13 +55,6 @@ function test_query() { $query_results = (new Log_Query())->query( ['posts_per_page' => 1] ); $first_log_row_from_query = $query_results['log_rows'][0]; - // On MariaDB $first_log_row->id is the same as the value in $added_rows_id[0] (the first added row) - // but it should be the id from the last added row, i.e. the value in $added_rows_id[9]. - // sh_d('$first_log_row id', $first_log_row_from_query->id); - // sh_d('$added_rows_id[0]', $added_rows_ids[0]); - // sh_d('$added_rows_id[max]', $added_rows_ids[$num_rows_to_add-1]); - - // $this->markTestIncomplete('This test will fail in MariaDB until bug is fixed.'); $this->assertEquals( $added_rows_ids[$num_rows_to_add-1], $first_log_row_from_query->id, @@ -231,6 +217,47 @@ function test_query() { ); } + function test_since_id() { + // Add and set current user to admin user, so user can read all logs. + $user_id = $this->factory->user->create( + array( + 'role' => 'administrator', + ) + ); + wp_set_current_user( $user_id ); + + $logger = SimpleLogger()->info( + 'Test info message 1', + [ + '_occasionsID' => 'my_occasion_id', + 'message_num' => 1, + ] + ); + + $last_insert_id = $logger->last_insert_id; + + // Query log again and use $last_insert_id as since_id. + // It should not have any new rows yet. + $query_results = (new Log_Query())->query( ['since_id' => $last_insert_id] ); + $this->assertEmpty($query_results['log_rows'], 'There should be no new rows yet.'); + + // Add two new events. + for ($i=0; $i<2; $i++) { + echo "log $i"; + $logger = SimpleLogger()->info( + 'Test info message ' . $i, + [ + '_occasionsID' => 'my_occasion_id_in_loop_' . $i, + 'message_num' => $i, + ] + ); + } + + // Test that we get two new rows. + $query_results = (new Log_Query())->query( ['since_id' => $last_insert_id] ); + $this->assertEquals(2, $query_results['total_row_count'], 'There should be two new rows now.'); + } + /** * Test that the Log_Query returns the expected things. */ From 951c90457b0ca3b79d1d1509325346a6d40f3c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sun, 7 Jan 2024 16:27:08 +0100 Subject: [PATCH 51/59] Update composer packages --- composer.lock | 701 +++++++++++++++++++++++++++++--------------------- 1 file changed, 413 insertions(+), 288 deletions(-) diff --git a/composer.lock b/composer.lock index 561a46a8..eb5bb825 100644 --- a/composer.lock +++ b/composer.lock @@ -9,16 +9,16 @@ "packages-dev": [ { "name": "antecedent/patchwork", - "version": "2.1.26", + "version": "2.1.27", "source": { "type": "git", "url": "https://github.com/antecedent/patchwork.git", - "reference": "f2dae0851b2eae4c51969af740fdd0356d7f8f55" + "reference": "16a1ab81559aabf14acb616141e801b32777f085" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antecedent/patchwork/zipball/f2dae0851b2eae4c51969af740fdd0356d7f8f55", - "reference": "f2dae0851b2eae4c51969af740fdd0356d7f8f55", + "url": "https://api.github.com/repos/antecedent/patchwork/zipball/16a1ab81559aabf14acb616141e801b32777f085", + "reference": "16a1ab81559aabf14acb616141e801b32777f085", "shasum": "" }, "require": { @@ -39,7 +39,7 @@ } ], "description": "Method redefinition (monkey-patching) functionality for PHP.", - "homepage": "http://patchwork2.org/", + "homepage": "https://antecedent.github.io/patchwork/", "keywords": [ "aop", "aspect", @@ -51,9 +51,9 @@ ], "support": { "issues": "https://github.com/antecedent/patchwork/issues", - "source": "https://github.com/antecedent/patchwork/tree/2.1.26" + "source": "https://github.com/antecedent/patchwork/tree/2.1.27" }, - "time": "2023-09-18T08:18:37+00:00" + "time": "2023-12-03T18:46:49+00:00" }, { "name": "behat/gherkin", @@ -174,6 +174,75 @@ }, "time": "2012-08-31T00:00:00+00:00" }, + { + "name": "carbonphp/carbon-doctrine-types", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", + "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "doctrine/dbal": "<3.7.0 || >=4.0.0" + }, + "require-dev": { + "doctrine/dbal": "^3.7.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/2.1.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2023-12-11T17:09:12+00:00" + }, { "name": "codeception/codeception", "version": "4.2.2", @@ -836,16 +905,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.3.7", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "76e46335014860eec1aa5a724799a00a2e47cc85" + "reference": "b66d11b7479109ab547f9405b97205640b17d385" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/76e46335014860eec1aa5a724799a00a2e47cc85", - "reference": "76e46335014860eec1aa5a724799a00a2e47cc85", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/b66d11b7479109ab547f9405b97205640b17d385", + "reference": "b66d11b7479109ab547f9405b97205640b17d385", "shasum": "" }, "require": { @@ -857,7 +926,7 @@ "phpstan/phpstan": "^0.12.55", "psr/log": "^1.0", "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0" + "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", "extra": { @@ -892,7 +961,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.3.7" + "source": "https://github.com/composer/ca-bundle/tree/1.4.0" }, "funding": [ { @@ -908,7 +977,7 @@ "type": "tidelift" } ], - "time": "2023-08-30T09:31:38+00:00" + "time": "2023-12-18T12:05:55+00:00" }, { "name": "composer/composer", @@ -1232,16 +1301,16 @@ }, { "name": "composer/spdx-licenses", - "version": "1.5.7", + "version": "1.5.8", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "c848241796da2abf65837d51dce1fae55a960149" + "reference": "560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/c848241796da2abf65837d51dce1fae55a960149", - "reference": "c848241796da2abf65837d51dce1fae55a960149", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a", + "reference": "560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a", "shasum": "" }, "require": { @@ -1290,9 +1359,9 @@ "validator" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/spdx-licenses/issues", - "source": "https://github.com/composer/spdx-licenses/tree/1.5.7" + "source": "https://github.com/composer/spdx-licenses/tree/1.5.8" }, "funding": [ { @@ -1308,7 +1377,7 @@ "type": "tidelift" } ], - "time": "2022-05-23T07:37:50+00:00" + "time": "2023-11-20T07:44:33+00:00" }, { "name": "composer/xdebug-handler", @@ -1873,16 +1942,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.8.0", + "version": "7.8.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9" + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/1110f66a6530a40fe7aea0378fe608ee2b2248f9", - "reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", "shasum": "" }, "require": { @@ -1897,11 +1966,11 @@ "psr/http-client-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -1979,7 +2048,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.8.0" + "source": "https://github.com/guzzle/guzzle/tree/7.8.1" }, "funding": [ { @@ -1995,28 +2064,28 @@ "type": "tidelift" } ], - "time": "2023-08-27T10:20:53+00:00" + "time": "2023-12-03T20:35:24+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "111166291a0f8130081195ac4556a5587d7f1b5d" + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/111166291a0f8130081195ac4556a5587d7f1b5d", - "reference": "111166291a0f8130081195ac4556a5587d7f1b5d", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "type": "library", "extra": { @@ -2062,7 +2131,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.1" + "source": "https://github.com/guzzle/promises/tree/2.0.2" }, "funding": [ { @@ -2078,20 +2147,20 @@ "type": "tidelift" } ], - "time": "2023-08-03T15:11:55+00:00" + "time": "2023-12-03T20:19:20+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.6.1", + "version": "2.6.2", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727" + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/be45764272e8873c72dbe3d2edcfdfcc3bc9f727", - "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", "shasum": "" }, "require": { @@ -2105,9 +2174,9 @@ "psr/http-message-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -2178,7 +2247,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.1" + "source": "https://github.com/guzzle/psr7/tree/2.6.2" }, "funding": [ { @@ -2194,7 +2263,7 @@ "type": "tidelift" } ], - "time": "2023-08-27T10:13:57+00:00" + "time": "2023-12-03T20:05:35+00:00" }, { "name": "illuminate/collections", @@ -2484,16 +2553,16 @@ }, { "name": "lucatume/wp-browser", - "version": "3.2.1", + "version": "3.2.2", "source": { "type": "git", "url": "https://github.com/lucatume/wp-browser.git", - "reference": "95a189fda634fd52cb44eebbbda3fabe0fdabdb2" + "reference": "88456c4d73ded85132c6bfa594f685a90952630c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lucatume/wp-browser/zipball/95a189fda634fd52cb44eebbbda3fabe0fdabdb2", - "reference": "95a189fda634fd52cb44eebbbda3fabe0fdabdb2", + "url": "https://api.github.com/repos/lucatume/wp-browser/zipball/88456c4d73ded85132c6bfa594f685a90952630c", + "reference": "88456c4d73ded85132c6bfa594f685a90952630c", "shasum": "" }, "require": { @@ -2583,7 +2652,7 @@ ], "support": { "issues": "https://github.com/lucatume/wp-browser/issues", - "source": "https://github.com/lucatume/wp-browser/tree/3.2.1" + "source": "https://github.com/lucatume/wp-browser/tree/3.2.2" }, "funding": [ { @@ -2591,7 +2660,7 @@ "type": "github" } ], - "time": "2023-09-21T06:36:09+00:00" + "time": "2023-11-20T15:05:37+00:00" }, { "name": "mck89/peast", @@ -2912,19 +2981,20 @@ }, { "name": "nesbot/carbon", - "version": "2.71.0", + "version": "2.72.1", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "98276233188583f2ff845a0f992a235472d9466a" + "reference": "2b3b3db0a2d0556a177392ff1a3bf5608fa09f78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/98276233188583f2ff845a0f992a235472d9466a", - "reference": "98276233188583f2ff845a0f992a235472d9466a", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/2b3b3db0a2d0556a177392ff1a3bf5608fa09f78", + "reference": "2b3b3db0a2d0556a177392ff1a3bf5608fa09f78", "shasum": "" }, "require": { + "carbonphp/carbon-doctrine-types": "*", "ext-json": "*", "php": "^7.1.8 || ^8.0", "psr/clock": "^1.0", @@ -2936,8 +3006,8 @@ "psr/clock-implementation": "1.0" }, "require-dev": { - "doctrine/dbal": "^2.0 || ^3.1.4", - "doctrine/orm": "^2.7", + "doctrine/dbal": "^2.0 || ^3.1.4 || ^4.0", + "doctrine/orm": "^2.7 || ^3.0", "friendsofphp/php-cs-fixer": "^3.0", "kylekatarnls/multi-tester": "^2.0", "ondrejmirtes/better-reflection": "*", @@ -3014,20 +3084,20 @@ "type": "tidelift" } ], - "time": "2023-09-25T11:31:05+00:00" + "time": "2023-12-08T23:47:49+00:00" }, { "name": "nikic/php-parser", - "version": "v4.17.1", + "version": "v4.18.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", "shasum": "" }, "require": { @@ -3068,9 +3138,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" }, - "time": "2023-08-13T19:53:39+00:00" + "time": "2023-12-10T21:03:43+00:00" }, { "name": "phar-io/manifest", @@ -3185,25 +3255,27 @@ }, { "name": "php-stubs/wordpress-stubs", - "version": "v6.3.2", + "version": "v6.4.1", "source": { "type": "git", "url": "https://github.com/php-stubs/wordpress-stubs.git", - "reference": "f22b00cacd3b9addc2b07ff48290084503c48574" + "reference": "6d6063cf9464a306ca2a0529705d41312b08500b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/f22b00cacd3b9addc2b07ff48290084503c48574", - "reference": "f22b00cacd3b9addc2b07ff48290084503c48574", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/6d6063cf9464a306ca2a0529705d41312b08500b", + "reference": "6d6063cf9464a306ca2a0529705d41312b08500b", "shasum": "" }, "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", "nikic/php-parser": "^4.13", "php": "^7.4 || ~8.0.0", "php-stubs/generator": "^0.8.3", "phpdocumentor/reflection-docblock": "^5.3", "phpstan/phpstan": "^1.10.12", - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^9.5", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^0.8" }, "suggest": { "paragonie/sodium_compat": "Pure PHP implementation of libsodium", @@ -3224,9 +3296,9 @@ ], "support": { "issues": "https://github.com/php-stubs/wordpress-stubs/issues", - "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.3.2" + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.4.1" }, - "time": "2023-10-14T10:08:05+00:00" + "time": "2023-11-10T00:33:47+00:00" }, { "name": "php-stubs/wp-cli-stubs", @@ -3515,29 +3587,29 @@ }, { "name": "phpcsstandards/phpcsextra", - "version": "1.1.2", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", - "reference": "746c3190ba8eb2f212087c947ba75f4f5b9a58d5" + "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/746c3190ba8eb2f212087c947ba75f4f5b9a58d5", - "reference": "746c3190ba8eb2f212087c947ba75f4f5b9a58d5", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/11d387c6642b6e4acaf0bd9bf5203b8cca1ec489", + "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489", "shasum": "" }, "require": { "php": ">=5.4", - "phpcsstandards/phpcsutils": "^1.0.8", - "squizlabs/php_codesniffer": "^3.7.1" + "phpcsstandards/phpcsutils": "^1.0.9", + "squizlabs/php_codesniffer": "^3.8.0" }, "require-dev": { "php-parallel-lint/php-console-highlighter": "^1.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcsstandards/phpcsdevcs": "^1.1.6", "phpcsstandards/phpcsdevtools": "^1.2.1", - "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "type": "phpcodesniffer-standard", "extra": { @@ -3572,35 +3644,50 @@ ], "support": { "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", + "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy", "source": "https://github.com/PHPCSStandards/PHPCSExtra" }, - "time": "2023-09-20T22:06:18+00:00" + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2023-12-08T16:49:07+00:00" }, { "name": "phpcsstandards/phpcsutils", - "version": "1.0.8", + "version": "1.0.9", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", - "reference": "69465cab9d12454e5e7767b9041af0cd8cd13be7" + "reference": "908247bc65010c7b7541a9551e002db12e9dae70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/69465cab9d12454e5e7767b9041af0cd8cd13be7", - "reference": "69465cab9d12454e5e7767b9041af0cd8cd13be7", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/908247bc65010c7b7541a9551e002db12e9dae70", + "reference": "908247bc65010c7b7541a9551e002db12e9dae70", "shasum": "" }, "require": { "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", "php": ">=5.4", - "squizlabs/php_codesniffer": "^3.7.1 || 4.0.x-dev@dev" + "squizlabs/php_codesniffer": "^3.8.0 || 4.0.x-dev@dev" }, "require-dev": { "ext-filter": "*", "php-parallel-lint/php-console-highlighter": "^1.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcsstandards/phpcsdevcs": "^1.1.6", - "yoast/phpunit-polyfills": "^1.0.5 || ^2.0.0" + "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0" }, "type": "phpcodesniffer-standard", "extra": { @@ -3645,9 +3732,24 @@ "support": { "docs": "https://phpcsutils.com/", "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", + "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy", "source": "https://github.com/PHPCSStandards/PHPCSUtils" }, - "time": "2023-07-16T21:39:41+00:00" + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2023-12-08T14:50:00+00:00" }, { "name": "phpstan/extension-installer", @@ -3695,16 +3797,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.40", + "version": "1.10.54", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "93c84b5bf7669920d823631e39904d69b9c7dc5d" + "reference": "3e25f279dada0adc14ffd7bad09af2e2fc3523bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/93c84b5bf7669920d823631e39904d69b9c7dc5d", - "reference": "93c84b5bf7669920d823631e39904d69b9c7dc5d", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3e25f279dada0adc14ffd7bad09af2e2fc3523bb", + "reference": "3e25f279dada0adc14ffd7bad09af2e2fc3523bb", "shasum": "" }, "require": { @@ -3753,27 +3855,27 @@ "type": "tidelift" } ], - "time": "2023-10-30T14:48:31+00:00" + "time": "2024-01-05T15:50:47+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.29", + "version": "9.2.30", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -3823,7 +3925,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" }, "funding": [ { @@ -3831,7 +3933,7 @@ "type": "github" } ], - "time": "2023-09-19T04:57:46+00:00" + "time": "2023-12-22T06:47:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -4076,16 +4178,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.13", + "version": "9.6.15", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be" + "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3d767f7f9e191eab4189abe41ab37797e30b1be", - "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/05017b80304e0eb3f31d90194a563fd53a6021f1", + "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1", "shasum": "" }, "require": { @@ -4159,7 +4261,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.13" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.15" }, "funding": [ { @@ -4175,7 +4277,7 @@ "type": "tidelift" } ], - "time": "2023-09-19T05:39:22+00:00" + "time": "2023-12-01T16:55:19+00:00" }, { "name": "psr/clock", @@ -4630,23 +4732,23 @@ }, { "name": "react/promise", - "version": "v2.10.0", + "version": "v2.11.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38" + "reference": "1a8460931ea36dc5c76838fec5734d55c88c6831" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38", - "reference": "f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38", + "url": "https://api.github.com/repos/reactphp/promise/zipball/1a8460931ea36dc5c76838fec5734d55c88c6831", + "reference": "1a8460931ea36dc5c76838fec5734d55c88c6831", "shasum": "" }, "require": { "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.36" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, "type": "library", "autoload": { @@ -4690,7 +4792,7 @@ ], "support": { "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v2.10.0" + "source": "https://github.com/reactphp/promise/tree/v2.11.0" }, "funding": [ { @@ -4698,7 +4800,7 @@ "type": "open_collective" } ], - "time": "2023-05-02T15:15:43+00:00" + "time": "2023-11-16T16:16:50+00:00" }, { "name": "rector/rector", @@ -4999,20 +5101,20 @@ }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -5044,7 +5146,7 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" }, "funding": [ { @@ -5052,7 +5154,7 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-22T06:19:30+00:00" }, { "name": "sebastian/diff", @@ -5326,20 +5428,20 @@ }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -5371,7 +5473,7 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" }, "funding": [ { @@ -5379,7 +5481,7 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", @@ -5722,16 +5824,16 @@ }, { "name": "seld/jsonlint", - "version": "1.10.0", + "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "594fd6462aad8ecee0b45ca5045acea4776667f1" + "reference": "76d449a358ece77d6f1d6331c68453e657172202" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/594fd6462aad8ecee0b45ca5045acea4776667f1", - "reference": "594fd6462aad8ecee0b45ca5045acea4776667f1", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/76d449a358ece77d6f1d6331c68453e657172202", + "reference": "76d449a358ece77d6f1d6331c68453e657172202", "shasum": "" }, "require": { @@ -5758,7 +5860,7 @@ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "homepage": "https://seld.be" } ], "description": "JSON Linter", @@ -5770,7 +5872,7 @@ ], "support": { "issues": "https://github.com/Seldaek/jsonlint/issues", - "source": "https://github.com/Seldaek/jsonlint/tree/1.10.0" + "source": "https://github.com/Seldaek/jsonlint/tree/1.10.1" }, "funding": [ { @@ -5782,7 +5884,7 @@ "type": "tidelift" } ], - "time": "2023-05-11T13:16:46+00:00" + "time": "2023-12-18T13:03:25+00:00" }, { "name": "seld/phar-utils", @@ -5834,16 +5936,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.7.2", + "version": "3.8.0", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "5805f7a4e4958dbb5e944ef1e6edae0a303765e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5805f7a4e4958dbb5e944ef1e6edae0a303765e7", + "reference": "5805f7a4e4958dbb5e944ef1e6edae0a303765e7", "shasum": "" }, "require": { @@ -5853,7 +5955,7 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/phpcs", @@ -5872,35 +5974,58 @@ "authors": [ { "name": "Greg Sherwood", - "role": "lead" + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", "standards", "static analysis" ], "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" }, - "time": "2023-02-22T23:07:41+00:00" + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2023-12-08T12:32:31+00:00" }, { "name": "symfony/browser-kit", - "version": "v5.4.21", + "version": "v5.4.31", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "a866ca7e396f15d7efb6d74a8a7d364d4e05b704" + "reference": "0ed1f634a36606f2065eec221b3975e05016cbbe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/a866ca7e396f15d7efb6d74a8a7d364d4e05b704", - "reference": "a866ca7e396f15d7efb6d74a8a7d364d4e05b704", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/0ed1f634a36606f2065eec221b3975e05016cbbe", + "reference": "0ed1f634a36606f2065eec221b3975e05016cbbe", "shasum": "" }, "require": { @@ -5943,7 +6068,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v5.4.21" + "source": "https://github.com/symfony/browser-kit/tree/v5.4.31" }, "funding": [ { @@ -5959,20 +6084,20 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-10-31T07:58:33+00:00" }, { "name": "symfony/console", - "version": "v5.4.28", + "version": "v5.4.34", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f4f71842f24c2023b91237c72a365306f3c58827" + "reference": "4b4d8cd118484aa604ec519062113dd87abde18c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f4f71842f24c2023b91237c72a365306f3c58827", - "reference": "f4f71842f24c2023b91237c72a365306f3c58827", + "url": "https://api.github.com/repos/symfony/console/zipball/4b4d8cd118484aa604ec519062113dd87abde18c", + "reference": "4b4d8cd118484aa604ec519062113dd87abde18c", "shasum": "" }, "require": { @@ -6042,7 +6167,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.28" + "source": "https://github.com/symfony/console/tree/v5.4.34" }, "funding": [ { @@ -6058,7 +6183,7 @@ "type": "tidelift" } ], - "time": "2023-08-07T06:12:30+00:00" + "time": "2023-12-08T13:33:03+00:00" }, { "name": "symfony/css-selector", @@ -6195,16 +6320,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v5.4.25", + "version": "v5.4.32", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "d2aefa5a7acc5511422792931d14d1be96fe9fea" + "reference": "728f1fc136252a626ba5a69c02bd66a3697ff201" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/d2aefa5a7acc5511422792931d14d1be96fe9fea", - "reference": "d2aefa5a7acc5511422792931d14d1be96fe9fea", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/728f1fc136252a626ba5a69c02bd66a3697ff201", + "reference": "728f1fc136252a626ba5a69c02bd66a3697ff201", "shasum": "" }, "require": { @@ -6250,7 +6375,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v5.4.25" + "source": "https://github.com/symfony/dom-crawler/tree/v5.4.32" }, "funding": [ { @@ -6266,20 +6391,20 @@ "type": "tidelift" } ], - "time": "2023-06-05T08:05:41+00:00" + "time": "2023-11-17T20:43:48+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.4.26", + "version": "v5.4.34", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "5dcc00e03413f05c1e7900090927bb7247cb0aac" + "reference": "e3bca343efeb613f843c254e7718ef17c9bdf7a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/5dcc00e03413f05c1e7900090927bb7247cb0aac", - "reference": "5dcc00e03413f05c1e7900090927bb7247cb0aac", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e3bca343efeb613f843c254e7718ef17c9bdf7a3", + "reference": "e3bca343efeb613f843c254e7718ef17c9bdf7a3", "shasum": "" }, "require": { @@ -6335,7 +6460,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.26" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.34" }, "funding": [ { @@ -6351,7 +6476,7 @@ "type": "tidelift" } ], - "time": "2023-07-06T06:34:20+00:00" + "time": "2023-12-27T21:12:56+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -7053,16 +7178,16 @@ }, { "name": "symfony/process", - "version": "v5.4.28", + "version": "v5.4.34", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b" + "reference": "8fa22178dfc368911dbd513b431cd9b06f9afe7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b", - "reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b", + "url": "https://api.github.com/repos/symfony/process/zipball/8fa22178dfc368911dbd513b431cd9b06f9afe7a", + "reference": "8fa22178dfc368911dbd513b431cd9b06f9afe7a", "shasum": "" }, "require": { @@ -7095,7 +7220,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.28" + "source": "https://github.com/symfony/process/tree/v5.4.34" }, "funding": [ { @@ -7111,7 +7236,7 @@ "type": "tidelift" } ], - "time": "2023-08-07T10:36:04+00:00" + "time": "2023-12-02T08:41:43+00:00" }, { "name": "symfony/service-contracts", @@ -7198,16 +7323,16 @@ }, { "name": "symfony/string", - "version": "v5.4.29", + "version": "v5.4.34", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "e41bdc93def20eaf3bfc1537c4e0a2b0680a152d" + "reference": "e3f98bfc7885c957488f443df82d97814a3ce061" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/e41bdc93def20eaf3bfc1537c4e0a2b0680a152d", - "reference": "e41bdc93def20eaf3bfc1537c4e0a2b0680a152d", + "url": "https://api.github.com/repos/symfony/string/zipball/e3f98bfc7885c957488f443df82d97814a3ce061", + "reference": "e3f98bfc7885c957488f443df82d97814a3ce061", "shasum": "" }, "require": { @@ -7264,7 +7389,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.29" + "source": "https://github.com/symfony/string/tree/v5.4.34" }, "funding": [ { @@ -7280,20 +7405,20 @@ "type": "tidelift" } ], - "time": "2023-09-13T11:47:41+00:00" + "time": "2023-12-09T13:20:28+00:00" }, { "name": "symfony/translation", - "version": "v5.4.30", + "version": "v5.4.31", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "8560dc532e4e48d331937532a7cbfd2a9f9f53ce" + "reference": "ba72f72fceddf36f00bd495966b5873f2d17ad8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/8560dc532e4e48d331937532a7cbfd2a9f9f53ce", - "reference": "8560dc532e4e48d331937532a7cbfd2a9f9f53ce", + "url": "https://api.github.com/repos/symfony/translation/zipball/ba72f72fceddf36f00bd495966b5873f2d17ad8f", + "reference": "ba72f72fceddf36f00bd495966b5873f2d17ad8f", "shasum": "" }, "require": { @@ -7361,7 +7486,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v5.4.30" + "source": "https://github.com/symfony/translation/tree/v5.4.31" }, "funding": [ { @@ -7377,7 +7502,7 @@ "type": "tidelift" } ], - "time": "2023-10-28T09:19:54+00:00" + "time": "2023-11-03T16:16:43+00:00" }, { "name": "symfony/translation-contracts", @@ -7459,16 +7584,16 @@ }, { "name": "symfony/yaml", - "version": "v5.4.30", + "version": "v5.4.31", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "c6980e82a6656f6ebfabfd82f7585794cb122554" + "reference": "f387675d7f5fc4231f7554baa70681f222f73563" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/c6980e82a6656f6ebfabfd82f7585794cb122554", - "reference": "c6980e82a6656f6ebfabfd82f7585794cb122554", + "url": "https://api.github.com/repos/symfony/yaml/zipball/f387675d7f5fc4231f7554baa70681f222f73563", + "reference": "f387675d7f5fc4231f7554baa70681f222f73563", "shasum": "" }, "require": { @@ -7514,7 +7639,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.30" + "source": "https://github.com/symfony/yaml/tree/v5.4.31" }, "funding": [ { @@ -7530,7 +7655,7 @@ "type": "tidelift" } ], - "time": "2023-10-27T18:36:14+00:00" + "time": "2023-11-03T14:41:28+00:00" }, { "name": "szepeviktor/phpstan-wordpress", @@ -7538,12 +7663,12 @@ "source": { "type": "git", "url": "https://github.com/szepeviktor/phpstan-wordpress.git", - "reference": "28802acf3747e588981f6a4190a9c89a10a23340" + "reference": "6fbfbdfc822e70a669cff31d87545fad043f5a55" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/28802acf3747e588981f6a4190a9c89a10a23340", - "reference": "28802acf3747e588981f6a4190a9c89a10a23340", + "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/6fbfbdfc822e70a669cff31d87545fad043f5a55", + "reference": "6fbfbdfc822e70a669cff31d87545fad043f5a55", "shasum": "" }, "require": { @@ -7593,20 +7718,20 @@ "issues": "https://github.com/szepeviktor/phpstan-wordpress/issues", "source": "https://github.com/szepeviktor/phpstan-wordpress/tree/master" }, - "time": "2023-10-16T17:31:07+00:00" + "time": "2023-11-09T00:15:27+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -7635,7 +7760,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -7643,7 +7768,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" }, { "name": "voku/portable-ascii", @@ -7849,16 +7974,16 @@ }, { "name": "wp-cli/checksum-command", - "version": "v2.2.4", + "version": "v2.2.5", "source": { "type": "git", "url": "https://github.com/wp-cli/checksum-command.git", - "reference": "7ae020192bc6ee9042be0bf664bd998b1861e994" + "reference": "f6911998734018da08f75464a168feb0d07b4475" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/checksum-command/zipball/7ae020192bc6ee9042be0bf664bd998b1861e994", - "reference": "7ae020192bc6ee9042be0bf664bd998b1861e994", + "url": "https://api.github.com/repos/wp-cli/checksum-command/zipball/f6911998734018da08f75464a168feb0d07b4475", + "reference": "f6911998734018da08f75464a168feb0d07b4475", "shasum": "" }, "require": { @@ -7902,22 +8027,22 @@ "homepage": "https://github.com/wp-cli/checksum-command", "support": { "issues": "https://github.com/wp-cli/checksum-command/issues", - "source": "https://github.com/wp-cli/checksum-command/tree/v2.2.4" + "source": "https://github.com/wp-cli/checksum-command/tree/v2.2.5" }, - "time": "2023-08-30T13:34:47+00:00" + "time": "2023-11-10T21:54:15+00:00" }, { "name": "wp-cli/config-command", - "version": "v2.3.2", + "version": "v2.3.3", "source": { "type": "git", "url": "https://github.com/wp-cli/config-command.git", - "reference": "9de6ce3536a2db56ae5264c61b6f1a35e9132e01" + "reference": "890b6e3c8fd945dcad2bff4bf565ba6dfb33e35d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/config-command/zipball/9de6ce3536a2db56ae5264c61b6f1a35e9132e01", - "reference": "9de6ce3536a2db56ae5264c61b6f1a35e9132e01", + "url": "https://api.github.com/repos/wp-cli/config-command/zipball/890b6e3c8fd945dcad2bff4bf565ba6dfb33e35d", + "reference": "890b6e3c8fd945dcad2bff4bf565ba6dfb33e35d", "shasum": "" }, "require": { @@ -7926,7 +8051,7 @@ }, "require-dev": { "wp-cli/db-command": "^1.3 || ^2", - "wp-cli/wp-cli-tests": "^4" + "wp-cli/wp-cli-tests": "^4.2.8" }, "type": "wp-cli-package", "extra": { @@ -7976,22 +8101,22 @@ "homepage": "https://github.com/wp-cli/config-command", "support": { "issues": "https://github.com/wp-cli/config-command/issues", - "source": "https://github.com/wp-cli/config-command/tree/v2.3.2" + "source": "https://github.com/wp-cli/config-command/tree/v2.3.3" }, - "time": "2023-10-20T10:15:46+00:00" + "time": "2023-12-21T10:01:16+00:00" }, { "name": "wp-cli/core-command", - "version": "v2.1.15", + "version": "v2.1.16", "source": { "type": "git", "url": "https://github.com/wp-cli/core-command.git", - "reference": "7a81a8658620078bf5f2785836cb33aa382e8bb4" + "reference": "9d6ebb4545df0b8bc7e688a49910ddcdd86dbe93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/core-command/zipball/7a81a8658620078bf5f2785836cb33aa382e8bb4", - "reference": "7a81a8658620078bf5f2785836cb33aa382e8bb4", + "url": "https://api.github.com/repos/wp-cli/core-command/zipball/9d6ebb4545df0b8bc7e688a49910ddcdd86dbe93", + "reference": "9d6ebb4545df0b8bc7e688a49910ddcdd86dbe93", "shasum": "" }, "require": { @@ -8047,9 +8172,9 @@ "homepage": "https://github.com/wp-cli/core-command", "support": { "issues": "https://github.com/wp-cli/core-command/issues", - "source": "https://github.com/wp-cli/core-command/tree/v2.1.15" + "source": "https://github.com/wp-cli/core-command/tree/v2.1.16" }, - "time": "2023-08-30T15:54:16+00:00" + "time": "2023-11-10T23:54:33+00:00" }, { "name": "wp-cli/cron-command", @@ -8122,16 +8247,16 @@ }, { "name": "wp-cli/db-command", - "version": "v2.0.26", + "version": "v2.0.27", "source": { "type": "git", "url": "https://github.com/wp-cli/db-command.git", - "reference": "0908bf5182b830c302199037070292e20d9f4ea6" + "reference": "eea28dd115fb381c82641a2a3060856d3a67242d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/db-command/zipball/0908bf5182b830c302199037070292e20d9f4ea6", - "reference": "0908bf5182b830c302199037070292e20d9f4ea6", + "url": "https://api.github.com/repos/wp-cli/db-command/zipball/eea28dd115fb381c82641a2a3060856d3a67242d", + "reference": "eea28dd115fb381c82641a2a3060856d3a67242d", "shasum": "" }, "require": { @@ -8190,9 +8315,9 @@ "homepage": "https://github.com/wp-cli/db-command", "support": { "issues": "https://github.com/wp-cli/db-command/issues", - "source": "https://github.com/wp-cli/db-command/tree/v2.0.26" + "source": "https://github.com/wp-cli/db-command/tree/v2.0.27" }, - "time": "2023-08-30T15:50:59+00:00" + "time": "2023-11-13T12:34:44+00:00" }, { "name": "wp-cli/embed-command", @@ -8595,16 +8720,16 @@ }, { "name": "wp-cli/extension-command", - "version": "v2.1.15", + "version": "v2.1.16", "source": { "type": "git", "url": "https://github.com/wp-cli/extension-command.git", - "reference": "1fe271c5ebb1815732a8cf6bb6979c9261ee6375" + "reference": "71183254b1e403bc0f9b4a97fab48e284cfe1367" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/extension-command/zipball/1fe271c5ebb1815732a8cf6bb6979c9261ee6375", - "reference": "1fe271c5ebb1815732a8cf6bb6979c9261ee6375", + "url": "https://api.github.com/repos/wp-cli/extension-command/zipball/71183254b1e403bc0f9b4a97fab48e284cfe1367", + "reference": "71183254b1e403bc0f9b4a97fab48e284cfe1367", "shasum": "" }, "require": { @@ -8687,22 +8812,22 @@ "homepage": "https://github.com/wp-cli/extension-command", "support": { "issues": "https://github.com/wp-cli/extension-command/issues", - "source": "https://github.com/wp-cli/extension-command/tree/v2.1.15" + "source": "https://github.com/wp-cli/extension-command/tree/v2.1.16" }, - "time": "2023-10-11T14:55:49+00:00" + "time": "2023-11-10T12:24:35+00:00" }, { "name": "wp-cli/i18n-command", - "version": "v2.4.4", + "version": "v2.5.0", "source": { "type": "git", "url": "https://github.com/wp-cli/i18n-command.git", - "reference": "7d82e675f271359b1af614e6325d8eeaeb7d7474" + "reference": "9cf9b40f6bad64ade8660cc26bf1f28f2d223268" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/7d82e675f271359b1af614e6325d8eeaeb7d7474", - "reference": "7d82e675f271359b1af614e6325d8eeaeb7d7474", + "url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/9cf9b40f6bad64ade8660cc26bf1f28f2d223268", + "reference": "9cf9b40f6bad64ade8660cc26bf1f28f2d223268", "shasum": "" }, "require": { @@ -8755,9 +8880,9 @@ "homepage": "https://github.com/wp-cli/i18n-command", "support": { "issues": "https://github.com/wp-cli/i18n-command/issues", - "source": "https://github.com/wp-cli/i18n-command/tree/v2.4.4" + "source": "https://github.com/wp-cli/i18n-command/tree/v2.5.0" }, - "time": "2023-08-30T18:00:10+00:00" + "time": "2023-11-16T17:09:37+00:00" }, { "name": "wp-cli/import-command", @@ -8821,16 +8946,16 @@ }, { "name": "wp-cli/language-command", - "version": "v2.0.16", + "version": "v2.0.18", "source": { "type": "git", "url": "https://github.com/wp-cli/language-command.git", - "reference": "829cdf985fc1fdbe0572ef9a281b8a590aa4ee29" + "reference": "24f76e35f477887d09f1fd40a60d9011a6da18bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/language-command/zipball/829cdf985fc1fdbe0572ef9a281b8a590aa4ee29", - "reference": "829cdf985fc1fdbe0572ef9a281b8a590aa4ee29", + "url": "https://api.github.com/repos/wp-cli/language-command/zipball/24f76e35f477887d09f1fd40a60d9011a6da18bf", + "reference": "24f76e35f477887d09f1fd40a60d9011a6da18bf", "shasum": "" }, "require": { @@ -8894,22 +9019,22 @@ "homepage": "https://github.com/wp-cli/language-command", "support": { "issues": "https://github.com/wp-cli/language-command/issues", - "source": "https://github.com/wp-cli/language-command/tree/v2.0.16" + "source": "https://github.com/wp-cli/language-command/tree/v2.0.18" }, - "time": "2023-10-18T21:57:04+00:00" + "time": "2023-11-10T13:27:16+00:00" }, { "name": "wp-cli/maintenance-mode-command", - "version": "v2.0.10", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/wp-cli/maintenance-mode-command.git", - "reference": "599f8f08045ed2ef26a53d1432a4845b39d54f7d" + "reference": "2e9845a1cd1678b960dd8d0dd0c936648113788c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/maintenance-mode-command/zipball/599f8f08045ed2ef26a53d1432a4845b39d54f7d", - "reference": "599f8f08045ed2ef26a53d1432a4845b39d54f7d", + "url": "https://api.github.com/repos/wp-cli/maintenance-mode-command/zipball/2e9845a1cd1678b960dd8d0dd0c936648113788c", + "reference": "2e9845a1cd1678b960dd8d0dd0c936648113788c", "shasum": "" }, "require": { @@ -8955,22 +9080,22 @@ "homepage": "https://github.com/wp-cli/maintenance-mode-command", "support": { "issues": "https://github.com/wp-cli/maintenance-mode-command/issues", - "source": "https://github.com/wp-cli/maintenance-mode-command/tree/v2.0.10" + "source": "https://github.com/wp-cli/maintenance-mode-command/tree/v2.1.0" }, - "time": "2023-08-30T14:54:15+00:00" + "time": "2023-11-06T14:04:13+00:00" }, { "name": "wp-cli/media-command", - "version": "v2.0.20", + "version": "v2.0.21", "source": { "type": "git", "url": "https://github.com/wp-cli/media-command.git", - "reference": "49ef52c657de7ac1e50010ce3f6b0cdc40c02dc7" + "reference": "4950ed4ded35c52068d30fec080d545a33baa85c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/media-command/zipball/49ef52c657de7ac1e50010ce3f6b0cdc40c02dc7", - "reference": "49ef52c657de7ac1e50010ce3f6b0cdc40c02dc7", + "url": "https://api.github.com/repos/wp-cli/media-command/zipball/4950ed4ded35c52068d30fec080d545a33baa85c", + "reference": "4950ed4ded35c52068d30fec080d545a33baa85c", "shasum": "" }, "require": { @@ -9017,9 +9142,9 @@ "homepage": "https://github.com/wp-cli/media-command", "support": { "issues": "https://github.com/wp-cli/media-command/issues", - "source": "https://github.com/wp-cli/media-command/tree/v2.0.20" + "source": "https://github.com/wp-cli/media-command/tree/v2.0.21" }, - "time": "2023-09-01T13:08:38+00:00" + "time": "2023-11-10T21:56:52+00:00" }, { "name": "wp-cli/mustangostang-spyc", @@ -9074,20 +9199,20 @@ }, { "name": "wp-cli/package-command", - "version": "v2.4.0", + "version": "v2.5.0", "source": { "type": "git", "url": "https://github.com/wp-cli/package-command.git", - "reference": "f295538382b970cca506172b892f5f1c8adbedb2" + "reference": "71683195f8c27ad97009628e2a72d2a4155503b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/package-command/zipball/f295538382b970cca506172b892f5f1c8adbedb2", - "reference": "f295538382b970cca506172b892f5f1c8adbedb2", + "url": "https://api.github.com/repos/wp-cli/package-command/zipball/71683195f8c27ad97009628e2a72d2a4155503b2", + "reference": "71683195f8c27ad97009628e2a72d2a4155503b2", "shasum": "" }, "require": { - "composer/composer": "^1.10.23 || ~2.2.17", + "composer/composer": "^1.10.23 || ^2.2.17", "ext-json": "*", "wp-cli/wp-cli": "^2.8" }, @@ -9133,22 +9258,22 @@ "homepage": "https://github.com/wp-cli/package-command", "support": { "issues": "https://github.com/wp-cli/package-command/issues", - "source": "https://github.com/wp-cli/package-command/tree/v2.4.0" + "source": "https://github.com/wp-cli/package-command/tree/v2.5.0" }, - "time": "2023-09-06T21:10:16+00:00" + "time": "2023-12-08T10:38:16+00:00" }, { "name": "wp-cli/php-cli-tools", - "version": "v0.11.21", + "version": "v0.11.22", "source": { "type": "git", "url": "https://github.com/wp-cli/php-cli-tools.git", - "reference": "b3457a8d60cd0b1c48cab76ad95df136d266f0b6" + "reference": "a6bb94664ca36d0962f9c2ff25591c315a550c51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/b3457a8d60cd0b1c48cab76ad95df136d266f0b6", - "reference": "b3457a8d60cd0b1c48cab76ad95df136d266f0b6", + "url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/a6bb94664ca36d0962f9c2ff25591c315a550c51", + "reference": "a6bb94664ca36d0962f9c2ff25591c315a550c51", "shasum": "" }, "require": { @@ -9196,9 +9321,9 @@ ], "support": { "issues": "https://github.com/wp-cli/php-cli-tools/issues", - "source": "https://github.com/wp-cli/php-cli-tools/tree/v0.11.21" + "source": "https://github.com/wp-cli/php-cli-tools/tree/v0.11.22" }, - "time": "2023-09-29T15:28:10+00:00" + "time": "2023-12-03T19:25:05+00:00" }, { "name": "wp-cli/rewrite-command", @@ -9329,16 +9454,16 @@ }, { "name": "wp-cli/scaffold-command", - "version": "v2.1.3", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/wp-cli/scaffold-command.git", - "reference": "4125a31134e1bad3d28cff71c178dcee1393f605" + "reference": "8aa906c3ec6ae7d95f38c962fc2561714f9c7145" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/scaffold-command/zipball/4125a31134e1bad3d28cff71c178dcee1393f605", - "reference": "4125a31134e1bad3d28cff71c178dcee1393f605", + "url": "https://api.github.com/repos/wp-cli/scaffold-command/zipball/8aa906c3ec6ae7d95f38c962fc2561714f9c7145", + "reference": "8aa906c3ec6ae7d95f38c962fc2561714f9c7145", "shasum": "" }, "require": { @@ -9389,22 +9514,22 @@ "homepage": "https://github.com/wp-cli/scaffold-command", "support": { "issues": "https://github.com/wp-cli/scaffold-command/issues", - "source": "https://github.com/wp-cli/scaffold-command/tree/v2.1.3" + "source": "https://github.com/wp-cli/scaffold-command/tree/v2.2.0" }, - "time": "2023-08-30T14:29:02+00:00" + "time": "2023-11-16T15:25:33+00:00" }, { "name": "wp-cli/search-replace-command", - "version": "v2.1.3", + "version": "v2.1.4", "source": { "type": "git", "url": "https://github.com/wp-cli/search-replace-command.git", - "reference": "0b662a372483224fe93f113c2a4fa2089e951d34" + "reference": "88c9f23eda4eff4803a3eeeabd46d1a99cd17340" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/search-replace-command/zipball/0b662a372483224fe93f113c2a4fa2089e951d34", - "reference": "0b662a372483224fe93f113c2a4fa2089e951d34", + "url": "https://api.github.com/repos/wp-cli/search-replace-command/zipball/88c9f23eda4eff4803a3eeeabd46d1a99cd17340", + "reference": "88c9f23eda4eff4803a3eeeabd46d1a99cd17340", "shasum": "" }, "require": { @@ -9449,9 +9574,9 @@ "homepage": "https://github.com/wp-cli/search-replace-command", "support": { "issues": "https://github.com/wp-cli/search-replace-command/issues", - "source": "https://github.com/wp-cli/search-replace-command/tree/v2.1.3" + "source": "https://github.com/wp-cli/search-replace-command/tree/v2.1.4" }, - "time": "2023-09-01T12:41:32+00:00" + "time": "2023-12-19T12:41:53+00:00" }, { "name": "wp-cli/server-command", @@ -9842,16 +9967,16 @@ }, { "name": "wp-cli/wp-config-transformer", - "version": "v1.3.4", + "version": "v1.3.5", "source": { "type": "git", "url": "https://github.com/wp-cli/wp-config-transformer.git", - "reference": "1f80df413c0d779a813223d9dd5dd58358eee60c" + "reference": "202aa80528939159d52bc4026cee5453aec382db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/wp-config-transformer/zipball/1f80df413c0d779a813223d9dd5dd58358eee60c", - "reference": "1f80df413c0d779a813223d9dd5dd58358eee60c", + "url": "https://api.github.com/repos/wp-cli/wp-config-transformer/zipball/202aa80528939159d52bc4026cee5453aec382db", + "reference": "202aa80528939159d52bc4026cee5453aec382db", "shasum": "" }, "require": { @@ -9880,9 +10005,9 @@ "homepage": "https://github.com/wp-cli/wp-config-transformer", "support": { "issues": "https://github.com/wp-cli/wp-config-transformer/issues", - "source": "https://github.com/wp-cli/wp-config-transformer/tree/v1.3.4" + "source": "https://github.com/wp-cli/wp-config-transformer/tree/v1.3.5" }, - "time": "2023-08-31T10:11:36+00:00" + "time": "2023-11-10T14:28:03+00:00" }, { "name": "wp-coding-standards/wpcs", From b00d0451557093a336842df322592bdaa548c61b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sun, 7 Jan 2024 17:31:17 +0100 Subject: [PATCH 52/59] Composer: Upgrade wp-cli --- composer.json | 2 +- composer.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index ada51578..cb31a0e5 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "codeception/module-cli": "^1.0", "codeception/util-universalframework": "^1.0", "rector/rector": "^0.17.0", - "wp-cli/wp-cli-bundle": "2.8.1", + "wp-cli/wp-cli-bundle": "^2.9", "phpstan/phpstan": "^1.10.0", "szepeviktor/phpstan-wordpress": "dev-master", "phpstan/extension-installer": "^1.3.1", diff --git a/composer.lock b/composer.lock index eb5bb825..09ef5070 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b95bc88a23567874b9f29e94eecf7a64", + "content-hash": "4c357ca873ffffff2a63276a6d1a1839", "packages": [], "packages-dev": [ { @@ -9893,16 +9893,16 @@ }, { "name": "wp-cli/wp-cli-bundle", - "version": "v2.8.1", + "version": "v2.9.0", "source": { "type": "git", "url": "https://github.com/wp-cli/wp-cli-bundle.git", - "reference": "42e9cb9c16831c31ff720ed9dd1604e0f9871695" + "reference": "3512737e69e55507172e8dce400e09231ba95919" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/wp-cli-bundle/zipball/42e9cb9c16831c31ff720ed9dd1604e0f9871695", - "reference": "42e9cb9c16831c31ff720ed9dd1604e0f9871695", + "url": "https://api.github.com/repos/wp-cli/wp-cli-bundle/zipball/3512737e69e55507172e8dce400e09231ba95919", + "reference": "3512737e69e55507172e8dce400e09231ba95919", "shasum": "" }, "require": { @@ -9933,11 +9933,11 @@ "wp-cli/shell-command": "^2", "wp-cli/super-admin-command": "^2", "wp-cli/widget-command": "^2", - "wp-cli/wp-cli": "^2.8.1" + "wp-cli/wp-cli": "^2.9.0" }, "require-dev": { "roave/security-advisories": "dev-latest", - "wp-cli/wp-cli-tests": "^3.0.7" + "wp-cli/wp-cli-tests": "^4" }, "suggest": { "psy/psysh": "Enhanced `wp shell` functionality" @@ -9945,7 +9945,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.8.x-dev" + "dev-main": "2.9.x-dev" } }, "notification-url": "https://packagist.org/downloads/", @@ -9963,7 +9963,7 @@ "issues": "https://github.com/wp-cli/wp-cli-bundle/issues", "source": "https://github.com/wp-cli/wp-cli-bundle" }, - "time": "2023-06-05T07:33:43+00:00" + "time": "2023-10-25T09:28:58+00:00" }, { "name": "wp-cli/wp-config-transformer", From d38c23a5f675e91887141777053f3282575b9c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sun, 7 Jan 2024 17:31:25 +0100 Subject: [PATCH 53/59] Fix phpstan --- inc/class-log-query.php | 8 +++----- inc/global-helpers.php | 6 +++--- phpstan.neon | 2 ++ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 1ebcfaa2..604cc050 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -22,7 +22,7 @@ * - [x] Date filtering is broken (sql where clause missing/not added) * - [x] Add caching to SQLite * - [x] Add tests for single event occasions. - * - [ ] Add tests for log row notifier. + * - [x] Add tests for log row notifier. * - [ ] Run PHPStan and Rector. * - [ ] Merge together all git commits to one commit with close-##-messages. */ @@ -942,10 +942,10 @@ protected function add_contexts_to_log_rows( $log_rows ) { * @return array Array with max and min id. */ protected function get_max_min_ids( $log_rows ) { - /** @var null|int */ + /** @var null|int $min_id */ $min_id = null; - /** @var null|int */ + /** @var null|int $max_id */ $max_id = null; // Bail of no log rows. @@ -1012,7 +1012,6 @@ protected function get_inner_where( $args ) { $contexts_table_name = $simple_history->get_contexts_table_name(); $db_engine = $this->get_db_engine(); - /** @var array Where clauses for inner query. */ $inner_where = []; // Only include loggers that the current user can view @@ -1272,7 +1271,6 @@ protected function get_inner_where( $args ) { * @return array Where clauses. */ protected function get_outer_where( $args ) { - /** @var array Where clauses for outer query. */ $outer_where = []; // messages. diff --git a/inc/global-helpers.php b/inc/global-helpers.php index 0cc859e9..6efe0a7f 100644 --- a/inc/global-helpers.php +++ b/inc/global-helpers.php @@ -62,12 +62,12 @@ function sh_error_log() { // phpcs:ignore WordPress.NamingConventions.PrefixAllG * sh_d('$_POST', $_POST); * sh_d('My vars', $varOne, $varTwo, $varXYZ); * - * @mixed Vars Variables to output. + * @param mixed[] ...$args Variables to output. */ - function sh_d() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound + function sh_d( ...$args ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound $output = ''; - foreach ( func_get_args() as $var ) { + foreach ( $args as $var ) { $loopOutput = ''; if ( is_bool( $var ) ) { $bool_string = $var ? 'true' : 'false'; diff --git a/phpstan.neon b/phpstan.neon index 6596b40c..daa26dab 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -13,3 +13,5 @@ parameters: bootstrapFiles: - tests/phpstan/bootstrap.php level: 2 + ignoreErrors: + - '#Access to an undefined property object::\$context_message_key.#' From d6ce00613f37c72b515233c01e72ab59495dcc1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sun, 7 Jan 2024 17:33:39 +0100 Subject: [PATCH 54/59] Log query: Fail silently --- inc/class-log-query.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 604cc050..e8719c35 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -162,8 +162,6 @@ protected function query_overview_sqlite( $args ) { $result_log_rows = $wpdb->get_results( $sql_query_log_rows, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared if ( ! empty( $wpdb->last_error ) ) { - sh_d( '$wpdb->last_error', $wpdb->last_error ); - sh_d( '$sql_query_log_rows', $sql_query_log_rows ); exit; } @@ -411,8 +409,6 @@ protected function query_overview_mysql( $args ) { $result_log_rows = $wpdb->get_results( $sql_query_log_rows, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared if ( ! empty( $wpdb->last_error ) ) { - sh_d( '$wpdb->last_error', $wpdb->last_error ); - sh_d( '$sql_query_log_rows', $sql_query_log_rows ); exit; } From f403dd8adfdd36dcecbee077c022dd752d2b6287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sun, 7 Jan 2024 20:02:33 +0100 Subject: [PATCH 55/59] Correct add-on link in sidebar --- dropins/class-sidebar-add-ons-dropin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dropins/class-sidebar-add-ons-dropin.php b/dropins/class-sidebar-add-ons-dropin.php index 75fa852f..cf2dcd1c 100644 --- a/dropins/class-sidebar-add-ons-dropin.php +++ b/dropins/class-sidebar-add-ons-dropin.php @@ -54,7 +54,7 @@ public function on_sidebar_html() { ?>

    - +

    From 61711aaa33b1d811d259a372da606c2f5fd7000e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sun, 7 Jan 2024 20:02:58 +0100 Subject: [PATCH 56/59] Load occasions using more specific link target --- js/scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/scripts.js b/js/scripts.js index b73156d4..ee8fdd23 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -399,7 +399,7 @@ var simple_history = (function ($) { }, events: { - 'click .SimpleHistoryLogitem__occasions a': 'showOccasions', + 'click .SimpleHistoryLogitem__occasionsLink': 'showOccasions', 'click .SimpleHistoryLogitem__permalink': 'permalink' }, From ce413c6808685bdc4d08aa325d6621b0725c0e46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sun, 7 Jan 2024 20:03:14 +0100 Subject: [PATCH 57/59] Update readme --- readme.txt | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/readme.txt b/readme.txt index e0a0eb1d..fe4d8c3e 100644 --- a/readme.txt +++ b/readme.txt @@ -14,7 +14,7 @@ Simple History shows recent changes made within WordPress, directly on your dash The plugin works as a log/history/audit log/version history of the most important events that occur in WordPress. -It’s a plugin that is good to have on websites where several people are involved in editing the content. +It's a plugin that is good to have on websites where several people are involved in editing the content. Out of the box Simple History has support for: @@ -211,12 +211,21 @@ This can be modified using the filter [`simple_history/db_purge_days_interval`]( ## Changelog -Unreleased +4.9.0 Unreleased -- IP addresses are now shown on occasions. -- Remove columns "rep", "repeated" and "occasionsIDType" from Log_Query response. -- Add helper functions `get_cache_group()`, `clear_cache()`. -- Fix stats widget counting due to incorrect loggers included in stats query. +This release comes with improvements to the SQL queries that the plugin use to fetch events. These optimizations enhance query performance and reliability on both MySQL and MariaDB. + +Additionally, the plugin now provides support for SQLite databases. + +- Added: support for SQLite Database. Tested with the WordPress [SQLite Database Integration](https://wordpress.org/plugins/sqlite-database-integration/) feature plugin. See [Let's make WordPress officially support SQLite](https://make.wordpress.org/core/2022/09/12/lets-make-wordpress-officially-support-sqlite/) and [Help us test the SQLite implementation](https://make.wordpress.org/core/2022/12/20/help-us-test-the-sqlite-implementation/) for more information about the SQLite integration in WordPress and the current status. Fixes [#394](https://github.com/bonny/WordPress-Simple-History/issues/394) and [#411](https://github.com/bonny/WordPress-Simple-History/issues/411). +- Added: Support for plugin preview button that soon will be available in the WordPress.org plugin directory. This is a very nice way to quickly test plugins in your web browser. Read more in blog post ["Plugin Directory: Preview button revisited"](https://make.wordpress.org/meta/2023/11/22/plugin-directory-preview-button-revisited/) and follow progress in [trac ticket "Add a Preview in Playground button to the plugin directory"](https://meta.trac.wordpress.org/ticket/7251). You can however already test the functionality using this link: [preview Simple History plugin](https://playground.wordpress.net/?plugin=simple-history&blueprint-url=https://wordpress.org/plugins/wp-json/plugins/v1/plugin/simple-history/blueprint.json). +- Added: IP addresses are now shown on occasions. +- Added: Helper functions `get_cache_group()`, `clear_cache()`. +- Changed: Better support for MariaDB and MySQL 8 by using full group by in the query. Hopefully fixes multiple database related errors. Fixes [#397](https://github.com/bonny/WordPress-Simple-History/issues/397), [#409](https://github.com/bonny/WordPress-Simple-History/issues/409), and [#405](https://github.com/bonny/WordPress-Simple-History/issues/405). +- Changed: Misc code cleanup and improvements and GUI changes. +- Removed: Usage of `SQL_CALC_FOUND_ROWS` since it's deprecated in MySQL 8.0.17. Also [this should make the query faster](https://stackoverflow.com/a/188682). Fixes [#312](https://github.com/bonny/WordPress-Simple-History/issues/312). +- Removed: Columns "rep", "repeated" and "occasionsIDType" are removed from return value in `Log_Query()`. +- Fixed: Stats widget counting could be wrong due to incorrect loggers included in stats query. ### 4.8.0 (December 2023) From 4a4ae86df7a86d70855a756c54688eeaf6789646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Sun, 7 Jan 2024 20:03:29 +0100 Subject: [PATCH 58/59] Prepare for add-ons info in occasions text --- css/styles.css | 7 ++++++- inc/class-simple-history.php | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/css/styles.css b/css/styles.css index df80fb5e..f725804a 100644 --- a/css/styles.css +++ b/css/styles.css @@ -277,7 +277,12 @@ Style different log levels. color: rgb(137, 143, 156); margin-top: 0.4em; line-height: 1; - max-height: 1em; + /* max-height: 1em; */ +} + +.SimpleHistoryLogitem__occasionsAddOnsText { + margin: 0.4em 0 0 0; + line-height: 1; } .SimpleHistoryLogitem__details { diff --git a/inc/class-simple-history.php b/inc/class-simple-history.php index d8c60988..76db12ca 100644 --- a/inc/class-simple-history.php +++ b/inc/class-simple-history.php @@ -803,6 +803,7 @@ public function get_log_row_html_output( $one_log_row, $args ) { $occasions_count = $one_log_row->subsequentOccasions - 1; $occasions_html = ''; + // Add markup for occasions. if ( $occasions_count > 0 ) { $occasions_html = '
    '; @@ -826,6 +827,22 @@ public function get_log_row_html_output( $one_log_row, $args ) { ); $occasions_html .= ''; + // Div with information about add-ons. + // TODO: Finalize copy. Make it shorter. Remember that it will be shown many times. + // Also only show for failed logins. + // $occasions_html .= '
    '; + // $occasions_html .= '

    '; + // $occasions_html .= sprintf( + // /* translators: 1 is link to add-on page */ + // __( + // 'Set number of login attempts to store using the Extended Settings add-on.', + // 'simple-history' + // ), + // 'https://simple-history.com/add-ons/extended-settings/?utm_source=wpadmin' + // ); + // $occasions_html .= '

    '; + // $occasions_html .= '
    '; + $occasions_html .= '
    '; } From 005b20e4bc17242630b38acf721596c884b7f2a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A4r=20Thernstr=C3=B6m?= Date: Tue, 9 Jan 2024 19:30:15 +0100 Subject: [PATCH 59/59] Update readme --- readme.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.txt b/readme.txt index fe4d8c3e..e53558da 100644 --- a/readme.txt +++ b/readme.txt @@ -218,10 +218,10 @@ This release comes with improvements to the SQL queries that the plugin use to f Additionally, the plugin now provides support for SQLite databases. - Added: support for SQLite Database. Tested with the WordPress [SQLite Database Integration](https://wordpress.org/plugins/sqlite-database-integration/) feature plugin. See [Let's make WordPress officially support SQLite](https://make.wordpress.org/core/2022/09/12/lets-make-wordpress-officially-support-sqlite/) and [Help us test the SQLite implementation](https://make.wordpress.org/core/2022/12/20/help-us-test-the-sqlite-implementation/) for more information about the SQLite integration in WordPress and the current status. Fixes [#394](https://github.com/bonny/WordPress-Simple-History/issues/394) and [#411](https://github.com/bonny/WordPress-Simple-History/issues/411). -- Added: Support for plugin preview button that soon will be available in the WordPress.org plugin directory. This is a very nice way to quickly test plugins in your web browser. Read more in blog post ["Plugin Directory: Preview button revisited"](https://make.wordpress.org/meta/2023/11/22/plugin-directory-preview-button-revisited/) and follow progress in [trac ticket "Add a Preview in Playground button to the plugin directory"](https://meta.trac.wordpress.org/ticket/7251). You can however already test the functionality using this link: [preview Simple History plugin](https://playground.wordpress.net/?plugin=simple-history&blueprint-url=https://wordpress.org/plugins/wp-json/plugins/v1/plugin/simple-history/blueprint.json). +- Added: Support for plugin preview button that soon will be available in the WordPress.org plugin directory. This is a very nice way to quickly test plugins in your web browser. Read more in blog post ["Plugin Directory: Preview button revisited"](https://make.wordpress.org/meta/2023/11/22/plugin-directory-preview-button-revisited/) and follow progress in [trac ticket "Add a Preview in Playground button to the plugin directory"](https://meta.trac.wordpress.org/ticket/7251). You can however already test the functionality using this link: [Preview Simple History plugin](https://playground.wordpress.net/?plugin=simple-history&blueprint-url=https://wordpress.org/plugins/wp-json/plugins/v1/plugin/simple-history/blueprint.json). - Added: IP addresses are now shown on occasions. - Added: Helper functions `get_cache_group()`, `clear_cache()`. -- Changed: Better support for MariaDB and MySQL 8 by using full group by in the query. Hopefully fixes multiple database related errors. Fixes [#397](https://github.com/bonny/WordPress-Simple-History/issues/397), [#409](https://github.com/bonny/WordPress-Simple-History/issues/409), and [#405](https://github.com/bonny/WordPress-Simple-History/issues/405). +- Changed: Better support for MariaDB and MySQL 8 by using full group by in the query. Fixes multiple database related errors. Fixes [#397](https://github.com/bonny/WordPress-Simple-History/issues/397), [#409](https://github.com/bonny/WordPress-Simple-History/issues/409), and [#405](https://github.com/bonny/WordPress-Simple-History/issues/405). - Changed: Misc code cleanup and improvements and GUI changes. - Removed: Usage of `SQL_CALC_FOUND_ROWS` since it's deprecated in MySQL 8.0.17. Also [this should make the query faster](https://stackoverflow.com/a/188682). Fixes [#312](https://github.com/bonny/WordPress-Simple-History/issues/312). - Removed: Columns "rep", "repeated" and "occasionsIDType" are removed from return value in `Log_Query()`.