diff --git a/.distignore b/.distignore index 47ccea3c3..4f2b58537 100755 --- a/.distignore +++ b/.distignore @@ -27,6 +27,9 @@ package-lock.json phpcs.xml phpcs.xml.dist phpcs.ruleset.xml +phpstan.neon +phpstan.neon.dist +phpstan.stubs phpunit.xml phpunit.xml.dist playwright.config.js diff --git a/.github/workflows/phpstan-tests.yml b/.github/workflows/phpstan-tests.yml new file mode 100644 index 000000000..74584e725 --- /dev/null +++ b/.github/workflows/phpstan-tests.yml @@ -0,0 +1,44 @@ +name: PHPStan Tests +on: + push: + branches: + - main + pull_request: + paths: + - '.github/workflows/phpstan-tests.yml' + - 'includes/**' + # - 'test/unit/php**' + # - '*.php' + - 'phpstan.neon.dist' + - 'composer.*' +jobs: + test-phpstan: + name: PHPStan for WordPress + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + # phpstan requires PHP 7.1+. + php-version: 7.4 + extensions: dom, iconv, json, libxml, zip + coverage: none + tools: cs2pr + + - name: Composer Install + run: composer install --optimize-autoloader --prefer-dist + + - name: Log debug information + run: | + git --version + php --version + composer --version + + - name: Running PHPStan Analyze + if: ${{ success() || failure() }} + run: | + vendor/bin/phpstan --version + vendor/bin/phpstan analyze -vv --memory-limit=2G --error-format=checkstyle | cs2pr diff --git a/.gitignore b/.gitignore index 6aa213174..95070a5dc 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ typings # Composer # ########## vendor +phpstan.neon # .wp-env # ########## diff --git a/composer.json b/composer.json index 6ee451593..55cb65467 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,8 @@ "wp-coding-standards/wpcs": "*", "phpcompatibility/phpcompatibility-wp": "*", "phpunit/phpunit": "*", - "yoast/phpunit-polyfills": "*" + "yoast/phpunit-polyfills": "*", + "szepeviktor/phpstan-wordpress": "^1.3" }, "suggest": { "wp-cli/wp-cli-bundle": "Combines the most common WP-CLI commands, including the wp-cli/i18n-command which should be used to create translation-files." @@ -28,7 +29,8 @@ "format": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf --report=summary,source", "lint": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --report=summary,source", "lint:errors": "@lint -n", - "test": "@php ./vendor/phpunit/phpunit/phpunit" + "test": "@php ./vendor/phpunit/phpunit/phpunit", + "test:phpstan": "@php ./vendor/bin/phpstan analyze -vv --memory-limit=2G" }, "config": { "allow-plugins": { diff --git a/composer.lock b/composer.lock index ddb98007a..cc56c924a 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": "4392128689f0f8df116afe7925bd51c3", + "content-hash": "a0329d7079caf0254604e8149663aa90", "packages": [ { "name": "hamcrest/hamcrest-php", @@ -552,6 +552,54 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "php-stubs/wordpress-stubs", + "version": "v6.6.2", + "source": { + "type": "git", + "url": "https://github.com/php-stubs/wordpress-stubs.git", + "reference": "f50fd7ed45894d036e4fef9ab7e5bbbaff6a30cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/f50fd7ed45894d036e4fef9ab7e5bbbaff6a30cc", + "reference": "f50fd7ed45894d036e4fef9ab7e5bbbaff6a30cc", + "shasum": "" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "nikic/php-parser": "^4.13", + "php": "^7.4 || ^8.0", + "php-stubs/generator": "^0.8.3", + "phpdocumentor/reflection-docblock": "^5.4.1", + "phpstan/phpstan": "^1.10.49", + "phpunit/phpunit": "^9.5", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.0", + "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" + }, + "suggest": { + "paragonie/sodium_compat": "Pure PHP implementation of libsodium", + "symfony/polyfill-php80": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WordPress function and class declaration stubs for static analysis.", + "homepage": "https://github.com/php-stubs/wordpress-stubs", + "keywords": [ + "PHPStan", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/php-stubs/wordpress-stubs/issues", + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.6.2" + }, + "time": "2024-09-30T07:10:48+00:00" + }, { "name": "phpcompatibility/php-compatibility", "version": "9.3.5", @@ -892,6 +940,64 @@ ], "time": "2023-12-08T14:50:00+00:00" }, + { + "name": "phpstan/phpstan", + "version": "1.12.5", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17", + "reference": "7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2024-09-26T12:45:22+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "9.2.30", @@ -2358,6 +2464,145 @@ ], "time": "2024-01-11T20:47:48+00:00" }, + { + "name": "symfony/polyfill-php73", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "szepeviktor/phpstan-wordpress", + "version": "v1.3.5", + "source": { + "type": "git", + "url": "https://github.com/szepeviktor/phpstan-wordpress.git", + "reference": "7f8cfe992faa96b6a33bbd75c7bace98864161e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/7f8cfe992faa96b6a33bbd75c7bace98864161e7", + "reference": "7f8cfe992faa96b6a33bbd75c7bace98864161e7", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "php-stubs/wordpress-stubs": "^4.7 || ^5.0 || ^6.0", + "phpstan/phpstan": "^1.10.31", + "symfony/polyfill-php73": "^1.12.0" + }, + "require-dev": { + "composer/composer": "^2.1.14", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "phpunit/phpunit": "^8.0 || ^9.0", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.0", + "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" + }, + "suggest": { + "swissspidy/phpstan-no-private": "Detect usage of internal core functions, classes and methods" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "SzepeViktor\\PHPStan\\WordPress\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WordPress extensions for PHPStan", + "keywords": [ + "PHPStan", + "code analyse", + "code analysis", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/szepeviktor/phpstan-wordpress/issues", + "source": "https://github.com/szepeviktor/phpstan-wordpress/tree/v1.3.5" + }, + "time": "2024-06-28T22:27:19+00:00" + }, { "name": "theseer/tokenizer", "version": "1.2.2", diff --git a/gatherpress.php b/gatherpress.php index 5cceb802e..b5c9df735 100644 --- a/gatherpress.php +++ b/gatherpress.php @@ -22,6 +22,7 @@ defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore // Constants. +define( 'GATHERPRESS_CACHE_GROUP', 'gatherpress_cache' ); define( 'GATHERPRESS_CORE_FILE', __FILE__ ); define( 'GATHERPRESS_CORE_PATH', __DIR__ ); define( 'GATHERPRESS_CORE_URL', plugin_dir_url( __FILE__ ) ); diff --git a/includes/core/classes/class-assets.php b/includes/core/classes/class-assets.php index 347c595fb..057414a4b 100644 --- a/includes/core/classes/class-assets.php +++ b/includes/core/classes/class-assets.php @@ -105,7 +105,7 @@ protected function setup_hooks(): void { */ public function add_global_object(): void { ?> - + is_page = false; $query->is_singular = false; $query->is_archive = true; - $query->is_post_type_archive = array( Event::POST_TYPE ); + $query->is_post_type_archive = true; // This will force a page to behave like an archive page. Use -1 as that is not a valid ID. - $query->queried_object_id = '-1'; + $query->queried_object_id = -1; // Option adjustments for page_for_posts and show_on_front to force archive page. add_filter( 'pre_option', static function ( $pre, $option ) { if ( 'page_for_posts' === $option ) { - return '-1'; + return -1; } if ( 'show_on_front' === $option ) { diff --git a/includes/core/classes/class-event-rest-api.php b/includes/core/classes/class-event-rest-api.php index 54c49adb1..bab6a1f2c 100644 --- a/includes/core/classes/class-event-rest-api.php +++ b/includes/core/classes/class-event-rest-api.php @@ -57,7 +57,7 @@ protected function __construct() { */ protected function setup_hooks(): void { add_action( 'rest_api_init', array( $this, 'register_endpoints' ) ); - add_action( 'gatherpress_send_emails', array( $this, 'send_emails' ), 10, 3 ); + add_action( 'gatherpress_send_emails', array( $this, 'handle_email_send_action' ), 10, 3 ); add_filter( sprintf( 'rest_prepare_%s', Event::POST_TYPE ), array( $this, 'prepare_event_data' ) ); } @@ -231,19 +231,35 @@ public function email( WP_REST_Request $request ): WP_REST_Response { } /** - * Send event-related emails to selected members. + * Hooked method to trigger the sending of related emails. * - * This method is responsible for sending event-related emails to specific members. It first checks if the given - * `$post_id` corresponds to an event post type, and if not, it returns early. Then, it retrieves a list of members - * to send the email to and constructs the email subject, body, and headers. Finally, it sends the email to each - * selected member. + * This method hooks into a WordPress action, triggering the `send_emails` method to send emails to selected members. + * It doesn't return any value, as it's intended to be called by an action hook. * * @since 1.0.0 * - * @param int $post_id Event Post ID. + * @param int $post_id Post ID. * @param array $send Members to send the email to. * @param string $message Optional message to include in the email. - * @return bool + * @return void + */ + public function handle_email_send_action( int $post_id, array $send, string $message ): void { + $this->send_emails( $post_id, $send, $message ); + } + + /** + * Send emails to selected members. + * + * This method is responsible for sending emails to specific members. It checks if the given + * `$post_id` corresponds to a specific post type, retrieves the list of members to email, and sends the email with + * the appropriate subject, body, and headers. + * + * @since 1.0.0 + * + * @param int $post_id Post ID. + * @param array $send Members to send the email to. + * @param string $message Optional message to include in the email. + * @return bool True if emails were successfully sent, false otherwise. */ public function send_emails( int $post_id, array $send, string $message ): bool { if ( Event::POST_TYPE !== get_post_type( $post_id ) ) { @@ -252,12 +268,13 @@ public function send_emails( int $post_id, array $send, string $message ): bool // Keep the currently logged-in user. $current_user = wp_get_current_user(); + $members = $this->get_members( $send, $post_id ); - $members = $this->get_members( $send, $post_id ); foreach ( $members as $member ) { if ( '0' === get_user_meta( $member->ID, 'gatherpress_event_updates_opt_in', true ) ) { continue; } + if ( $member->user_email ) { $to = $member->user_email; $switched_locale = switch_to_user_locale( $member->ID ); diff --git a/includes/core/classes/class-event-setup.php b/includes/core/classes/class-event-setup.php index 0a94f3448..3b4365203 100644 --- a/includes/core/classes/class-event-setup.php +++ b/includes/core/classes/class-event-setup.php @@ -329,7 +329,7 @@ public function delete_event( int $post_id ): void { return; } - $table = sprintf( Event::TABLE_FORMAT, $wpdb->prefix, Event::POST_TYPE ); + $table = sprintf( Event::TABLE_FORMAT, $wpdb->prefix ); $wpdb->delete( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $table, diff --git a/includes/core/classes/class-event.php b/includes/core/classes/class-event.php index 66e491363..df24be8d5 100644 --- a/includes/core/classes/class-event.php +++ b/includes/core/classes/class-event.php @@ -412,7 +412,7 @@ public function get_venue_information(): array { $venue_information['full_address'] = $venue_meta->fullAddress ?? ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase $venue_information['phone_number'] = $venue_meta->phoneNumber ?? ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase $venue_information['website'] = $venue_meta->website ?? ''; - $venue_information['permalink'] = get_permalink( $venue->ID ) ?? ''; + $venue_information['permalink'] = (string) get_permalink( $venue->ID ); } return $venue_information; @@ -525,8 +525,8 @@ protected function get_yahoo_calendar_link(): string { $duration = ( ( strtotime( $diff_end ) - strtotime( $diff_start ) ) / 60 / 60 ); $full = intval( $duration ); $fraction = ( $duration - $full ); - $hours = str_pad( intval( $duration ), 2, '0', STR_PAD_LEFT ); - $minutes = str_pad( intval( $fraction * 60 ), 2, '0', STR_PAD_LEFT ); + $hours = str_pad( strval( $duration ), 2, '0', STR_PAD_LEFT ); + $minutes = str_pad( strval( $fraction * 60 ), 2, '0', STR_PAD_LEFT ); $venue = $this->get_venue_information(); $location = $venue['name']; $description = $this->get_calendar_description(); diff --git a/includes/core/classes/class-export.php b/includes/core/classes/class-export.php index aac01eeb8..bc4ebd2a4 100644 --- a/includes/core/classes/class-export.php +++ b/includes/core/classes/class-export.php @@ -73,7 +73,7 @@ protected function setup_hooks(): void { * @return void */ public function export(): void { - add_action( 'the_post', array( $this, 'prepare' ), 10, 2 ); + add_action( 'the_post', array( $this, 'prepare' ) ); add_filter( 'wxr_export_skip_postmeta', array( $this, 'extend' ), 10, 3 ); } diff --git a/includes/core/classes/class-import.php b/includes/core/classes/class-import.php index afb2d3ef8..dcdb74105 100644 --- a/includes/core/classes/class-import.php +++ b/includes/core/classes/class-import.php @@ -86,7 +86,7 @@ public function prepare( array $post_data_raw ): array { * * @since 1.0.0 * - * @param {array} $post_data_raw Unprocessesd 'gatherpress_event' post being imported. + * @param array $post_data_raw Unprocessesd 'gatherpress_event' post being imported. */ do_action( 'gatherpress_import', $post_data_raw ); } diff --git a/includes/core/classes/class-rsvp-query.php b/includes/core/classes/class-rsvp-query.php index 974bf3c2a..505641be0 100644 --- a/includes/core/classes/class-rsvp-query.php +++ b/includes/core/classes/class-rsvp-query.php @@ -15,7 +15,7 @@ defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore use GatherPress\Core\Traits\Singleton; -use WP_comment; +use WP_Comment; use WP_Comment_Query; use WP_Tax_Query; @@ -54,7 +54,7 @@ protected function __construct() { * @return void */ protected function setup_hooks(): void { - add_filter( 'pre_get_comments', array( $this, 'exclude_rsvp_from_comment_query' ) ); + add_action( 'pre_get_comments', array( $this, 'exclude_rsvp_from_comment_query' ) ); add_filter( 'comments_clauses', array( $this, 'taxonomy_query' ), 10, 2 ); } @@ -67,17 +67,17 @@ protected function setup_hooks(): void { * @since 1.0.0 * * @param array $clauses The clauses for the query. - * @param WP_Comment_Query $comment_query The comment query object. + * @param WP_Comment_Query $comment_query Current instance of WP_Comment_Query (passed by reference). * @return array Modified query clauses. */ public function taxonomy_query( array $clauses, WP_Comment_Query $comment_query ): array { global $wpdb; if ( ! empty( $comment_query->query_vars['tax_query'] ) ) { - $comment_query->tax_query = new WP_Tax_Query( $comment_query->query_vars['tax_query'] ); - $pieces = $comment_query->tax_query->get_sql( $wpdb->comments, 'comment_ID' ); - $clauses['join'] .= $pieces['join']; - $clauses['where'] .= $pieces['where']; + $comment_tax_query = new WP_Tax_Query( $comment_query->query_vars['tax_query'] ); + $pieces = $comment_tax_query->get_sql( $wpdb->comments, 'comment_ID' ); + $clauses['join'] .= $pieces['join']; + $clauses['where'] .= $pieces['where']; } return $clauses; @@ -107,11 +107,11 @@ public function get_rsvps( array $args ): array { // Never allow count-only return, we always want array. $args['count'] = false; - remove_filter( 'pre_get_comments', array( $this, 'exclude_rsvp_from_comment_query' ) ); + remove_action( 'pre_get_comments', array( $this, 'exclude_rsvp_from_comment_query' ) ); $rsvps = get_comments( $args ); - add_filter( 'pre_get_comments', array( $this, 'exclude_rsvp_from_comment_query' ) ); + add_action( 'pre_get_comments', array( $this, 'exclude_rsvp_from_comment_query' ) ); return (array) $rsvps; } @@ -153,13 +153,10 @@ public function get_rsvp( array $args ): ?WP_Comment { * * @since 1.0.0 * - * @param WP_Comment_Query $query The comment query object. + * @param WP_Comment_Query $query Current instance of WP_Comment_Query (passed by reference). * @return void */ - public function exclude_rsvp_from_comment_query( $query ) { - if ( ! $query instanceof WP_Comment_Query ) { - return; - } + public function exclude_rsvp_from_comment_query( WP_Comment_Query $query ) { $current_comment_types = $query->query_vars['type']; diff --git a/includes/core/classes/class-rsvp-setup.php b/includes/core/classes/class-rsvp-setup.php index 81b774a1d..7720350d8 100644 --- a/includes/core/classes/class-rsvp-setup.php +++ b/includes/core/classes/class-rsvp-setup.php @@ -98,6 +98,6 @@ public function adjust_comments_number( int $comments_number, int $post_id ): in $comment_count = get_comment_count( $post_id ); - return $comment_count['approved'] ?? 0; + return $comment_count['approved']; } } diff --git a/includes/core/classes/class-rsvp.php b/includes/core/classes/class-rsvp.php index 4da755339..80de2772e 100644 --- a/includes/core/classes/class-rsvp.php +++ b/includes/core/classes/class-rsvp.php @@ -137,9 +137,9 @@ public function get( int $user_id ): array { if ( ! empty( $rsvp ) ) { $data['id'] = $rsvp->user_id; $data['timestamp'] = $rsvp->comment_date; - $data['anonymous'] = intval( get_comment_meta( $rsvp->comment_ID, 'gatherpress_rsvp_anonymous', true ) ); - $data['guests'] = intval( get_comment_meta( $rsvp->comment_ID, 'gatherpress_rsvp_guests', true ) ); - $terms = wp_get_object_terms( $rsvp->comment_ID, self::TAXONOMY ); + $data['anonymous'] = intval( get_comment_meta( intval( $rsvp->comment_ID ), 'gatherpress_rsvp_anonymous', true ) ); + $data['guests'] = intval( get_comment_meta( intval( $rsvp->comment_ID ), 'gatherpress_rsvp_guests', true ) ); + $terms = wp_get_object_terms( intval( $rsvp->comment_ID ), self::TAXONOMY ); if ( ! empty( $terms ) && is_array( $terms ) ) { $data['status'] = $terms[0]->slug; @@ -239,7 +239,7 @@ public function save( int $user_id, string $status, int $anonymous = 0, int $gue wp_update_comment( $args ); } - if ( is_wp_error( $comment_id ) || empty( $comment_id ) ) { + if ( empty( $comment_id ) ) { return $data; } @@ -277,7 +277,7 @@ public function save( int $user_id, string $status, int $anonymous = 0, int $gue 'anonymous' => intval( $anonymous ), ); - wp_cache_delete( sprintf( self::CACHE_KEY, $post_id ) ); + wp_cache_delete( sprintf( self::CACHE_KEY, $post_id ), GATHERPRESS_CACHE_GROUP ); if ( ! $limit_reached ) { $this->check_waiting_list(); @@ -383,7 +383,7 @@ public function attending_limit_reached( array $current_response, int $guests = public function responses(): array { $post_id = $this->event->ID; $cache_key = sprintf( self::CACHE_KEY, $post_id ); - $retval = wp_cache_get( $cache_key ); + $retval = wp_cache_get( $cache_key, GATHERPRESS_CACHE_GROUP ); $rsvp_query = Rsvp_Query::get_instance(); // @todo add testing with cache. @@ -496,7 +496,7 @@ static function ( $response ) use ( $status ) { $retval[ $status ]['count'] = count( $retval[ $status ]['responses'] ) + $guests; } - wp_cache_set( $cache_key, $retval, 15 * MINUTE_IN_SECONDS ); + wp_cache_set( $cache_key, $retval, GATHERPRESS_CACHE_GROUP, 15 * MINUTE_IN_SECONDS ); return $retval; } @@ -542,9 +542,11 @@ static function ( $role ) { * * @param array $first First response to compare in the sort. * @param array $second Second response to compare in the sort. - * @return bool True if the first response's timestamp is earlier than the second response's timestamp; otherwise, false. + * @return int Returns a negative number if the first response's timestamp is earlier, + * a positive number if the second response's timestamp is earlier, + * or 0 if both are equal. */ - public function sort_by_timestamp( array $first, array $second ): bool { - return ( strtotime( $first['timestamp'] ) > strtotime( $second['timestamp'] ) ); + public function sort_by_timestamp( array $first, array $second ): int { + return strtotime( $first['timestamp'] ) <=> strtotime( $second['timestamp'] ); } } diff --git a/includes/core/classes/class-settings.php b/includes/core/classes/class-settings.php index d42643bc1..92d04e054 100644 --- a/includes/core/classes/class-settings.php +++ b/includes/core/classes/class-settings.php @@ -614,7 +614,7 @@ public function sort_sub_pages_by_priority( array $first, array $second ): int { $first['priority'] = isset( $first['priority'] ) ? intval( $first['priority'] ) : 10; $second['priority'] = isset( $second['priority'] ) ? intval( $second['priority'] ) : 10; - return ( $first['priority'] > $second['priority'] ); + return $first['priority'] <=> $second['priority']; } /** @@ -724,9 +724,6 @@ public function urlrewrite_preview( string $name, string $value ): void { case 'gatherpress_general[urls][topics]': $suffix = _x( 'sample-topic-term', 'sample topic term slug', 'gatherpress' ); break; - - default: - break; } Utility::render_template( sprintf( '%s/includes/templates/admin/settings/partials/urlrewrite-preview.php', GATHERPRESS_CORE_PATH ), diff --git a/includes/core/classes/class-setup.php b/includes/core/classes/class-setup.php index 9752ea043..f1e9763ef 100644 --- a/includes/core/classes/class-setup.php +++ b/includes/core/classes/class-setup.php @@ -285,7 +285,7 @@ public function add_online_event_term(): void { ); } else { wp_update_term( - $term['term_id'], + intval( $term['term_id'] ), Venue::TAXONOMY, array( 'name' => $term_name, @@ -310,7 +310,7 @@ public function add_online_event_term(): void { */ public function on_site_create( WP_Site $new_site ): void { if ( is_plugin_active_for_network( 'gatherpress/gatherpress.php' ) ) { - switch_to_blog( $new_site->blog_id ); + switch_to_blog( intval( $new_site->blog_id ) ); $this->create_tables(); restore_current_blog(); } @@ -331,7 +331,7 @@ public function on_site_create( WP_Site $new_site ): void { public function on_site_delete( array $tables ): array { global $wpdb; - $tables[] = sprintf( Event::TABLE_FORMAT, $wpdb->prefix, Event::POST_TYPE ); + $tables[] = sprintf( Event::TABLE_FORMAT, $wpdb->prefix ); return $tables; } diff --git a/includes/core/classes/class-venue.php b/includes/core/classes/class-venue.php index 7022e287f..c6c546d17 100644 --- a/includes/core/classes/class-venue.php +++ b/includes/core/classes/class-venue.php @@ -331,7 +331,7 @@ public function maybe_update_term_slug( int $post_id, WP_Post $post_after, WP_Po } else { // Update the existing term with the new name and slug. wp_update_term( - $term['term_id'], + intval( $term['term_id'] ), self::TAXONOMY, array( 'name' => $title, diff --git a/includes/core/classes/traits/class-singleton.php b/includes/core/classes/traits/class-singleton.php index c207100fc..4c6f08808 100644 --- a/includes/core/classes/traits/class-singleton.php +++ b/includes/core/classes/traits/class-singleton.php @@ -27,9 +27,9 @@ trait Singleton { * The single instance of the class. * * @since 1.0.0 - * @var ?self|null The instance of the class. + * @var self|null The instance of the class or null if not instantiated. */ - private static $instance = null; + private static ?self $instance = null; /** * Get the instance of the Singleton class. diff --git a/includes/core/requirements-check.php b/includes/core/requirements-check.php index 0f2c2f674..0d1786973 100644 --- a/includes/core/requirements-check.php +++ b/includes/core/requirements-check.php @@ -14,7 +14,7 @@ $gatherpress_activation = true; // Check the PHP version to ensure compatibility with the plugin. -if ( version_compare( PHP_VERSION_ID, GATHERPRESS_REQUIRES_PHP, '<' ) ) { +if ( version_compare( PHP_VERSION, GATHERPRESS_REQUIRES_PHP, '<' ) ) { add_action( 'admin_notices', static function () { diff --git a/includes/templates/admin/settings/fields/select.php b/includes/templates/admin/settings/fields/select.php index a847bdb65..9bee92139 100644 --- a/includes/templates/admin/settings/fields/select.php +++ b/includes/templates/admin/settings/fields/select.php @@ -10,6 +10,7 @@ * @param string $name The name attribute for the input field. * @param string $label The label text displayed next to the checkbox. * @param string $option The option name in which the field value is stored. + * @param string $options The options for the select field. * @param mixed $value The current value of the checkbox (boolean or equivalent). * @param string $description Optional. The description or tooltip text for the field. */ @@ -17,7 +18,7 @@ // Exit if accessed directly. defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore -if ( ! isset( $name, $label, $options, $options['items'], $value, $description ) ) { +if ( ! isset( $name, $label, $option, $options, $options['items'], $value, $description ) ) { return; } ?> @@ -27,7 +28,7 @@ $gatherpress_label ) : ?> - prefix, Event::POST_TYPE ); + $table = sprintf( Event::TABLE_FORMAT, $wpdb->prefix ); $retval = $instance->adjust_event_sql( array(), 'all', 'DESC' ); $this->assertStringContainsString( 'DESC', $retval['orderby'] ); diff --git a/test/unit/php/includes/core/classes/class-test-event-rest-api.php b/test/unit/php/includes/core/classes/class-test-event-rest-api.php index d26f28b3b..4a4cbb315 100644 --- a/test/unit/php/includes/core/classes/class-test-event-rest-api.php +++ b/test/unit/php/includes/core/classes/class-test-event-rest-api.php @@ -42,7 +42,7 @@ public function test_setup_hooks(): void { 'type' => 'action', 'name' => 'gatherpress_send_emails', 'priority' => 10, - 'callback' => array( $instance, 'send_emails' ), + 'callback' => array( $instance, 'handle_email_send_action' ), ), array( 'type' => 'filter', diff --git a/test/unit/php/includes/core/classes/class-test-rsvp.php b/test/unit/php/includes/core/classes/class-test-rsvp.php index f94bc115a..a77078d02 100644 --- a/test/unit/php/includes/core/classes/class-test-rsvp.php +++ b/test/unit/php/includes/core/classes/class-test-rsvp.php @@ -265,6 +265,9 @@ public function test_responses(): void { wp_delete_user( $user_id_2 ); + // User will remain while cached until it expires. + wp_cache_delete( sprintf( Rsvp::CACHE_KEY, $post->ID ), GATHERPRESS_CACHE_GROUP ); + $responses = $rsvp->responses(); $this->assertEmpty( @@ -344,13 +347,22 @@ public function test_sort_by_timestamp(): void { $newer = array( 'timestamp' => '2023-05-11 08:30:00' ); $older = array( 'timestamp' => '2022-05-11 08:30:00' ); - $this->assertTrue( + $this->assertSame( + -1, + $rsvp->sort_by_timestamp( $older, $newer ), + 'Failed to assert that it returns a negative number while the first response\'s timestamp is earlier.' + ); + + $this->assertSame( + 1, $rsvp->sort_by_timestamp( $newer, $older ), - 'Failed to assert correct sorting of timestamp.' + 'Failed to assert that it returns a positive number while the second response\'s timestamp is earlier.' ); - $this->assertFalse( - $rsvp->sort_by_timestamp( $older, $newer ), - 'Failed to assert correct sorting of timestamp.' + + $this->assertSame( + 0, + $rsvp->sort_by_timestamp( $newer, $newer ), + 'Failed to assert that it returns 0 while both response\'s timestamps are equal.' ); } } diff --git a/test/unit/php/includes/core/classes/class-test-settings.php b/test/unit/php/includes/core/classes/class-test-settings.php index 6483d8ee8..6d338a606 100644 --- a/test/unit/php/includes/core/classes/class-test-settings.php +++ b/test/unit/php/includes/core/classes/class-test-settings.php @@ -329,4 +329,34 @@ public function test_get_sub_pages(): void { 'Failed to assert that credits is last key.' ); } + + /** + * Coverage for sort_sub_pages_by_priority method. + * + * @covers ::sort_sub_pages_by_priority + * + * @return void + */ + public function test_sort_sub_pages_by_priority(): void { + $instance = Settings::get_instance(); + $sub_pages = $instance->get_sub_pages(); + + $this->assertSame( + -1, + $instance->sort_sub_pages_by_priority( array( 'priority' => 2 ), array( 'priority' => 42 ) ), + 'Failed to assert that it returns a negative number while the first sub-page has a lower priority.' + ); + + $this->assertSame( + 1, + $instance->sort_sub_pages_by_priority( array( 'priority' => 42 ), array( 'priority' => 2 ) ), + 'Failed to assert that it returns a positive number while the second sub-page has a lower priority.' + ); + + $this->assertSame( + 0, + $instance->sort_sub_pages_by_priority( array( 'priority' => 42 ), array( 'priority' => 42 ) ), + 'Failed to assert that it returns 0 while their priorities are equal.' + ); + } }