diff --git a/.github/workflows/build-stable.yml b/.github/workflows/build-stable.yml index 016039a0b..71ab22050 100644 --- a/.github/workflows/build-stable.yml +++ b/.github/workflows/build-stable.yml @@ -14,13 +14,22 @@ jobs: uses: actions/setup-node@v3 with: node-version-file: '.nvmrc' + - name: Set PHP version + uses: shivammathur/setup-php@2.17.0 + with: + php-version: 7.4 + tools: composer:v2 - name: Check versions - run: npm -v; node -v + run: | + npm -v + node -v + php -v + composer -v + - name: Install and build run: | composer install --no-dev npm install - npm run makepot npm run release - name: Push to Stable env: diff --git a/.github/workflows/generate-zip.yml b/.github/workflows/generate-zip.yml new file mode 100644 index 000000000..da116934d --- /dev/null +++ b/.github/workflows/generate-zip.yml @@ -0,0 +1,49 @@ +name: Generate ZIP + +on: + workflow_dispatch: + inputs: + ref: + description: 'Git Commit Ref (branch, tag, or hash)' + required: false + type: string + +jobs: + generate_zip: + name: New ZIP file + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2.4.0 + with: + ref: ${{ inputs.ref }} + - name: Use desired version of NodeJS + uses: actions/setup-node@v3 + with: + node-version-file: '.nvmrc' + - name: Set PHP version + uses: shivammathur/setup-php@2.17.0 + with: + php-version: 7.4 + tools: composer:v2, wp + - name: Check versions + run: | + npm -v + node -v + php -v + composer -v + + - name: Install and build + run: | + composer install --no-dev + npm install + npm run release + npm run plugin-zip + rm -rf ./release && unzip ${{ github.event.repository.name }}.zip -d ./release + + - name: Upload the ZIP file as an artifact + uses: actions/upload-artifact@v3 + with: + name: ${{ github.event.repository.name }} + path: release + retention-days: 5 diff --git a/.gitignore b/.gitignore index 91238cb15..4eaa1cab7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,12 @@ vendor/* !vendor/georgestephanis release/* dist/* -screenshots/ lang/ docs-built .phpunit.result.cache /.wp-env.override.json + +tests/cypress/videos +tests/cypress/screenshots + +distributor.zip diff --git a/assets/js/admin-external-connection.js b/assets/js/admin-external-connection.js index 90f348c58..a0e777bf1 100755 --- a/assets/js/admin-external-connection.js +++ b/assets/js/admin-external-connection.js @@ -4,6 +4,7 @@ import jQuery from 'jquery'; import _ from 'underscore'; import { addQueryArgs, isURL, prependHTTP } from '@wordpress/url'; import { speak } from '@wordpress/a11y'; +import { __, sprintf } from '@wordpress/i18n'; import compareVersions from 'compare-versions'; const { ajaxurl, alert, document, dt, history } = window; @@ -90,7 +91,12 @@ jQuery( authorizeConnectionButton ).on( 'click', ( event ) => { let siteURL = prependHTTP( externalSiteUrlField.value ); if ( ! isURL( siteURL ) ) { - jQuery( wizardError[ 0 ] ).text( dt.invalid_url ); + jQuery( wizardError[ 0 ] ).text( + __( + 'Please enter a valid URL, including the HTTP(S).', + 'distributor' + ) + ); return false; } @@ -128,11 +134,21 @@ jQuery( authorizeConnectionButton ).on( 'click', ( event ) => { 'version' ) ) { - jQuery( wizardError[ 0 ] ).text( dt.no_distributor ); + jQuery( wizardError[ 0 ] ).text( + __( + 'Distributor not installed on remote site.', + 'distributor' + ) + ); return; } - jQuery( wizardError[ 0 ] ).text( dt.noconnection ); + jQuery( wizardError[ 0 ] ).text( + __( + 'Distributor not installed on remote site.', + 'distributor' + ) + ); if ( Object.prototype.hasOwnProperty.call( response, 'data' ) && @@ -164,7 +180,12 @@ jQuery( authorizeConnectionButton ).on( 'click', ( event ) => { if ( compareVersions.compare( response.data.version, '1.6.0', '<' ) ) { - jQuery( wizardError[ 0 ] ).text( dt.minversion ); + jQuery( wizardError[ 0 ] ).text( + __( + 'Remote site requires Distributor version 1.6.0 or greater. Upgrade Distributor on the remote site to use the Authentication Wizard.', + 'distributor' + ) + ); return; } @@ -173,7 +194,10 @@ jQuery( authorizeConnectionButton ).on( 'click', ( event ) => { ! response.data.core_application_passwords_available ) { jQuery( wizardError[ 0 ] ).text( - dt.application_passwords_not_available + __( + 'Application Passwords is not available on the remote site. Please set up connection manually!', + 'distributor' + ) ); return; } @@ -207,7 +231,12 @@ jQuery( authorizeConnectionButton ).on( 'click', ( event ) => { } const authURL = addQueryArgs( auth_url, { - app_name: dt.distributor_from /*eslint camelcase: 0*/, + app_name: sprintf( + /* translators: %1$s: site name, %2$s: site URL */ + __( 'Distributor on %1$s (%2$s)', 'distributor' ), + dt.blog_name, + dt.home_url + ), success_url: encodeURI( successURL ) /*eslint camelcase: 0*/, reject_url: encodeURI( failureURL ) /*eslint camelcase: 0*/, } ); @@ -246,7 +275,7 @@ function checkConnections() { } endpointResult.setAttribute( 'data-endpoint-state', 'loading' ); - endpointResult.innerText = dt.endpoint_checking_message; + endpointResult.innerText = __( 'Checking endpoint…', 'distributor' ); endpointErrors.innerText = ''; @@ -289,7 +318,10 @@ function checkConnections() { endpointResult.setAttribute( 'data-endpoint-state', 'error' ); if ( response.data.endpoint_suggestion ) { - endpointResult.innerText = `${ dt.endpoint_suggestion } `; + endpointResult.innerText = `${ __( + 'Did you mean: ', + 'distributor' + ) } `; const suggestion = document.createElement( 'button' ); suggestion.classList.add( 'suggest' ); @@ -300,43 +332,86 @@ function checkConnections() { endpointResult.appendChild( suggestion ); speak( - `${ dt.endpoint_suggestion } ${ response.data.endpoint_suggestion }`, + `${ __( 'Did you mean: ', 'distributor' ) } ${ + response.data.endpoint_suggestion + }`, 'polite' ); } else { - endpointResult.innerText = dt.bad_connection; + endpointResult.innerText = __( + 'No connection found.', + 'distributor' + ); - speak( dt.bad_connection, 'polite' ); + speak( + __( 'No connection found.', 'distributor' ), + 'polite' + ); } } else if ( response.data.errors.no_distributor || ! response.data.can_post.length ) { endpointResult.setAttribute( 'data-endpoint-state', 'warning' ); - endpointResult.innerText = dt.limited_connection; + endpointResult.innerText = __( + 'Limited connection established.', + 'distributor' + ); const warnings = []; if ( response.data.errors.no_distributor ) { - endpointResult.innerText += ` ${ dt.no_distributor }`; + endpointResult.innerText += ` ${ __( + 'Distributor not installed on remote site.', + 'distributor' + ) }`; speak( - `${ dt.limited_connection } ${ dt.no_distributor }`, + `${ __( + 'Limited connection established.', + 'distributor' + ) } ${ __( + 'Distributor not installed on remote site.', + 'distributor' + ) }`, 'polite' ); } else { - speak( `${ dt.limited_connection }`, 'polite' ); + speak( + `${ __( + 'Limited connection established.', + 'distributor' + ) }`, + 'polite' + ); } if ( 'no' === response.data.is_authenticated ) { - warnings.push( dt.bad_auth ); + warnings.push( + __( + 'Authentication failed due to invalid credentials.', + 'distributor' + ) + ); } if ( 'yes' === response.data.is_authenticated ) { - warnings.push( dt.no_permissions ); + warnings.push( + __( + 'Authentication succeeded but your account does not have permissions to create posts on the external site.', + 'distributor' + ) + ); } - warnings.push( dt.no_push ); - warnings.push( dt.pull_limited ); + warnings.push( + __( 'Push distribution unavailable.', 'distributor' ) + ); + warnings.push( + __( + 'Pull distribution limited to basic content, i.e. title and content body.', + 'distributor' + ) + ); warnings.forEach( ( warning ) => { const warningNode = document.createElement( 'li' ); @@ -346,9 +421,15 @@ function checkConnections() { } ); } else { endpointResult.setAttribute( 'data-endpoint-state', 'valid' ); - endpointResult.innerText = dt.good_connection; + endpointResult.innerText = __( + 'Connection established.', + 'distributor' + ); - speak( dt.good_connection, 'polite' ); + speak( + __( 'Connection established.', 'distributor' ), + 'polite' + ); } } ) .always( () => { @@ -457,11 +538,11 @@ jQuery( changePassword ).on( 'click', ( event ) => { if ( passwordField.disabled ) { passwordField.disabled = false; passwordField.value = ''; - event.currentTarget.innerText = dt.cancel; + event.currentTarget.innerText = __( 'Cancel', 'distributor' ); } else { passwordField.disabled = true; passwordField.value = 'sdfdsfsdfdsfdsfsd'; // filler password - event.currentTarget.innerText = dt.change; + event.currentTarget.innerText = __( 'Change', 'distributor' ); } checkConnections(); @@ -480,7 +561,12 @@ jQuery( rolesAllowed ).on( 'click', '.dt-role-checkbox', ( event ) => { 'administrator' !== event.target.value && 'editor' !== event.target.value ) { - alert( dt.roles_warning ); // eslint-disable-line no-alert + alert( + __( + 'Be careful assigning less trusted roles push privileges as they will inherit the capabilities of the user on the remote site.', + 'distributor' + ) + ); // eslint-disable-line no-alert } } ); diff --git a/assets/js/admin-pull.js b/assets/js/admin-pull.js index cd60cd13a..3de9f9255 100755 --- a/assets/js/admin-pull.js +++ b/assets/js/admin-pull.js @@ -2,8 +2,9 @@ import '../css/admin-pull-table.scss'; import jQuery from 'jquery'; import { addQueryArgs } from '@wordpress/url'; +import { __ } from '@wordpress/i18n'; -const { document, dt } = window; +const { document } = window; const chooseConnection = document.getElementById( 'pull_connections' ); const choosePostType = document.getElementById( 'pull_post_type' ); @@ -13,7 +14,6 @@ const searchBtn = document.getElementById( 'search-submit' ); const form = document.getElementById( 'posts-filter' ); const asDraftCheckboxes = document.querySelectorAll( '[name=dt_as_draft]' ); const pullLinks = document.querySelectorAll( '.distributor_page_pull .pull a' ); -const { pull: pullText, as_draft: pullAsDraftText } = dt; jQuery( chooseConnection ).on( 'change', ( event ) => { document.location = @@ -58,7 +58,7 @@ if ( chooseConnection && choosePostType && form ) { pullLinks[ i ].href = addQueryArgs( pullLinks[ i ].href, { dt_as_draft: 'draft' /*eslint camelcase: 0*/, } ); - pullLinks[ i ].text = pullAsDraftText; + pullLinks[ i ].text = __( 'Pull as draft', 'distributor' ); } } else { for ( let i = 0; i < asDraftCheckboxes.length; ++i ) { @@ -69,7 +69,7 @@ if ( chooseConnection && choosePostType && form ) { pullLinks[ i ].href = addQueryArgs( pullLinks[ i ].href, { dt_as_draft: '' /*eslint camelcase: 0*/, } ); - pullLinks[ i ].text = pullText; + pullLinks[ i ].text = __( 'Pull', 'distributor' ); } } } ); diff --git a/assets/js/push.js b/assets/js/push.js index 4ebedb3fb..eb26e12db 100755 --- a/assets/js/push.js +++ b/assets/js/push.js @@ -3,7 +3,7 @@ import '../css/push.scss'; import jQuery from 'jquery'; import _ from 'underscore'; import Mustache from 'mustache'; -import { sprintf, _n } from '@wordpress/i18n'; +import { __, sprintf, _n } from '@wordpress/i18n'; const { document, dt } = window; let selectedText = ''; @@ -540,7 +540,9 @@ jQuery( window ).on( 'load', () => { } if ( ! response.data || ! response.data.results ) { - doError( dt.messages.empty_result ); + doError( + __( 'Received empty result.', 'distributor' ) + ); return; } @@ -552,7 +554,12 @@ jQuery( window ).on( 'load', () => { setTimeout( () => { distributorTopMenu.classList.remove( 'syncing' ); - doError( `${ dt.messages.ajax_error } ${ errorThrown }` ); + doError( + `${ __( + 'Ajax error:', + 'distributor' + ) } ${ errorThrown }` + ); }, 500 ); } ); } ); diff --git a/bin/generate-pot-with-wpcli.sh b/bin/generate-pot-with-wpcli.sh new file mode 100755 index 000000000..38c0ddf14 --- /dev/null +++ b/bin/generate-pot-with-wpcli.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# This script generates a POT file for a WordPress plugin or theme using WP-CLI. +# We are using wp-scripts to generate plugin zip which bundle files defined in 'package.json' in files param. +# https://developer.wordpress.org/block-editor/packages/packages-scripts/#files +# This script will run with npm run makepot command. +# +# @since x.x.x + +if ! command -v wp &>/dev/null; then + echo "Error: wp cli could not be found. Please install wp cli and try again." + exit +fi + +# Get the list of files and directories which bundle in final zip file. +FILES=$(node -p "require('./package.json').files.filter( directory_regex => -1 === directory_regex.indexOf('lang/') ).join(',')") + +# Run the WP-CLI command. +# This command will generate a POT file in the root directory of the plugin and store it in lang directory. +wp i18n make-pot . --include="$FILES" + +echo '.pot file updated' diff --git a/includes/bootstrap.php b/includes/bootstrap.php index cb6608a42..1accb41a2 100644 --- a/includes/bootstrap.php +++ b/includes/bootstrap.php @@ -35,61 +35,6 @@ function( $class ) { } ); -/** - * Generate translation hashes based on the source directory. - * - * The translations hashes generated by WordPress when registering a script use - * a hash of the file name relative to the plugin's root directory. For file hashes - * generated based on the source file, this hash is incorrect. - * - * This converts the translation path's file name to use a hash based on the original - * file path. - * - * @since x.x.x - * - * @param string|false $translation_file Path to the translation file to load. False if there isn't one. - * @param string $script_handle Name of the script to register a translation domain to. - * @param string $domain The text domain. - * @return string|false The modified translation file to load. - */ -add_filter( - 'load_script_translation_file', - function( $translation_file, $script_handle, $domain ) { - if ( - ! $translation_file || - 'distributor' !== $domain - || ! str_ends_with( $translation_file, '.json' ) - || file_exists( $translation_file ) - ) { - return $translation_file; - } - - $src_script_file_name = preg_replace( '/^(dt|distributor)-/i', '', $script_handle ); - $src_script_file_name = "assets/js/{$src_script_file_name}.js"; - - $i18n_hash = md5( $src_script_file_name ); - - // Determine the path to the translation file. - if ( str_contains( $translation_file, "-{$script_handle}.json" ) ) { - $real_file = preg_replace( - '/-' . preg_quote( $script_handle, '/' ) . '.json$/', - "-{$i18n_hash}.json", - $translation_file - ); - } else { - $real_file = preg_replace( - '/-[0-9a-f]{32}.json$/', - "-{$i18n_hash}.json", - $translation_file - ); - } - - return $real_file; - }, - 10, - 4 -); - /** * Tell the world this site supports Distributor. We need this for external connections. */ @@ -261,6 +206,7 @@ function() { ) { \Distributor\Connections::factory()->register( '\Distributor\InternalConnections\NetworkSiteConnection' ); } + load_plugin_textdomain( 'distributor', false, basename( dirname( DT_PLUGIN_FULL_FILE ) ) . '/lang' ); }, 1 ); diff --git a/includes/classes/EnqueueScript.php b/includes/classes/EnqueueScript.php new file mode 100644 index 000000000..e381bcce4 --- /dev/null +++ b/includes/classes/EnqueueScript.php @@ -0,0 +1,294 @@ +plugin_dir_path = DT_PLUGIN_PATH; + $this->plugin_dir_url = trailingslashit( plugin_dir_url( DT_PLUGIN_FULL_FILE ) ); + $this->text_domain = 'distributor'; + $this->script_handle = $script_handle; + $this->relative_script_path = 'dist/js/' . $script_file_name . '.js'; + $this->absolute_script_path = $this->plugin_dir_path . $this->relative_script_path; + } + + /** + * Flag to decide whether load script in footer. + * + * @since x.x.x + */ + public function load_in_footer(): EnqueueScript { + $this->load_script_in_footer = true; + + return $this; + } + + /** + * Set script dependencies. + * + * @since x.x.x + * + * @param array $script_dependencies Script dependencies. + */ + public function dependencies( array $script_dependencies ): EnqueueScript { + $this->script_dependencies = $script_dependencies; + + return $this; + } + + /** + * Register script. + * + * @since x.x.x + */ + public function register(): EnqueueScript { + $script_url = $this->plugin_dir_url . $this->relative_script_path; + $script_asset = $this->get_asset_file_data(); + + $this->version = $script_asset['version']; + + wp_register_script( + $this->script_handle, + $script_url, + $script_asset['dependencies'], + $script_asset['version'], + $this->load_script_in_footer + ); + + if ( $this->register_translations ) { + wp_set_script_translations( + $this->script_handle, + $this->text_domain, + $this->plugin_dir_path . 'lang' + ); + } + + if ( $this->localize_script_param_data ) { + wp_localize_script( + $this->script_handle, + $this->localize_script_param_name, + $this->localize_script_param_data + ); + } + + return $this; + } + + /** + * This function should be called before enqueue or register method. + * + * @since x.x.x + */ + public function register_translations(): EnqueueScript { + $this->register_translations = true; + + return $this; + } + + /** + * This function should be called after enqueue or register method. + * + * @since x.x.x + * + * @param string $js_variable_name JS variable name. + * @param array $data Data to be localized. + */ + public function register_localize_data( string $js_variable_name, array $data ): EnqueueScript { + $this->localize_script_param_name = $js_variable_name; + $this->localize_script_param_data = $data; + + return $this; + } + + /** + * Enqueue script. + * + * @since x.x.x + */ + public function enqueue(): EnqueueScript { + if ( ! wp_script_is( $this->script_handle, 'registered' ) ) { + $this->register(); + } + wp_enqueue_script( $this->script_handle ); + + return $this; + } + + /** + * Should return script handle. + * + * @since x.x.x + * + * @return string + */ + public function get_script_handle(): string { + return $this->script_handle; + } + + /** + * Get asset file data. + * + * @since x.x.x + * + * @return array + */ + public function get_asset_file_data(): array { + $script_asset_path = trailingslashit( dirname( $this->absolute_script_path ) ) + . basename( $this->absolute_script_path, '.js' ) + . '.asset.php'; + + if ( file_exists( $script_asset_path ) ) { + $script_asset = require $script_asset_path; + } else { + $script_asset = [ + 'dependencies' => [], + 'version' => $this->version, + ]; + } + + if ( $this->script_dependencies ) { + $script_asset['dependencies'] = array_merge( $this->script_dependencies, $script_asset['dependencies'] ); + } + + return $script_asset; + } + + /** + * Should return script version. + * + * @since x.x.x + * + * @return string + */ + public function get_version(): string { + $script_asset = $this->get_asset_file_data(); + + return $script_asset['version']; + } +} diff --git a/includes/distributed-post-ui.php b/includes/distributed-post-ui.php index 94dad5476..6711399e1 100644 --- a/includes/distributed-post-ui.php +++ b/includes/distributed-post-ui.php @@ -7,6 +7,8 @@ namespace Distributor\DistributedPostUI; +use Distributor\EnqueueScript; + /** * Setup actions and filters * @@ -127,16 +129,16 @@ function enqueue_post_scripts_styles( $hook ) { return; } - $asset_file = DT_PLUGIN_PATH . '/dist/js/admin-distributed-post.min.asset.php'; - // Fallback asset data. - $asset_data = array( - 'version' => DT_VERSION, - 'dependencies' => array(), + $admin_distributed_post_script = new EnqueueScript( + 'dt-admin-distributed-post', + 'admin-distributed-post.min' ); - if ( file_exists( $asset_file ) ) { - $asset_data = require $asset_file; - } + $admin_distributed_post_script->load_in_footer()->enqueue(); - wp_enqueue_style( 'dt-admin-distributed-post', plugins_url( '/dist/css/admin-distributed-post.min.css', __DIR__ ), array(), $asset_data['version'] ); - wp_enqueue_script( 'dt-admin-distributed-post', plugins_url( '/dist/js/admin-distributed-post.min.js', __DIR__ ), $asset_data['dependencies'], $asset_data['version'], true ); + wp_enqueue_style( + 'dt-admin-distributed-post', + plugins_url( '/dist/css/admin-distributed-post.min.css', __DIR__ ), + array(), + $admin_distributed_post_script->get_version() + ); } diff --git a/includes/external-connection-cpt.php b/includes/external-connection-cpt.php index 59ef70d55..937c3a932 100644 --- a/includes/external-connection-cpt.php +++ b/includes/external-connection-cpt.php @@ -7,6 +7,7 @@ namespace Distributor\ExternalConnectionCPT; +use Distributor\EnqueueScript; use Distributor\Utils; /** @@ -271,68 +272,51 @@ function ajax_verify_external_connection() { */ function admin_enqueue_scripts( $hook ) { if ( ( 'post.php' === $hook && 'dt_ext_connection' === get_post_type() ) || ( 'post-new.php' === $hook && ! empty( $_GET['post_type'] ) && 'dt_ext_connection' === $_GET['post_type'] ) ) { // @codingStandardsIgnoreLine Nonce not required. - - $asset_file = DT_PLUGIN_PATH . '/dist/js/admin-external-connection.min.asset.php'; - // Fallback asset data. - $asset_data = array( - 'version' => DT_VERSION, - 'dependencies' => array(), + $admin_external_connect_script = new EnqueueScript( + 'dt-admin-external-connection', + 'admin-external-connection.min' ); - if ( file_exists( $asset_file ) ) { - $asset_data = require $asset_file; - } - wp_enqueue_style( 'dt-admin-external-connection', plugins_url( '/dist/css/admin-external-connection.min.css', __DIR__ ), array(), $asset_data['version'] ); - wp_enqueue_script( 'dt-admin-external-connection', plugins_url( '/dist/js/admin-external-connection.min.js', __DIR__ ), $asset_data['dependencies'], $asset_data['version'], true ); + wp_enqueue_style( + 'dt-admin-external-connection', + plugins_url( '/dist/css/admin-external-connection.min.css', __DIR__ ), + array(), + $admin_external_connect_script->get_version() + ); $blog_name = get_bloginfo( 'name ' ); $wizard_return = get_wizard_return_data(); - wp_localize_script( - 'dt-admin-external-connection', + $admin_external_connect_script->register_localize_data( 'dt', array( - 'nonce' => wp_create_nonce( 'dt-verify-ext-conn' ), - 'bad_connection' => esc_html__( 'No connection found.', 'distributor' ), - 'good_connection' => esc_html__( 'Connection established.', 'distributor' ), - 'limited_connection' => esc_html__( 'Limited connection established.', 'distributor' ), - 'no_push' => esc_html__( 'Push distribution unavailable.', 'distributor' ), - 'no_permissions' => esc_html__( 'Authentication succeeded but your account does not have permissions to create posts on the external site.', 'distributor' ), - 'pull_limited' => esc_html__( 'Pull distribution limited to basic content, i.e. title and content body.', 'distributor' ), - 'endpoint_suggestion' => esc_html__( 'Did you mean: ', 'distributor' ), - 'endpoint_checking_message' => esc_html__( 'Checking endpoint...', 'distributor' ), - 'bad_auth' => esc_html__( 'Authentication failed due to invalid credentials.', 'distributor' ), - 'change' => esc_html__( 'Change', 'distributor' ), - 'cancel' => esc_html__( 'Cancel', 'distributor' ), - 'invalid_url' => esc_html__( 'Please enter a valid URL, including the HTTP(S).', 'distributor' ), - 'norest' => esc_html__( 'No REST API endpoint was located for this site.', 'distributor' ), - 'noconnection' => esc_html__( 'Unable to connect to site.', 'distributor' ), - 'minversion' => esc_html__( 'Remote site requires Distributor version 1.6.0 or greater. Upgrade Distributor on the remote site to use the Authentication Wizard.', 'distributor' ), - 'no_distributor' => esc_html__( 'Distributor not installed on remote site.', 'distributor' ), - 'roles_warning' => esc_html__( 'Be careful assigning less trusted roles push privileges as they will inherit the capabilities of the user on the remote site.', 'distributor' ), - 'admin_url' => admin_url(), - /* translators: %1$s: site name, %2$s: site URL */ - 'distributor_from' => sprintf( esc_html__( 'Distributor on %1$s (%2$s)', 'distributor' ), $blog_name, esc_url( home_url() ) ), - 'wizard_return' => $wizard_return, - 'application_passwords_not_available' => __( 'Application Passwords is not available on the remote site. Please set up connection manually!', 'distributor' ), + 'nonce' => wp_create_nonce( 'dt-verify-ext-conn' ), + 'blog_name' => $blog_name, + 'home_url' => esc_url( home_url() ), + 'admin_url' => admin_url(), + 'wizard_return' => $wizard_return, ) ); + $admin_external_connect_script->load_in_footer() + ->register_translations() + ->enqueue(); + wp_dequeue_script( 'autosave' ); } if ( ! empty( $_GET['page'] ) && 'distributor' === $_GET['page'] ) { // @codingStandardsIgnoreLine Nonce not required - $asset_file = DT_PLUGIN_PATH . '/dist/js/admin-external-connections-css.min.asset.php'; - // Fallback asset data. - $asset_data = array( - 'version' => DT_VERSION, - 'dependencies' => array(), + $admin_external_connect_script = new EnqueueScript( + 'dt-admin-external-connection', + 'admin-external-connection.min' ); - if ( file_exists( $asset_file ) ) { - $asset_data = require $asset_file; - } - wp_enqueue_style( 'dt-admin-external-connections', plugins_url( '/dist/css/admin-external-connections.min.css', __DIR__ ), array(), $asset_data['version'] ); + wp_enqueue_style( + 'dt-admin-external-connections', + plugins_url( '/dist/css/admin-external-connections.min.css', __DIR__ ), + array(), + $admin_external_connect_script->get_version() + ); } } diff --git a/includes/pull-ui.php b/includes/pull-ui.php index cb5c4048a..14226b813 100644 --- a/includes/pull-ui.php +++ b/includes/pull-ui.php @@ -8,6 +8,7 @@ namespace Distributor\PullUI; //phpcs:ignoreFile WordPressVIPMinimum.Functions.RestrictedFunctions.cookies_setcookie -- Admin file, no full page caching. +use Distributor\EnqueueScript; /** * Setup actions and filters @@ -120,26 +121,16 @@ function admin_enqueue_scripts( $hook ) { return; } - $asset_file = DT_PLUGIN_PATH . '/dist/js/admin-pull.min.asset.php'; - // Fallback asset data. - $asset_data = array( - 'version' => DT_VERSION, - 'dependencies' => array(), - ); - if ( file_exists( $asset_file ) ) { - $asset_data = require $asset_file; - } - - wp_enqueue_script( 'dt-admin-pull', plugins_url( '/dist/js/admin-pull.min.js', __DIR__ ), $asset_data['dependencies'], $asset_data['version'], true ); - wp_enqueue_style( 'dt-admin-pull', plugins_url( '/dist/css/admin-pull-table.min.css', __DIR__ ), array(), $asset_data['version'] ); + $admin_pull_script = new EnqueueScript( 'admin-pull', 'admin-pull.min' ); + $admin_pull_script->load_in_footer() + ->register_translations() + ->enqueue(); - wp_localize_script( + wp_enqueue_style( 'dt-admin-pull', - 'dt', - [ - 'pull' => __( 'Pull', 'distributor' ), - 'as_draft' => __( 'Pull as draft', 'distributor' ), - ] + plugins_url( '/dist/css/admin-pull-table.min.css', __DIR__ ), + array(), + $admin_pull_script->get_version() ); } diff --git a/includes/push-ui.php b/includes/push-ui.php index 89c8d7e8b..8dab2ee06 100644 --- a/includes/push-ui.php +++ b/includes/push-ui.php @@ -7,6 +7,8 @@ namespace Distributor\PushUI; +use Distributor\EnqueueScript; + /** * Setup actions and filters * @@ -404,48 +406,42 @@ function enqueue_scripts( $hook ) { return; } - $asset_file = DT_PLUGIN_PATH . '/dist/js/push.min.asset.php'; - // Fallback asset data. - $asset_data = array( - 'version' => DT_VERSION, - 'dependencies' => array(), + $push_script = new EnqueueScript( 'dt-push', 'push.min' ); + $localize_data = array( + 'nonce' => wp_create_nonce( 'dt-push' ), + 'loadConnectionsNonce' => wp_create_nonce( 'dt-load-connections' ), + 'postId' => (int) get_the_ID(), + 'postTitle' => get_the_title(), + 'ajaxurl' => esc_url( admin_url( 'admin-ajax.php' ) ), + + /** + * Filter whether front end ajax requests should use xhrFields credentials:true. + * + * Front end ajax requests may require xhrFields with credentials when the front end and + * back end domains do not match. This filter lets themes opt in. + * See {@link https://vip.wordpress.com/documentation/handling-frontend-file-uploads/#handling-ajax-requests} + * + * @since 1.0.0 + * @hook dt_ajax_requires_with_credentials + * + * @param {bool} false Whether front end ajax requests should use xhrFields credentials:true. + * + * @return {bool} Whether front end ajax requests should use xhrFields credentials:true. + */ + 'usexhr' => apply_filters( 'dt_ajax_requires_with_credentials', false ), ); - if ( file_exists( $asset_file ) ) { - $asset_data = require $asset_file; - } - wp_enqueue_style( 'dt-push', plugins_url( '/dist/css/push.min.css', __DIR__ ), array(), $asset_data['version'] ); - wp_enqueue_script( 'dt-push', plugins_url( '/dist/js/push.min.js', __DIR__ ), $asset_data['dependencies'], $asset_data['version'], true ); - wp_localize_script( - 'dt-push', - 'dt', - array( - 'nonce' => wp_create_nonce( 'dt-push' ), - 'loadConnectionsNonce' => wp_create_nonce( 'dt-load-connections' ), - 'postId' => (int) get_the_ID(), - 'postTitle' => get_the_title(), - 'ajaxurl' => esc_url( admin_url( 'admin-ajax.php' ) ), - 'messages' => array( - 'ajax_error' => __( 'Ajax error:', 'distributor' ), - 'empty_result' => __( 'Received empty result.', 'distributor' ), - ), + $push_script + ->load_in_footer() + ->register_localize_data( 'dt', $localize_data ) + ->register_translations() + ->enqueue(); - /** - * Filter whether front end ajax requests should use xhrFields credentials:true. - * - * Front end ajax requests may require xhrFields with credentials when the front end and - * back end domains do not match. This filter lets themes opt in. - * See {@link https://vip.wordpress.com/documentation/handling-frontend-file-uploads/#handling-ajax-requests} - * - * @since 1.0.0 - * @hook dt_ajax_requires_with_credentials - * - * @param {bool} false Whether front end ajax requests should use xhrFields credentials:true. - * - * @return {bool} Whether front end ajax requests should use xhrFields credentials:true. - */ - 'usexhr' => apply_filters( 'dt_ajax_requires_with_credentials', false ), - ) + wp_enqueue_style( + 'dt-push', + plugins_url( '/dist/css/push.min.css', __DIR__ ), + array(), + $push_script->get_version() ); } diff --git a/includes/syndicated-post-ui.php b/includes/syndicated-post-ui.php index 1f5e2e505..43c8d66e5 100644 --- a/includes/syndicated-post-ui.php +++ b/includes/syndicated-post-ui.php @@ -7,6 +7,7 @@ namespace Distributor\SyndicatedPostUI; +use Distributor\EnqueueScript; use Distributor\Utils; /** @@ -633,57 +634,48 @@ function enqueue_gutenberg_edit_scripts() { $original_location_name = $original_site_name; } - $post_type_singular = $post_type_object->labels->singular_name; - - $asset_file = DT_PLUGIN_PATH . '/dist/js/gutenberg-syndicated-post.min.asset.php'; - // Fallback asset data. - $asset_data = array( - 'version' => DT_VERSION, - 'dependencies' => array(), + $post_type_singular = $post_type_object->labels->singular_name; + $gutenberg_syndicated_post_script = new EnqueueScript( + 'dt-gutenberg-syndicated-post', + 'gutenberg-syndicated-post.min' ); - if ( file_exists( $asset_file ) ) { - $asset_data = require $asset_file; - } - $asset_data['dependencies'][] = 'dt-push'; - wp_enqueue_script( 'dt-gutenberg-syndicated-post', plugins_url( '/dist/js/gutenberg-syndicated-post.min.js', __DIR__ ), $asset_data['dependencies'], $asset_data['version'], true ); - wp_set_script_translations( 'dt-gutenberg-syndicated-post', 'distributor', DT_PLUGIN_PATH . 'lang' ); - - $asset_file = DT_PLUGIN_PATH . '/dist/js/gutenberg-plugin.min.asset.php'; - // Fallback asset data. - $asset_data = array( - 'version' => DT_VERSION, - 'dependencies' => array(), + $localize_data = [ + 'originalBlogId' => (int) $original_blog_id, + 'originalPostId' => (int) $original_post_id, + 'originalSourceId' => (int) $original_source_id, + 'originalDelete' => (int) $original_deleted, + 'unlinked' => (int) $unlinked, + 'postTypeSingular' => sanitize_text_field( $post_type_singular ), + 'postUrl' => sanitize_text_field( $post_url ), + 'originalSiteName' => sanitize_text_field( $original_site_name ), + 'syndicationTime' => ( ! empty( $syndication_time ) ) ? esc_html( gmdate( 'M j, Y @ h:i', ( $syndication_time + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ) ) ) : 0, + 'syndicationCount' => $total_connections, + 'originalLocationName' => sanitize_text_field( $original_location_name ), + 'unlinkNonceUrl' => wp_nonce_url( add_query_arg( 'action', 'unlink', admin_url( sprintf( $post_type_object->_edit_link, $post->ID ) ) ), "unlink-post_{$post->ID}" ), + 'linkNonceUrl' => wp_nonce_url( add_query_arg( 'action', 'link', admin_url( sprintf( $post_type_object->_edit_link, $post->ID ) ) ), "link-post_{$post->ID}" ), + 'supportedPostTypes' => \Distributor\Utils\distributable_post_types(), + 'supportedPostStati' => \Distributor\Utils\distributable_post_statuses(), + // Filter documented in includes/push-ui.php. + 'noPermissions' => ! is_user_logged_in() || ! current_user_can( apply_filters( 'dt_syndicatable_capabilities', 'edit_posts' ) ), + ]; + + $gutenberg_syndicated_post_script + ->load_in_footer() + ->register_translations() + ->register_localize_data( 'dtGutenberg', $localize_data ) + ->enqueue(); + + $gutenberg_plugin_script = new EnqueueScript( + 'dt-gutenberg-plugin', + 'gutenberg-plugin.min' ); - if ( file_exists( $asset_file ) ) { - $asset_data = require $asset_file; - } - wp_enqueue_script( 'dt-gutenberg-plugin', plugins_url( '/dist/js/gutenberg-plugin.min.js', __DIR__ ), $asset_data['dependencies'], $asset_data['version'], true ); - wp_set_script_translations( 'dt-gutenberg-plugin', 'distributor', DT_PLUGIN_PATH . 'lang' ); - wp_localize_script( - 'dt-gutenberg-syndicated-post', - 'dtGutenberg', - [ - 'originalBlogId' => (int) $original_blog_id, - 'originalPostId' => (int) $original_post_id, - 'originalSourceId' => (int) $original_source_id, - 'originalDelete' => (int) $original_deleted, - 'unlinked' => (int) $unlinked, - 'postTypeSingular' => sanitize_text_field( $post_type_singular ), - 'postUrl' => sanitize_text_field( $post_url ), - 'originalSiteName' => sanitize_text_field( $original_site_name ), - 'syndicationTime' => ( ! empty( $syndication_time ) ) ? esc_html( gmdate( 'M j, Y @ h:i', ( $syndication_time + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ) ) ) : 0, - 'syndicationCount' => $total_connections, - 'originalLocationName' => sanitize_text_field( $original_location_name ), - 'unlinkNonceUrl' => wp_nonce_url( add_query_arg( 'action', 'unlink', admin_url( sprintf( $post_type_object->_edit_link, $post->ID ) ) ), "unlink-post_{$post->ID}" ), - 'linkNonceUrl' => wp_nonce_url( add_query_arg( 'action', 'link', admin_url( sprintf( $post_type_object->_edit_link, $post->ID ) ) ), "link-post_{$post->ID}" ), - 'supportedPostTypes' => \Distributor\Utils\distributable_post_types(), - 'supportedPostStati' => \Distributor\Utils\distributable_post_statuses(), - // Filter documented in includes/push-ui.php. - 'noPermissions' => ! is_user_logged_in() || ! current_user_can( apply_filters( 'dt_syndicatable_capabilities', 'edit_posts' ) ), - ] - ); + $gutenberg_plugin_script + ->load_in_footer() + ->dependencies( array( 'dt-push' ) ) + ->register_translations() + ->enqueue(); } /** diff --git a/package.json b/package.json index eb17ff335..7c2a40e36 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "lint:css": "wp-scripts lint-style", "lint:js": "wp-scripts lint-js", "lint:pkg-json": "wp-scripts lint-pkg-json", - "makepot": "wpi18n makepot && echo '.pot file updated'", + "makepot": "./bin/generate-pot-with-wpcli.sh", "packages-update": "wp-scripts packages-update", "plugin-zip": "wp-scripts plugin-zip", "start": "wp-scripts start", diff --git a/phpcs.xml b/phpcs.xml index be2cf863d..3288dc58d 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,9 +1,7 @@ - - - - + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 40cd51ce2..f4dd771df 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,5 +1,5 @@ diff --git a/tests/php/EnqueueScriptTest.php b/tests/php/EnqueueScriptTest.php new file mode 100644 index 000000000..3a0c117af --- /dev/null +++ b/tests/php/EnqueueScriptTest.php @@ -0,0 +1,41 @@ +markTestIncomplete(); + } + + public function test_script_enqueue(): void { + $this->markTestIncomplete(); + } + + public function test_custom_version(): void { + $this->markTestIncomplete(); + } + + public function test_custom_dependencies(): void { + $this->markTestIncomplete(); + } + + public function test_translations(): void { + $this->markTestIncomplete(); + } + + public function test_localize_data(): void { + $this->markTestIncomplete(); + } + + public function testload_script_in_footer(): void { + $this->markTestIncomplete(); + } +} + +class EnqueueScriptMock extends EnqueueScript { +} diff --git a/tests/php/bootstrap.php.dist b/tests/php/bootstrap.php similarity index 88% rename from tests/php/bootstrap.php.dist rename to tests/php/bootstrap.php index 371de0f5f..16602dd84 100644 --- a/tests/php/bootstrap.php.dist +++ b/tests/php/bootstrap.php @@ -9,12 +9,14 @@ WP_Mock::setUsePatchwork( true ); WP_Mock::bootstrap(); +define( 'DT_PLUGIN_PATH', dirname( __DIR__, 2 ) ); + require_once __DIR__ . '/includes/common.php'; +require_once __DIR__ . '/includes/TestCase.php'; +// Load plugin files. require_once( __DIR__ . '/../../includes/hooks.php' ); require_once( __DIR__ . '/../../includes/utils.php' ); require_once( __DIR__ . '/../../includes/debug-info.php' ); require_once( __DIR__ . '/../../includes/subscriptions.php' ); require_once( __DIR__ . '/../../includes/global-functions.php' ); - -require_once __DIR__ . '/includes/TestCase.php'; diff --git a/webpack.config.release.js b/webpack.config.release.js index 522217029..98edf3b98 100644 --- a/webpack.config.release.js +++ b/webpack.config.release.js @@ -21,10 +21,6 @@ module.exports = { { from: 'includes/**/*', to: './' }, { from: 'lang/**/*', to: './' }, { from: 'templates/**/*', to: './' }, - { - from: 'vendor/georgestephanis/application-passwords/**/*', - to: './', - }, { from: 'vendor/yahnis-elsts/plugin-update-checker/**/*', to: './',