From 336960897d9be2efb22b0b57e9163922dd34deb9 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers Date: Wed, 25 Sep 2019 13:46:55 +0000 Subject: [PATCH] Build/Test Tools: Introduce automated PHP compatibility checking. This change introduces a new Composer script, `compat` that will scan the codebase for (detectable) potential PHP compatibility issues using the `PHP_CodeSniffer` and a custom ruleset based off of the `PHPCompayibilityWP` ruleset (`phpcompat.xml.dist`). The command will be run as a separate job within each Travis build. While many compatibility issues and false positives have already been corrected in this commit and other Trac tickets, there are still some remaining. For that reason, the job is allowed to fail while the remainder of the potential compatibility issues are investigated and addressed. After those are resolved, the job should be set as required to pass to help prevent new compatibility issues from being introduced. Props desrosj, jrf, all PHPCompatibilityWP and PHPCompatibility contributors. Fixes #46152. git-svn-id: https://develop.svn.wordpress.org/trunk@46290 602fd350-edb4-49c9-b593-d223f7449a82 --- .travis.yml | 5 + composer.json | 4 +- composer.lock | 164 +++++++++++++++++- phpcompat.xml.dist | 64 +++++++ src/wp-admin/includes/class-wp-debug-data.php | 2 +- .../includes/class-wp-site-health.php | 4 +- src/wp-admin/includes/file.php | 3 + src/wp-admin/includes/upgrade.php | 2 +- src/wp-includes/author-template.php | 2 +- src/wp-includes/deprecated.php | 4 +- src/wp-includes/functions.php | 12 +- src/wp-includes/general-template.php | 2 +- 12 files changed, 251 insertions(+), 17 deletions(-) create mode 100644 phpcompat.xml.dist diff --git a/.travis.yml b/.travis.yml index a1ffd8aa23..cfd15e10b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,8 @@ matrix: name: PHP Linting - env: WP_TRAVISCI=travis:js WP_INSTALL=false name: JS Tests + - env: WP_TRAVISCI=test:compat COMPOSER_INSTALL=true NPM_INSTALL=false WP_INSTALL=false + name: "PHP Compatibility Check" - env: LOCAL_PHP=7.3-fpm WP_TRAVISCI=test:php name: "PHPUnit Tests: PHP 7.3" - env: LOCAL_PHP=7.3-fpm LOCAL_PHP_MEMCACHED=true WP_TRAVISCI=test:php @@ -46,6 +48,7 @@ matrix: - env: LOCAL_PHP=8.0-fpm WP_TRAVISCI=test:php name: "PHPUnit Tests: PHP 8.0" allow_failures: + - env: WP_TRAVISCI=test:compat COMPOSER_INSTALL=true NPM_INSTALL=false WP_INSTALL=false - env: LOCAL_PHP=7.4-fpm WP_TRAVISCI=test:php - env: LOCAL_PHP=8.0-fpm WP_TRAVISCI=test:php fast_finish: true @@ -120,6 +123,8 @@ script: docker-compose run --rm php composer format && docker-compose run --rm php composer lint:errors && docker-compose run --rm php composer lint tests + elif [[ "$WP_TRAVISCI" == "test:compat" ]]; then + docker-compose run --rm php composer compat else npm run grunt $WP_TRAVISCI fi diff --git a/composer.json b/composer.json index ccc0e2a796..1e0a2b383f 100644 --- a/composer.json +++ b/composer.json @@ -14,9 +14,11 @@ }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "~0.5.0", - "wp-coding-standards/wpcs": "~2.1.0" + "wp-coding-standards/wpcs": "~2.1.0", + "phpcompatibility/phpcompatibility-wp": "^2.1.0" }, "scripts": { + "compat": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --standard=phpcompat.xml.dist --report=summary,source", "format": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf --report=summary,source --cache -d memory_limit=256M", "lint": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --report=summary,source --cache -d memory_limit=256M", "lint:errors": "@lint -n" diff --git a/composer.lock b/composer.lock index 18664c1933..c725fb4e8c 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": "56c797c5309702adf0f49bd274a364f3", + "content-hash": "ca8f46e0b3cba61c076c033cc7143d3a", "packages": [], "packages-dev": [ { @@ -73,6 +73,166 @@ ], "time": "2018-10-26T13:21:45+00:00" }, + { + "name": "phpcompatibility/php-compatibility", + "version": "9.3.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "9999344e47e7af6b00e1a898eacc4e4368fb7196" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9999344e47e7af6b00e1a898eacc4e4368fb7196", + "reference": "9999344e47e7af6b00e1a898eacc4e4368fb7196", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "homepage": "https://github.com/wimg", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "time": "2019-09-05T18:36:49+00:00" + }, + { + "name": "phpcompatibility/phpcompatibility-paragonie", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", + "reference": "b1bb79a7cab1fb856b56f1b5cf110b6e52d8e936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/b1bb79a7cab1fb856b56f1b5cf110b6e52d8e936", + "reference": "b1bb79a7cab1fb856b56f1b5cf110b6e52d8e936", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5", + "paragonie/random_compat": "dev-master", + "paragonie/sodium_compat": "dev-master" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "paragonie", + "phpcs", + "polyfill", + "standards" + ], + "time": "2019-08-28T15:58:19+00:00" + }, + { + "name": "phpcompatibility/phpcompatibility-wp", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", + "reference": "41bef18ba688af638b7310666db28e1ea9158b2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/41bef18ba688af638b7310666db28e1ea9158b2f", + "reference": "41bef18ba688af638b7310666db28e1ea9158b2f", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0", + "phpcompatibility/phpcompatibility-paragonie": "^1.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "phpcs", + "standards", + "wordpress" + ], + "time": "2019-08-28T14:22:28+00:00" + }, { "name": "squizlabs/php_codesniffer", "version": "3.4.0", @@ -176,7 +336,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=5.6" + "php": ">=5.6" }, "platform-dev": [] } diff --git a/phpcompat.xml.dist b/phpcompat.xml.dist new file mode 100644 index 0000000000..110b19cc63 --- /dev/null +++ b/phpcompat.xml.dist @@ -0,0 +1,64 @@ + + + Apply PHP compatibility checks to all WordPress Core files + + + + + + + + + + + + + + + + + + + + + + + + + + ./src/ + + + /node_modules/* + + + /vendor/* + + + + /random_compat/byte_safe_strings\.php$ + + + /random_compat/random_bytes_mcrypt\.php$ + + + /random_compat/random_bytes_mcrypt\.php$ + + + /random_compat/random_bytes_mcrypt\.php$ + + + + + /src/wp-includes/wp-db\.php + + diff --git a/src/wp-admin/includes/class-wp-debug-data.php b/src/wp-admin/includes/class-wp-debug-data.php index 4708e6187c..5ffec9825f 100644 --- a/src/wp-admin/includes/class-wp-debug-data.php +++ b/src/wp-admin/includes/class-wp-debug-data.php @@ -728,7 +728,7 @@ class WP_Debug_Data { if ( isset( $wpdb->use_mysqli ) && $wpdb->use_mysqli ) { $client_version = $wpdb->dbh->client_info; } else { - // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_client_info + // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_client_info,PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved if ( preg_match( '|[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}|', mysql_get_client_info(), $matches ) ) { $client_version = $matches[0]; } else { diff --git a/src/wp-admin/includes/class-wp-site-health.php b/src/wp-admin/includes/class-wp-site-health.php index 5f7c6bc7b7..27b29ad76f 100644 --- a/src/wp-admin/includes/class-wp-site-health.php +++ b/src/wp-admin/includes/class-wp-site-health.php @@ -158,7 +158,7 @@ class WP_Site_Health { // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_get_server_info $mysql_server_type = mysqli_get_server_info( $wpdb->dbh ); } else { - // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_server_info + // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_server_info,PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved $mysql_server_type = mysql_get_server_info( $wpdb->dbh ); } @@ -1171,7 +1171,7 @@ class WP_Site_Health { // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_get_client_info $mysql_client_version = mysqli_get_client_info(); } else { - // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_client_info + // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_client_info,PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved $mysql_client_version = mysql_get_client_info(); } diff --git a/src/wp-admin/includes/file.php b/src/wp-admin/includes/file.php index 11977a8662..9cd626ec0b 100644 --- a/src/wp-admin/includes/file.php +++ b/src/wp-admin/includes/file.php @@ -1280,6 +1280,7 @@ function verify_file_signature( $filename, $signatures, $filename_for_errors = f ), array( 'php' => phpversion(), + // phpcs:ignore PHPCompatibility.Constants.NewConstants.sodium_library_versionFound 'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ), ) ); @@ -1313,6 +1314,7 @@ function verify_file_signature( $filename, $signatures, $filename_for_errors = f ), array( 'php' => phpversion(), + // phpcs:ignore PHPCompatibility.Constants.NewConstants.sodium_library_versionFound 'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ), 'polyfill_is_fast' => false, 'max_execution_time' => ini_get( 'max_execution_time' ), @@ -1386,6 +1388,7 @@ function verify_file_signature( $filename, $signatures, $filename_for_errors = f 'skipped_key' => $skipped_key, 'skipped_sig' => $skipped_signature, 'php' => phpversion(), + // phpcs:ignore PHPCompatibility.Constants.NewConstants.sodium_library_versionFound 'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ), ) ); diff --git a/src/wp-admin/includes/upgrade.php b/src/wp-admin/includes/upgrade.php index 79a23a6308..3b2d62da7c 100644 --- a/src/wp-admin/includes/upgrade.php +++ b/src/wp-admin/includes/upgrade.php @@ -2475,7 +2475,7 @@ function get_alloptions_110() { * @param string $setting Option name. * @return mixed */ -function __get_option( $setting ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore +function __get_option( $setting ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore global $wpdb; if ( $setting == 'home' && defined( 'WP_HOME' ) ) { diff --git a/src/wp-includes/author-template.php b/src/wp-includes/author-template.php index 878fb9e409..a14b628909 100644 --- a/src/wp-includes/author-template.php +++ b/src/wp-includes/author-template.php @@ -565,6 +565,6 @@ function is_multi_author() { * @since 3.2.0 * @access private */ -function __clear_multi_author_cache() { //phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore +function __clear_multi_author_cache() { //phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore delete_transient( 'is_multi_author' ); } diff --git a/src/wp-includes/deprecated.php b/src/wp-includes/deprecated.php index 215213f70b..98da670cc1 100644 --- a/src/wp-includes/deprecated.php +++ b/src/wp-includes/deprecated.php @@ -1798,7 +1798,7 @@ function _nc( $single, $plural, $number, $domain = 'default' ) { * @deprecated 2.8.0 Use _n() * @see _n() */ -function __ngettext( ...$args ) { +function __ngettext( ...$args ) { // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore _deprecated_function( __FUNCTION__, '2.8.0', '_n()' ); return _n( ...$args ); } @@ -1810,7 +1810,7 @@ function __ngettext( ...$args ) { * @deprecated 2.8.0 Use _n_noop() * @see _n_noop() */ -function __ngettext_noop( ...$args ) { +function __ngettext_noop( ...$args ) { // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore _deprecated_function( __FUNCTION__, '2.8.0', '_n_noop()' ); return _n_noop( ...$args ); diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index d3dded8078..c49b0e0e7e 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -5743,7 +5743,7 @@ function get_file_data( $file, $default_headers, $context = '' ) { * * @return true True. */ -function __return_true() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore +function __return_true() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore return true; } @@ -5758,7 +5758,7 @@ function __return_true() { // phpcs:ignore WordPress.NamingConventions.ValidFunc * * @return false False. */ -function __return_false() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore +function __return_false() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore return false; } @@ -5771,7 +5771,7 @@ function __return_false() { // phpcs:ignore WordPress.NamingConventions.ValidFun * * @return int 0. */ -function __return_zero() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore +function __return_zero() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore return 0; } @@ -5784,7 +5784,7 @@ function __return_zero() { // phpcs:ignore WordPress.NamingConventions.ValidFunc * * @return array Empty array. */ -function __return_empty_array() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore +function __return_empty_array() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore return array(); } @@ -5797,7 +5797,7 @@ function __return_empty_array() { // phpcs:ignore WordPress.NamingConventions.Va * * @return null Null value. */ -function __return_null() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore +function __return_null() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore return null; } @@ -5812,7 +5812,7 @@ function __return_null() { // phpcs:ignore WordPress.NamingConventions.ValidFunc * * @return string Empty string. */ -function __return_empty_string() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore +function __return_empty_string() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore return ''; } diff --git a/src/wp-includes/general-template.php b/src/wp-includes/general-template.php index 28d0e399e9..32d5595bb1 100644 --- a/src/wp-includes/general-template.php +++ b/src/wp-includes/general-template.php @@ -4684,7 +4684,7 @@ function readonly( $readonly, $current = true, $echo = true ) { * @param string $type The type of checked|selected|disabled|readonly we are doing * @return string html attribute or empty string */ -function __checked_selected_helper( $helper, $current, $echo, $type ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore +function __checked_selected_helper( $helper, $current, $echo, $type ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore if ( (string) $helper === (string) $current ) { $result = " $type='$type'"; } else {