From 08c810e4bafcf311a909c531be0be0f6fca3b088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeff=20Chi=20=E6=9D=B0=E5=A4=AB=E8=BF=9F?= Date: Fri, 16 Feb 2024 15:33:14 +0100 Subject: [PATCH 1/3] Make Traduttore work again with wp-cli 2.10.0 (#265) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make Traduttore work again with php-cli 2.10.0 * Restore usage of ‚php‘ key * restore empty line --------- Co-authored-by: Dominik Schilling --- inc/Export.php | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/inc/Export.php b/inc/Export.php index 0c2145b..4ba5da8 100644 --- a/inc/Export.php +++ b/inc/Export.php @@ -149,25 +149,29 @@ protected function map_entries_to_source( array $entries ): array { foreach ( $entries as $entry ) { // Find all unique sources this translation originates from. - $sources = array_map( - function ( $reference ) { - $parts = explode( ':', $reference ); - $file = $parts[0]; - - if ( substr( $file, -7 ) === '.min.js' ) { - return substr( $file, 0, -7 ) . '.js'; - } - - if ( substr( $file, -3 ) === '.js' ) { - return $file; - } - - return 'php'; - }, - $entry->references - ); - - $sources = array_unique( $sources ); + if ( ! empty( $entry->references ) ) { + $sources = array_map( + function ( $reference ) { + $parts = explode( ':', $reference ); + $file = $parts[0]; + + if ( substr( $file, -7 ) === '.min.js' ) { + return substr( $file, 0, -7 ) . '.js'; + } + + if ( substr( $file, -3 ) === '.js' ) { + return $file; + } + + return 'php'; + }, + $entry->references + ); + + $sources = array_unique( $sources ); + } else { + $sources = [ 'php' ]; + } foreach ( $sources as $source ) { $mapping[ $source ][] = $entry; From 8a4a120e8c5c96028408b05658380128c387fd78 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 16 May 2024 19:55:57 +0200 Subject: [PATCH 2/3] Cleanup, add PHP file support (#270) * Cleanup, add PHP file support * Update changelog * Update Behat matrix * Lint fixes * PHPStan level 6 * PHPStan level 7 * PHPStan level 8 * Fix requests, PHPStan level 9 * Remove unused imports * PHStan for tests & lint fixes * Bump to PHP 8.1 * Fix Behat coverage config * Fix Behat tests * PHPStan fixes * Update changelog * Fix normalize step * Fix lint step * Update command * Remove php 8.4 step for now * Apply feedback from code review * Replace deprecated set-output command --------- Co-authored-by: Dominik Schilling --- .github/workflows/behat.yml | 39 ++-- .github/workflows/lint.yml | 21 +- .github/workflows/phpunit.yml | 34 ++-- .gitignore | 1 + CHANGELOG.md | 4 +- bin/install-wp-tests.sh | 10 +- composer.json | 27 ++- inc/CLI/InfoCommand.php | 4 +- inc/CLI/LanguagePackCommand.php | 8 +- inc/CLI/ProjectCommand.php | 11 +- inc/Configuration.php | 40 +++- inc/Export.php | 52 ++++- inc/Loader/Base.php | 4 +- inc/Loader/Git.php | 6 +- inc/Loader/Mercurial.php | 6 +- inc/Loader/Subversion.php | 7 +- inc/Plugin.php | 27 ++- inc/Project.php | 57 +++++- inc/ProjectLocator.php | 18 +- inc/Repository/Base.php | 10 +- inc/Runner.php | 8 +- inc/Updater.php | 6 +- inc/WebhookHandler.php | 6 +- inc/WebhookHandler/Base.php | 8 +- inc/WebhookHandler/Bitbucket.php | 16 +- inc/WebhookHandler/GitHub.php | 42 +++- inc/WebhookHandler/GitLab.php | 18 +- inc/WebhookHandlerFactory.php | 2 + inc/ZipProvider.php | 30 ++- phpcs.xml.dist | 14 +- phpstan.neon.dist | 12 +- .../behat/maybe-generate-wp-cli-coverage.php | 4 +- tests/features/info.feature | 3 +- tests/features/project.feature | 3 +- tests/features/testing.feature | 4 +- tests/phpstan/bootstrap.php | 1 - tests/phpstan/stubs/factory.php | 129 ++++++++++++ tests/phpstan/stubs/test-stubs.php | 13 ++ tests/phpstan/stubs/testcase-route.php | 55 ++++++ tests/phpstan/stubs/testcase.php | 68 +++++++ tests/phpunit/bootstrap.php | 4 +- tests/phpunit/tests/Behat/FeatureContext.php | 24 +-- tests/phpunit/tests/Configuration.php | 17 +- tests/phpunit/tests/Export.php | 100 ++++++---- tests/phpunit/tests/Loader/Git.php | 22 +-- tests/phpunit/tests/Loader/Mercurial.php | 22 +-- tests/phpunit/tests/Loader/Subversion.php | 21 +- tests/phpunit/tests/LoaderFactory.php | 20 +- tests/phpunit/tests/Project.php | 28 ++- tests/phpunit/tests/ProjectLocator.php | 51 ++--- tests/phpunit/tests/Repository/Bitbucket.php | 39 ++-- tests/phpunit/tests/Repository/GitHub.php | 34 ++-- tests/phpunit/tests/Repository/GitLab.php | 32 ++- tests/phpunit/tests/RepositoryFactory.php | 30 +-- tests/phpunit/tests/RestrictedSiteAccess.php | 2 - tests/phpunit/tests/Runner.php | 39 ++-- tests/phpunit/tests/TestCase.php | 27 ++- tests/phpunit/tests/TranslationApiRoute.php | 128 +++++++----- tests/phpunit/tests/Updater.php | 36 ++-- .../tests/WebhookHandler/Bitbucket.php | 138 +++++++------ tests/phpunit/tests/WebhookHandler/GitHub.php | 187 ++++++++++-------- tests/phpunit/tests/WebhookHandler/GitLab.php | 18 +- .../tests/WebhookHandler/LegacyGitHub.php | 150 +++++++------- tests/phpunit/tests/ZipProvider.php | 105 +++++----- traduttore.php | 2 + 65 files changed, 1319 insertions(+), 785 deletions(-) create mode 100644 tests/phpstan/stubs/factory.php create mode 100644 tests/phpstan/stubs/test-stubs.php create mode 100644 tests/phpstan/stubs/testcase-route.php create mode 100644 tests/phpstan/stubs/testcase.php diff --git a/.github/workflows/behat.yml b/.github/workflows/behat.yml index f8d3d0a..a5765a9 100644 --- a/.github/workflows/behat.yml +++ b/.github/workflows/behat.yml @@ -32,42 +32,38 @@ jobs: strategy: matrix: os: [ ubuntu-latest ] - php: [ '7.4' ] - wordpress: [ 'latest', 'nightly' ] - glotpress: [ 'develop' ] + php: [ '8.1' ] + wordpress: [ 'latest' ] + glotpress: [ 'latest' ] experimental: [ false ] include: - # - php: '8.0' - # os: ubuntu-latest - # experimental: true - os: ubuntu-latest - php: '7.2' + php: '8.2' wordpress: 'latest' glotpress: 'latest' experimental: false + coverage: true - os: ubuntu-latest - php: '7.3' - wordpress: 'latest' - glotpress: 'latest' - experimental: false + php: '8.2' + wordpress: 'nightly' + glotpress: 'develop' + experimental: true - os: ubuntu-latest - php: '7.4' - wordpress: 'latest' + php: '8.3' + wordpress: 'nightly' glotpress: 'develop' - experimental: false - coverage: true + experimental: true steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - coverage: ${{ matrix.coverage && 'pcov' || 'none' }} + coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} tools: composer - ini-values: pcov.directory=.,pcov.exclude=~(vendor|tests)~ - name: Shutdown default MySQL service run: sudo service mysql stop @@ -79,7 +75,7 @@ jobs: done - name: Install PHP dependencies - uses: ramsey/composer-install@v2 + uses: ramsey/composer-install@v3 - name: Make Composer packages available globally run: | @@ -130,12 +126,13 @@ jobs: FILES=$(ls -d -1 "$GITHUB_WORKSPACE/build/logs/clover-behat/"*.* | paste --serial --delimiters=",") test -n "$FILES" echo "Coverage files: $FILES" - echo "::set-output name=COVERAGE_FILES::$FILES" + echo "COVERAGE_FILES=$FILES" >> $GITHUB_OUTPUT - name: Upload code coverage report if: ${{ matrix.coverage }} - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: + token: ${{ secrets.CODECOV_TOKEN }} files: ${{ steps.coverage_files.outputs.COVERAGE_FILES }} flags: feature fail_ci_if_error: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7b5e91b..6db76e9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,33 +13,20 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up PHP uses: shivammathur/setup-php@v2 with: - php-version: "7.4" + php-version: latest coverage: none tools: composer, cs2pr - - name: Get Composer home directory - id: composer-home - run: | - echo "::set-output name=dir::$(composer config home)" - - name: Install PHP dependencies - uses: ramsey/composer-install@v2 - - - name: Install composer-normalize - run: composer global require ergebnis/composer-normalize - - - name: Make Composer packages available globally - run: | - echo "${PWD}/vendor/bin" >> $GITHUB_PATH - echo "${{ steps.composer-home.outputs.dir }}/vendor/bin" >> $GITHUB_PATH + uses: ramsey/composer-install@v3 - name: Lint PHP files - run: phpcs -q --report=checkstyle | cs2pr + run: vendor/bin/phpcs -q --report=checkstyle --severity=1 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 | cs2pr --graceful-warnings - name: Analyze PHP files run: composer run-script analyze diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 7f8e6ba..45a465e 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -28,34 +28,31 @@ jobs: strategy: matrix: os: [ ubuntu-latest ] - php: [ '7.4' ] - wordpress: [ 'latest', 'nightly' ] - glotpress: [ 'develop' ] + php: [ '8.1' ] + wordpress: [ 'latest' ] + glotpress: [ 'latest' ] experimental: [ false ] include: - # - php: '8.0' - # os: ubuntu-latest - # experimental: true - os: ubuntu-latest - php: '7.2' + php: '8.2' wordpress: 'latest' glotpress: 'latest' experimental: false + coverage: true - os: ubuntu-latest - php: '7.3' - wordpress: 'latest' - glotpress: 'latest' - experimental: false + php: '8.2' + wordpress: 'nightly' + glotpress: 'develop' + experimental: true - os: ubuntu-latest - php: '7.4' - wordpress: 'latest' + php: '8.3' + wordpress: 'nightly' glotpress: 'develop' - experimental: false - coverage: true + experimental: true steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -65,7 +62,7 @@ jobs: tools: composer - name: Install PHP dependencies - uses: ramsey/composer-install@v2 + uses: ramsey/composer-install@v3 - name: Make Composer packages available globally run: | @@ -99,7 +96,8 @@ jobs: - name: Upload coverage to Codecov if: ${{ matrix.coverage }} - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: + token: ${{ secrets.CODECOV_TOKEN }} file: coverage-clover-${{ github.sha }}.xml flags: php diff --git a/.gitignore b/.gitignore index dfe326b..59f3442 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ # PHPUnit /coverage*.xml +.phpunit.result.cache # Behat *.log diff --git a/CHANGELOG.md b/CHANGELOG.md index cce5dd6..7045ac2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Changed -* Require at least PHP 7.2. [#236] +* Require GlotPress 4.0. [#270] +* Require at least PHP 8.1. [#270] +* Added PHP translation file support. [#270] ### Fixed * Pass correct parameter for translation set in `traduttore.generate_zip_delay` filter. [#236] diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh index 9470cf2..ab87670 100755 --- a/bin/install-wp-tests.sh +++ b/bin/install-wp-tests.sh @@ -16,7 +16,9 @@ SKIP_DB_CREATE=${7-false} TMPDIR=${TMPDIR-/tmp} TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//") WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib} +WP_TESTS_FILE="$WP_TESTS_DIR"/includes/functions.php WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress/} +WP_CORE_FILE="$WP_CORE_DIR"/wp-includs.php GP_TESTS_DIR="$WP_CORE_DIR/build/wp-content/plugins/glotpress/tests/phpunit" download() { @@ -56,10 +58,11 @@ set -ex install_wp() { - if [ -d $WP_CORE_DIR ]; then + if [ -f $WP_CORE_FILE ]; then return; fi + rm -rf $WP_CORE_DIR mkdir -p $WP_CORE_DIR if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then @@ -104,9 +107,10 @@ install_test_suite() { local ioption='-i' fi - # set up testing suite if it doesn't yet exist - if [ ! -d $WP_TESTS_DIR ]; then + # set up testing suite if it doesn't yet exist or only partially exists + if [ ! -f $WP_TESTS_FILE ]; then # set up testing suite + rm -rf $WP_TESTS_DIR mkdir -p $WP_TESTS_DIR svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data diff --git a/composer.json b/composer.json index 63edb3e..140e4e3 100644 --- a/composer.json +++ b/composer.json @@ -35,22 +35,27 @@ "issues": "https://github.com/wearerequired/traduttore/issues" }, "require": { - "php": ">=7.2", + "php": ">=8.1", "ext-json": "*", "ext-zip": "*", "wearerequired/traduttore-registry": "^2.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "php-stubs/wp-cli-stubs": "^2.4", - "phpunit/phpunit": "^7.5.20", - "szepeviktor/phpstan-wordpress": "^1.1", - "wearerequired/coding-standards": "^3.0", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0.0", + "ergebnis/composer-normalize": "^2.42", + "php-stubs/wordpress-tests-stubs": "^6.5", + "php-stubs/wp-cli-stubs": "^2.10", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan-deprecation-rules": "^1.2", + "phpstan/phpstan-phpunit": "^1.4", + "swissspidy/phpstan-no-private": "^0.2.0", + "szepeviktor/phpstan-wordpress": "^1.3", + "wearerequired/coding-standards": "^6.0", "wp-cli/extension-command": "^2.0", "wp-cli/rewrite-command": "^2.0", - "wp-cli/wp-cli-tests": "^3.0.11", - "wpackagist-plugin/glotpress": "^3.0.0", - "yoast/phpunit-polyfills": "^1.0" + "wp-cli/wp-cli-tests": "^4.2.9", + "wpackagist-plugin/glotpress": "^4.0.0", + "yoast/phpunit-polyfills": "^1.1" }, "suggest": { "wpackagist-plugin/slack": "Send Slack notifications for various events" @@ -73,8 +78,10 @@ }, "config": { "allow-plugins": { + "composer/installers": true, "dealerdirect/phpcodesniffer-composer-installer": true, - "composer/installers": true + "ergebnis/composer-normalize": true, + "phpstan/extension-installer": true }, "process-timeout": 7200, "sort-packages": true diff --git a/inc/CLI/InfoCommand.php b/inc/CLI/InfoCommand.php index cf94f1b..1325f6f 100644 --- a/inc/CLI/InfoCommand.php +++ b/inc/CLI/InfoCommand.php @@ -76,7 +76,7 @@ public function __invoke( array $args, array $assoc_args ): void { 'cache_dir' => $cache_dir, ]; - WP_CLI::line( json_encode( $info ) ); + WP_CLI::line( (string) json_encode( $info ) ); } else { WP_CLI::line( "Traduttore version:\t" . $plugin_version ); WP_CLI::line( "WordPress version:\t" . $wp_version ); @@ -149,7 +149,7 @@ protected function get_svn_binary_path(): ?string { * @return null|string Binary path on success, null otherwise. */ protected function get_wp_binary_path(): ?string { - if ( \defined( 'TRADUTTORE_WP_BIN' ) && TRADUTTORE_WP_BIN ) { + if ( \defined( 'TRADUTTORE_WP_BIN' ) && \is_string( TRADUTTORE_WP_BIN ) ) { return TRADUTTORE_WP_BIN; } diff --git a/inc/CLI/LanguagePackCommand.php b/inc/CLI/LanguagePackCommand.php index c3d38ae..cba707e 100644 --- a/inc/CLI/LanguagePackCommand.php +++ b/inc/CLI/LanguagePackCommand.php @@ -96,12 +96,14 @@ public function list( array $args, array $assoc_args ): void { $zip_provider = new ZipProvider( $set ); + $last_updated = $zip_provider->get_last_build_time(); + $language_packs[] = [ 'Locale' => $locale->wp_locale, 'English Name' => $locale->english_name, 'Native Name' => $locale->native_name, 'Completed' => sprintf( '%s%%', $set->percent_translated() ), - 'Updated' => $zip_provider->get_last_build_time()->format( DATE_ATOM ), + 'Updated' => $last_updated ? $last_updated->format( DATE_ATOM ) : 'n/a', 'Package' => file_exists( $zip_provider->get_zip_path() ) ? $zip_provider->get_zip_url() : 'n/a', ]; } @@ -160,8 +162,8 @@ public function list( array $args, array $assoc_args ): void { * @param string[] $assoc_args Associative args. */ public function build( array $args, array $assoc_args ): void { - $all = get_flag_value( $assoc_args, 'all', false ); - $force = get_flag_value( $assoc_args, 'force', false ); + $all = (bool) get_flag_value( $assoc_args, 'all', false ); + $force = (bool) get_flag_value( $assoc_args, 'force', false ); $projects = $this->check_optional_args_and_all( $args, $all ); if ( ! $projects ) { diff --git a/inc/CLI/ProjectCommand.php b/inc/CLI/ProjectCommand.php index 5da100d..e06025e 100644 --- a/inc/CLI/ProjectCommand.php +++ b/inc/CLI/ProjectCommand.php @@ -88,8 +88,8 @@ public function info( array $args, array $assoc_args ): void { $repository_visibility = $project->get_repository_visibility() ?? '(unknown)'; $repository_ssh_url = $repository ? $repository->get_ssh_url() : '(unknown)'; $repository_https_url = $repository ? $repository->get_https_url() : '(unknown)'; - $repository_instance = $repository ? \get_class( $repository ) : '(unknown)'; - $loader_instance = $loader ? \get_class( $loader ) : '(unknown)'; + $repository_instance = $repository ? $repository::class : '(unknown)'; + $loader_instance = $loader ? $loader::class : '(unknown)'; if ( get_flag_value( $assoc_args, 'format' ) === 'json' ) { $info = [ @@ -110,7 +110,7 @@ public function info( array $args, array $assoc_args ): void { 'loader_instance' => $loader_instance, ]; - WP_CLI::line( json_encode( $info ) ); + WP_CLI::line( (string) json_encode( $info ) ); } else { WP_CLI::line( "Project ID:\t\t" . $project_id ); WP_CLI::line( "Project name:\t\t" . $project_name ); @@ -166,8 +166,8 @@ public function info( array $args, array $assoc_args ): void { * @param string[] $assoc_args Associative args. */ public function update( array $args, array $assoc_args ): void { - $delete = get_flag_value( $assoc_args, 'delete', false ); - $cached = get_flag_value( $assoc_args, 'cached', false ); + $delete = (bool) get_flag_value( $assoc_args, 'delete', false ); + $cached = (bool) get_flag_value( $assoc_args, 'cached', false ); $locator = new ProjectLocator( $args[0] ); $project = $locator->get_project(); @@ -205,5 +205,4 @@ public function update( array $args, array $assoc_args ): void { WP_CLI::warning( sprintf( 'Could not update translations for project (ID: %d)!', $project->get_id() ) ); } - } diff --git a/inc/Configuration.php b/inc/Configuration.php index 7c727fe..c6dd0be 100644 --- a/inc/Configuration.php +++ b/inc/Configuration.php @@ -11,6 +11,8 @@ * Configuration class. * * @since 3.0.0 + * + * @phpstan-type ProjectConfig array{ mergeWith?: string, textDomain?: string,exclude?: string[] } */ class Configuration { /** @@ -20,7 +22,7 @@ class Configuration { * * @var string Repository path. */ - protected $path; + protected string $path; /** * Repository configuration. @@ -28,8 +30,10 @@ class Configuration { * @since 3.0.0 * * @var array Repository configuration. + * + * @phpstan-var ProjectConfig */ - protected $config = []; + protected array $config = []; /** * Class constructor. @@ -60,7 +64,9 @@ public function get_path(): string { * * @since 3.0.0 * - * @return array Repository configuration. + * @return array Repository configuration. + * + * @phpstan-return ProjectConfig */ public function get_config(): array { return $this->config; @@ -72,9 +78,13 @@ public function get_config(): array { * @since 3.0.0 * * @param string $key Config key. - * @return mixed|null Config value. + * @return string|string[]|null Config value. + * + * @phpstan-template T of key-of + * @phpstan-param T $key + * @phpstan-return ProjectConfig[T] | null */ - public function get_config_value( string $key ) { + public function get_config_value( string $key ): mixed { if ( isset( $this->config[ $key ] ) ) { return $this->config[ $key ]; } @@ -87,14 +97,22 @@ public function get_config_value( string $key ) { * * @since 3.0.0 * - * @return array> Configuration data if found. + * @return array Configuration data if found. + * + * @phpstan-return ProjectConfig */ protected function load_config(): array { $config_file = trailingslashit( $this->path ) . 'traduttore.json'; $composer_file = trailingslashit( $this->path ) . 'composer.json'; if ( file_exists( $config_file ) ) { - $config = json_decode( file_get_contents( $config_file ), true ); + /** + * Traduttore configuration. + * + * @phpstan-var ProjectConfig $config + * @var array $config + */ + $config = json_decode( (string) file_get_contents( $config_file ), true ); if ( $config ) { return $config; @@ -102,7 +120,13 @@ protected function load_config(): array { } if ( file_exists( $composer_file ) ) { - $config = json_decode( file_get_contents( $composer_file ), true ); + /** + * Composer configuration. + * + * @phpstan-var array{extra?: array{ traduttore?: ProjectConfig } } $config + * @var array{extra?: array{ traduttore?: array } } $config + */ + $config = json_decode( (string) file_get_contents( $composer_file ), true ); if ( $config && isset( $config['extra']['traduttore'] ) ) { return $config['extra']['traduttore']; diff --git a/inc/Export.php b/inc/Export.php index 4ba5da8..eb7014e 100644 --- a/inc/Export.php +++ b/inc/Export.php @@ -24,7 +24,7 @@ class Export { * * @var \GP_Translation_Set */ - protected $translation_set; + protected GP_Translation_Set $translation_set; /** * The current locale. @@ -42,7 +42,7 @@ class Export { * * @var \Required\Traduttore\Project */ - protected $project; + protected Project $project; /** * List of generated files. @@ -51,7 +51,7 @@ class Export { * * @var string[] */ - protected $files; + protected array $files; /** * Export constructor. @@ -61,7 +61,15 @@ class Export { public function __construct( GP_Translation_Set $translation_set ) { $this->translation_set = $translation_set; $this->locale = GP_Locales::by_slug( $translation_set->locale ); - $this->project = new Project( GP::$project->get( $translation_set->project_id ) ); + + /** + * GlotPress project. + * + * @var \GP_Project $gp_project + */ + $gp_project = GP::$project->get( $translation_set->project_id ); + + $this->project = new Project( $gp_project ); } /** @@ -79,6 +87,7 @@ public function export_strings(): ?array { } // Build a mapping based on where the translation entries occur and separate the po entries. + $mapping = $this->map_entries_to_source( $entries ); $php_entries = \array_key_exists( 'php', $mapping ) ? $mapping['php'] : []; @@ -88,6 +97,7 @@ public function export_strings(): ?array { $this->build_json_files( $mapping ); $this->build_po_file( $php_entries ); $this->build_mo_file( $php_entries ); + $this->build_php_file( $php_entries ); return $this->files; } @@ -113,6 +123,10 @@ protected function write_to_file( string $file, string $contents ): bool { } } + if ( ! $wp_filesystem ) { + return false; + } + return $wp_filesystem->put_contents( $file, $contents, FS_CHMOD_FILE ); } @@ -142,7 +156,7 @@ protected function get_base_file_name(): string { * @since 3.0.0 * * @param \Translation_Entry[] $entries The translation entries to map. - * @return array The mapping of sources to translation entries. + * @return array The mapping of sources to translation entries. */ protected function map_entries_to_source( array $entries ): array { $mapping = []; @@ -197,7 +211,7 @@ function ( $reference ) { * * @since 3.0.0 * - * @param array $mapping A mapping of files to translation entries. + * @param array $mapping A mapping of files to translation entries. */ protected function build_json_files( array $mapping ): void { /** @var \GP_Format $format */ @@ -214,9 +228,9 @@ protected function build_json_files( array $mapping ): void { $contents = $format->print_exported_file( $this->project->get_project(), $this->locale, $this->translation_set, $entries ); // Add comment with file reference for debugging. - $contents_decoded = json_decode( $contents ); + $contents_decoded = (object) json_decode( $contents ); $contents_decoded->comment = [ 'reference' => $file ]; - $contents = wp_json_encode( $contents_decoded ); + $contents = (string) wp_json_encode( $contents_decoded ); $hash = md5( $file ); $file_name = "{$base_file_name}-{$hash}.json"; @@ -271,4 +285,26 @@ protected function build_mo_file( array $entries ): void { $this->files[ $file_name ] = $temp_file; } } + + /** + * Builds a PHP file for translations. + * + * @since 3.3.0 + * + * @param \Translation_Entry[] $entries The translation entries. + */ + protected function build_php_file( array $entries ): void { + /** @var \GP_Format $format */ + $format = gp_array_get( GP::$formats, 'php' ); + + $base_file_name = $this->get_base_file_name(); + $file_name = "{$base_file_name}.l10n.php"; + $temp_file = wp_tempnam( $file_name ); + + $contents = $format->print_exported_file( $this->project->get_project(), $this->locale, $this->translation_set, $entries ); + + if ( $this->write_to_file( $temp_file, $contents ) ) { + $this->files[ $file_name ] = $temp_file; + } + } } diff --git a/inc/Loader/Base.php b/inc/Loader/Base.php index fb8211b..b9f7bf6 100644 --- a/inc/Loader/Base.php +++ b/inc/Loader/Base.php @@ -21,7 +21,7 @@ abstract class Base implements Loader { * * @var \Required\Traduttore\Repository Repository object. */ - protected $repository; + protected Repository $repository; /** * Class constructor. @@ -46,7 +46,7 @@ public function get_local_path(): string { '%1$straduttore-%2$s-%3$s', get_temp_dir(), $this->repository->get_host(), - sanitize_title( $this->repository->get_name() ) + sanitize_title( $this->repository->get_name() ?? '' ) ); } } diff --git a/inc/Loader/Git.php b/inc/Loader/Git.php index 38eb1db..9d45d1a 100644 --- a/inc/Loader/Git.php +++ b/inc/Loader/Git.php @@ -30,7 +30,9 @@ public function download(): ?string { chdir( $target ); exec( escapeshellcmd( 'git reset --hard -q' ), $output, $status ); exec( escapeshellcmd( 'git pull -q' ), $output, $status ); - chdir( $current_dir ); + if ( $current_dir ) { + chdir( $current_dir ); + } return 0 === $status ? $target : null; } @@ -87,6 +89,6 @@ protected function get_clone_url(): string { * @param string $clone_url The URL to clone a Git repository. * @param \Required\Traduttore\Repository $repository The current repository. */ - return apply_filters( 'traduttore.git_clone_url', $clone_url, $this->repository ); + return apply_filters( 'traduttore.git_clone_url', (string) $clone_url, $this->repository ); } } diff --git a/inc/Loader/Mercurial.php b/inc/Loader/Mercurial.php index 8d0cbf9..309e89f 100644 --- a/inc/Loader/Mercurial.php +++ b/inc/Loader/Mercurial.php @@ -30,7 +30,9 @@ public function download(): ?string { chdir( $target ); exec( escapeshellcmd( 'hg update --clean -q' ), $output, $status ); exec( escapeshellcmd( 'hg pull -q' ), $output, $status ); - chdir( $current_dir ); + if ( $current_dir ) { + chdir( $current_dir ); + } return 0 === $status ? $target : null; } @@ -87,6 +89,6 @@ protected function get_clone_url(): string { * @param string $clone_url The URL to clone a Mercurial repository. * @param \Required\Traduttore\Repository $repository The current repository. */ - return apply_filters( 'traduttore.hg_clone_url', $clone_url, $this->repository ); + return apply_filters( 'traduttore.hg_clone_url', (string) $clone_url, $this->repository ); } } diff --git a/inc/Loader/Subversion.php b/inc/Loader/Subversion.php index c41f22b..a49f5ce 100644 --- a/inc/Loader/Subversion.php +++ b/inc/Loader/Subversion.php @@ -53,7 +53,6 @@ protected function download_new_repository(): ?string { ); return 0 === $status ? $target : null; - } /** @@ -70,7 +69,9 @@ protected function update_existing_repository(): ?string { chdir( $target ); exec( escapeshellcmd( 'svn revert --recursive .' ), $output, $status ); exec( escapeshellcmd( 'svn update .' ), $output, $status ); - chdir( $current_dir ); + if ( $current_dir ) { + chdir( $current_dir ); + } return 0 === $status ? $target : null; } @@ -112,6 +113,6 @@ protected function get_checkout_url(): string { * @param string $checkout_url The URL to check out a Subversion repository. * @param \Required\Traduttore\Repository $repository The current repository. */ - return apply_filters( 'traduttore.svn_checkout_url', $checkout_url, $this->repository ); + return apply_filters( 'traduttore.svn_checkout_url', (string) $checkout_url, $this->repository ); } } diff --git a/inc/Plugin.php b/inc/Plugin.php index fbd6d2d..8babe7a 100644 --- a/inc/Plugin.php +++ b/inc/Plugin.php @@ -106,7 +106,7 @@ static function ( $project_id, $originals_added, $originals_existing, $originals add_action( 'traduttore.generate_zip', - static function( $translation_set_id ): void { + static function ( $translation_set_id ): void { /** @var \GP_Translation_Set $translation_set */ $translation_set = GP::$translation_set->get( $translation_set_id ); @@ -117,7 +117,7 @@ static function( $translation_set_id ): void { add_filter( 'gp_update_meta', - static function( $meta_tuple ) { + static function ( $meta_tuple ) { $allowed_keys = [ Project::VERSION_KEY, // '_traduttore_version'. Project::TEXT_DOMAIN_KEY, // '_traduttore_text_domain'. @@ -183,14 +183,21 @@ function ( $project_id ): void { add_filter( 'slack_get_events', - static function( $events ) { + static function ( $events ) { $events['traduttore.zip_generated'] = [ 'action' => 'traduttore.zip_generated', 'description' => __( 'When a new translation ZIP file is built', 'traduttore' ), - 'message' => function( $zip_path, $zip_url, GP_Translation_Set $translation_set ) { + 'message' => function ( $zip_path, $zip_url, GP_Translation_Set $translation_set ) { /** @var \GP_Locale $locale */ - $locale = GP_Locales::by_slug( $translation_set->locale ); - $project = new Project( GP::$project->get( $translation_set->project_id ) ); + $locale = GP_Locales::by_slug( $translation_set->locale ); + + $gp_project = GP::$project->get( $translation_set->project_id ); + + if ( ! $gp_project ) { + return false; + } + + $project = new Project( $gp_project ); /** * Filters whether a Slack notification for translation updates from GitHub should be sent. @@ -231,7 +238,7 @@ static function( $events ) { $events['traduttore.updated'] = [ 'action' => 'traduttore.updated', 'description' => __( 'When new translations are updated for a project', 'traduttore' ), - 'message' => function( Project $project, array $stats ) { + 'message' => function ( Project $project, array $stats ) { [ $originals_added, $originals_existing, // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable @@ -392,6 +399,8 @@ public function filter_restricted_site_access_is_restricted( bool $is_restricted * * @param \WP_REST_Request $request Request object. * @return bool True if permission is granted, false otherwise. + * + * @phpstan-param \WP_REST_Request $request */ public function incoming_webhook_permission_callback( WP_REST_Request $request ): bool { $result = false; @@ -422,8 +431,10 @@ public function incoming_webhook_permission_callback( WP_REST_Request $request ) * * @param \WP_REST_Request $request Request object. * @return \WP_Error|\WP_REST_Response REST response on success, error object on failure. + * + * @phpstan-param \WP_REST_Request $request */ - public function incoming_webhook_callback( WP_REST_Request $request ) { + public function incoming_webhook_callback( WP_REST_Request $request ): \WP_Error|\WP_REST_Response { $result = new \WP_Error( '400', 'Bad request' ); $handler = ( new WebhookHandlerFactory() )->get_handler( $request ); diff --git a/inc/Project.php b/inc/Project.php index d00a83b..cbdb5c9 100644 --- a/inc/Project.php +++ b/inc/Project.php @@ -101,7 +101,7 @@ class Project { * * @var \GP_Project Project information. */ - protected $project; + protected GP_Project $project; /** * Project constructor. @@ -190,6 +190,11 @@ public function get_source_url_template(): ?string { * @return string|null Repository type if stored, null otherwise. */ public function get_repository_type(): ?string { + /** + * Project type. + * + * @var string|false $type + */ $type = gp_get_meta( 'project', $this->project->id, static::REPOSITORY_TYPE_KEY ); return $type ?: null; @@ -215,6 +220,11 @@ public function set_repository_type( string $type ): bool { * @return null|string VCS type if stored, null otherwise. */ public function get_repository_vcs_type(): ?string { + /** + * VCS type. + * + * @var string|false $type + */ $type = gp_get_meta( 'project', $this->project->id, static::REPOSITORY_VCS_TYPE_KEY ); return $type ?: null; @@ -240,6 +250,11 @@ public function set_repository_vcs_type( string $type ): bool { * @return null|string Repository URL if stored, null otherwise. */ public function get_repository_url(): ?string { + /** + * Repository URL. + * + * @var string|false $url + */ $url = gp_get_meta( 'project', $this->project->id, static::REPOSITORY_URL_KEY ); return $url ?: null; @@ -265,6 +280,11 @@ public function set_repository_url( string $url ): bool { * @return null|string Repository name if stored, null otherwise. */ public function get_repository_name(): ?string { + /** + * Repository name. + * + * @var string|false $name + */ $name = gp_get_meta( 'project', $this->project->id, static::REPOSITORY_NAME_KEY ); return $name ?: null; @@ -290,6 +310,11 @@ public function set_repository_name( string $name ): bool { * @return null|string Repository visibility if stored, null otherwise. */ public function get_repository_visibility(): ?string { + /** + * Repository visibility. + * + * @var string|false $visibility + */ $visibility = gp_get_meta( 'project', $this->project->id, static::REPOSITORY_VISIBILITY_KEY ); return $visibility ?: null; @@ -313,6 +338,11 @@ public function set_repository_visibility( string $visibility ): bool { * @return null|string Repository SSH URL if stored, null otherwise. */ public function get_repository_ssh_url(): ?string { + /** + * Repository SSH URL. + * + * @var string|false $url + */ $url = gp_get_meta( 'project', $this->project->id, static::REPOSITORY_SSH_URL_KEY ); return $url ?: null; @@ -338,6 +368,11 @@ public function set_repository_ssh_url( string $url ): bool { * @return null|string Repository HTTPS URL if stored, null otherwise. */ public function get_repository_https_url(): ?string { + /** + * Repository HTTPS URL. + * + * @var string|false $url + */ $url = gp_get_meta( 'project', $this->project->id, static::REPOSITORY_HTTPS_URL_KEY ); return $url ?: null; @@ -363,6 +398,11 @@ public function set_repository_https_url( string $url ): bool { * @return null|string Webhook sync secret if stored, null otherwise. */ public function get_repository_webhook_secret(): ?string { + /** + * Repository webhook secret. + * + * @var string|false $name + */ $name = gp_get_meta( 'project', $this->project->id, static::REPOSITORY_WEBHOOK_SECRET_KEY ); return $name ?: null; @@ -388,6 +428,11 @@ public function set_repository_webhook_secret( string $secret ): bool { * @return null|string Text domain if stored, null otherwise. */ public function get_text_domain(): ?string { + /** + * Project text domain + * + * @var string|false $name + */ $name = gp_get_meta( 'project', $this->project->id, static::TEXT_DOMAIN_KEY ); return $name ?: null; @@ -413,6 +458,11 @@ public function set_text_domain( string $name ): bool { * @return null|\DateTime Last updated time if stored, null otherwise. */ public function get_last_updated_time(): ?DateTime { + /** + * Project last updated time. + * + * @var string|false $time + */ $time = gp_get_meta( 'project', $this->project->id, static::UPDATE_TIME_KEY ); return $time ? new DateTime( $time, new DateTimeZone( 'UTC' ) ) : null; @@ -438,6 +488,11 @@ public function set_last_updated_time( DateTime $time ): bool { * @return null|string Version number if stored, null otherwise. */ public function get_version(): ?string { + /** + * Project version number. + * + * @var string|false $version + */ $version = gp_get_meta( 'project', $this->project->id, static::VERSION_KEY ); return $version ?: null; diff --git a/inc/ProjectLocator.php b/inc/ProjectLocator.php index 8f9f73c..215ffa0 100644 --- a/inc/ProjectLocator.php +++ b/inc/ProjectLocator.php @@ -21,18 +21,18 @@ class ProjectLocator { * * @since 2.0.0 * - * @var \Required\Traduttore\Project Project instance. + * @var \Required\Traduttore\Project|null Project instance. */ - protected $project; + protected ?Project $project; /** * ProjectLocator constructor. * * @since 2.0.0 * - * @param mixed $project Possible GlotPress project ID or path or source code repository path. + * @param int|string|\Required\Traduttore\Project|\GP_Project $project Possible GlotPress project ID or path or source code repository path. */ - public function __construct( $project ) { + public function __construct( int|string|Project|\GP_Project $project ) { $this->project = $this->find_project( $project ); } @@ -52,10 +52,10 @@ public function get_project(): ?Project { * * @since 2.0.0 * - * @param mixed $project Possible GlotPress project ID or path or source code repository path. + * @param int|string|\Required\Traduttore\Project|\GP_Project $project Possible GlotPress project ID or path or source code repository path. * @return \Required\Traduttore\Project Project instance. */ - protected function find_project( $project ): ?Project { + protected function find_project( int|string|Project|\GP_Project $project ): ?Project { if ( ! $project ) { return null; } @@ -75,15 +75,15 @@ protected function find_project( $project ): ?Project { } if ( ! $found ) { - $found = $this->find_by_repository_name( $project ); + $found = $this->find_by_repository_name( (string) $project ); } if ( ! $found ) { - $found = $this->find_by_repository_url( $project ); + $found = $this->find_by_repository_url( (string) $project ); } if ( ! $found ) { - $found = $this->find_by_source_url_template( $project ); + $found = $this->find_by_source_url_template( (string) $project ); } return $found ? new Project( $found ) : null; diff --git a/inc/Repository/Base.php b/inc/Repository/Base.php index 8621da5..661ae56 100644 --- a/inc/Repository/Base.php +++ b/inc/Repository/Base.php @@ -23,7 +23,7 @@ abstract class Base implements Repository { * * @var \Required\Traduttore\Project Project information. */ - protected $project; + protected Project $project; /** * Loader constructor. @@ -79,9 +79,10 @@ public function get_project(): Project { * @return string Repository host name. */ public function get_host(): ?string { - $url = $this->project->get_repository_url(); + $url = $this->project->get_repository_url(); + $host = $url ? wp_parse_url( $url, PHP_URL_HOST ) : null; - return $url ? wp_parse_url( $url, PHP_URL_HOST ) : null; + return $host ?: null; } /** @@ -105,7 +106,8 @@ public function get_name(): string { } if ( $url ) { - $path = trim( wp_parse_url( $url, PHP_URL_PATH ), '/' ); + $path = wp_parse_url( $url, PHP_URL_PATH ); + $path = $path ? trim( $path, '/' ) : ''; $parts = explode( '/', $path ); $name = implode( '/', array_splice( $parts, 0, 2 ) ); } diff --git a/inc/Runner.php b/inc/Runner.php index 941a399..9e5ff09 100644 --- a/inc/Runner.php +++ b/inc/Runner.php @@ -22,7 +22,7 @@ class Runner { * * @var \Required\Traduttore\Loader VCS loader. */ - protected $loader; + protected Loader $loader; /** * Updater instance. @@ -31,7 +31,7 @@ class Runner { * * @var \Required\Traduttore\Updater Translation updater. */ - protected $updater; + protected Updater $updater; /** * Runner constructor. @@ -65,6 +65,10 @@ public function delete_local_repository(): bool { } } + if ( ! $wp_filesystem ) { + return false; + } + return $wp_filesystem->rmdir( $this->loader->get_local_path(), true ); } diff --git a/inc/Updater.php b/inc/Updater.php index e7bec64..dc566b5 100644 --- a/inc/Updater.php +++ b/inc/Updater.php @@ -32,7 +32,7 @@ class Updater { * * @var \Required\Traduttore\Project Project information. */ - protected $project; + protected Project $project; /** * Returns a new loader instance for a given project. @@ -128,7 +128,7 @@ public function update( Configuration $config ): bool { $this->project->set_text_domain( sanitize_text_field( $translations->headers['X-Domain'] ) ); if ( $translations->headers['Project-Id-Version'] ) { - $this->project->set_version( $this->extract_version( $translations->headers['Project-Id-Version'] ) ); + $this->project->set_version( $this->extract_version( $translations->headers['Project-Id-Version'] ) ?? '' ); } $stats = GP::$original->import_for_project( $this->project->get_project(), $translations ); @@ -221,7 +221,7 @@ protected function create_pot_file( Configuration $config ): ?string { * @return string WP-CLI binary path. */ protected function get_wp_bin(): string { - if ( \defined( 'TRADUTTORE_WP_BIN' ) && TRADUTTORE_WP_BIN ) { + if ( \defined( 'TRADUTTORE_WP_BIN' ) && \is_string( TRADUTTORE_WP_BIN ) ) { return TRADUTTORE_WP_BIN; } diff --git a/inc/WebhookHandler.php b/inc/WebhookHandler.php index da2312a..055eb00 100644 --- a/inc/WebhookHandler.php +++ b/inc/WebhookHandler.php @@ -21,6 +21,8 @@ interface WebhookHandler { * @since 3.0.0 * * @param \WP_REST_Request $request Request object. + * + * @phpstan-param \WP_REST_Request $request */ public function __construct( WP_REST_Request $request ); @@ -31,7 +33,7 @@ public function __construct( WP_REST_Request $request ); * * @return bool True if permission is granted, false otherwise. */ - public function permission_callback(): ?bool; + public function permission_callback(): bool; /** * Callback for incoming webhooks. @@ -40,5 +42,5 @@ public function permission_callback(): ?bool; * * @return \WP_Error|\WP_REST_Response REST response on success, error object on failure. */ - public function callback(); + public function callback(): \WP_Error|\WP_REST_Response; } diff --git a/inc/WebhookHandler/Base.php b/inc/WebhookHandler/Base.php index 92b2a5e..f59f339 100644 --- a/inc/WebhookHandler/Base.php +++ b/inc/WebhookHandler/Base.php @@ -23,8 +23,10 @@ abstract class Base implements WebhookHandler { * @since 3.0.0 * * @var \WP_REST_Request The current REST request. + * + * @phpstan-var \WP_REST_Request */ - protected $request; + protected WP_REST_Request $request; /** * Class constructor. @@ -32,6 +34,8 @@ abstract class Base implements WebhookHandler { * @since 3.0.0 * * @param \WP_REST_Request $request Request object. + * + * @phpstan-param \WP_REST_Request $request */ public function __construct( WP_REST_Request $request ) { $this->request = $request; @@ -75,7 +79,7 @@ protected function get_secret( ?Project $project = null ): ?string { * * @since 3.0.0 * - * @param string $secret Webhook sync secret. + * @param string|null $secret Webhook sync secret. * @param \Required\Traduttore\WebhookHandler $handler The current webhook handler instance. * @param \Required\Traduttore\Project|null $project The current project if passed through. */ diff --git a/inc/WebhookHandler/Bitbucket.php b/inc/WebhookHandler/Bitbucket.php index 681504b..fe4adf5 100644 --- a/inc/WebhookHandler/Bitbucket.php +++ b/inc/WebhookHandler/Bitbucket.php @@ -27,16 +27,22 @@ class Bitbucket extends Base { * * @return bool True if permission is granted, false otherwise. */ - public function permission_callback(): ?bool { + public function permission_callback(): bool { $event_name = $this->request->get_header( 'x-event-key' ); if ( 'repo:push' !== $event_name ) { return false; } - $token = $this->request->get_header( 'x-hub-signature' ); - $params = $this->request->get_params(); - $locator = new ProjectLocator( $params['repository']['links']['html']['href'] ?? null ); + $token = $this->request->get_header( 'x-hub-signature-256' ); + $params = $this->request->get_params(); + $repository = $params['repository']['links']['html']['href'] ?? null; + + if ( ! $repository ) { + return false; + } + + $locator = new ProjectLocator( $repository ); $project = $locator->get_project(); $secret = $this->get_secret( $project ); @@ -60,7 +66,7 @@ public function permission_callback(): ?bool { * * @return \WP_Error|\WP_REST_Response REST response on success, error object on failure. */ - public function callback() { + public function callback(): \WP_Error|\WP_REST_Response { $params = $this->request->get_params(); $locator = new ProjectLocator( $params['repository']['links']['html']['href'] ); diff --git a/inc/WebhookHandler/GitHub.php b/inc/WebhookHandler/GitHub.php index f72f46b..d0ac7a4 100644 --- a/inc/WebhookHandler/GitHub.php +++ b/inc/WebhookHandler/GitHub.php @@ -27,7 +27,7 @@ class GitHub extends Base { * * @return bool True if permission is granted, false otherwise. */ - public function permission_callback(): ?bool { + public function permission_callback(): bool { $event_name = $this->request->get_header( 'x-github-event' ); if ( 'ping' === $event_name ) { @@ -38,19 +38,42 @@ public function permission_callback(): ?bool { return false; } - $token = $this->request->get_header( 'x-hub-signature' ); + $token = $this->request->get_header( 'x-hub-signature-256' ); if ( ! $token ) { return false; } - $params = $this->request->get_params(); - $locator = new ProjectLocator( $params['repository']['html_url'] ?? null ); + $params = $this->request->get_params(); + $content_type = $this->request->get_content_type(); + + // See https://developer.github.com/webhooks/creating/#content-type. + if ( ! empty( $content_type ) && 'application/x-www-form-urlencoded' === $content_type['value'] ) { + $params = json_decode( $params['payload'], true ); + } + + /** + * Request params. + * + * @var array{repository: array{ default_branch?: string, html_url?: string, full_name: string, ssh_url: string, clone_url: string, private: bool }, ref: string } $params + */ + + $repository = $params['repository']['html_url'] ?? null; + + if ( ! $repository ) { + return false; + } + + $locator = new ProjectLocator( $repository ); $project = $locator->get_project(); $secret = $this->get_secret( $project ); - $payload_signature = 'sha1=' . hash_hmac( 'sha1', $this->request->get_body(), $secret ); + if ( ! $secret ) { + return false; + } + + $payload_signature = 'sha256=' . hash_hmac( 'sha256', $this->request->get_body(), $secret ); return hash_equals( $token, $payload_signature ); } @@ -62,7 +85,7 @@ public function permission_callback(): ?bool { * * @return \WP_Error|\WP_REST_Response REST response on success, error object on failure. */ - public function callback() { + public function callback(): \WP_Error|\WP_REST_Response { $event_name = $this->request->get_header( 'x-github-event' ); if ( 'ping' === $event_name ) { @@ -71,11 +94,18 @@ public function callback() { $params = $this->request->get_params(); $content_type = $this->request->get_content_type(); + // See https://developer.github.com/webhooks/creating/#content-type. if ( ! empty( $content_type ) && 'application/x-www-form-urlencoded' === $content_type['value'] ) { $params = json_decode( $params['payload'], true ); } + /** + * Request params. + * + * @var array{repository: array{ default_branch?: string, html_url: string, full_name: string, ssh_url: string, clone_url: string, private: bool }, ref: string } $params + */ + if ( ! isset( $params['repository']['default_branch'] ) ) { return new \WP_Error( '400', 'Request incomplete', [ 'status' => 400 ] ); } diff --git a/inc/WebhookHandler/GitLab.php b/inc/WebhookHandler/GitLab.php index 120cc57..e6515e4 100644 --- a/inc/WebhookHandler/GitLab.php +++ b/inc/WebhookHandler/GitLab.php @@ -27,7 +27,7 @@ class GitLab extends Base { * * @return bool True if permission is granted, false otherwise. */ - public function permission_callback(): ?bool { + public function permission_callback(): bool { $event_name = $this->request->get_header( 'x-gitlab-event' ); if ( 'Push Hook' !== $event_name ) { @@ -40,12 +40,22 @@ public function permission_callback(): ?bool { return false; } - $params = $this->request->get_params(); - $locator = new ProjectLocator( $params['project']['homepage'] ?? null ); + $params = $this->request->get_params(); + $repository = $params['project']['homepage'] ?? null; + + if ( ! $repository ) { + return false; + } + + $locator = new ProjectLocator( $repository ); $project = $locator->get_project(); $secret = $this->get_secret( $project ); + if ( ! $secret ) { + return false; + } + return hash_equals( $token, $secret ); } @@ -56,7 +66,7 @@ public function permission_callback(): ?bool { * * @return \WP_Error|\WP_REST_Response REST response on success, error object on failure. */ - public function callback() { + public function callback(): \WP_Error|\WP_REST_Response { $params = $this->request->get_params(); // We only care about the default branch but don't want to send an error still. diff --git a/inc/WebhookHandlerFactory.php b/inc/WebhookHandlerFactory.php index 80929ae..8bfb5d6 100644 --- a/inc/WebhookHandlerFactory.php +++ b/inc/WebhookHandlerFactory.php @@ -25,6 +25,8 @@ class WebhookHandlerFactory { * * @param \WP_REST_Request $request Request object. * @return \Required\Traduttore\WebhookHandler Webhook handler instance. + * + * @phpstan-param \WP_REST_Request $request */ public function get_handler( WP_REST_Request $request ): ?WebhookHandler { $handler = null; diff --git a/inc/ZipProvider.php b/inc/ZipProvider.php index ffba7bf..ed912fc 100644 --- a/inc/ZipProvider.php +++ b/inc/ZipProvider.php @@ -10,6 +10,7 @@ use DateTime; use DateTimeZone; use GP; +use GP_Locale; use GP_Locales; use GP_Translation_Set; use ZipArchive; @@ -41,7 +42,7 @@ class ZipProvider { * * @var \GP_Translation_Set The translation set. */ - protected $translation_set; + protected GP_Translation_Set $translation_set; /** * The current GlotPress locale. @@ -50,7 +51,7 @@ class ZipProvider { * * @var \GP_Locale The locale. */ - protected $locale; + protected GP_Locale $locale; /** * The current project. @@ -59,7 +60,7 @@ class ZipProvider { * * @var \Required\Traduttore\Project The project. */ - protected $project; + protected Project $project; /** * ZipProvider constructor. @@ -71,7 +72,15 @@ class ZipProvider { public function __construct( GP_Translation_Set $translation_set ) { $this->translation_set = $translation_set; $this->locale = GP_Locales::by_slug( $this->translation_set->locale ); - $this->project = new Project( GP::$project->get( $this->translation_set->project_id ) ); + + /** + * GlotPress project. + * + * @var \GP_Project $gp_project + */ + $gp_project = GP::$project->get( $this->translation_set->project_id ); + + $this->project = new Project( $gp_project ); } /** @@ -124,6 +133,10 @@ public function generate_zip_file(): bool { } } + if ( ! $wp_filesystem ) { + return false; + } + // Make sure the cache directory exists. if ( ! is_dir( static::get_cache_dir() ) ) { $wp_filesystem->mkdir( static::get_cache_dir(), FS_CHMOD_DIR ); @@ -205,6 +218,10 @@ public function remove_zip_file(): bool { } } + if ( ! $wp_filesystem ) { + return false; + } + $success = $wp_filesystem->rmdir( $this->get_zip_path(), true ); if ( $success ) { @@ -249,6 +266,11 @@ protected function get_zip_filename(): string { * @return \DateTime Last build time. */ public function get_last_build_time(): ?DateTime { + /** + * Build time. + * + * @var string|false $meta + */ $meta = gp_get_meta( 'translation_set', $this->translation_set->id, static::BUILD_TIME_KEY ); return $meta ? new DateTime( $meta, new DateTimeZone( 'UTC' ) ) : null; diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 2e624dc..55d0f1e 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -2,10 +2,19 @@ Coding Standard for Traduttore + + + . - /tests/* + /tests/behat/* + /tests/features/* + /tests/phpstan/* + /tests/phpunit/data/* + /tests/phpunit/bootstrap.php - + + /tests/phpunit/tests + traduttore\.php @@ -19,6 +28,7 @@ + diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 8e9278f..5d481b7 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,8 +1,5 @@ -includes: - - vendor/szepeviktor/phpstan-wordpress/extension.neon parameters: -# DREAM level: max - level: 5 + level: 9 inferPrivatePropertyTypeFromConstructor: true bootstrapFiles: - tests/phpstan/bootstrap.php @@ -10,12 +7,11 @@ parameters: - vendor/wordpress-plugin/glotpress/gp-includes/routes/_main.php scanFiles: - vendor/php-stubs/wp-cli-stubs/wp-cli-stubs.php + - vendor/php-stubs/wordpress-tests-stubs/wordpress-tests-stubs.php scanDirectories: + - tests/phpstan/stubs/ - vendor/wordpress-plugin/glotpress/gp-includes/ - vendor/wordpress-plugin/glotpress/locales/ paths: - inc/ -# - test/ - ignoreErrors: - # Uses func_get_args() - - '#^Function apply_filters(_ref_array)? invoked with [34567] parameters, 2 required\.$#' + - tests/phpunit diff --git a/tests/behat/maybe-generate-wp-cli-coverage.php b/tests/behat/maybe-generate-wp-cli-coverage.php index 9a0d529..43e40fb 100644 --- a/tests/behat/maybe-generate-wp-cli-coverage.php +++ b/tests/behat/maybe-generate-wp-cli-coverage.php @@ -12,8 +12,8 @@ } $filter = new Filter(); -$filter->includeDirectory( "{$root_folder}/includes" ); -$filter->includeDirectory( "{$root_folder}/src" ); +$filter->includeDirectory( "{$root_folder}/inc" ); +$filter->includeDirectory( "{$root_folder}/traduttore.php" ); $coverage = new CodeCoverage( ( new Selector() )->forLineCoverage( $filter ), diff --git a/tests/features/info.feature b/tests/features/info.feature index 4f4b342..ff3ca96 100644 --- a/tests/features/info.feature +++ b/tests/features/info.feature @@ -2,8 +2,9 @@ Feature: Print various details about the environment. Background: - Given a WP installation with the Traduttore plugin + Given a WP installation And GlotPress develop being active + And Traduttore being active Scenario: Run info command When I run the WP-CLI command `traduttore info` diff --git a/tests/features/project.feature b/tests/features/project.feature index e7e6d77..f945091 100644 --- a/tests/features/project.feature +++ b/tests/features/project.feature @@ -2,8 +2,9 @@ Feature: Print various details about the environment. Background: - Given a WP installation with the Traduttore plugin + Given a WP installation And GlotPress develop being active + And Traduttore being active Scenario: Run info command with invalid project ID When I try the WP-CLI command `traduttore project info 99999` diff --git a/tests/features/testing.feature b/tests/features/testing.feature index 7eebf37..28922bc 100644 --- a/tests/features/testing.feature +++ b/tests/features/testing.feature @@ -1,11 +1,12 @@ Feature: Test that the tests are working. Background: - Given a WP installation with the Traduttore plugin + Given a WP installation @require-php-7.4 Scenario: Traduttore and GlotPress develop should be active. Given GlotPress develop being active + And Traduttore being active When I run `wp plugin status glotpress` Then STDOUT should contain: @@ -31,6 +32,7 @@ Feature: Test that the tests are working. Scenario: Traduttore and GlotPress stable should be active. Given GlotPress stable being active + And Traduttore being active When I run `wp plugin status glotpress` Then STDOUT should contain: diff --git a/tests/phpstan/bootstrap.php b/tests/phpstan/bootstrap.php index 6c48b45..6db9b02 100644 --- a/tests/phpstan/bootstrap.php +++ b/tests/phpstan/bootstrap.php @@ -1,7 +1,6 @@ factory = new GP_UnitTest_Factory(); + + global $wp_rewrite; + if ( GP_TESTS_PERMALINK_STRUCTURE != $wp_rewrite->permalink_structure ) { + $this->set_permalink_structure( GP_TESTS_PERMALINK_STRUCTURE ); + } + } + + /** + * Utility method that resets permalinks and flushes rewrites. + * + * Also updates the pre_option filter for `permalink_structure`. + * + * @global WP_Rewrite $wp_rewrite + * + * @param string $structure Optional. Permalink structure to set. Default empty. + */ + public function set_permalink_structure( $structure = '' ) { + global $wp_tests_options; + + $wp_tests_options['permalink_structure'] = $structure; + + parent::set_permalink_structure( $structure ); + } + + function clean_up_global_scope() { + parent::clean_up_global_scope(); + + $locales = &GP_Locales::instance(); + $locales->locales = []; + $_GET = []; + $_POST = []; + /** + * @todo re-initialize all thing objects + */ + GP::$translation_set = new GP_Translation_Set; + GP::$original = new GP_Original; + } + + function set_normal_user_as_current() { + $user = $this->factory->user->create(); + wp_set_current_user( $user ); + return $user; + } + + function set_admin_user_as_current() { + $admin = $this->factory->user->create_admin(); + wp_set_current_user( $admin ); + return $admin; + } +} diff --git a/tests/phpunit/bootstrap.php b/tests/phpunit/bootstrap.php index b55717a..ca54fcb 100644 --- a/tests/phpunit/bootstrap.php +++ b/tests/phpunit/bootstrap.php @@ -1,8 +1,6 @@ install_wp(); - + public function given_traduttore_being_active(): void { // Symlink the current project folder into the WP folder as a plugin. $project_dir = realpath( self::get_vendor_dir() . '/../' ); $plugin_dir = $this->variables['RUN_DIR'] . '/wp-content/plugins'; @@ -85,7 +83,7 @@ public function given_a_wp_installation_with_the_traduttore_plugin(): void { /** * @Given /^GlotPress (.*) being active$/ */ - public function given_the_glotpress_plugin_being_active( $gp_version ): void { + public function given_the_glotpress_plugin_being_active( string $gp_version ): void { $branch_name = $gp_version; if ( 'nightly' === $gp_version || 'develop' === $gp_version || 'trunk' === $gp_version ) { $branch_name = 'develop'; @@ -120,7 +118,7 @@ public function given_the_glotpress_plugin_being_active( $gp_version ): void { /** * @When /^I (run|try) the WP-CLI command `([^`]+)`$/ */ - public function when_i_run_the_wp_cli_command( $mode, $command ): void { + public function when_i_run_the_wp_cli_command( string $mode, string $command ): void { $command = "wp {$command}"; $with_code_coverage = getenv( 'BEHAT_CODE_COVERAGE' ); @@ -135,8 +133,8 @@ public function when_i_run_the_wp_cli_command( $mode, $command ): void { $command, [ 'BEHAT_PROJECT_DIR' => $this->variables['PROJECT_DIR'], - 'BEHAT_FEATURE_TITLE' => self::$feature->getTitle(), - 'BEHAT_SCENARIO_TITLE' => $this->scenario->getTitle(), + 'BEHAT_FEATURE_TITLE' => self::$feature ? self::$feature->getTitle() : null, + 'BEHAT_SCENARIO_TITLE' => $this->scenario ? $this->scenario->getTitle() : null, ] ), $mode @@ -150,7 +148,7 @@ public function when_i_run_the_wp_cli_command( $mode, $command ): void { * * @param string $directory Directory to ensure the existence of. */ - private function ensure_dir_exists( $directory ): void { + private function ensure_dir_exists( string $directory ): void { $parent = dirname( $directory ); if ( ! empty( $parent ) && ! is_dir( $parent ) ) { @@ -165,11 +163,11 @@ private function ensure_dir_exists( $directory ): void { /** * Create a new process with added environment variables. * - * @param string $command Command to run. - * @param array $env Associative array of environment variables to add. + * @param string $command Command to run. + * @param array $env Associative array of environment variables to add. * @return \WP_CLI\Process Process to execute. */ - public function proc_with_env( $command, $env = [] ): Process { + public function proc_with_env( string $command, array $env = [] ): Process { $env = array_merge( self::get_process_env_variables(), $env @@ -192,6 +190,8 @@ public function proc_with_env( $command, $env = [] ): Process { * Get the environment variables required for launched `wp` processes. * * This is copied over from WP_CLI\Tests\Context\FeatureContext, to enable an adaption of FeatureContext::proc(). + * + * @return array */ private static function get_process_env_variables(): array { // Ensure we're using the expected `wp` binary. diff --git a/tests/phpunit/tests/Configuration.php b/tests/phpunit/tests/Configuration.php index 35d51f3..c0e29a7 100644 --- a/tests/phpunit/tests/Configuration.php +++ b/tests/phpunit/tests/Configuration.php @@ -1,38 +1,37 @@ assertSame( dirname( __DIR__ ) . '/data/example-no-config', $config->get_path() ); + $this->assertSame( \dirname( __DIR__ ) . '/data/example-no-config', $config->get_path() ); } public function test_get_config_empty_directory(): void { - $config = new Config( dirname( __DIR__ ) . '/data/example-no-config' ); + $config = new Config( \dirname( __DIR__ ) . '/data/example-no-config' ); $this->assertEmpty( $config->get_config() ); } public function test_get_config_value_empty_directory(): void { - $config = new Config( dirname( __DIR__ ) . '/data/example-no-config' ); + $config = new Config( \dirname( __DIR__ ) . '/data/example-no-config' ); + // @phpstan-ignore-next-line $this->assertNull( $config->get_config_value( 'foo' ) ); } public function test_get_config_composer(): void { - $config = new Config( dirname( __DIR__ ) . '/data/example-with-composer' ); + $config = new Config( \dirname( __DIR__ ) . '/data/example-with-composer' ); $this->assertEqualSets( [ @@ -48,7 +47,7 @@ public function test_get_config_composer(): void { } public function test_get_config_traduttore(): void { - $config = new Config( dirname( __DIR__ ) . '/data/example-with-config' ); + $config = new Config( \dirname( __DIR__ ) . '/data/example-with-config' ); $this->assertEqualSets( [ diff --git a/tests/phpunit/tests/Export.php b/tests/phpunit/tests/Export.php index 81b09ab..88c913c 100644 --- a/tests/phpunit/tests/Export.php +++ b/tests/phpunit/tests/Export.php @@ -1,29 +1,23 @@ factory->locale->create( + $locale = $this->factory()->locale->create( [ 'english_name' => 'German', 'native_name' => 'Deutsch', @@ -32,7 +26,7 @@ public function setUp(): void { ] ); - $this->translation_set = $this->factory->translation_set->create_with_project( + $this->translation_set = $this->factory()->translation_set->create_with_project( [ 'locale' => $locale->slug, ], @@ -49,14 +43,14 @@ public function test_does_nothing_for_empty_translation_set(): void { } public function test_creates_only_po_and_mo_files(): void { - $original = $this->factory->original->create( + $original = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id, 'references' => 'my-plugin.php', ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original->id, 'translation_set_id' => $this->translation_set->id, @@ -68,12 +62,15 @@ public function test_creates_only_po_and_mo_files(): void { $actual = $export->export_strings(); + $this->assertIsArray( $actual ); + array_map( 'unlink', $actual ); $this->assertEqualSets( [ 'foo-project-de_DE.po', 'foo-project-de_DE.mo', + 'foo-project-de_DE.l10n.php', ], array_keys( $actual ) ); @@ -84,7 +81,7 @@ public function test_creates_multiple_json_files(): void { $filename_2 = 'my-super-minified-script'; /* @var \GP_Original $original_1 */ - $original_1 = $this->factory->original->create( + $original_1 = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id, 'references' => $filename_1 . '.js', @@ -92,14 +89,14 @@ public function test_creates_multiple_json_files(): void { ); /* @var \GP_Original $original_2 */ - $original_2 = $this->factory->original->create( + $original_2 = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id, 'references' => $filename_2 . '.min.js', ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original_1->id, 'translation_set_id' => $this->translation_set->id, @@ -107,7 +104,7 @@ public function test_creates_multiple_json_files(): void { ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original_2->id, 'translation_set_id' => $this->translation_set->id, @@ -119,6 +116,8 @@ public function test_creates_multiple_json_files(): void { $actual = $export->export_strings(); + $this->assertIsArray( $actual ); + $json_filename_1 = 'foo-project-de_DE-' . md5( $filename_1 . '.js' ) . '.json'; $json_filename_2 = 'foo-project-de_DE-' . md5( $filename_2 . '.js' ) . '.json'; @@ -127,12 +126,15 @@ public function test_creates_multiple_json_files(): void { array_map( 'unlink', $actual ); + $this->assertIsString( $json_1 ); + $this->assertIsString( $json_2 ); $this->assertJson( $json_1 ); $this->assertJson( $json_2 ); $this->assertEqualSets( [ 'foo-project-de_DE.po', 'foo-project-de_DE.mo', + 'foo-project-de_DE.l10n.php', $json_filename_1, $json_filename_2, ], @@ -143,9 +145,8 @@ public function test_creates_multiple_json_files(): void { /** * Modify the mapping of sources to translation entries. * - * @param array $mapping The mapping of sources to translation entries. - * - * @return array The maybe modified mapping. + * @param array $mapping The mapping of sources to translation entries. + * @return array The maybe modified mapping. */ public function filter_map_entries_to_source( array $mapping ): array { $mapping['build.js'] = array_merge( $mapping['my-super-script.js'], $mapping['my-other-script.js'] ); @@ -161,7 +162,7 @@ public function test_map_entries_to_source_filter(): void { $filename_target = 'build.js'; /* @var \GP_Original $original_1 */ - $original_1 = $this->factory->original->create( + $original_1 = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id, 'references' => $filename_1, @@ -169,14 +170,14 @@ public function test_map_entries_to_source_filter(): void { ); /* @var \GP_Original $original_2 */ - $original_2 = $this->factory->original->create( + $original_2 = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id, 'references' => $filename_2, ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original_1->id, 'translation_set_id' => $this->translation_set->id, @@ -184,7 +185,7 @@ public function test_map_entries_to_source_filter(): void { ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original_2->id, 'translation_set_id' => $this->translation_set->id, @@ -198,6 +199,8 @@ public function test_map_entries_to_source_filter(): void { $actual = $export->export_strings(); + $this->assertIsArray( $actual ); + remove_filter( 'traduttore.map_entries_to_source', [ $this, 'filter_map_entries_to_source' ] ); $json_filename_1 = 'foo-project-de_DE-' . md5( $filename_1 ) . '.json'; @@ -211,11 +214,13 @@ public function test_map_entries_to_source_filter(): void { array_map( 'unlink', $actual ); + $this->assertIsString( $json ); $this->assertJson( $json ); $this->assertEqualSets( [ 'foo-project-de_DE.po', 'foo-project-de_DE.mo', + 'foo-project-de_DE.l10n.php', $json_filename_target, ], array_keys( $actual ) @@ -227,7 +232,7 @@ public function test_js_entries_are_not_in_po_file(): void { $filename_2 = 'my-super-minified-script'; /* @var \GP_Original $original_1 */ - $original_1 = $this->factory->original->create( + $original_1 = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id, 'references' => $filename_1 . '.js', @@ -235,7 +240,7 @@ public function test_js_entries_are_not_in_po_file(): void { ); /* @var \GP_Original $original_2 */ - $original_2 = $this->factory->original->create( + $original_2 = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id, 'references' => $filename_2 . '.min.js', @@ -243,28 +248,28 @@ public function test_js_entries_are_not_in_po_file(): void { ); /* @var \GP_Original $original_3 */ - $original_3 = $this->factory->original->create( + $original_3 = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id, 'references' => 'foo.php', ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original_1->id, 'translation_set_id' => $this->translation_set->id, 'status' => 'current', ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original_2->id, 'translation_set_id' => $this->translation_set->id, 'status' => 'current', ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original_3->id, 'translation_set_id' => $this->translation_set->id, @@ -276,14 +281,19 @@ public function test_js_entries_are_not_in_po_file(): void { $actual = $export->export_strings(); + $this->assertIsArray( $actual ); + $translations = new PO(); $translations->import_from_file( $actual['foo-project-de_DE.po'] ); $json_filename_1 = 'foo-project-de_DE-' . md5( $filename_1 . '.js' ) . '.json'; $json_filename_2 = 'foo-project-de_DE-' . md5( $filename_2 . '.js' ) . '.json'; - $json_1 = json_decode( file_get_contents( $actual[ $json_filename_1 ] ), true ); - $json_2 = json_decode( file_get_contents( $actual[ $json_filename_2 ] ), true ); + $json_1 = json_decode( (string) file_get_contents( $actual[ $json_filename_1 ] ), true ); + $json_2 = json_decode( (string) file_get_contents( $actual[ $json_filename_2 ] ), true ); + + $this->assertIsArray( $json_1 ); + $this->assertIsArray( $json_2 ); array_map( 'unlink', $actual ); @@ -301,7 +311,7 @@ public function test_js_source_entries_are_not_exported_as_json_files(): void { $filename_3 = 'dist/build.js'; /* @var \GP_Original $original_1 */ - $original_1 = $this->factory->original->create( + $original_1 = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id, 'references' => "$filename_1 $filename_3", @@ -309,7 +319,7 @@ public function test_js_source_entries_are_not_exported_as_json_files(): void { ); /* @var \GP_Original $original_2 */ - $original_2 = $this->factory->original->create( + $original_2 = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id, 'references' => "$filename_2 $filename_3", @@ -317,28 +327,28 @@ public function test_js_source_entries_are_not_exported_as_json_files(): void { ); /* @var \GP_Original $original_3 */ - $original_3 = $this->factory->original->create( + $original_3 = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id, 'references' => $filename_3, ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original_1->id, 'translation_set_id' => $this->translation_set->id, 'status' => 'current', ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original_2->id, 'translation_set_id' => $this->translation_set->id, 'status' => 'current', ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original_3->id, 'translation_set_id' => $this->translation_set->id, @@ -350,13 +360,15 @@ public function test_js_source_entries_are_not_exported_as_json_files(): void { $actual = $export->export_strings(); + $this->assertIsArray( $actual ); + $json_filename = 'foo-project-de_DE-' . md5( $filename_3 ) . '.json'; - $json = json_decode( file_get_contents( $actual[ $json_filename ] ), true ); + $json = json_decode( (string) file_get_contents( $actual[ $json_filename ] ), true ); array_map( 'unlink', $actual ); - $this->assertInternalType( 'array', $json ); + $this->assertIsArray( $json ); $this->assertCount( 4, $json['locale_data']['messages'] ); $this->assertArrayHasKey( $original_1->singular, $json['locale_data']['messages'] ); $this->assertArrayHasKey( $original_2->singular, $json['locale_data']['messages'] ); @@ -365,6 +377,7 @@ public function test_js_source_entries_are_not_exported_as_json_files(): void { [ 'foo-project-de_DE.po', 'foo-project-de_DE.mo', + 'foo-project-de_DE.l10n.php', $json_filename, ], array_keys( $actual ) @@ -375,14 +388,14 @@ public function test_json_files_include_file_reference_comment(): void { $filename_1 = 'my-super-script'; /* @var \GP_Original $original_1 */ - $original_1 = $this->factory->original->create( + $original_1 = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id, 'references' => $filename_1 . '.js', ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original_1->id, 'translation_set_id' => $this->translation_set->id, @@ -394,15 +407,18 @@ public function test_json_files_include_file_reference_comment(): void { $actual = $export->export_strings(); + $this->assertIsArray( $actual ); + $json_filename_1 = 'foo-project-de_DE-' . md5( $filename_1 . '.js' ) . '.json'; $json_1 = file_get_contents( $actual[ $json_filename_1 ] ); array_map( 'unlink', $actual ); + $this->assertIsString( $json_1 ); $this->assertJson( $json_1 ); - $json1_encoded = json_decode( $json_1 ); + $json1_encoded = (object) json_decode( $json_1 ); $this->assertSame( $filename_1 . '.js', $json1_encoded->comment->reference ); } } diff --git a/tests/phpunit/tests/Loader/Git.php b/tests/phpunit/tests/Loader/Git.php index 3500b71..13ee09a 100644 --- a/tests/phpunit/tests/Loader/Git.php +++ b/tests/phpunit/tests/Loader/Git.php @@ -1,15 +1,12 @@ project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Sample Project', 'slug' => 'sample-project', @@ -45,8 +39,9 @@ public function test_get_local_path(): void { } public function test_download_repository(): void { - $this->markTestSkipped( 'Need to mock shell command execution' ); + $this->markTestIncomplete( 'Need to mock shell command execution' ); + // @phpstan-ignore-next-line $loader = new GitLoader( new GitHub( $this->project ) ); add_filter( 'traduttore.git_clone_use_https', '__return_true' ); @@ -57,8 +52,9 @@ public function test_download_repository(): void { } public function test_download_existing_repository(): void { - $this->markTestSkipped( 'Need to mock shell command execution' ); + $this->markTestIncomplete( 'Need to mock shell command execution' ); + // @phpstan-ignore-next-line $loader = new GitLoader( new GitHub( $this->project ) ); add_filter( 'traduttore.git_clone_use_https', '__return_true' ); diff --git a/tests/phpunit/tests/Loader/Mercurial.php b/tests/phpunit/tests/Loader/Mercurial.php index 5aa596d..b130d0f 100644 --- a/tests/phpunit/tests/Loader/Mercurial.php +++ b/tests/phpunit/tests/Loader/Mercurial.php @@ -1,15 +1,12 @@ project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Sample Project', 'slug' => 'sample-project', @@ -45,8 +39,9 @@ public function test_get_local_path(): void { } public function test_download_repository(): void { - $this->markTestSkipped( 'Need to mock shell command execution' ); + $this->markTestIncomplete( 'Need to mock shell command execution' ); + // @phpstan-ignore-next-line $loader = new MercurialLoader( new Bitbucket( $this->project ) ); add_filter( 'traduttore.hg_clone_use_https', '__return_true' ); @@ -57,8 +52,9 @@ public function test_download_repository(): void { } public function test_download_existing_repository(): void { - $this->markTestSkipped( 'Need to mock shell command execution' ); + $this->markTestIncomplete( 'Need to mock shell command execution' ); + // @phpstan-ignore-next-line $loader = new MercurialLoader( new Bitbucket( $this->project ) ); add_filter( 'traduttore.hg_clone_use_https', '__return_true' ); diff --git a/tests/phpunit/tests/Loader/Subversion.php b/tests/phpunit/tests/Loader/Subversion.php index ee9841b..e0ffa86 100644 --- a/tests/phpunit/tests/Loader/Subversion.php +++ b/tests/phpunit/tests/Loader/Subversion.php @@ -1,14 +1,12 @@ project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Sample Project', 'slug' => 'sample-project', @@ -44,8 +39,9 @@ public function test_get_local_path(): void { } public function test_download_repository(): void { - $this->markTestSkipped( 'Need to mock shell command execution' ); + $this->markTestIncomplete( 'Need to mock shell command execution' ); + // @phpstan-ignore-next-line $loader = new SubversionLoader( new Bitbucket( $this->project ) ); add_filter( 'traduttore.svn_checkout_use_https', '__return_true' ); @@ -56,8 +52,9 @@ public function test_download_repository(): void { } public function test_download_existing_repository(): void { - $this->markTestSkipped( 'Need to mock shell command execution' ); + $this->markTestIncomplete( 'Need to mock shell command execution' ); + // @phpstan-ignore-next-line $loader = new SubversionLoader( new Bitbucket( $this->project ) ); add_filter( 'traduttore.svn_checkout_use_https', '__return_true' ); diff --git a/tests/phpunit/tests/LoaderFactory.php b/tests/phpunit/tests/LoaderFactory.php index 938d9a4..ad367a1 100644 --- a/tests/phpunit/tests/LoaderFactory.php +++ b/tests/phpunit/tests/LoaderFactory.php @@ -1,16 +1,16 @@ factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', ] @@ -39,7 +39,7 @@ public function test_get_mercurial_loader(): void { public function test_get_git_loader_for_bitbucket_repository(): void { $factory = new Factory(); $project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', ] @@ -54,7 +54,7 @@ public function test_get_git_loader_for_bitbucket_repository(): void { public function test_get_git_loader_for_github_repository(): void { $factory = new Factory(); $project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', ] @@ -69,7 +69,7 @@ public function test_get_git_loader_for_github_repository(): void { public function test_get_git_loader_for_gitlab_repository(): void { $factory = new Factory(); $project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', ] diff --git a/tests/phpunit/tests/Project.php b/tests/phpunit/tests/Project.php index 3497a51..00abd5b 100644 --- a/tests/phpunit/tests/Project.php +++ b/tests/phpunit/tests/Project.php @@ -1,33 +1,27 @@ gp_project = $this->factory->project->create( + $this->gp_project = $this->factory()->project->create( [ 'name' => 'Project', 'active' => 1, @@ -153,12 +147,12 @@ public function test_get_last_updated_time_returns_null_if_missing(): void { } public function test_get_last_updated_time(): void { - $time = new DateTime( 'now' ); + $time = new DateTime( 'now', new DateTimeZone( 'UTC' ) ); $this->project->set_last_updated_time( $time ); $this->assertInstanceOf( DateTime::class, $this->project->get_last_updated_time() ); - $this->assertEquals( $time, $this->project->get_last_updated_time(), 'Last updated time is not identical', 1 ); + $this->assertSame( $time->getTimestamp(), $this->project->get_last_updated_time()->getTimestamp(), 'Last updated time is not identical' ); } public function test_get_repository_webhook_secret(): void { diff --git a/tests/phpunit/tests/ProjectLocator.php b/tests/phpunit/tests/ProjectLocator.php index c7ec51e..d7be4e5 100644 --- a/tests/phpunit/tests/ProjectLocator.php +++ b/tests/phpunit/tests/ProjectLocator.php @@ -1,14 +1,12 @@ root = $this->factory->project->create( + $this->root = $this->factory()->project->create( [ 'name' => 'Root', ] ); - $this->sub = $this->factory->project->create( + $this->sub = $this->factory()->project->create( [ 'name' => 'Sub', 'parent_project_id' => $this->root->id, ] ); - $this->subsub = $this->factory->project->create( + $this->subsub = $this->factory()->project->create( [ 'name' => 'SubSub', 'parent_project_id' => $this->sub->id, @@ -58,12 +56,6 @@ public function test_empty_string(): void { $this->assertNull( $locator->get_project() ); } - public function test_false(): void { - $locator = new Locator( false ); - - $this->assertNull( $locator->get_project() ); - } - public function test_invalid_project_id(): void { $locator = new Locator( 0 ); @@ -81,48 +73,55 @@ public function test_existing_glotpress_project_instance(): void { $project = $this->root; $locator = new Locator( $project ); - $this->assertEquals( $this->root->id, $locator->get_project()->get_id() ); + $this->assertInstanceOf( Project::class, $locator->get_project() ); + $this->assertSame( $this->root->id, $locator->get_project()->get_id() ); } public function test_find_project_by_glotpress_path(): void { $locator = new Locator( 'root' ); - $this->assertEquals( $this->root->id, $locator->get_project()->get_id() ); + $this->assertInstanceOf( Project::class, $locator->get_project() ); + $this->assertSame( $this->root->id, $locator->get_project()->get_id() ); } public function test_find_project_by_glotpress_subpath(): void { $locator = new Locator( 'root/sub' ); - $this->assertEquals( $this->sub->id, $locator->get_project()->get_id() ); + $this->assertInstanceOf( Project::class, $locator->get_project() ); + $this->assertSame( $this->sub->id, $locator->get_project()->get_id() ); } public function test_find_project_by_glotpress_subsubpath(): void { $locator = new Locator( 'root/sub/subsub' ); - $this->assertEquals( $this->subsub->id, $locator->get_project()->get_id() ); + $this->assertInstanceOf( Project::class, $locator->get_project() ); + $this->assertSame( $this->subsub->id, $locator->get_project()->get_id() ); } public function test_find_project_by_glotpress_id(): void { $locator = new Locator( (int) $this->sub->id ); - $this->assertEquals( $this->sub->id, $locator->get_project()->get_id() ); + $this->assertInstanceOf( Project::class, $locator->get_project() ); + $this->assertSame( $this->sub->id, $locator->get_project()->get_id() ); } public function test_find_project_by_glotpress_id_as_string(): void { $locator = new Locator( (string) $this->sub->id ); - $this->assertEquals( $this->sub->id, $locator->get_project()->get_id() ); + $this->assertInstanceOf( Project::class, $locator->get_project() ); + $this->assertSame( $this->sub->id, $locator->get_project()->get_id() ); } public function test_find_project_by_github_url(): void { $locator = new Locator( 'https://github.com/wearerequired/traduttore' ); - $this->assertEquals( $this->subsub->id, $locator->get_project()->get_id() ); + $this->assertInstanceOf( Project::class, $locator->get_project() ); + $this->assertSame( $this->subsub->id, $locator->get_project()->get_id() ); } public function test_find_project_by_repository_name(): void { $project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Foo Bar', ] @@ -133,12 +132,13 @@ public function test_find_project_by_repository_name(): void { $locator = new Locator( 'wearerequired/traduttore-registry' ); - $this->assertEquals( $project->get_id(), $locator->get_project()->get_id() ); + $this->assertInstanceOf( Project::class, $locator->get_project() ); + $this->assertSame( $project->get_id(), $locator->get_project()->get_id() ); } public function test_find_project_by_repository_url(): void { $project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Foo Bar', ] @@ -149,6 +149,7 @@ public function test_find_project_by_repository_url(): void { $locator = new Locator( 'https://github.com/wearerequired/traduttore-registry' ); - $this->assertEquals( $project->get_id(), $locator->get_project()->get_id() ); + $this->assertInstanceOf( Project::class, $locator->get_project() ); + $this->assertSame( $project->get_id(), $locator->get_project()->get_id() ); } } diff --git a/tests/phpunit/tests/Repository/Bitbucket.php b/tests/phpunit/tests/Repository/Bitbucket.php index 0c41401..9cfdc3f 100644 --- a/tests/phpunit/tests/Repository/Bitbucket.php +++ b/tests/phpunit/tests/Repository/Bitbucket.php @@ -1,36 +1,33 @@ project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', ] @@ -54,7 +51,7 @@ public function test_get_name_falls_back_to_project_slug(): void { public function test_get_name_falls_back_to_source_url_template(): void { $project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', 'source_url_template' => 'https://bitbucket.org/wearerequired/traduttore/src/master/%file%#L%line%', @@ -89,11 +86,11 @@ public function test_get_name(): void { * @param false $preempt Whether to preempt an HTTP request's return value. Default false. * @param mixed $r HTTP request arguments. * @param string $url The request URL. - * @return array|false Response data. + * @return array{response: array{ code: int }, body: string}|false Response data. */ - public function mock_repository_visibility_request( $preempt, $r, $url ) { + public function mock_repository_visibility_request( bool $preempt, mixed $r, string $url ): array|false { if ( BitbucketRepository::API_BASE . '/repositories/wearerequired/traduttore' === $url ) { - ++ $this->http_request_count; + ++$this->http_request_count; return [ 'response' => [ @@ -109,7 +106,7 @@ public function mock_repository_visibility_request( $preempt, $r, $url ) { public function test_is_public_performs_http_request_and_caches_it(): void { $this->project->set_repository_name( 'wearerequired/traduttore' ); - add_filter( 'pre_http_request', array( $this, 'mock_repository_visibility_request' ), 10, 3 ); + add_filter( 'pre_http_request', [ $this, 'mock_repository_visibility_request' ], 10, 3 ); $repository = new BitbucketRepository( $this->project ); @@ -118,7 +115,7 @@ public function test_is_public_performs_http_request_and_caches_it(): void { $is_public_after = $repository->is_public(); $visibility_after = $repository->get_project()->get_repository_visibility(); - remove_filter( 'pre_http_request', array( $this, 'mock_repository_visibility_request' ), 10 ); + remove_filter( 'pre_http_request', [ $this, 'mock_repository_visibility_request' ], 10 ); $this->assertNull( $visibility_before ); $this->assertTrue( $is_public ); @@ -130,7 +127,7 @@ public function test_is_public_performs_http_request_and_caches_it(): void { public function test_is_public_performs_no_unnecessary_http_request(): void { $this->project->set_repository_name( 'wearerequired/traduttore' ); - add_filter( 'pre_http_request', array( $this, 'mock_repository_visibility_request' ), 10, 3 ); + add_filter( 'pre_http_request', [ $this, 'mock_repository_visibility_request' ], 10, 3 ); $this->project->set_repository_visibility( 'private' ); @@ -139,7 +136,7 @@ public function test_is_public_performs_no_unnecessary_http_request(): void { $visibility_before = $repository->get_project()->get_repository_visibility(); $is_public = $repository->is_public(); - remove_filter( 'pre_http_request', array( $this, 'mock_repository_visibility_request' ), 10 ); + remove_filter( 'pre_http_request', [ $this, 'mock_repository_visibility_request' ], 10 ); $this->assertSame( 'private', $visibility_before ); $this->assertFalse( $is_public ); @@ -273,9 +270,7 @@ public function test_get_https_url_with_credentials(): void { add_filter( 'traduttore.git_https_credentials', - function() { - return 'foo:bar'; - } + fn() => 'foo:bar' ); $url = $repository->get_https_url(); @@ -291,9 +286,7 @@ public function test_get_https_url_with_credentials_for_ht_repository(): void { add_filter( 'traduttore.hg_https_credentials', - function() { - return 'foo:bar'; - } + fn() => 'foo:bar' ); $url = $repository->get_https_url(); diff --git a/tests/phpunit/tests/Repository/GitHub.php b/tests/phpunit/tests/Repository/GitHub.php index aa30422..41bd894 100644 --- a/tests/phpunit/tests/Repository/GitHub.php +++ b/tests/phpunit/tests/Repository/GitHub.php @@ -1,37 +1,33 @@ project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', ] @@ -55,7 +51,7 @@ public function test_get_name_falls_back_to_project_slug(): void { public function test_get_name_falls_back_to_source_url_template(): void { $project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', 'source_url_template' => 'https://github.com/wearerequired/traduttore/blob/master/%file%#L%line%', @@ -70,7 +66,7 @@ public function test_get_name_falls_back_to_source_url_template(): void { public function test_get_name_falls_back_to_different_source_url_template(): void { $project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', 'source_url_template' => 'https://github.com/wearerequired/traduttore/tree/master/%file%#L%line%', @@ -105,11 +101,11 @@ public function test_get_name(): void { * @param false $preempt Whether to preempt an HTTP request's return value. Default false. * @param mixed $r HTTP request arguments. * @param string $url The request URL. - * @return array|false Response data. + * @return array{response: array{ code: int }, body: string}|false Response data. */ - public function mock_repository_visibility_request( $preempt, $r, $url ) { + public function mock_repository_visibility_request( bool $preempt, mixed $r, string $url ): array|false { if ( GitHubRepository::API_BASE . '/repos/wearerequired/traduttore' === $url ) { - ++ $this->http_request_count; + ++$this->http_request_count; return [ 'response' => [ @@ -125,7 +121,7 @@ public function mock_repository_visibility_request( $preempt, $r, $url ) { public function test_is_public_performs_http_request_and_caches_it(): void { $this->project->set_repository_name( 'wearerequired/traduttore' ); - add_filter( 'pre_http_request', array( $this, 'mock_repository_visibility_request' ), 10, 3 ); + add_filter( 'pre_http_request', [ $this, 'mock_repository_visibility_request' ], 10, 3 ); $repository = new GitHubRepository( $this->project ); @@ -134,7 +130,7 @@ public function test_is_public_performs_http_request_and_caches_it(): void { $is_public_after = $repository->is_public(); $visibility_after = $repository->get_project()->get_repository_visibility(); - remove_filter( 'pre_http_request', array( $this, 'mock_repository_visibility_request' ), 10 ); + remove_filter( 'pre_http_request', [ $this, 'mock_repository_visibility_request' ], 10 ); $this->assertNull( $visibility_before ); $this->assertTrue( $is_public ); @@ -146,7 +142,7 @@ public function test_is_public_performs_http_request_and_caches_it(): void { public function test_is_public_performs_no_unnecessary_http_request(): void { $this->project->set_repository_name( 'wearerequired/traduttore' ); - add_filter( 'pre_http_request', array( $this, 'mock_repository_visibility_request' ), 10, 3 ); + add_filter( 'pre_http_request', [ $this, 'mock_repository_visibility_request' ], 10, 3 ); $this->project->set_repository_visibility( 'private' ); @@ -155,7 +151,7 @@ public function test_is_public_performs_no_unnecessary_http_request(): void { $visibility_before = $repository->get_project()->get_repository_visibility(); $is_public = $repository->is_public(); - remove_filter( 'pre_http_request', array( $this, 'mock_repository_visibility_request' ), 10 ); + remove_filter( 'pre_http_request', [ $this, 'mock_repository_visibility_request' ], 10 ); $this->assertSame( 'private', $visibility_before ); $this->assertFalse( $is_public ); diff --git a/tests/phpunit/tests/Repository/GitLab.php b/tests/phpunit/tests/Repository/GitLab.php index 7b086f5..5d0865f 100644 --- a/tests/phpunit/tests/Repository/GitLab.php +++ b/tests/phpunit/tests/Repository/GitLab.php @@ -1,37 +1,33 @@ project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', ] @@ -55,7 +51,7 @@ public function test_get_name_falls_back_to_project_slug(): void { public function test_get_name_falls_back_to_source_url_template(): void { $project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', 'source_url_template' => 'https://gitlab.com/wearerequired/traduttore/blob/master/%file%#L%line%', @@ -104,11 +100,11 @@ public function test_get_host(): void { * @param false $preempt Whether to preempt an HTTP request's return value. Default false. * @param mixed $r HTTP request arguments. * @param string $url The request URL. - * @return array|false Response data. + * @return array{response: array{ code: int }, body: string}|false Response data. */ - public function mock_repository_visibility_request( $preempt, $r, $url ) { + public function mock_repository_visibility_request( bool $preempt, mixed $r, string $url ): array|false { if ( GitLabRepository::API_BASE . '/projects/wearerequired%2Ftraduttore' === $url ) { - ++ $this->http_request_count; + ++$this->http_request_count; return [ 'response' => [ @@ -124,7 +120,7 @@ public function mock_repository_visibility_request( $preempt, $r, $url ) { public function test_is_public_performs_http_request_and_caches_it(): void { $this->project->set_repository_name( 'wearerequired/traduttore' ); - add_filter( 'pre_http_request', array( $this, 'mock_repository_visibility_request' ), 10, 3 ); + add_filter( 'pre_http_request', [ $this, 'mock_repository_visibility_request' ], 10, 3 ); $repository = new GitLabRepository( $this->project ); @@ -133,7 +129,7 @@ public function test_is_public_performs_http_request_and_caches_it(): void { $is_public_after = $repository->is_public(); $visibility_after = $repository->get_project()->get_repository_visibility(); - remove_filter( 'pre_http_request', array( $this, 'mock_repository_visibility_request' ), 10 ); + remove_filter( 'pre_http_request', [ $this, 'mock_repository_visibility_request' ], 10 ); $this->assertNull( $visibility_before ); $this->assertTrue( $is_public ); @@ -145,7 +141,7 @@ public function test_is_public_performs_http_request_and_caches_it(): void { public function test_is_public_performs_no_unnecessary_http_request(): void { $this->project->set_repository_name( 'wearerequired/traduttore' ); - add_filter( 'pre_http_request', array( $this, 'mock_repository_visibility_request' ), 10, 3 ); + add_filter( 'pre_http_request', [ $this, 'mock_repository_visibility_request' ], 10, 3 ); $this->project->set_repository_visibility( 'private' ); @@ -154,7 +150,7 @@ public function test_is_public_performs_no_unnecessary_http_request(): void { $visibility_before = $repository->get_project()->get_repository_visibility(); $is_public = $repository->is_public(); - remove_filter( 'pre_http_request', array( $this, 'mock_repository_visibility_request' ), 10 ); + remove_filter( 'pre_http_request', [ $this, 'mock_repository_visibility_request' ], 10 ); $this->assertSame( 'private', $visibility_before ); $this->assertFalse( $is_public ); diff --git a/tests/phpunit/tests/RepositoryFactory.php b/tests/phpunit/tests/RepositoryFactory.php index c431d01..b378b24 100644 --- a/tests/phpunit/tests/RepositoryFactory.php +++ b/tests/phpunit/tests/RepositoryFactory.php @@ -1,15 +1,15 @@ factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', ] @@ -31,7 +31,7 @@ public function test_get_unknown_repository(): void { public function test_get_unknown_repository_by_type(): void { $factory = new Factory(); $project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', ] @@ -46,7 +46,7 @@ public function test_get_unknown_repository_by_type(): void { public function test_get_bitbucket_repository_by_type(): void { $factory = new Factory(); $project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', ] @@ -61,7 +61,7 @@ public function test_get_bitbucket_repository_by_type(): void { public function test_get_bitbucket_repository_by_url(): void { $factory = new Factory(); $project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', ] @@ -76,7 +76,7 @@ public function test_get_bitbucket_repository_by_url(): void { public function test_get_bitbucket_repository_by_source_url_template(): void { $factory = new Factory(); $project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', 'source_url_template' => 'https://bitbucket.org/wearerequired/traduttore/src/master/%file%#L%line%', @@ -90,7 +90,7 @@ public function test_get_bitbucket_repository_by_source_url_template(): void { public function test_get_github_repository_by_type(): void { $factory = new Factory(); $project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', ] @@ -105,7 +105,7 @@ public function test_get_github_repository_by_type(): void { public function test_get_github_repository_by_url(): void { $factory = new Factory(); $project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', ] @@ -120,7 +120,7 @@ public function test_get_github_repository_by_url(): void { public function test_get_github_repository_by_source_url_template(): void { $factory = new Factory(); $project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', 'source_url_template' => 'https://github.com/wearerequired/traduttore/blob/master/%file%#L%line%', @@ -134,7 +134,7 @@ public function test_get_github_repository_by_source_url_template(): void { public function test_get_gitlab_repository_by_type(): void { $factory = new Factory(); $project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', ] @@ -149,7 +149,7 @@ public function test_get_gitlab_repository_by_type(): void { public function test_get_gitlab_repository_by_source_url_template(): void { $factory = new Factory(); $project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Project', 'source_url_template' => 'https://gitlab.com/wearerequired/traduttore/blob/master/%file%#L%line%', diff --git a/tests/phpunit/tests/RestrictedSiteAccess.php b/tests/phpunit/tests/RestrictedSiteAccess.php index d086b53..a47301a 100644 --- a/tests/phpunit/tests/RestrictedSiteAccess.php +++ b/tests/phpunit/tests/RestrictedSiteAccess.php @@ -1,8 +1,6 @@ project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Sample Project', 'slug' => 'sample-project', @@ -63,7 +52,7 @@ public function test_delete_local_repository(): void { $this->assertFileExists( $this->loader->get_local_path() . '/foo.txt' ); $this->assertTrue( $this->runner->delete_local_repository() ); - $this->assertFileNotExists( $this->loader->get_local_path() . '/foo.txt' ); + $this->assertFileDoesNotExist( $this->loader->get_local_path() . '/foo.txt' ); } public function test_delete_local_repository_without_filesystem(): void { @@ -138,7 +127,7 @@ public function test_run_cached_does_not_download_repository(): void { $loader = $this->createMock( Loader::class ); $loader->expects( $this->once() )->method( 'get_local_path' )->willReturn( $test_path ); - $loader->expects( $this->never() )->method( 'download' )->willReturn( false ); + $loader->expects( $this->never() )->method( 'download' )->willReturn( null ); mkdir( $loader->get_local_path() ); diff --git a/tests/phpunit/tests/TestCase.php b/tests/phpunit/tests/TestCase.php index d374540..c0c3619 100644 --- a/tests/phpunit/tests/TestCase.php +++ b/tests/phpunit/tests/TestCase.php @@ -1,12 +1,11 @@ as_error(); } $this->assertInstanceOf( 'WP_Error', $response ); - $this->assertEquals( $code, $response->get_error_code() ); + $this->assertSame( $code, $response->get_error_code() ); if ( null !== $status ) { $data = $response->get_error_data(); + $this->assertIsArray( $data ); $this->assertArrayHasKey( 'status', $data ); - $this->assertEquals( $status, $data['status'] ); + $this->assertSame( $status, $data['status'] ); } } } diff --git a/tests/phpunit/tests/TranslationApiRoute.php b/tests/phpunit/tests/TranslationApiRoute.php index 9245c72..a722d89 100644 --- a/tests/phpunit/tests/TranslationApiRoute.php +++ b/tests/phpunit/tests/TranslationApiRoute.php @@ -1,15 +1,16 @@ locale = $this->factory->locale->create( + $this->locale = $this->factory()->locale->create( [ 'english_name' => 'German', 'native_name' => 'Deutsch', @@ -41,7 +52,7 @@ public function setUp() { ] ); - $this->translation_set = $this->factory->translation_set->create_with_project( + $this->translation_set = $this->factory()->translation_set->create_with_project( [ 'locale' => $this->locale->slug, ], @@ -51,8 +62,7 @@ public function setUp() { ); } - public function tearDown() { - /* @var \WP_Filesystem_Base $wp_filesystem */ + public function tearDown(): void { global $wp_filesystem; if ( ! $wp_filesystem ) { @@ -70,17 +80,30 @@ public function assert404(): void { $this->assertSame( 404, $this->route->http_status ); } - protected function get_route_callback( $project_path ) { + /** + * @return array{error?: string, translations?: array>} Response data. + */ + protected function get_route_callback( string $project_path ): array { $route = $this->route; + /** + * Route response. + * + * @var string $response + */ $response = get_echo( - function() use ( $route, $project_path ) { - /** @var Route $route */ - return $route->route_callback( $project_path ); + function () use ( $route, $project_path ): void { + /** @var \Required\Traduttore\TranslationApiRoute $route */ + $route->route_callback( $project_path ); } ); - return json_decode( $response, true ); + /** + * @var array{error?: string, translations?: array>} $result + */ + $result = (array) json_decode( $response, true ); + + return $result; } /** @@ -92,7 +115,14 @@ public function test_route_exists(): void { $property = $class->getProperty( 'urls' ); $property->setAccessible( true ); - $this->assertTrue( isset( $property->getValue( GP::$router )['get:/api/translations/(.+?)'] ) ); + /** + * Registered routes. + * + * @var array $routes + */ + $routes = $property->getValue( GP::$router ); + + $this->assertTrue( isset( $routes['get:/api/translations/(.+?)'] ) ); } public function test_invalid_project(): void { @@ -110,9 +140,9 @@ public function test_no_zip_files(): void { } public function test_one_zip_file(): void { - $original = $this->factory->original->create( [ 'project_id' => $this->translation_set->project_id ] ); + $original = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original->id, 'translation_set_id' => $this->translation_set->id, @@ -126,23 +156,24 @@ public function test_one_zip_file(): void { $response = $this->get_route_callback( 'foo-project' ); + $this->assertArrayHasKey( 'translations', $response ); $this->assertCount( 1, $response['translations'] ); - $this->assertArraySubset( - [ - 'language' => 'de_DE', - 'version' => '1.0', - 'english_name' => 'German', - 'native_name' => 'Deutsch', - 'package' => $provider->get_zip_url(), - ], - $response['translations'][0] - ); + $this->assertArrayHasKey( 'language', $response['translations'][0] ); + $this->assertSame( 'de_DE', $response['translations'][0]['language'] ); + $this->assertArrayHasKey( 'version', $response['translations'][0] ); + $this->assertSame( '1.0', $response['translations'][0]['version'] ); + $this->assertArrayHasKey( 'english_name', $response['translations'][0] ); + $this->assertSame( 'German', $response['translations'][0]['english_name'] ); + $this->assertArrayHasKey( 'native_name', $response['translations'][0] ); + $this->assertSame( 'Deutsch', $response['translations'][0]['native_name'] ); + $this->assertArrayHasKey( 'package', $response['translations'][0] ); + $this->assertSame( $provider->get_zip_url(), $response['translations'][0]['package'] ); } public function test_missing_build_time(): void { - $original = $this->factory->original->create( [ 'project_id' => $this->translation_set->project_id ] ); + $original = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original->id, 'translation_set_id' => $this->translation_set->id, @@ -158,16 +189,20 @@ public function test_missing_build_time(): void { $response = $this->get_route_callback( 'foo-project' ); + $this->assertArrayHasKey( 'translations', $response ); $this->assertCount( 0, $response['translations'] ); } public function test_uses_stored_project_version(): void { $project = ( new \Required\Traduttore\ProjectLocator( $this->translation_set->project_id ) )->get_project(); + + $this->assertInstanceOf( \Required\Traduttore\Project::class, $project ); + $project->set_version( '1.2.3' ); - $original = $this->factory->original->create( [ 'project_id' => $this->translation_set->project_id ] ); + $original = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original->id, 'translation_set_id' => $this->translation_set->id, @@ -181,16 +216,17 @@ public function test_uses_stored_project_version(): void { $response = $this->get_route_callback( 'foo-project' ); + $this->assertArrayHasKey( 'translations', $response ); $this->assertCount( 1, $response['translations'] ); - $this->assertArraySubset( - [ - 'language' => 'de_DE', - 'version' => '1.2.3', - 'english_name' => 'German', - 'native_name' => 'Deutsch', - 'package' => $provider->get_zip_url(), - ], - $response['translations'][0] - ); + $this->assertArrayHasKey( 'language', $response['translations'][0] ); + $this->assertSame( 'de_DE', $response['translations'][0]['language'] ); + $this->assertArrayHasKey( 'version', $response['translations'][0] ); + $this->assertSame( '1.2.3', $response['translations'][0]['version'] ); + $this->assertArrayHasKey( 'english_name', $response['translations'][0] ); + $this->assertSame( 'German', $response['translations'][0]['english_name'] ); + $this->assertArrayHasKey( 'native_name', $response['translations'][0] ); + $this->assertSame( 'Deutsch', $response['translations'][0]['native_name'] ); + $this->assertArrayHasKey( 'package', $response['translations'][0] ); + $this->assertSame( $provider->get_zip_url(), $response['translations'][0]['package'] ); } } diff --git a/tests/phpunit/tests/Updater.php b/tests/phpunit/tests/Updater.php index 02c7350..0a1475d 100644 --- a/tests/phpunit/tests/Updater.php +++ b/tests/phpunit/tests/Updater.php @@ -1,36 +1,28 @@ project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Sample Project', 'slug' => 'sample-project', @@ -43,7 +35,7 @@ public function setUp() { } public function test_update_without_config(): void { - $config = new Configuration( dirname( __DIR__ ) . '/data/example-no-config' ); + $config = new Configuration( \dirname( __DIR__ ) . '/data/example-no-config' ); $result = $this->updater->update( $config ); @@ -57,7 +49,7 @@ public function test_update_without_config(): void { } public function test_update_with_composer_config(): void { - $config = new Configuration( dirname( __DIR__ ) . '/data/example-with-composer' ); + $config = new Configuration( \dirname( __DIR__ ) . '/data/example-with-composer' ); $result = $this->updater->update( $config ); @@ -71,7 +63,7 @@ public function test_update_with_composer_config(): void { } public function test_update_with_config_file(): void { - $config = new Configuration( dirname( __DIR__ ) . '/data/example-with-config' ); + $config = new Configuration( \dirname( __DIR__ ) . '/data/example-with-config' ); $result = $this->updater->update( $config ); @@ -109,7 +101,7 @@ public function test_schedule_update_unschedules_existing_event(): void { foreach ( $crons as $timestamp => $cron ) { if ( isset( $cron['traduttore.update'][ $key ] ) ) { - $scheduled_count ++; + $scheduled_count++; } } @@ -121,7 +113,7 @@ public function test_schedule_update_unschedules_existing_event(): void { foreach ( $crons as $timestamp => $cron ) { if ( isset( $cron['traduttore.update'][ $key ] ) ) { - $scheduled_count ++; + $scheduled_count++; } } @@ -135,7 +127,7 @@ public function test_schedule_update_unschedules_existing_event(): void { foreach ( $crons as $timestamp => $cron ) { if ( isset( $cron['traduttore.update'][ $key ] ) ) { - $scheduled_count ++; + $scheduled_count++; } } diff --git a/tests/phpunit/tests/WebhookHandler/Bitbucket.php b/tests/phpunit/tests/WebhookHandler/Bitbucket.php index d33ffc4..5c00786 100644 --- a/tests/phpunit/tests/WebhookHandler/Bitbucket.php +++ b/tests/phpunit/tests/WebhookHandler/Bitbucket.php @@ -1,8 +1,6 @@ project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Sample Project', 'source_url_template' => 'https://bitbucket.org/wearerequired/traduttore/blob/master/%file%#L%line%', @@ -51,10 +46,11 @@ public function test_invalid_event_header(): void { public function test_invalid_signature(): void { $request = new WP_REST_Request( 'POST', '/traduttore/v1/incoming-webhook' ); - $request->set_body_params( [] ); + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( (string) wp_json_encode( [] ) ); $signature = 'sha256=' . hash_hmac( 'sha256', $request->get_body(), 'foo' ); $request->add_header( 'x-event-key', 'repo:push' ); - $request->add_header( 'x-hub-signature', $signature ); + $request->add_header( 'x-hub-signature-256', $signature ); $response = rest_get_server()->dispatch( $request ); $this->assertErrorResponse( 'rest_forbidden', $response, 401 ); @@ -62,20 +58,23 @@ public function test_invalid_signature(): void { public function test_missing_signature_is_valid(): void { $request = new WP_REST_Request( 'POST', '/traduttore/v1/incoming-webhook' ); - $request->set_body_params( - [ - 'ref' => 'refs/heads/master', - 'repository' => [ - 'links' => [ - 'html' => [ - 'href' => 'https://bitbucket.org/wearerequired/not-traduttore', + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( + (string) wp_json_encode( + [ + 'ref' => 'refs/heads/master', + 'repository' => [ + 'links' => [ + 'html' => [ + 'href' => 'https://bitbucket.org/wearerequired/not-traduttore', + ], ], + 'full_name' => 'wearerequired/not-traduttore', + 'scm' => 'git', + 'is_private' => false, ], - 'full_name' => 'wearerequired/not-traduttore', - 'scm' => 'git', - 'is_private' => false, - ], - ] + ] + ) ); $request->add_header( 'x-event-key', 'repo:push' ); $response = rest_get_server()->dispatch( $request ); @@ -85,24 +84,27 @@ public function test_missing_signature_is_valid(): void { public function test_invalid_project(): void { $request = new WP_REST_Request( 'POST', '/traduttore/v1/incoming-webhook' ); - $request->set_body_params( - [ - 'ref' => 'refs/heads/master', - 'repository' => [ - 'links' => [ - 'html' => [ - 'href' => 'https://bitbucket.org/wearerequired/not-traduttore', + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( + (string) wp_json_encode( + [ + 'ref' => 'refs/heads/master', + 'repository' => [ + 'links' => [ + 'html' => [ + 'href' => 'https://bitbucket.org/wearerequired/not-traduttore', + ], ], + 'full_name' => 'wearerequired/not-traduttore', + 'scm' => 'git', + 'is_private' => false, ], - 'full_name' => 'wearerequired/not-traduttore', - 'scm' => 'git', - 'is_private' => false, - ], - ] + ] + ) ); $signature = 'sha256=' . hash_hmac( 'sha256', $request->get_body(), 'traduttore-test' ); $request->add_header( 'x-event-key', 'repo:push' ); - $request->add_header( 'x-hub-signature', $signature ); + $request->add_header( 'x-hub-signature-256', $signature ); $response = rest_get_server()->dispatch( $request ); $this->assertErrorResponse( 404, $response ); @@ -110,27 +112,30 @@ public function test_invalid_project(): void { public function test_valid_project(): void { $request = new WP_REST_Request( 'POST', '/traduttore/v1/incoming-webhook' ); - $request->set_body_params( - [ - 'ref' => 'refs/heads/master', - 'repository' => [ - 'links' => [ - 'html' => [ - 'href' => 'https://bitbucket.org/wearerequired/traduttore', + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( + (string) wp_json_encode( + [ + 'ref' => 'refs/heads/master', + 'repository' => [ + 'links' => [ + 'html' => [ + 'href' => 'https://bitbucket.org/wearerequired/traduttore', + ], ], + 'full_name' => 'wearerequired/traduttore', + 'scm' => 'git', + 'is_private' => false, ], - 'full_name' => 'wearerequired/traduttore', - 'scm' => 'git', - 'is_private' => false, - ], - ] + ] + ) ); $signature = 'sha256=' . hash_hmac( 'sha256', $request->get_body(), 'traduttore-test' ); $request->add_header( 'x-event-key', 'repo:push' ); - $request->add_header( 'x-hub-signature', $signature ); + $request->add_header( 'x-hub-signature-256', $signature ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( 200, $response->get_status() ); $this->assertSame( [ 'result' => 'OK' ], $response->get_data() ); $this->assertSame( Repository::VCS_TYPE_GIT, $this->project->get_repository_vcs_type() ); $this->assertSame( Repository::TYPE_BITBUCKET, $this->project->get_repository_type() ); @@ -145,27 +150,30 @@ public function test_valid_mercurial_project(): void { $this->project->set_repository_vcs_type( Repository::VCS_TYPE_HG ); $request = new WP_REST_Request( 'POST', '/traduttore/v1/incoming-webhook' ); - $request->set_body_params( - [ - 'ref' => 'refs/heads/master', - 'repository' => [ - 'links' => [ - 'html' => [ - 'href' => 'https://bitbucket.org/wearerequired/traduttore', + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( + (string) wp_json_encode( + [ + 'ref' => 'refs/heads/master', + 'repository' => [ + 'links' => [ + 'html' => [ + 'href' => 'https://bitbucket.org/wearerequired/traduttore', + ], ], + 'full_name' => 'wearerequired/traduttore', + 'scm' => 'git', + 'is_private' => false, ], - 'full_name' => 'wearerequired/traduttore', - 'scm' => 'git', - 'is_private' => false, - ], - ] + ] + ) ); $signature = 'sha256=' . hash_hmac( 'sha256', $request->get_body(), 'traduttore-test' ); $request->add_header( 'x-event-key', 'repo:push' ); - $request->add_header( 'x-hub-signature', $signature ); + $request->add_header( 'x-hub-signature-256', $signature ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( 200, $response->get_status() ); $this->assertSame( [ 'result' => 'OK' ], $response->get_data() ); $this->assertSame( Repository::VCS_TYPE_HG, $this->project->get_repository_vcs_type() ); $this->assertSame( Repository::TYPE_BITBUCKET, $this->project->get_repository_type() ); diff --git a/tests/phpunit/tests/WebhookHandler/GitHub.php b/tests/phpunit/tests/WebhookHandler/GitHub.php index ecb9396..0d00c43 100644 --- a/tests/phpunit/tests/WebhookHandler/GitHub.php +++ b/tests/phpunit/tests/WebhookHandler/GitHub.php @@ -1,8 +1,6 @@ project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Sample Project', 'source_url_template' => 'https://github.com/wearerequired/traduttore/blob/master/%file%#L%line%', @@ -54,7 +52,7 @@ public function test_ping_request(): void { $request->add_header( 'x-github-event', 'ping' ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( 200, $response->get_status() ); $this->assertSame( [ 'result' => 'OK' ], $response->get_data() ); } @@ -68,10 +66,11 @@ public function test_missing_signature(): void { public function test_invalid_signature(): void { $request = new WP_REST_Request( 'POST', '/traduttore/v1/incoming-webhook' ); - $request->set_body_params( [] ); - $signature = 'sha1=' . hash_hmac( 'sha1', $request->get_body(), 'foo' ); + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( (string) wp_json_encode( [] ) ); + $signature = 'sha256=' . hash_hmac( 'sha256', $request->get_body(), 'foo' ); $request->add_header( 'x-github-event', 'push' ); - $request->add_header( 'x-hub-signature', $signature ); + $request->add_header( 'x-hub-signature-256', $signature ); $response = rest_get_server()->dispatch( $request ); $this->assertErrorResponse( 'rest_forbidden', $response, 401 ); @@ -79,43 +78,49 @@ public function test_invalid_signature(): void { public function test_invalid_branch(): void { $request = new WP_REST_Request( 'POST', '/traduttore/v1/incoming-webhook' ); - $request->set_body_params( - [ - 'ref' => 'refs/heads/master', - 'repository' => [ - 'html_url' => 'https://github.com/wearerequired/traduttore', - 'default_branch' => 'develop', - ], - ] + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( + (string) wp_json_encode( + [ + 'ref' => 'refs/heads/master', + 'repository' => [ + 'html_url' => 'https://github.com/wearerequired/traduttore', + 'default_branch' => 'develop', + ], + ] + ) ); - $signature = 'sha1=' . hash_hmac( 'sha1', $request->get_body(), 'traduttore-test' ); + $signature = 'sha256=' . hash_hmac( 'sha256', $request->get_body(), 'traduttore-test' ); $request->add_header( 'x-github-event', 'push' ); - $request->add_header( 'x-hub-signature', $signature ); + $request->add_header( 'x-hub-signature-256', $signature ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( 200, $response->get_status() ); $this->assertSame( [ 'result' => 'Not the default branch' ], $response->get_data() ); } public function test_invalid_project(): void { $request = new WP_REST_Request( 'POST', '/traduttore/v1/incoming-webhook' ); - $request->set_body_params( - [ - 'ref' => 'refs/heads/master', - 'repository' => [ - 'default_branch' => 'master', - 'full_name' => 'wearerequired/not-traduttore', - 'html_url' => 'https://github.com/wearerequired/not-traduttore', - 'ssh_url' => 'git@github.com:wearerequired/not-traduttore.git', - 'clone_url' => 'https://github.com/wearerequired/not-traduttore.git', - 'url' => 'https://github.com/wearerequired/not-traduttore', - 'private' => false, - ], - ] + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( + (string) wp_json_encode( + [ + 'ref' => 'refs/heads/master', + 'repository' => [ + 'default_branch' => 'master', + 'full_name' => 'wearerequired/not-traduttore', + 'html_url' => 'https://github.com/wearerequired/not-traduttore', + 'ssh_url' => 'git@github.com:wearerequired/not-traduttore.git', + 'clone_url' => 'https://github.com/wearerequired/not-traduttore.git', + 'url' => 'https://github.com/wearerequired/not-traduttore', + 'private' => false, + ], + ] + ) ); - $signature = 'sha1=' . hash_hmac( 'sha1', $request->get_body(), 'traduttore-test' ); + $signature = 'sha256=' . hash_hmac( 'sha256', $request->get_body(), 'traduttore-test' ); $request->add_header( 'x-github-event', 'push' ); - $request->add_header( 'x-hub-signature', $signature ); + $request->add_header( 'x-hub-signature-256', $signature ); $response = rest_get_server()->dispatch( $request ); $this->assertErrorResponse( 404, $response ); @@ -123,41 +128,47 @@ public function test_invalid_project(): void { public function test_invalid_request(): void { $request = new WP_REST_Request( 'POST', '/traduttore/v1/incoming-webhook' ); - $request->set_body_params( - [ - 'ref' => 'refs/heads/master', - ] + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( + (string) wp_json_encode( + [ + 'ref' => 'refs/heads/master', + ] + ) ); - $signature = 'sha1=' . hash_hmac( 'sha1', $request->get_body(), 'traduttore-test' ); + $signature = 'sha256=' . hash_hmac( 'sha256', $request->get_body(), 'traduttore-test' ); $request->add_header( 'x-github-event', 'push' ); - $request->add_header( 'x-hub-signature', $signature ); + $request->add_header( 'x-hub-signature-256', $signature ); $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 400, $response ); + $this->assertErrorResponse( 'rest_forbidden', $response, 401 ); } public function test_valid_project(): void { $request = new WP_REST_Request( 'POST', '/traduttore/v1/incoming-webhook' ); - $request->set_body_params( - [ - 'ref' => 'refs/heads/master', - 'repository' => [ - 'full_name' => 'wearerequired/traduttore', - 'default_branch' => 'master', - 'html_url' => 'https://github.com/wearerequired/traduttore', - 'ssh_url' => 'git@github.com:wearerequired/traduttore.git', - 'clone_url' => 'https://github.com/wearerequired/traduttore.git', - 'url' => 'https://github.com/wearerequired/traduttore', - 'private' => false, - ], - ] + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( + (string) wp_json_encode( + [ + 'ref' => 'refs/heads/master', + 'repository' => [ + 'full_name' => 'wearerequired/traduttore', + 'default_branch' => 'master', + 'html_url' => 'https://github.com/wearerequired/traduttore', + 'ssh_url' => 'git@github.com:wearerequired/traduttore.git', + 'clone_url' => 'https://github.com/wearerequired/traduttore.git', + 'url' => 'https://github.com/wearerequired/traduttore', + 'private' => false, + ], + ] + ) ); - $signature = 'sha1=' . hash_hmac( 'sha1', $request->get_body(), 'traduttore-test' ); + $signature = 'sha256=' . hash_hmac( 'sha256', $request->get_body(), 'traduttore-test' ); $request->add_header( 'x-github-event', 'push' ); - $request->add_header( 'x-hub-signature', $signature ); + $request->add_header( 'x-hub-signature-256', $signature ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( 200, $response->get_status() ); $this->assertSame( [ 'result' => 'OK' ], $response->get_data() ); $this->assertSame( Repository::VCS_TYPE_GIT, $this->project->get_repository_vcs_type() ); $this->assertSame( Repository::TYPE_GITHUB, $this->project->get_repository_type() ); @@ -171,9 +182,9 @@ public function test_valid_project(): void { public function test_valid_project_with_x_www_form_urlencoded_content_type(): void { $request = new WP_REST_Request( 'POST', '/traduttore/v1/incoming-webhook' ); $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); - $request->set_body_params( - [ - 'payload' => json_encode( [ + $data = [ + 'payload' => json_encode( + [ 'ref' => 'refs/heads/master', 'repository' => [ 'full_name' => 'wearerequired/traduttore', @@ -184,15 +195,18 @@ public function test_valid_project_with_x_www_form_urlencoded_content_type(): vo 'url' => 'https://github.com/wearerequired/traduttore', 'private' => false, ], - ] ) - ] - ); - $signature = 'sha1=' . hash_hmac( 'sha1', $request->get_body(), 'traduttore-test' ); + ] + ), + ]; + $request->set_body_params( $data ); + $request->set_body( http_build_query( $data ) ); + + $signature = 'sha256=' . hash_hmac( 'sha256', $request->get_body(), 'traduttore-test' ); $request->add_header( 'x-github-event', 'push' ); - $request->add_header( 'x-hub-signature', $signature ); + $request->add_header( 'x-hub-signature-256', $signature ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( 200, $response->get_status() ); $this->assertSame( [ 'result' => 'OK' ], $response->get_data() ); $this->assertSame( Repository::VCS_TYPE_GIT, $this->project->get_repository_vcs_type() ); $this->assertSame( Repository::TYPE_GITHUB, $this->project->get_repository_type() ); @@ -209,26 +223,29 @@ public function test_valid_project_custom_webhook_secret(): void { $this->project->set_repository_webhook_secret( $secret ); $request = new WP_REST_Request( 'POST', '/traduttore/v1/incoming-webhook' ); - $request->set_body_params( - [ - 'ref' => 'refs/heads/master', - 'repository' => [ - 'full_name' => 'wearerequired/traduttore', - 'default_branch' => 'master', - 'html_url' => 'https://github.com/wearerequired/traduttore', - 'ssh_url' => 'git@github.com:wearerequired/traduttore.git', - 'clone_url' => 'https://github.com/wearerequired/traduttore.git', - 'url' => 'https://github.com/wearerequired/traduttore', - 'private' => false, - ], - ] + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( + (string) wp_json_encode( + [ + 'ref' => 'refs/heads/master', + 'repository' => [ + 'full_name' => 'wearerequired/traduttore', + 'default_branch' => 'master', + 'html_url' => 'https://github.com/wearerequired/traduttore', + 'ssh_url' => 'git@github.com:wearerequired/traduttore.git', + 'clone_url' => 'https://github.com/wearerequired/traduttore.git', + 'url' => 'https://github.com/wearerequired/traduttore', + 'private' => false, + ], + ] + ) ); - $signature = 'sha1=' . hash_hmac( 'sha1', $request->get_body(), $secret ); + $signature = 'sha256=' . hash_hmac( 'sha256', $request->get_body(), $secret ); $request->add_header( 'x-github-event', 'push' ); - $request->add_header( 'x-hub-signature', $signature ); + $request->add_header( 'x-hub-signature-256', $signature ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( 200, $response->get_status() ); $this->assertSame( [ 'result' => 'OK' ], $response->get_data() ); $this->assertSame( Repository::VCS_TYPE_GIT, $this->project->get_repository_vcs_type() ); $this->assertSame( Repository::TYPE_GITHUB, $this->project->get_repository_type() ); diff --git a/tests/phpunit/tests/WebhookHandler/GitLab.php b/tests/phpunit/tests/WebhookHandler/GitLab.php index cc41103..eebaa6a 100644 --- a/tests/phpunit/tests/WebhookHandler/GitLab.php +++ b/tests/phpunit/tests/WebhookHandler/GitLab.php @@ -1,8 +1,6 @@ project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Sample Project', 'source_url_template' => 'https://gitlab.com/wearerequired/traduttore/blob/master/%file%#L%line%', @@ -82,7 +80,7 @@ public function test_invalid_branch(): void { $request->add_header( 'x-gitlab-token', 'traduttore-test' ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( 200, $response->get_status() ); $this->assertSame( [ 'result' => 'Not the default branch' ], $response->get_data() ); } @@ -127,7 +125,7 @@ public function test_valid_project(): void { $request->add_header( 'x-gitlab-token', 'traduttore-test' ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( 200, $response->get_status() ); $this->assertSame( [ 'result' => 'OK' ], $response->get_data() ); $this->assertSame( Repository::VCS_TYPE_GIT, $this->project->get_repository_vcs_type() ); $this->assertSame( Repository::TYPE_GITLAB, $this->project->get_repository_type() ); @@ -161,7 +159,7 @@ public function test_valid_project_custom_webhook_secret(): void { $request->add_header( 'x-gitlab-token', $secret ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( 200, $response->get_status() ); $this->assertSame( [ 'result' => 'OK' ], $response->get_data() ); $this->assertSame( Repository::VCS_TYPE_GIT, $this->project->get_repository_vcs_type() ); $this->assertSame( Repository::TYPE_GITLAB, $this->project->get_repository_type() ); diff --git a/tests/phpunit/tests/WebhookHandler/LegacyGitHub.php b/tests/phpunit/tests/WebhookHandler/LegacyGitHub.php index c72d53d..4bc801d 100644 --- a/tests/phpunit/tests/WebhookHandler/LegacyGitHub.php +++ b/tests/phpunit/tests/WebhookHandler/LegacyGitHub.php @@ -1,8 +1,6 @@ project = new Project( - $this->factory->project->create( + $this->factory()->project->create( [ 'name' => 'Sample Project', 'source_url_template' => 'https://github.com/wearerequired/traduttore/blob/master/%file%#L%line%', @@ -54,7 +49,7 @@ public function test_ping_request(): void { $request->add_header( 'x-github-event', 'ping' ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( 200, $response->get_status() ); $this->assertSame( [ 'result' => 'OK' ], $response->get_data() ); } @@ -68,10 +63,11 @@ public function test_missing_signature(): void { public function test_invalid_signature(): void { $request = new WP_REST_Request( 'POST', '/github-webhook/v1/push-event' ); - $request->set_body_params( [] ); - $signature = 'sha1=' . hash_hmac( 'sha1', $request->get_body(), 'foo' ); + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( (string) wp_json_encode( [] ) ); + $signature = 'sha256=' . hash_hmac( 'sha256', $request->get_body(), 'foo' ); $request->add_header( 'x-github-event', 'push' ); - $request->add_header( 'x-hub-signature', $signature ); + $request->add_header( 'x-hub-signature-256', $signature ); $response = rest_get_server()->dispatch( $request ); $this->assertErrorResponse( 'rest_forbidden', $response, 401 ); @@ -79,43 +75,49 @@ public function test_invalid_signature(): void { public function test_invalid_branch(): void { $request = new WP_REST_Request( 'POST', '/github-webhook/v1/push-event' ); - $request->set_body_params( - [ - 'ref' => 'refs/heads/master', - 'repository' => [ - 'html_url' => 'https://github.com/wearerequired/traduttore', - 'default_branch' => 'develop', - ], - ] + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( + (string) wp_json_encode( + [ + 'ref' => 'refs/heads/master', + 'repository' => [ + 'html_url' => 'https://github.com/wearerequired/traduttore', + 'default_branch' => 'develop', + ], + ] + ) ); - $signature = 'sha1=' . hash_hmac( 'sha1', $request->get_body(), 'traduttore-test' ); + $signature = 'sha256=' . hash_hmac( 'sha256', $request->get_body(), 'traduttore-test' ); $request->add_header( 'x-github-event', 'push' ); - $request->add_header( 'x-hub-signature', $signature ); + $request->add_header( 'x-hub-signature-256', $signature ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( 200, $response->get_status() ); $this->assertSame( [ 'result' => 'Not the default branch' ], $response->get_data() ); } public function test_invalid_project(): void { $request = new WP_REST_Request( 'POST', '/github-webhook/v1/push-event' ); - $request->set_body_params( - [ - 'ref' => 'refs/heads/master', - 'repository' => [ - 'default_branch' => 'master', - 'full_name' => 'wearerequired/not-traduttore', - 'html_url' => 'https://github.com/wearerequired/not-traduttore', - 'ssh_url' => 'git@github.com:wearerequired/not-traduttore.git', - 'clone_url' => 'https://github.com/wearerequired/not-traduttore.git', - 'url' => 'https://github.com/wearerequired/not-traduttore', - 'private' => false, - ], - ] + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( + (string) wp_json_encode( + [ + 'ref' => 'refs/heads/master', + 'repository' => [ + 'default_branch' => 'master', + 'full_name' => 'wearerequired/not-traduttore', + 'html_url' => 'https://github.com/wearerequired/not-traduttore', + 'ssh_url' => 'git@github.com:wearerequired/not-traduttore.git', + 'clone_url' => 'https://github.com/wearerequired/not-traduttore.git', + 'url' => 'https://github.com/wearerequired/not-traduttore', + 'private' => false, + ], + ] + ) ); - $signature = 'sha1=' . hash_hmac( 'sha1', $request->get_body(), 'traduttore-test' ); + $signature = 'sha256=' . hash_hmac( 'sha256', $request->get_body(), 'traduttore-test' ); $request->add_header( 'x-github-event', 'push' ); - $request->add_header( 'x-hub-signature', $signature ); + $request->add_header( 'x-hub-signature-256', $signature ); $response = rest_get_server()->dispatch( $request ); $this->assertErrorResponse( 404, $response ); @@ -123,26 +125,29 @@ public function test_invalid_project(): void { public function test_valid_project(): void { $request = new WP_REST_Request( 'POST', '/github-webhook/v1/push-event' ); - $request->set_body_params( - [ - 'ref' => 'refs/heads/master', - 'repository' => [ - 'full_name' => 'wearerequired/traduttore', - 'default_branch' => 'master', - 'html_url' => 'https://github.com/wearerequired/traduttore', - 'ssh_url' => 'git@github.com:wearerequired/traduttore.git', - 'clone_url' => 'https://github.com/wearerequired/traduttore.git', - 'url' => 'https://github.com/wearerequired/traduttore', - 'private' => false, - ], - ] + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( + (string) wp_json_encode( + [ + 'ref' => 'refs/heads/master', + 'repository' => [ + 'full_name' => 'wearerequired/traduttore', + 'default_branch' => 'master', + 'html_url' => 'https://github.com/wearerequired/traduttore', + 'ssh_url' => 'git@github.com:wearerequired/traduttore.git', + 'clone_url' => 'https://github.com/wearerequired/traduttore.git', + 'url' => 'https://github.com/wearerequired/traduttore', + 'private' => false, + ], + ] + ) ); - $signature = 'sha1=' . hash_hmac( 'sha1', $request->get_body(), 'traduttore-test' ); + $signature = 'sha256=' . hash_hmac( 'sha256', $request->get_body(), 'traduttore-test' ); $request->add_header( 'x-github-event', 'push' ); - $request->add_header( 'x-hub-signature', $signature ); + $request->add_header( 'x-hub-signature-256', $signature ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( 200, $response->get_status() ); $this->assertSame( [ 'result' => 'OK' ], $response->get_data() ); $this->assertSame( Repository::VCS_TYPE_GIT, $this->project->get_repository_vcs_type() ); $this->assertSame( Repository::TYPE_GITHUB, $this->project->get_repository_type() ); @@ -159,26 +164,29 @@ public function test_valid_project_custom_webhook_secret(): void { $this->project->set_repository_webhook_secret( $secret ); $request = new WP_REST_Request( 'POST', '/github-webhook/v1/push-event' ); - $request->set_body_params( - [ - 'ref' => 'refs/heads/master', - 'repository' => [ - 'full_name' => 'wearerequired/traduttore', - 'default_branch' => 'master', - 'html_url' => 'https://github.com/wearerequired/traduttore', - 'ssh_url' => 'git@github.com:wearerequired/traduttore.git', - 'clone_url' => 'https://github.com/wearerequired/traduttore.git', - 'url' => 'https://github.com/wearerequired/traduttore', - 'private' => false, - ], - ] + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( + (string) wp_json_encode( + [ + 'ref' => 'refs/heads/master', + 'repository' => [ + 'full_name' => 'wearerequired/traduttore', + 'default_branch' => 'master', + 'html_url' => 'https://github.com/wearerequired/traduttore', + 'ssh_url' => 'git@github.com:wearerequired/traduttore.git', + 'clone_url' => 'https://github.com/wearerequired/traduttore.git', + 'url' => 'https://github.com/wearerequired/traduttore', + 'private' => false, + ], + ] + ) ); - $signature = 'sha1=' . hash_hmac( 'sha1', $request->get_body(), $secret ); + $signature = 'sha256=' . hash_hmac( 'sha256', $request->get_body(), $secret ); $request->add_header( 'x-github-event', 'push' ); - $request->add_header( 'x-hub-signature', $signature ); + $request->add_header( 'x-hub-signature-256', $signature ); $response = rest_get_server()->dispatch( $request ); - $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( 200, $response->get_status() ); $this->assertSame( [ 'result' => 'OK' ], $response->get_data() ); $this->assertSame( Repository::VCS_TYPE_GIT, $this->project->get_repository_vcs_type() ); $this->assertSame( Repository::TYPE_GITHUB, $this->project->get_repository_type() ); diff --git a/tests/phpunit/tests/ZipProvider.php b/tests/phpunit/tests/ZipProvider.php index f5d7d4a..5145dac 100644 --- a/tests/phpunit/tests/ZipProvider.php +++ b/tests/phpunit/tests/ZipProvider.php @@ -1,17 +1,14 @@ locale = $this->factory->locale->create( + $this->locale = $this->factory()->locale->create( [ 'slug' => 'de', 'wp_locale' => 'de_DE', ] ); - $this->translation_set = $this->factory->translation_set->create_with_project( + $this->translation_set = $this->factory()->translation_set->create_with_project( [ 'locale' => $this->locale->slug, ], @@ -58,7 +55,7 @@ public function setUp() { ] ); - $this->sub_translation_set = $this->factory->translation_set->create_with_project( + $this->sub_translation_set = $this->factory()->translation_set->create_with_project( [ 'locale' => $this->locale->slug, ], @@ -68,10 +65,14 @@ public function setUp() { ] ); - $this->project = new \Required\Traduttore\Project( GP::$project->get( $this->translation_set->project_id ) ); + $gp_project = GP::$project->get( $this->translation_set->project_id ); + + $this->assertNotFalse( $gp_project ); + + $this->project = new \Required\Traduttore\Project( $gp_project ); } - public function tearDown() { + public function tearDown(): void { /* @var WP_Filesystem_Base $wp_filesystem */ global $wp_filesystem; @@ -79,7 +80,7 @@ public function tearDown() { require_once ABSPATH . '/wp-admin/includes/admin.php'; if ( ! \WP_Filesystem() ) { - return false; + return; } } @@ -145,9 +146,9 @@ public function test_generate_zip_file_no_filesystem(): void { } public function test_generate_zip_file(): void { - $original = $this->factory->original->create( [ 'project_id' => $this->translation_set->project_id ] ); + $original = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original->id, 'translation_set_id' => $this->translation_set->id, @@ -165,9 +166,9 @@ public function test_generate_zip_file(): void { * @preserveGlobalState disabled */ public function test_generate_zip_file_missing_wp_filesystem(): void { - $original = $this->factory->original->create( [ 'project_id' => $this->translation_set->project_id ] ); + $original = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original->id, 'translation_set_id' => $this->translation_set->id, @@ -185,9 +186,9 @@ public function test_generate_zip_file_missing_wp_filesystem(): void { } public function test_replaces_existing_zip_file(): void { - $original = $this->factory->original->create( [ 'project_id' => $this->translation_set->project_id ] ); + $original = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original->id, 'translation_set_id' => $this->translation_set->id, @@ -214,14 +215,14 @@ public function test_replaces_existing_zip_file(): void { $file_after = $zip_after->statName( 'foo.txt' ); - $this->assertInternalType( 'array', $file_before ); + $this->assertIsArray( $file_before ); $this->assertFalse( $file_after ); } public function test_get_last_build_time_after_zip_generation(): void { - $original = $this->factory->original->create( [ 'project_id' => $this->translation_set->project_id ] ); + $original = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original->id, 'translation_set_id' => $this->translation_set->id, @@ -237,9 +238,9 @@ public function test_get_last_build_time_after_zip_generation(): void { } public function test_remove_zip_file(): void { - $original = $this->factory->original->create( [ 'project_id' => $this->translation_set->project_id ] ); + $original = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original->id, 'translation_set_id' => $this->translation_set->id, @@ -255,9 +256,9 @@ public function test_remove_zip_file(): void { } public function test_remove_zip_file_resets_build_time(): void { - $original = $this->factory->original->create( [ 'project_id' => $this->translation_set->project_id ] ); + $original = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original->id, 'translation_set_id' => $this->translation_set->id, @@ -284,9 +285,9 @@ public function test_remove_zip_file_does_not_exist(): void { } public function test_remove_zip_file_no_filesystem(): void { - $original = $this->factory->original->create( [ 'project_id' => $this->translation_set->project_id ] ); + $original = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id ] ); - $this->factory->translation->create( + $this->factory()->translation->create( [ 'original_id' => $original->id, 'translation_set_id' => $this->translation_set->id, @@ -308,10 +309,13 @@ public function test_remove_zip_file_no_filesystem(): void { } public function test_use_text_domain_for_translation_files(): void { - $project = ( new ProjectLocator( $this->translation_set->project_id ) )->get_project(); - $original = $this->factory->original->create( [ 'project_id' => $this->translation_set->project_id ] ); + $project = ( new ProjectLocator( $this->translation_set->project_id ) )->get_project(); + + $this->assertInstanceOf( \Required\Traduttore\Project::class, $project ); - $this->factory->translation->create( + $original = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id ] ); + + $this->factory()->translation->create( [ 'original_id' => $original->id, 'translation_set_id' => $this->translation_set->id, @@ -324,17 +328,24 @@ public function test_use_text_domain_for_translation_files(): void { $provider = new Provider( $this->translation_set ); $result = $provider->generate_zip_file(); - $expected_files = [ 'foo-bar-baz-de_DE.po', 'foo-bar-baz-de_DE.mo' ]; + $expected_files = [ + 'foo-bar-baz-de_DE.po', + 'foo-bar-baz-de_DE.mo', + 'foo-bar-baz-de_DE.l10n.php', + ]; $actual_files = []; $zip = new ZipArchive(); $zip->open( $provider->get_zip_path() ); - // phpcs:ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar - for ( $i = 0; $i < $zip->numFiles; $i ++ ) { - $stat = $zip->statIndex( $i ); - $actual_files[] = $stat['name']; + // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + for ( $i = 0; $i < $zip->numFiles; $i++ ) { + $stat = $zip->statIndex( $i ); + + if ( $stat ) { + $actual_files[] = $stat['name']; + } } $zip->close(); @@ -352,7 +363,7 @@ public function test_schedule_generation_schedules_event(): void { $after = wp_next_scheduled( 'traduttore.generate_zip', [ $this->translation_set->id ] ); $this->assertFalse( $before ); - $this->assertInternalType( 'int', $after ); + $this->assertIsInt( $after ); } public function test_schedule_generation_removes_existing_event(): void { @@ -366,9 +377,9 @@ public function test_schedule_generation_removes_existing_event(): void { $crons = _get_cron_array(); $key = md5( serialize( [ $this->translation_set->id ] ) ); - foreach ( $crons as $timestamp => $cron ) { + foreach ( $crons as $cron ) { if ( isset( $cron['traduttore.generate_zip'][ $key ] ) ) { - $actual_count ++; + $actual_count++; } } @@ -376,11 +387,11 @@ public function test_schedule_generation_removes_existing_event(): void { } public function test_does_not_schedule_generation_after_saving_translation_for_inactive_project(): void { - $original = $this->factory->original->create( [ 'project_id' => $this->translation_set->project_id ] ); + $original = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id ] ); $translation_set_id = (int) $this->translation_set->id; /** @var \GP_Translation $translation */ - $translation = $this->factory->translation->create( + $translation = $this->factory()->translation->create( [ 'original_id' => $original->id, 'translation_set_id' => $this->translation_set->id, @@ -404,11 +415,11 @@ public function test_does_not_schedule_generation_after_saving_translation_for_i public function test_schedules_generation_after_saving_translation(): void { $this->project->get_project()->save( [ 'active' => 1 ] ); - $original = $this->factory->original->create( [ 'project_id' => $this->translation_set->project_id ] ); + $original = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id ] ); $translation_set_id = (int) $this->translation_set->id; /** @var \GP_Translation $translation */ - $translation = $this->factory->translation->create( + $translation = $this->factory()->translation->create( [ 'original_id' => $original->id, 'translation_set_id' => $this->translation_set->id, @@ -426,12 +437,12 @@ public function test_schedules_generation_after_saving_translation(): void { $this->assertFalse( $before ); $this->assertTrue( $result ); - $this->assertInternalType( 'int', $after ); + $this->assertIsInt( $after ); } public function test_does_not_schedule_generation_after_importing_originals_for_inactive_project(): void { /** @var \GP_Original $original */ - $original = $this->factory->original->create( [ 'project_id' => $this->translation_set->project_id ] ); + $original = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id ] ); $before = wp_next_scheduled( 'traduttore.generate_zip', [ $this->translation_set->id ] ); @@ -449,7 +460,7 @@ public function test_schedules_generation_after_importing_originals(): void { $this->project->get_project()->save( [ 'active' => 1 ] ); /** @var \GP_Original $original */ - $original = $this->factory->original->create( [ 'project_id' => $this->translation_set->project_id ] ); + $original = $this->factory()->original->create( [ 'project_id' => $this->translation_set->project_id ] ); $before = wp_next_scheduled( 'traduttore.generate_zip', [ $this->translation_set->id ] ); @@ -459,7 +470,7 @@ public function test_schedules_generation_after_importing_originals(): void { $after = wp_next_scheduled( 'traduttore.generate_zip', [ $this->translation_set->id ] ); $this->assertFalse( $before ); - $this->assertInternalType( 'int', $after ); + $this->assertIsInt( $after ); $this->assertCount( 0, $originals_for_project ); } } diff --git a/traduttore.php b/traduttore.php index 6d1c332..b9cc973 100644 --- a/traduttore.php +++ b/traduttore.php @@ -11,6 +11,8 @@ * Text Domain: traduttore * Domain Path: /languages * Update URI: false + * Requires PHP: 8.1 + * Requires Plugins: glotpress * * Copyright (c) 2017-2022 required (email: info@required.ch) * From 5f499238f20df268af9ec7977295b6979862b7df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mendon=C3=A7a?= Date: Thu, 16 May 2024 21:42:07 +0100 Subject: [PATCH 3/3] Add filter to the entries Export status (#260) --- inc/Export.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/inc/Export.php b/inc/Export.php index eb7014e..05246e8 100644 --- a/inc/Export.php +++ b/inc/Export.php @@ -80,7 +80,19 @@ public function __construct( GP_Translation_Set $translation_set ) { * @return array List of files with names as key and temporary file location as value. */ public function export_strings(): ?array { - $entries = GP::$translation->for_export( $this->project->get_project(), $this->translation_set, [ 'status' => 'current' ] ); + + /** + * Filters the status of the entries to export. + * + * @since 4.0.0 + * + * @param string $export_status The status of the entries to export. Default is 'current'. + * @param \GP_Translation_Set $translation_set Translation set the language pack is for. + * @param \Required\Traduttore\Project $project The project that was updated. + */ + $export_status = apply_filters( 'traduttore.export_status', 'current', $this->translation_set, $this->project ); + + $entries = GP::$translation->for_export( $this->project->get_project(), $this->translation_set, [ 'status' => $export_status ] ); if ( ! $entries ) { return null;