diff --git a/.github/files/generate-ci-matrix.php b/.github/files/generate-ci-matrix.php index 08cfa32b3b6bf..ce67ff260078d 100755 --- a/.github/files/generate-ci-matrix.php +++ b/.github/files/generate-ci-matrix.php @@ -89,6 +89,11 @@ 'with-woocommerce' => true, ); +/** + * Here is the place where wpcomsh tests would be introduced by adding 'with-wpcomsh' property set to true. + * This adds a new run into the matrix that would enable wpcomsh loading with unit tests. + */ + // Add JS tests. $matrix[] = array( 'name' => 'JS tests', diff --git a/.github/files/setup-wordpress-env.sh b/.github/files/setup-wordpress-env.sh index 480eec5565827..1b233f1095790 100755 --- a/.github/files/setup-wordpress-env.sh +++ b/.github/files/setup-wordpress-env.sh @@ -166,6 +166,16 @@ if [[ "$WITH_WOOCOMMERCE" == true ]]; then echo "::endgroup::" fi +# Install the wpcomsh plugin used for some Jetpack integration tests. +if [[ "$WITH_WPCOMSH" == true ]]; then + echo "::group::Installing wpcomsh into WordPress" + + mkdir "/tmp/wordpress-$WP_BRANCH/src/wp-content/mu-plugins" + cp -r "/tmp/wordpress-$WP_BRANCH/src/wp-content/plugins/wpcomsh" "/tmp/wordpress-$WP_BRANCH/src/wp-content/mu-plugins/wpcomsh" + + echo "::endgroup::" +fi + cd "/tmp/wordpress-$WP_BRANCH" cp wp-tests-config-sample.php wp-tests-config.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e80e158c70d09..78082fc0f54dc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -46,6 +46,7 @@ jobs: NODE_VERSION: ${{ matrix.node }} MONOREPO_BASE: ${{ github.workspace }} WITH_WOOCOMMERCE: ${{ matrix.with-woocommerce }} + WITH_WPCOMSH: ${{ matrix.with-wpcomsh }} strategy: fail-fast: false matrix: diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20c9f3fe57c68..6f691e7bb267b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4422,6 +4422,30 @@ importers: specifier: workspace:* version: link:../../../../../tools/e2e-commons + projects/plugins/wpcomsh: + devDependencies: + '@babel/core': + specifier: ^7.5.5 + version: 7.24.7 + '@babel/preset-env': + specifier: ^7.5.5 + version: 7.24.7(@babel/core@7.24.7) + '@wordpress/babel-preset-default': + specifier: ^8.0.0 + version: 8.0.0 + babel-jest: + specifier: ^29.7.0 + version: 29.7.0(@babel/core@7.24.7) + jest: + specifier: ^29.7.0 + version: 29.7.0 + lodash: + specifier: ^4.17.15 + version: 4.17.21 + node-fetch: + specifier: ^2.6.0 + version: 2.6.7 + tools/cli: dependencies: '@octokit/auth-token': diff --git a/projects/plugins/jetpack/.phan/config.php b/projects/plugins/jetpack/.phan/config.php index 524d514184337..8c1e345705f82 100644 --- a/projects/plugins/jetpack/.phan/config.php +++ b/projects/plugins/jetpack/.phan/config.php @@ -39,6 +39,7 @@ __DIR__ . '/../../../plugins/crm/includes/ZeroBSCRM.Core.Extensions.php', // functions zeroBSCRM_isExtensionInstalled, zeroBSCRM_extension_install_jetpackforms // Make an exception to the above for packages/jetpack-mu-wpcom. Pulling in that whole package here seems more risky than beneficial. + __DIR__ . '/../../../packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php', // class Jetpack_Mu_Wpcom __DIR__ . '/../../../packages/jetpack-mu-wpcom/src/features/launchpad/launchpad.php', // function wpcom_launchpad_is_fse_next_steps_modal_hidden ), ) diff --git a/projects/plugins/jetpack/changelog/try-wpcomsh-skeleton-ci b/projects/plugins/jetpack/changelog/try-wpcomsh-skeleton-ci new file mode 100644 index 0000000000000..bfa76cda3e850 --- /dev/null +++ b/projects/plugins/jetpack/changelog/try-wpcomsh-skeleton-ci @@ -0,0 +1,4 @@ +Significance: minor +Type: other + +Added ability to test Jetpack together with wpcomsh. diff --git a/projects/plugins/jetpack/tests/action-test-php.sh b/projects/plugins/jetpack/tests/action-test-php.sh index 1c67dbe5573a7..eb82fd51d98cd 100755 --- a/projects/plugins/jetpack/tests/action-test-php.sh +++ b/projects/plugins/jetpack/tests/action-test-php.sh @@ -11,6 +11,10 @@ if [[ "$WITH_WOOCOMMERCE" == true ]]; then exit 0 fi +if [[ "$WITH_WPCOMSH" == true ]]; then + export JETPACK_TEST_WPCOMSH=1 +fi + echo "::group::Jetpack tests" phpunit echo "::endgroup::" diff --git a/projects/plugins/jetpack/tests/php/bootstrap.php b/projects/plugins/jetpack/tests/php/bootstrap.php index fefc82355bad5..e684daa65fa90 100644 --- a/projects/plugins/jetpack/tests/php/bootstrap.php +++ b/projects/plugins/jetpack/tests/php/bootstrap.php @@ -87,6 +87,11 @@ echo "Disregard Core's -c tests/phpunit/multisite.xml notice below." . PHP_EOL; } +if ( '1' !== getenv( 'JETPACK_TEST_WPCOMSH' ) ) { + echo 'To run tests with the WordPress.com Site Helper plugin activated and Atomic mode enabled,' . PHP_EOL; + echo 'prefix phpunit with JETPACK_TEST_WPCOMSH=1' . PHP_EOL; +} + if ( '1' !== getenv( 'JETPACK_TEST_WOOCOMMERCE' ) ) { echo 'To run Jetpack woocommerce tests, prefix phpunit with JETPACK_TEST_WOOCOMMERCE=1' . PHP_EOL; } else { @@ -101,6 +106,7 @@ function _manually_load_plugin() { if ( '1' === getenv( 'JETPACK_TEST_WOOCOMMERCE' ) ) { require JETPACK_WOOCOMMERCE_INSTALL_DIR . '/woocommerce.php'; } + require __DIR__ . '/../../jetpack.php'; $jetpack = Jetpack::init(); $jetpack->configure(); @@ -120,9 +126,36 @@ function _manually_install_woocommerce() { echo 'Installing WooCommerce...' . PHP_EOL; } +/** + * Loading required mu-wpcom plugin files to be able to test with all required code. + */ +function _manually_load_muplugin() { + if ( getenv( 'GITHUB_ACTIONS' ) ) { + + // Using plugin code installed by .github/files/setup-wordpress-env.sh. + require_once __DIR__ . '/../../../../mu-plugins/wpcomsh/wpcomsh.php'; + require_once __DIR__ . '/../../../../mu-plugins/wpcomsh/vendor/autoload.php'; + } else { + require_once __DIR__ . '/../../../wpcomsh/wpcomsh.php'; + require_once __DIR__ . '/../../../wpcomsh/vendor/autoload.php'; + } + \Automattic\Jetpack\Jetpack_Mu_Wpcom::init(); + + defined( 'WPCOMSH_PREMIUM_THEMES_PATH' ) || define( 'WPCOMSH_PREMIUM_THEMES_PATH', sys_get_temp_dir() . '/premium' ); + if ( ! is_dir( WPCOMSH_PREMIUM_THEMES_PATH ) ) { + mkdir( WPCOMSH_PREMIUM_THEMES_PATH, 0777 ); + } +} + // If we are running the uninstall tests don't load jetpack. if ( ! ( in_running_uninstall_group() ) ) { tests_add_filter( 'plugins_loaded', '_manually_load_plugin', 1 ); + + if ( '1' === getenv( 'JETPACK_TEST_WPCOMSH' ) ) { + define( 'IS_ATOMIC', true ); + tests_add_filter( 'muplugins_loaded', '_manually_load_muplugin' ); + } + if ( '1' === getenv( 'JETPACK_TEST_WOOCOMMERCE' ) ) { tests_add_filter( 'setup_theme', '_manually_install_woocommerce' ); } diff --git a/projects/plugins/jetpack/tests/php/lib/class-wpcom-features.php b/projects/plugins/jetpack/tests/php/lib/class-wpcom-features.php index b602c12962252..b0a5b81f64229 100644 --- a/projects/plugins/jetpack/tests/php/lib/class-wpcom-features.php +++ b/projects/plugins/jetpack/tests/php/lib/class-wpcom-features.php @@ -5,6 +5,10 @@ * @package Jetpack */ +if ( class_exists( 'WPCOM_Features' ) ) { + return; +} + /** * Class WPCOM_Features. */ diff --git a/projects/plugins/jetpack/tests/php/lib/mock-functions.php b/projects/plugins/jetpack/tests/php/lib/mock-functions.php index 46988da979655..0d4ba3031df1f 100644 --- a/projects/plugins/jetpack/tests/php/lib/mock-functions.php +++ b/projects/plugins/jetpack/tests/php/lib/mock-functions.php @@ -21,7 +21,7 @@ function has_blog_sticker( $sticker ) { function wp_cache_is_enabled() {} } -if ( ! function_exists( 'wpcom_site_has_feature' ) ) { +if ( ! function_exists( 'wpcom_site_has_feature' ) && false === getenv( 'JETPACK_TEST_WPCOMSH' ) ) { /** * Mock feature support. * diff --git a/projects/plugins/wpcomsh/.circleci/config.yml b/projects/plugins/wpcomsh/.circleci/config.yml new file mode 100644 index 0000000000000..b7491ffc94649 --- /dev/null +++ b/projects/plugins/wpcomsh/.circleci/config.yml @@ -0,0 +1,95 @@ +workflows: + version: 2 + main: + jobs: + - php74-build + - php80-build + - php81-build + - public-access + - private-access + +version: 2 + +job-references: + mariadb_image: &mariadb_image + cimg/mariadb:10.11 + + setup_environment: &setup_environment + name: "Setup Environment Variables" + command: | + echo "export PATH=$HOME/.composer/vendor/bin:$PATH" >> $BASH_ENV + source /home/circleci/.bashrc + + install_dependencies: &install_dependencies + name: "Install Dependencies" + command: | + sudo apt-get update && sudo apt-get install subversion mariadb-client rsync + + php_job: &php_job + environment: + - WP_TESTS_DIR: "/tmp/wordpress-tests-lib" + - WP_CORE_DIR: "/tmp/wordpress/" + steps: + - checkout + - run: *setup_environment + - run: *install_dependencies + - run: + name: "Run Tests" + command: | + composer global require "phpunit/phpunit=^9.6" --ignore-platform-reqs --dev + composer global require "yoast/phpunit-polyfills" --ignore-platform-reqs --dev + rm -rf $WP_TESTS_DIR $WP_CORE_DIR + bash bin/install-wp-tests.sh wordpress_test root '' 127.0.0.1 latest + echo "Building wpcomsh" + make build + echo "php -l the build/" + find build/ -name "*.php" | xargs -I X php -l X + echo "Copying build to mu-plugins" + mkdir -p ${WP_CORE_DIR}wp-content/mu-plugins/ + cp -r ./build/wpcomsh ${WP_CORE_DIR}wp-content/mu-plugins/ + echo "Linking the wpcomsh-loader.php file into mu-plugins"; + ln -s ${WP_CORE_DIR}wp-content/mu-plugins/wpcomsh/wpcomsh-loader.php ${WP_CORE_DIR}wp-content/mu-plugins/wpcomsh-loader.php + mysql -uroot --protocol=tcp -e "ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password AS '';" + phpunit + WP_MULTISITE=1 phpunit + +jobs: + php74-build: + <<: *php_job + docker: + - image: cimg/php:7.4 + - image: *mariadb_image + + php80-build: + <<: *php_job + docker: + - image: cimg/php:8.0 + - image: *mariadb_image + + php81-build: + <<: *php_job + docker: + - image: cimg/php:8.1 + - image: *mariadb_image + + public-access: + docker: + - image: circleci/buildpack-deps:buster + steps: + - run: sudo su -c "apt-get update && apt-get -y upgrade && apt-get -y install composer rsync" + - run: php -v + - add_ssh_keys + - checkout + - setup_remote_docker + - run: make test-public-access + + private-access: + docker: + - image: circleci/buildpack-deps:buster + steps: + - run: sudo su -c "apt-get update && apt-get -y upgrade && apt-get -y install composer rsync" + - run: php -v + - add_ssh_keys + - checkout + - setup_remote_docker + - run: make test-private-access diff --git a/projects/plugins/wpcomsh/.dockerignore b/projects/plugins/wpcomsh/.dockerignore new file mode 100644 index 0000000000000..3c3629e647f5d --- /dev/null +++ b/projects/plugins/wpcomsh/.dockerignore @@ -0,0 +1 @@ +node_modules diff --git a/projects/plugins/wpcomsh/.eslintrc.js b/projects/plugins/wpcomsh/.eslintrc.js new file mode 100644 index 0000000000000..b8ac5c5ff030d --- /dev/null +++ b/projects/plugins/wpcomsh/.eslintrc.js @@ -0,0 +1,29 @@ +const loadIgnorePatterns = require( 'jetpack-js-tools/load-eslint-ignore.js' ); + +module.exports = { + root: true, + extends: [ + require.resolve( 'jetpack-js-tools/eslintrc/jest' ), + require.resolve( 'jetpack-js-tools/eslintrc/prettier' ), + ], + ignorePatterns: loadIgnorePatterns( __dirname ), + overrides: [], + env: { + browser: true, + jest: true, + node: true, + }, + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + }, + globals: {}, + settings: {}, + rules: { + 'jest/no-disabled-tests': 'warn', + 'jest/no-focused-tests': 'error', + 'jest/no-identical-title': 'error', + 'jest/prefer-to-have-length': 'warn', + 'jest/valid-expect': 'error', + }, +}; diff --git a/projects/plugins/wpcomsh/.gitattributes b/projects/plugins/wpcomsh/.gitattributes new file mode 100644 index 0000000000000..6e990962a533f --- /dev/null +++ b/projects/plugins/wpcomsh/.gitattributes @@ -0,0 +1,50 @@ +# Files not needed to be distributed in the package. +.gitattributes export-ignore +.github/ export-ignore +package.json export-ignore + +# Files to include in the mirror repo, but excluded via gitignore +# Remember to end all directories with `/**` to properly tag every file. +# /src/js/example.min.js production-include +# Files to include in Automattic/wpcom-site-helper +/vendor/** production-include +/vendor/autoload.php production-include +/vendor/automattic/** production-include +/vendor/composer/** production-include +/vendor/tubalmartin/** production-include + +# Files to exclude from the mirror repo, but included in the monorepo. +# Remember to end all directories with `/**` to properly tag every file. +.eslintignore production-exclude +.dockerignore production-exclude +.editorconfig production-exclude +composer.lock production-exclude +/.circleci/** production-exclude +/.gitignore production-exclude +/.phpcsignore production-exclude +/.phpcs.dir.xml production-exclude +/.phpcs.dir.phpcompatibility.xml production-exclude +/changelog/** production-exclude +/jetpack_vendor/automattic/**/README.md production-exclude +/jetpack_vendor/automattic/**/src/css/*.scss production-exclude +/jetpack_vendor/automattic/**/composer.json production-exclude +/node_modules/** production-exclude +/phpunit.xml.dist production-exclude +/README.md production-exclude +/vendor/automattic/**/src/css/*.scss production-exclude +/vendor/automattic/**/composer.json production-exclude +/vendor/automattic/jetpack-autoloader/** production-exclude +/vendor/automattic/jetpack-changelogger/** production-exclude +/vendor/automattic/jetpack-composer-plugin/** production-exclude +/vendor/**/.git* production-exclude +/vendor/**/.git*/** production-exclude +/vendor/**/*.md production-exclude +**/*.md production-exclude +/bin/** production-exclude +/build/** production-exclude +/tests/** production-exclude +/.circleci/** production-exclude +.*ntrc production-exclude +Makefile production-exclude +*.mdown production-exclude +error_log production-exclude diff --git a/projects/plugins/wpcomsh/.github/workflows/linting.yml b/projects/plugins/wpcomsh/.github/workflows/linting.yml new file mode 100644 index 0000000000000..49ec3e8eee2d6 --- /dev/null +++ b/projects/plugins/wpcomsh/.github/workflows/linting.yml @@ -0,0 +1,43 @@ +name: PHP Lint + +on: pull_request + +jobs: + phpcs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Check existence of composer.json & phpcs.xml.dist files + id: check_files + uses: andstor/file-existence-action@v2 + with: + files: "composer.json, phpcs.xml.dist" + + - name: Set up PHP environment + if: steps.check_files.outputs.files_exists == 'true' + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + tools: cs2pr + env: + COMPOSER_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} + + - name: Install Composer dependencies & cache dependencies + if: steps.check_files.outputs.files_exists == 'true' + uses: "ramsey/composer-install@v2" + env: + COMPOSER_ROOT_VERSION: dev-${{ github.event.repository.default_branch }} + with: + # Bust the cache at least once a month - output format: YYYY-MM. + custom-cache-suffix: $(date -u "+%Y-%m") + + - name: Detect coding standard violations (Expand for details) + # continue-on-error: true + run: composer phpcs --report-full --report-checkstyle=./phpcs-report.xml + + - name: Show coding standard violations in PR (Expand above section for details) + run: cs2pr --graceful-warnings ./phpcs-report.xml diff --git a/projects/plugins/wpcomsh/.github/workflows/wpcloud.yml b/projects/plugins/wpcomsh/.github/workflows/wpcloud.yml new file mode 100644 index 0000000000000..2fecab9257458 --- /dev/null +++ b/projects/plugins/wpcomsh/.github/workflows/wpcloud.yml @@ -0,0 +1,69 @@ +name: WPCloud Unit Testing + +on: pull_request + +concurrency: run-wpcloud-phpunit + +jobs: + deploy: + name: Run phpunit on WPCloud site + runs-on: ubuntu-latest + steps: + - name: Configure Github to be able to SSH to the Atomic site + run: | + echo "Intializing" + echo "::group::setup" + + mkdir -vp ~/.ssh/ + chmod -v 700 ~/.ssh + + touch ~/.ssh/id_site + chmod 600 ~/.ssh/id_site + echo "$WPCLOUD_SSH_KEY" > ~/.ssh/id_site + echo "wrote ~/.ssh/id_site" + + touch ~/.ssh/askpass + chmod -v 700 ~/.ssh/askpass + cat >>~/.ssh/askpass <>~/.ssh/config <>~/.ssh/stdin < [ + 'block-theme-footer-credits/class-wpcom-block-theme-footer-credits.php' => ['PhanUndeclaredFunction'], + 'class-jetpack-plugin-compatibility.php' => ['PhanUndeclaredClassMethod', 'PhanUndeclaredClassReference'], + 'class-wpcomsh-cli-commands.php' => ['PhanTypeVoidAssignment', 'PhanUndeclaredClassInCallable', 'PhanUndeclaredClassMethod', 'PhanUndeclaredFunctionInCallable'], + 'custom-colors/class-palette.php' => ['PhanTypeArraySuspiciousNullable'], + 'custom-colors/colors-api.php' => ['PhanNoopNewNoSideEffects'], + 'custom-colors/colors.php' => ['PhanTypeMismatchArgumentNullable', 'PhanTypeMismatchArgumentProbablyReal', 'PhanUndeclaredVariable'], + 'custom-colors/core-bg-admin-notice.php' => ['PhanContextNotObject', 'PhanUndeclaredClassMethod'], + 'endpoints/class-marketplace-webhook-response.php' => ['PhanPluginMixedKeyNoKey'], + 'endpoints/rest-api-export.php' => ['PhanTypeMismatchArgumentProbablyReal'], + 'feature-plugins/autosave-revision.php' => ['PhanPluginRedundantAssignment', 'PhanTypeMismatchArgumentNullable'], + 'feature-plugins/coblocks-mods.php' => ['PhanUndeclaredClassConstant', 'PhanUndeclaredClassMethod'], + 'feature-plugins/gutenberg-mods.php' => ['PhanUndeclaredFunctionInCallable'], + 'feature-plugins/managed-plugins.php' => ['PhanRedundantCondition', 'PhanUndeclaredClassMethod', 'PhanUndeclaredFunctionInCallable'], + 'feature-plugins/nav-redesign.php' => ['PhanEmptyFQSENInCallable'], + 'feature-plugins/sensei-pro-mods.php' => ['PhanUndeclaredClassMethod'], + 'footer-credit/theme-optimizations.php' => ['PhanUndeclaredClassMethod', 'PhanUndeclaredConstant', 'PhanUndeclaredFunction'], + 'frontend-notices/gifting-banner/gifting-banner.php' => ['PhanUndeclaredFunction'], + 'functions.php' => ['PhanImpossibleCondition', 'PhanUndeclaredClassStaticProperty'], + 'i18n.php' => ['PhanUndeclaredFunction'], + 'imports/playground/class-sql-importer.php' => ['PhanUndeclaredConstant'], + 'logo-tool/logo-tool.php' => ['PhanTypeMismatchArgumentNullableInternal', 'PhanUndeclaredFunction'], + 'notices/plan-notices.php' => ['PhanImpossibleCondition'], + 'plugin-hotfixes.php' => ['PhanUndeclaredFunctionInCallable'], + 'private-site/access-denied-coming-soon-template.php' => ['PhanTypeSuspiciousEcho'], + 'private-site/access-denied-preview-login-template.php' => ['PhanTypeSuspiciousEcho'], + 'private-site/access-denied-private-site-template.php' => ['PhanTypeSuspiciousEcho'], + 'safeguard/utils.php' => ['PhanPossiblyUndeclaredVariable', 'PhanTypeMismatchArgument'], + 'support-session.php' => ['PhanNoopNew'], + 'tests/feature-manager/test-feature-hooks.php' => ['PhanUndeclaredStaticMethod'], + 'tests/imports/test-sql-generator.php' => ['PhanTypeObjectUnsetDeclaredProperty'], + 'tests/test-anyone-can-register-notice.php' => ['PhanTypeMismatchArgument', 'PhanTypeVoidArgument', 'PhanTypeVoidAssignment'], + 'tests/test-blog-token-resilience.php' => ['PhanUndeclaredStaticMethod'], + 'tests/test-frontend-notices.php' => ['PhanUndeclaredStaticMethod'], + 'tests/test-plan-notices.php' => ['PhanDeprecatedProperty', 'PhanPluginUseReturnValueInternalKnown', 'PhanTypeVoidAssignment', 'PhanUndeclaredStaticMethod'], + 'tests/test-wpcom-features.php' => ['PhanTypeMismatchArgument', 'PhanUndeclaredStaticMethod'], + 'widgets/class-jetpack-posts-i-like-widget.php' => ['PhanRedundantCondition'], + 'widgets/class-pd-top-rated.php' => ['PhanRedundantCondition'], + 'widgets/class-widget-top-clicks.php' => ['PhanUndeclaredFunction'], + 'wpcom-features/class-wpcom-features.php' => ['PhanPluginMixedKeyNoKey'], + 'wpcom-features/functions-wpcom-features.php' => ['PhanImpossibleCondition', 'PhanTypeMismatchArgument', 'PhanUndeclaredClassInstanceof', 'PhanUndeclaredClassMethod', 'PhanUndeclaredClassProperty', 'PhanUndeclaredFunction', 'PhanUndeclaredMethod', 'PhanUndeclaredTypeParameter'], + 'wpcomsh.php' => ['PhanUndeclaredClassMethod', 'PhanUndeclaredClassReference', 'PhanUndeclaredFunctionInCallable'], + ], + // 'directory_suppressions' => ['src/directory_name' => ['PhanIssueName1', 'PhanIssueName2']] can be manually added if needed. + // (directory_suppressions will currently be ignored by subsequent calls to --save-baseline, but may be preserved in future Phan releases) +]; diff --git a/projects/plugins/wpcomsh/.phan/config.php b/projects/plugins/wpcomsh/.phan/config.php new file mode 100644 index 0000000000000..2433fa07564c0 --- /dev/null +++ b/projects/plugins/wpcomsh/.phan/config.php @@ -0,0 +1,39 @@ + array( 'tests/lib/mocks' ), + 'parse_file_list' => array( + // Reference files to handle code checking for stuff from Jetpack-the-plugin or other in-monorepo plugins. + // Wherever feasible we should really clean up this sort of thing instead of adding stuff here. + // + // DO NOT add references to files in other packages like this! Generally packages should be listed in composer.json 'require'. + // If there are truly optional dependencies or circular dependencies that can't be cleaned up, one package may list the + // other in 'require-dev' and `extra.dependencies.test-only' instead. See packages/config for an example. + // -- + // class.color.php provides the definition of the Jetpack_Color class. + __DIR__ . '/../../../plugins/jetpack/_inc/lib/class.color.php', + // class.jetpack.php provides the definition of the Jetpack megaclass. + __DIR__ . '/../../../plugins/jetpack/class.jetpack.php', + // class.jetpack-gutenberg.php provides the definition of the Jetpack_Gutenberg class. + __DIR__ . '/../../../plugins/jetpack/class.jetpack-gutenberg.php', + // jetpack.php provides the definition of constants like JETPACK__PLUGIN_DIR and others. + __DIR__ . '/../../../plugins/jetpack/jetpack.php', + // photon-cdn.php provides the definition of the Jetpack_Photon_Static_Assets_CDN. + __DIR__ . '/../../../plugins/jetpack/modules/photon-cdn.php', + ), + 'php_extensions_needed' => array( 'sqlite3', 'zip' ), + '+stubs' => array( 'woocommerce' ), + ) +); diff --git a/projects/plugins/wpcomsh/.phpcs.dir.phpcompatibility.xml b/projects/plugins/wpcomsh/.phpcs.dir.phpcompatibility.xml new file mode 100644 index 0000000000000..27eec16799c1a --- /dev/null +++ b/projects/plugins/wpcomsh/.phpcs.dir.phpcompatibility.xml @@ -0,0 +1,4 @@ + + + + diff --git a/projects/plugins/wpcomsh/.phpcs.dir.xml b/projects/plugins/wpcomsh/.phpcs.dir.xml new file mode 100644 index 0000000000000..941d020e50335 --- /dev/null +++ b/projects/plugins/wpcomsh/.phpcs.dir.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/projects/plugins/wpcomsh/CHANGELOG.md b/projects/plugins/wpcomsh/CHANGELOG.md new file mode 100644 index 0000000000000..119e1666b4b11 --- /dev/null +++ b/projects/plugins/wpcomsh/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +## 3.22.5 - 2024-05-31 +### Other Changes +- Phan: fixed bugs and problems that triggered static analysis warnings. + +[13.3]: https://wp.me/p1moTy-19qu + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + diff --git a/projects/plugins/wpcomsh/Makefile b/projects/plugins/wpcomsh/Makefile new file mode 100644 index 0000000000000..128f58d0f6855 --- /dev/null +++ b/projects/plugins/wpcomsh/Makefile @@ -0,0 +1,132 @@ +NAME := wpcomsh +SHELL := /bin/bash +UNAME := $(shell uname -s) +REQUIRED_BINS := zip git rsync composer + +## check required bins can be found in $PATH +$(foreach bin,$(REQUIRED_BINS),\ + $(if $(shell command -v $(bin) 2> /dev/null),, $(error `$(bin)` not found in $$PATH))) + +## handle version info from git tags +ifeq ($(shell git describe --tags --always > /dev/null 2>&1 ; echo $$?), 0) + VERSION_STRING := $(shell git describe --tags --always | sed -e 's/^v//') +endif + +## set paths from the location of the makefile +MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +BUILD_SRC := $(dir $(MAKEFILE)) +BUILD_DST := $(addsuffix build, $(dir $(MAKEFILE))) +BUILD_FILE := $(NAME).$(VERSION_STRING).zip + +## get version from wpcomsh.php for tagging +PLUGIN_VERSION_STRING = $(shell awk '/[^[:graph:]]Version/{print $$NF}' $(BUILD_SRC)/wpcomsh.php) + +## git related vars +GIT_BRANCH = $(shell git rev-parse --abbrev-ref HEAD) +GIT_REMOTE_FULL = $(shell git for-each-ref --format='%(upstream:short)' $$(git symbolic-ref -q HEAD)) +GIT_REMOTE_NAME = $(firstword $(subst /, , $(GIT_REMOTE_FULL))) +GIT_STATUS = $(shell git status -sb | wc -l | awk '{ if($$1 == 1){ print "clean" } else { print "dirty" } }') + +## checking for clean tree and all changes pushed/pulled +git.fetch: + @git fetch $(GIT_REMOTE_NAME) + +check: +ifeq ($(WPCOMSH_DEVMODE), 1) + @ echo Checks skipped: Make is running in development mode. +else + @ $(MAKE) checkandblockonfail +endif + +checkbeforetag: + @ $(MAKE) check + @ $(MAKE) checktagandblockonfail + +checkandblockonfail: git.fetch +ifneq ($(strip $(shell git diff --exit-code --quiet $(GIT_REMOTE_FULL)..HEAD 2>/dev/null ; echo $$?)),0) + $(error local branch not in sync with remote, need to git push/pull) +endif + +ifneq ($(GIT_STATUS), clean) + $(error un-committed changes detected in working tree) +endif + +checktagandblockonfail: git.fetch +ifneq ($(GIT_BRANCH), trunk) + $(error make tag only supports tagging trunk) +endif + +ifneq ($(strip $(shell awk '/define\([[:space:]]*\047WPCOMSH_VERSION.*\)/{print}' $(BUILD_SRC)/wpcomsh.php | grep -q $(PLUGIN_VERSION_STRING) 2>/dev/null; echo $$?)), 0) + $(error defined WPCOMSH_VERSION does not match plugin version `$(PLUGIN_VERSION_STRING)`) +endif + +ifneq ($(strip $(shell git ls-remote --exit-code $(GIT_REMOTE_NAME) refs/tags/v$(PLUGIN_VERSION_STRING) > /dev/null 2>&1; echo $$?)), 2) + $(error tag `v$(PLUGIN_VERSION_STRING)` already exists) +endif + +ifeq ($(strip $(shell git rev-parse --exit-code v$(PLUGIN_VERSION_STRING) 2>/dev/null ; echo $$?)), 0) + $(error local tag v$(PLUGIN_VERSION_STRING) exits. Did you forget to push the tag ? 'git push $(GIT_REMOTE_NAME) v$(PLUGIN_VERSION_STRING)' ) +endif + +$(BUILD_DST)/$(BUILD_FILE): $(BUILD_DST)/$(NAME) + @ echo "removing vendor dir..." + @ rm -rf vendor + + @ echo "running composer install..." + @ composer install --no-dev --optimize-autoloader &>/dev/null + + @ echo "rsync'ing to build dir..." + @ rsync \ + --quiet \ + --links \ + --recursive \ + --times \ + --perms \ + --exclude-from=$(BUILD_SRC)build-exclude.txt \ + $(BUILD_SRC) \ + $(BUILD_DST)/$(NAME)/ + + @ echo "creating zip file..." + @ cd $(BUILD_DST) && zip -q -r $(BUILD_FILE) $(NAME)/ -x "._*" + + @ echo "DONE!" + +$(BUILD_DST)/$(NAME): $(BUILD_DST) + @ mkdir -p $(BUILD_DST)/$(NAME) + +$(BUILD_DST): + @ mkdir -p $(BUILD_DST) + +## build +build: check clean $(BUILD_DST)/$(BUILD_FILE) + +## i18n +i18n: + @ /bin/bash $(BUILD_SRC)/bin/i18n/generate-pot.sh + @ /bin/bash $(BUILD_SRC)/bin/i18n/update-translations.sh + @ git add $(BUILD_SRC)/languages + @ git commit $(BUILD_SRC)/languages -m "Update language files" + +## tag +tag: checkbeforetag + $(shell git tag v$(PLUGIN_VERSION_STRING)) + @ echo "tag v$(PLUGIN_VERSION_STRING) added." + @ echo $(PUSH_TAG) +ifeq ($(PUSH_RELEASE_TAG), true) + $(shell git push $(GIT_REMOTE_NAME) v$(PLUGIN_VERSION_STRING)) + @ echo "tag pushed to $(GIT_REMOTE_NAME)." +else + @ echo "run 'git push $(GIT_REMOTE_NAME) v$(PLUGIN_VERSION_STRING)' before creating the release" +endif + +## CI & other testing +test-public-access: build + /bin/sh ./bin/ci-init-access-tests.sh + +test-private-access: build + /bin/sh ./bin/ci-init-access-tests.sh private + +## clean +clean: $(BUILD_DST) + @ echo "removing $(BUILD_DST)" + @ rm -rf $(BUILD_DST) diff --git a/projects/plugins/wpcomsh/README.md b/projects/plugins/wpcomsh/README.md new file mode 100644 index 0000000000000..09527cd310136 --- /dev/null +++ b/projects/plugins/wpcomsh/README.md @@ -0,0 +1,326 @@ +# wpcomsh + +A helper for connecting WordPress.com sites to external host infrastructure. + +# WordPress.com Site Helper + +[![PHP Lint](https://github.com/Automattic/wpcomsh/actions/workflows/linting.yml/badge.svg)](https://github.com/Automattic/wpcomsh/actions/workflows/linting.yml) [![WPCloud Unit Testing](https://github.com/Automattic/wpcomsh/actions/workflows/wpcloud.yml/badge.svg)](https://github.com/Automattic/wpcomsh/actions/workflows/wpcloud.yml) + +With the help of this mu-plugin, an Atomic site is transformed into a WP.com site. It lives in `wp-content/mu-plugins/wpcomsh` and is loaded with `wp-content/mu-plugins/wpcomsh-loader.php`. + +## Development + +### Quick Start + +``` +# From the root of a wp.org install +$ mkdir -p wp-content/mu-plugins +$ cd wp-content/mu-plugins +$ git clone git@github.com:Automattic/wpcomsh.git +$ cd wpcomsh +$ composer install # installs the composer dependencies +$ cd .. +$ ln -s wpcomsh/wpcomsh-loader.php ./ # or copy the loader to mu-plugins + +# define 'IS_ATOMIC', 'ATOMIC_SITE_ID' and 'ATOMIC_CLIENT_ID' as true so the loader will require wpcomsh + +define( 'IS_ATOMIC', true ); +define( 'ATOMIC_SITE_ID', true ); +define( 'ATOMIC_CLIENT_ID', true ); +``` + +To work on wpcomsh, you need a WP.org site and ideally the Jetpack plugin installed and connected to WP.com. +You will also need to install [Composer](https://getcomposer.org/) + +1. Clone the [wpcomsh git repo](https://github.com/Automattic/wpcomsh/) into `wp-content/mu-plugins` of that site. +2. Then, either copy or symlink the `wp-content/mu-plugins/wpcomsh/wpcomsh-loader.php` file to `wp-content/mu-plugins`. + It acts as a "loader" for wpcomsh and we need this because plugin folders put into `mu-plugins` are not automatically loaded like plugins in `wp-content/plugins`. +3. From the project root run `composer install` to install composer based dependencies. + +If you want to add some new code to wpcomsh, create a new git branch, push to it and then create a Pull Request (PR) against the `trunk` branch on [wpcomsh GitHub](https://github.com/Automattic/wpcomsh/). Make sure to test thoroughly on a WoA dev blog and send the PR to your team for review. + +When working on wpcomsh, follow the [WP.org coding standards](https://codex.wordpress.org/WordPress_Coding_Standards) and make sure to add enough logging (either by returning `WP_Error` and/or by using `error_log`) where needed. + +## Testing + +There are two stages of manually testing wpcomsh: + +The first one is to set up a WP.org site and test on it (more instructions in the [Development section](#development)). + +However, it's the best if you also install the Jetpack plugin and connect it to WP.com on the WP.org site as that's how AT sites communicate with WP.com -- many things can be tested only with connected Jetpack. We recommend either using your .wpsandbox.me site (PCYsg-5Q0-p2) or use [Vagrant](https://github.com/Varying-Vagrant-Vagrants/VVV) to set up the WP.org site locally on your machine and share it with world (so WP.com can connect to it). + +Note: if you use your `.wpsandbox.me` for testing wpcomsh, use ssh key forwarding so you have all your local ssh keys on the wpsandbox and can clone the wpcomsh GitHub repo. Either run ssh as `ssh -A` or add `ForwardAgent yes` into your `.ssh/config` file. p1490809471078673-slack-C2PDURDSL. + +### Automated Testing + +#### Unit Testing + +`wpcomsh` runs `phpunit` on CircleCI for every PR. + +Please try to add unit tests whenever you are adding new features, or modifying existing ones. + +#### Private Site Module + +There is an integration suite built on docker that makes testing what clients to "private" (and non-private) sites should and shouldn't be able to see. + +If you have `docker` installed on your system, the tests can be run like so: + +- `make test-public-access` +- `make test-private-access` + +Each of the above: + +- Cleans your build directory, etc. +- Builds the plugin from source (as would happen for the regular deployment process ) +- Spins up containers for: + - mysql database + - WordPress (php-fpm) + - nginx web server + - WP-CLI + - node / jest (for the actual testing) +- Coordinates communication and set up for the above +- Sets a site to be public or private according to the script invocation +- Kicks off test specs to validate that appropriate resources are accessible and, in the case of a site being set to private, inappropriate resources are not + +#### Development Mode + +You can enter "development mode" by declaring you are doing so via the `WPCOMSH_DEVMODE` environment variable. + +For example: + +`WPCOMSH_DEVMODE=1 make test-private-access` + +This will: + +- Instruct `make` to bypass the `check` directive (allowing for rules like `build` to run without a clean working directory) +- Set jest to "watch" for changes to the spec files inside the running container. +- Watch for changes to specific files on your local machine and copy them to the container on changes. +- Leave the services running (until you exit with `cmd+c`, etc.), so you can access the running WordPress site to do manual testing (see below) + +In order for WordPress to load the test site correctly, you'll need to access the site with the appropriate site name: + +- Add the following to your hosts file: `127.0.0.1 nginx` +- Browse to http://nginx:8989 in your favorite web client / browser + +## Deployment + +- Use the Monorepo deployment tools available in the `tools` folder in the root. For more information on releasing a plugin go to [the Monorepo deployment docs](../../../docs/monorepo.md#plugin-release-tooling). +- You can view your [successful release here](https://github.com/Automattic/wpcom-site-helper/releases). +- New versions are deployed when our monitoring detects its release. + - Note: You can monitor [#atomic-alerts](https://a8c.slack.com/archives/C05GLGHLM8U) channel to see when the new version is deployed. + +#### Deploying new languages + +After every deployment, as described above, please create a new +branch and run: + +- `make i18n` to update translatable strings and translations and create a new PR +- Follow Deployment instructions + +##### How the translation system works? + +- Use the `__( 'My string to be translated', 'wpcomsh' );` code to consume translations. +- Create a new branch and run the command `make i18n` to convert these new strings into the [wpcomsh.pot](./languages/wpcomsh.pot) file. It will also download previous translations and update .mo and .po files of the [languages folder](./languages/) and will commit them automatically. +- After merging everything on the trunk branch strings on [wpcomsh.pot](./languages/wpcomsh.pot) file will be automatically inserted into our translation system by this script: fbhepr%2Skers%2Sjcpbz%2Sova%2Sv18a%2Svzcbeg%2Qtvguho%2Qbevtvanyf.cuc%3Se%3Q1oq4q3oo%26zb%3Q12%26sv%3Q2%235-og. +- After they get translated we need to run again `make i18n` and it will download all translations done by our translation vendor and the community. +- Deploy and release the translations file. +- You'll now have your strings translated into production! + +## Troubleshooting + +When something doesn't work as it should, the first thing to do is [enable error logging in WP](https://codex.wordpress.org/Debugging_in_WordPress) or look into the global PHP error log. It's advisable to install and connect Jetpack when working on wpcomsh. Use [MGS](https://mc.a8c.com/mgs/) to search through Slack channels as that's where most of the development work/chats take place. You can also use PCYsg-5mr-p2 (for example, with PhPStorm). If you still can't figure out the problem, drop a message in the Automated Transfer Slack channel and/or ping [wpcomsh devs](https://github.com/Automattic/wpcomsh/graphs/contributors) directly. + +## Features + +If you add a feature to wpcomsh, please add it to the following list: + +### WP.com Footer Credit + +- Replaces any footer credit in our themes with "POWERED BY WORDPRESS.COM." +- Allows for customization of that message + +### WP.com Block Theme Footer Credit + +- Footer credits that work on block based themes. + +### Removal of VaultPress wp-admin notices + +Removal of activation notice, connection notice and error notices. Users should not have to manage VaultPress, report its issues, etc -- that’s why we are hiding those notices. + +### Hiding plugins links on wp-admin/plugins page + +We don’t allow users to deactivate and edit Akismet, Jetpack, VaultPress so that’s why we hide these links. + +We also hide the bulk plugins deactivation on the wp-admin/plugins page for all the plugins as it was not easily possible to do it only for the aforementioned plugins. + +### Hiding the Jetpack version number on wp-admin/plugins page + +That version is managed by the Atomic platform, so does not need to be displayed to site owners. They always run the latest version, which may be a version that is not yet released in the WordPress.org directory. + +### Hiding the Jetpack page "My Jetpack" + +This page is mostly about upselling and cross-discovery of Jetpack feature plugins. Not needed on Atomic sites. + +### Showing that a plugin is auto-managed + +We show a similar message to the update one under Akismet, Jetpack and VaultPress telling users that those plugins are auto-managed for them (an explanation why they can’t deactivate or edit them). + +### Denoting Plugins to enable WP.com features + +Plugins that bridge the gap between WP.com and Atomic, enabling WP.com-only features that are part of users' plans, receive a green banner with that information. +This allows users to make an informed decision when enabling/disabling these plugins. + +### Symlinking WP.com pub (free) and premium themes + +We keep the WP.com pub and premium themes checked out on Atomic. When users try to install WP.com themes from within Calypso (not possible from wp-admin), wpcomsh hooks into Jetpack themes API and symlinks the WP.com themes from the directory where we keep them on Atomic to user’s `wp-content/themes` folder. + +When a user tries to delete a WP.com theme (only available from Calypso), wpcomsh hooks into Jetpack themes API and unsymlinks the WP.com theme. + +If a WP.com theme is a child theme of another WP.com theme, wpcomsh symlinks both the parent and the child themes. Analogously, if users try to delete a child WP.com theme, wpcomsh unsymlinks both the child and the parent themes. However, if only the parent theme is removed/unsymlinked, wpcomsh doesn’t unsymlink the child theme, making it potentially broken (as the parent theme is removed). + +### Removal of the “delete” button from WP.com themes in wp-admin + +### Removal of Theme Editor access from 3-rd party WP.com premium themes in wp-admin + +If a user installs a premium WP.com theme created by a third-party shop (ie not Automattic), we remove access to the Theme Editor as we are prohibited to share the source code of this kind of themes. Both access to wp-admin/theme-editor.php page and the “Editor” link under wp-admin -> Appearance are removed. + +### Disabling ability to manage plugins/themes after canceling the Business plan subscription + +When a user cancels their Business plan in Calypso, an async job called `at_business_plan_cancellation_flow` is run. This job does various things, such as deactivating all the plugins except Akismet, Jetpack and VaultPress on the transferred site, switching to a WP.com pub theme (which was previously installed on the site) and setting the option `plan_slug` to `free` on the transferred site (options related to Automated Transfer are stored under `at_options` array). + +Setting `plan_slug` to `free`, in turn, adds the `do_not_allow` capability to the list of required capabilities for the following capabilities (for all users -- globally): + +- `activate_plugins` +- `install_plugins` +- `edit_plugins` +- `delete_plugins` +- `upload_plugins` +- `update_plugins` +- `switch_themes` +- `install_themes` +- `update_themes` +- `delete_themes` +- `upload_themes` +- `edit_themes` + +### Points attachment URLs to WP.com + +TODO: needs Jennifer’s clarification. + +After transferring a site from WP.com to Atomic, the media files (such as images) are not immediately moved there and stay on WP.com servers. That’s why we need to point the attachment URLs to WP.com temporarily. A job to move media files is queued shortly after the transfer process finishes. + +### Bypassing Jetpack Single Sign On login form + +By default, transferred sites have the Jetpack Single Sing On enabled as it’s the only way for WP.com users to log into the site’s wp-admin. However, we want it to be seamless (like on WP.com) without users having to click on the “Log in with WP.com account” button. + +That’s why we bypass this login form with wpcomsh entirely and log the user automatically to wp-admin (or redirect to WP.com if the user is not logged in to WP.com). + +### Theme_uri of a WP.com theme always wordpress.com + +To distinguish between WP.com themes installed by symlinking and themes uploaded manually, the `theme_uri` of the WP.com symlinked themes is always `wordpress.com` (for example, in Jetpack themes API response). + +### Add assets/admin-style.css + +All the wp-admin custom styles for transferred sites are in `assets/admin-style.css`. If you need to add some more, please add them to that file. + +### Checks for Full Site Editing Site Eligibility + +If a site has the `a8c-fse-is-eligible` site option, the site is eligible for Full-Site Editing flows. Flows are only active when a supported theme is active. + +### Updates Customizer Save/Publish Labels to be consistent with WordPress.com + +Because WordPress.com supports private sites by default, customizer label copy was updated to reduce confusion on what would launch a site or what will save changes on a site. + +### Custom colors and fonts (+ Typekit fonts) + +On WP.com, we provide custom colors and fonts in a site's Customizer. In order to get them supported on an AT site, wpcomsh imports the `colors`, `custom-fonts` and `custom-fonts-typekit` codebases. + +### Media Library used space + +Shows space used (e.g. `250MB of 100GB`) in Media library. + +### Logging + +WPCOMSH provides a hook to log arbitrary information in our Kibana instance. +You cannot use these function by itself, you need to call a proper hook like so: + +```php +do_action( 'wpcomsh_log', "test" ); +``` + +You will see the output here: +78d11cc3116d62c53c50ae95c04d265b-logstash + +But, logging needs to be turned on via `at_options_logging_on`. You can do that via `/option` endpoint. More tools coming. + +### WP CLI Commands + +wpcomsh includes [WP CLI](http://wp-cli.org/) commands. They are located in the `./class-wpcomsh-cli-commands.php` file. + +- `wp wpcomsh deactivate-user-plugins`: Bulk deactivate user installed plugins. Supports an `--interactive` mode that asks for each plugin. +- `wp wpcomsh reactivate-user-plugins`: Bulk re-activate user installed plugins. Supports an `--interactive` mode that asks for each plugin. +- `wp launch-site`: An easter egg added for WPCOM SSH launch. + +To learn more about writing WP CLI commands consult the [Commands Cookbook](https://make.wordpress.org/cli/handbook/commands-cookbook/). + +### Store Support + +wpcomsh adds the [wc-api-dev](https://github.com/woocommerce/wc-api-dev) plugin to support Store on WordPress.com + +### Private Site support + +Enables setting a site to "private." Doing so prevents viewing or interacting with site content to unauthenticated clients (and anyone without `read` capabilities). + +As this module is currently being developed & evaluated, it is only enabled when the `AT_PRIVACY_MODEL` constant is set is set to `wp_uploads` (such that `\Private_Site\is_module_active()` returns `true`). + +### Experimental Jetpack Blocks + +Enables the "experimental" block bundle [offered in Jetpack](https://github.com/Automattic/jetpack/pull/14104), instead of the default Production bundle. + +Those blocks are considered ready for production, but we don't want to serve to all Jetpack sites yet. When you use this bundle, you'll get all production blocks as well as experimental blocks. + +### SEO Description Block + +Enables the SEO Description Block that is currently only available for WordPress.com sites. + +### Navigation Sidebar in the Block Editor + +The navigation sidebar in the [WordPress.com Editing Toolkit](https://wordpress.org/plugins/full-site-editing/) is enabled using a filter. The site helper adds the filter and may use WordPress.com and Automattic specific logic to decide whether to enable it. + +### Coming Soon + +The Coming Soon PCYsg-u4S-p2 mode is provided via the jetpack-mu-wpcom package PCYsg-Osp-p2 and enabled using a filter. Coming Soon allows users to hide their site behind a Coming Soon page from the site settings page of Calypso. + +### Nav Unification + +Provides a series of customisations to enable the full Nav Unification experience on Atomic (see pbAPfg-Ou-p2). Includes: + +- force enable Masterbar module. +- hide admin color scheme picker and add notice pointing to WordPress.com Account Settings. +- persist important WP.com user data to user_option via Jetpack connected user data. +- activate the Nav Unification feature shipped in Jetpack on Atomic. +- add a WooCommerce install item to the menu when Woo isn't installed. +- force disable Nav Unification feature via query string. + +### Frontend Notices + +Allow the showing of notices on the frontend. Currently we are showing: + +- A top header notice on sites that are close to expire. + +### GitHub Deploy + +Includes a simple REST API for the GitHub deployment on Atomic: pet6gk-G-p2. Atomic sites that are connected to a GitHub repo will receive `git push` webhook events via WPCOM which are then forwarded onto the connected Atomic site so it can pull down the latest code from GitHub. The WPCOMSH code is also responsible for generating a log file which is returned back to WPCOM and displayed in the Calypso UI at `/hosting-config/:atomic-site`. + +### Site Monitoring + +The menu item for WoA logging (`/site-monitoring/:siteSlug` in Calypso) is toggled on and off in wpcomsh. + +## Security + +Need to report a security vulnerability? Go to [https://automattic.com/security/](https://automattic.com/security/) or directly to our security bug bounty site [https://hackerone.com/automattic](https://hackerone.com/automattic). + +## License + +wpcomsh is licensed under [GNU General Public License v2 (or later)](./LICENSE.txt) + diff --git a/projects/plugins/wpcomsh/assets/admin-style.css b/projects/plugins/wpcomsh/assets/admin-style.css new file mode 100644 index 0000000000000..931601fe81a10 --- /dev/null +++ b/projects/plugins/wpcomsh/assets/admin-style.css @@ -0,0 +1,102 @@ +/* + * Put all the styles in this file which are needed in wp-admin. + */ + +/************* Jetpack **************/ + +/* Connection block in "General" tab. */ +.dops-navigation + div > .dops-card.dops-foldable-card:first-child:not(.has-expanded-summary) { + display: none; +} + +/* SSO block in "Security" tab */ +.dops-navigation + div > .dops-card.dops-foldable-card.has-expanded-summary[data-reactid*="module-card_sso"] { + display: none; +} + +/* "Disconnect Jetpack" link in JP Footer */ +#jp-plugin-container .jp-footer > .jp-footer__links > .jp-footer__link-item:last-child { + display: none; +} + +/* Hide the "Plans" page in Jetpack Dashboard */ +.dops-navigation .dops-section-nav-tab__link[href*="jetpack-plans"] { + display: none; +} + +/* Hide the Jetpack Plans box in Jetpack Dashboard */ +#jp-plugin-container .jp-landing__plans { + display: none; +} + +/* Hide the connections info and disconnect button in Jetpack Dashboard */ +#jp-plugin-container .jp-lower .jp-dash-section-header__connections, +#jp-plugin-container .jp-lower .jp-connection-type { + display: none; +} + +/************* Plugins Page **************/ + +.wp-list-table tr[data-slug="akismet"] > *, +.wp-list-table tr[data-slug="full-site-editing"] > *, +.wp-list-table tr[data-slug="gutenberg"] > *, +.wp-list-table tr[data-slug="jetpack"] > *, +.wp-list-table tr[data-slug="layout-grid"] > *, +.wp-list-table tr[data-slug="coblocks"] > *, +.wp-list-table tr[data-slug="page-optimize"] > * { + -webkit-box-shadow: none !important; + box-shadow: none !important; /* Can otherwise be overridden by https://core.trac.wordpress.org/browser/tags/5.2.1/src/wp-admin/css/list-tables.css#L1217 */ +} + +/* + * Atomic notifications customized to standout. Based on Calypso notifications. + */ +.wpcomsh-notice, .rtl .wpcomsh-notice { + border: none; + background: #2D3337; + box-sizing: border-box; + color: #fff; + display: flex; + line-height: 1.5; + position: relative; + width: 100%; + padding: 0; +} + +.wpcomsh-notice a{ + color: #fff; +} + +.wpcomsh-notice .dashicons-info{ + height: 24px; + width: 24px; + font-size: 26px; +} + +.notice__icon-wrapper { + align-items: center; + align-self: stretch; + display: flex; + flex-shrink: 0; + justify-content: center; + position: relative; + width: 47px; +} + +.notice__icon-wrapper.notice__icon-wrapper-pink { + background: #e34c84; /* studio-pink-40 */ +} + +.notice__icon-wrapper.notice__icon-wrapper-orange { + background: #d67709; /* studio-orange-40 */ +} + +.notice__icon-wrapper.notice__icon-wrapper-red { + background: #e65054; /* studio-red-40 */ +} + +.notice__content { + flex-grow: 1; + font-size: .875rem; + padding: 13px; +} diff --git a/projects/plugins/wpcomsh/assets/wp-polyfill-dom-rect.js b/projects/plugins/wpcomsh/assets/wp-polyfill-dom-rect.js new file mode 100644 index 0000000000000..d1b85093a4dfe --- /dev/null +++ b/projects/plugins/wpcomsh/assets/wp-polyfill-dom-rect.js @@ -0,0 +1,99 @@ +(function (global) { + function number(v) { + return v === undefined ? 0 : Number(v); + } + + function different(u, v) { + return u !== v && !(isNaN(u) && isNaN(v)); + } + + function DOMRect(xArg, yArg, wArg, hArg) { + var x, y, width, height, left, right, top, bottom; + + x = number(xArg); + y = number(yArg); + width = number(wArg); + height = number(hArg); + + Object.defineProperties(this, { + x: { + get: function () { return x; }, + set: function (newX) { + if (different(x, newX)) { + x = newX; + left = right = undefined; + } + }, + enumerable: true + }, + y: { + get: function () { return y; }, + set: function (newY) { + if (different(y, newY)) { + y = newY; + top = bottom = undefined; + } + }, + enumerable: true + }, + width: { + get: function () { return width; }, + set: function (newWidth) { + if (different(width, newWidth)) { + width = newWidth; + left = right = undefined; + } + }, + enumerable: true + }, + height: { + get: function () { return height; }, + set: function (newHeight) { + if (different(height, newHeight)) { + height = newHeight; + top = bottom = undefined; + } + }, + enumerable: true + }, + left: { + get: function () { + if (left === undefined) { + left = x + Math.min(0, width); + } + return left; + }, + enumerable: true + }, + right: { + get: function () { + if (right === undefined) { + right = x + Math.max(0, width); + } + return right; + }, + enumerable: true + }, + top: { + get: function () { + if (top === undefined) { + top = y + Math.min(0, height); + } + return top; + }, + enumerable: true + }, + bottom: { + get: function () { + if (bottom === undefined) { + bottom = y + Math.max(0, height); + } + return bottom; + }, + enumerable: true + } + }); + } + + global.DOMRect = DOMRect; +}(this)); diff --git a/projects/plugins/wpcomsh/bin/ci-init-access-tests.sh b/projects/plugins/wpcomsh/bin/ci-init-access-tests.sh new file mode 100644 index 0000000000000..529d609554f43 --- /dev/null +++ b/projects/plugins/wpcomsh/bin/ci-init-access-tests.sh @@ -0,0 +1,205 @@ +#!/bin/sh + +if [ "$1" = "private" ]; then + echo Running Private Site Tests! + PROJECT=wpcomsh_private_access +else + echo Running Public Site Tests! + PROJECT=wpcomsh_public_access +fi +NETWORK=$PROJECT +WPDATA=${PROJECT}_wpdata +ALLCONTAINERS='db jest nginx wp wpcli' +CHOKIDAR_PID='' + +finish () { + STATUS=$?; + tidyupdocker; + if [ "${CHOKIDAR_PID}" ]; then + echo Stopping file watcher + kill -9 "${CHOKIDAR_PID}" 2>/dev/null + fi + exit $STATUS; +} +trap "finish" HUP INT TERM QUIT + +tidyupdocker () { + echo Tidying up containers + for C in $ALLCONTAINERS; do + docker rm -f -v wpcomsh_public_access_$C >/dev/null 2>/dev/null + docker rm -f -v wpcomsh_private_access_$C >/dev/null 2>/dev/null + done + + echo Tidying up network + docker network rm $NETWORK >/dev/null 2>/dev/null + + echo Tidying up shared volume + docker volume rm -f $WPDATA >/dev/null +} + +tidyupdocker + +echo Creating network +docker network create $NETWORK + +echo Creating wp data shared volume +docker volume create $WPDATA >/dev/null + +DB=`docker create \ + --name ${PROJECT}_db \ + --network-alias db \ + --network $NETWORK \ + --restart always \ + --env MYSQL_ROOT_PASSWORD=jfdsaf9wjfaospfopdsafjsda \ + --env MYSQL_DATABASE=wordpress --env MYSQL_USER=wp \ + --env MYSQL_PASSWORD=iojdgoisajsoife83489398f8ds9a \ + mariadb:lts` + +WP=`docker create \ + --name ${PROJECT}_wp \ + --network-alias wp \ + --network $NETWORK \ + --mount source=$WPDATA,target=/var/www/html \ + --restart always \ + --env WORDPRESS_DB_HOST=db \ + --env WORDPRESS_DB_USER=wp \ + --env WORDPRESS_DB_NAME=wordpress \ + --env WORDPRESS_DB_PASSWORD=iojdgoisajsoife83489398f8ds9a \ + --env WORDPRESS_TABLE_PREFIX=wp_ \ + --env WORDPRESS_DEBUG=1 \ + wordpress:6-fpm` + +WPCLI=`docker create \ + --name ${PROJECT}_wpcli \ + --network-alias wpcli \ + --network $NETWORK \ + --mount source=$WPDATA,target=/var/www/html \ + --entrypoint tail \ + --env WORDPRESS_DB_HOST=db \ + --env WORDPRESS_DB_USER=wp \ + --env WORDPRESS_DB_NAME=wordpress \ + --env WORDPRESS_DB_PASSWORD=iojdgoisajsoife83489398f8ds9a \ + --env WORDPRESS_TABLE_PREFIX=wp_ \ + --env WORDPRESS_DEBUG=1 \ + wordpress:cli-2 \ + -f /dev/null` # arguments for entrypoint go after the image + +NGINX=`docker create \ + --name ${PROJECT}_nginx \ + --network-alias nginx \ + --network $NETWORK \ + --restart always \ + --publish 8989:8989/tcp \ + --mount source=$WPDATA,target=/var/www/html \ + nginx:stable` + +echo Copying wpcli utils +docker cp ./bin/wait-for $WPCLI:/usr/local/bin/wait-for +docker cp ./bin/ci-init-cli.sh $WPCLI:/usr/local/bin/ci-init-cli.sh + +echo Copying nginx config +docker cp ./tests/e2e/config/nginx.conf $NGINX:/etc/nginx/conf.d/site.conf + +echo starting DB +docker start $DB + +echo starting WP +docker start $WP + +echo starting WPCLI +docker start $WPCLI + +echo Copying the built plugin to the shared volume +docker exec --user root $WPCLI mkdir -p /var/www/html/wp-content/mu-plugins +docker cp ./build/wpcomsh $WPCLI:/var/www/html/wp-content/mu-plugins/wpcomsh/ + +echo \"Fixing\" Permissions for WP-CLI +docker exec --user root $WPCLI chown -R www-data:www-data /var/www/html + +if [ "$1" = "private" ]; then + echo Setting the site to Private + docker exec $WPCLI /bin/sh /usr/local/bin/ci-init-cli.sh private +else + echo Setting the site to Public + docker exec $WPCLI /bin/sh /usr/local/bin/ci-init-cli.sh +fi + +echo starting NGINX +docker start $NGINX + +# Getting credentials so the test client can make logged-in requests +# @TODO Get an admin credential and check for the specific option on wp-admin/options-reading.php & other varied access + +SUBSCRIBER_USER_ID=`docker exec -it $WPCLI wp user create alice alice@example.com --role=subscriber --porcelain`; +echo "Got SUBSCRIBER_USER_ID from wpcli: ${SUBSCRIBER_USER_ID}" | cat -v; + +# For some reason, the value returned has a `\r` a the end and it breaks the next call unless we trim it :-/ +SUBSCRIBER_USER_ID=`echo $SUBSCRIBER_USER_ID | sed -E -e 's/^[^[:alnum:]]*//' -e 's/[^[:alnum:]]*$//'`; +echo "Trimmed SUBSCRIBER_USER_ID: ${SUBSCRIBER_USER_ID}" | cat -v; + +SUBSCRIBER_USER_ID=`echo $SUBSCRIBER_USER_ID | grep -E "^[[:digit:]]+$"`; +echo "Scrubbed SUBSCRIBER_USER_ID: ${SUBSCRIBER_USER_ID}"; + +if [ -z "$SUBSCRIBER_USER_ID" ]; then + echo "Could not create subscriber user. Result: ${SUBSCRIBER_USER_ID}"; + exit 1; +fi +echo SUBSCRIBER_USER_ID is ${SUBSCRIBER_USER_ID}; + +AUTH_COOKIE_NAME=`docker exec -it $WPCLI wp eval 'echo AUTH_COOKIE;'` +echo AUTH_COOKIE_NAME is ${AUTH_COOKIE_NAME}; +SUBSCRIBER_AUTH_COOKIE=`docker exec -it $WPCLI wp eval --user="${SUBSCRIBER_USER_ID}" "echo wp_generate_auth_cookie( get_current_user_id(), strtotime( '+99 day' ) );"` +echo SUBSCRIBER_AUTH_COOKIE is ${SUBSCRIBER_AUTH_COOKIE}; +SUBSCRIBER_RESTAPI_NONCE=`docker exec -it $WPCLI wp eval --user="${SUBSCRIBER_USER_ID}" "echo wp_create_nonce( 'wp_rest' );"`; +echo SUBSCRIBER_RESTAPI_NONCE is ${SUBSCRIBER_RESTAPI_NONCE}; + +echo \"Fixing\" Permissions for WP +docker exec --user root $WP chown -R www-data:www-data /var/www/html + +JEST=`docker create \ + --name ${PROJECT}_jest \ + --network-alias jest \ + --network ${NETWORK} \ + --env AUTH_COOKIE_NAME=${AUTH_COOKIE_NAME} \ + --env WPCOMSH_DEVMODE=${WPCOMSH_DEVMODE} \ + --env SUBSCRIBER_RESTAPI_NONCE=${SUBSCRIBER_RESTAPI_NONCE} \ + --env SUBSCRIBER_AUTH_COOKIE=${SUBSCRIBER_AUTH_COOKIE} \ + --env SUBSCRIBER_USER_ID=${SUBSCRIBER_USER_ID} \ + --entrypoint tail \ + node:18-bullseye-slim \ + -f /dev/null` # arguments for entrypoint go after the image + +echo Copying jest utils +docker cp ./bin/wait-for $JEST:/usr/local/bin/wait-for +docker cp ./bin/ci-init-e2e.sh $JEST:/usr/local/bin/ci-init-e2e.sh +TEMPDIR=`mktemp -d` +chmod 755 $TEMPDIR +mkdir -p $TEMPDIR/specs +cp ./package*.json $TEMPDIR/ +cp ./tests/e2e/jest.config.js $TEMPDIR/ +if [ "$1" = "private" ]; then + echo Copying Private Site test suite + SPEC=./tests/e2e/specs/private-site-access.test.js +else + echo Copying Public Site test suite + SPEC=./tests/e2e/specs/public-site-access.test.js +fi +cp $SPEC $TEMPDIR/specs/ +cp ./tests/e2e/specs/access-test-utils.js $TEMPDIR/specs/ +cp ./tests/e2e/specs/access-test-utils.test.js $TEMPDIR/specs/ +docker cp $TEMPDIR $JEST:/e2e +rm -rf $TEMPDIR + +[ "${WPCOMSH_DEVMODE}" = "1" ] && \ + echo HELLO SPEC DEVELOPER!; \ + npx chokidar-cli \ + "$SPEC" \ + "./tests/e2e/specs/access-test-utils.js" \ + "./tests/e2e/specs/access-test-utils.test.js" \ + -c "docker cp {path} $JEST:/e2e/specs/" -d 800 & \ + CHOKIDAR_PID=$! + +echo starting JEST +docker start $JEST +docker exec $JEST /bin/sh /usr/local/bin/ci-init-e2e.sh +finish diff --git a/projects/plugins/wpcomsh/bin/ci-init-cli.sh b/projects/plugins/wpcomsh/bin/ci-init-cli.sh new file mode 100644 index 0000000000000..bb62a2678a707 --- /dev/null +++ b/projects/plugins/wpcomsh/bin/ci-init-cli.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +echo "Waiting on db to be ready..."; +sh /usr/local/bin/wait-for db:3306 -t 30 || exit 1; + +echo "***** Making sure WordPress is installed"; +wp core install --url="http://nginx:8989" --title="wpcomsh test" --admin_user="admin" --admin_password="password" --admin_email="nobody@example.com"; + +echo "Emptying site"; +wp site empty --yes; + +echo "Setting permalink format"; +wp rewrite structure "/%year%/%monthnum%/%postname%/"; + +echo "Adding test content"; +POST_ID=`wp post create --post_title="this is a test post" --post_status="publish"`; + +echo "Linking the wpcomsh-loader.php file into mu-plugins"; +ln -s /var/www/html/wp-content/mu-plugins/wpcomsh/wpcomsh-loader.php /var/www/html/wp-content/mu-plugins/wpcomsh-loader.php + +echo "Defining various constants in a mini-plugin"; +echo " wp-content/mu-plugins/0-wpcomsh-early-constants.php + +if [ "$1" = "private" ]; then + echo "Setting the constant to indicate the site is private"; + echo "define( 'AT_PRIVACY_MODEL', 'wp_uploads' ); +" >> wp-content/mu-plugins/0-wpcomsh-early-constants.php + +fi + +echo "Initialized!"; diff --git a/projects/plugins/wpcomsh/bin/ci-init-e2e.sh b/projects/plugins/wpcomsh/bin/ci-init-e2e.sh new file mode 100644 index 0000000000000..f0ba7b24f6b02 --- /dev/null +++ b/projects/plugins/wpcomsh/bin/ci-init-e2e.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +echo "Setting up test dir"; +chown -R node:node /e2e; + +echo "Installing system dependencies"; +apt-get update; apt-get -y upgrade; apt-get -y install netcat; + +echo "Installing node dependencies"; +su -l node -c "npm --prefix /e2e ci"; + +echo "Waiting on nginx to be ready..."; +sh /usr/local/bin/wait-for nginx:8989 -t 30 || exit 1; + +echo "Waiting on WP php-fpm to be ready..."; +sh /usr/local/bin/wait-for wp:9000 -t 30 || exit 1; + +echo SUBSCRIBER_USER_ID is ${SUBSCRIBER_USER_ID}; +echo SUBSCRIBER_RESTAPI_NONCE is ${SUBSCRIBER_RESTAPI_NONCE}; +echo SUBSCRIBER_AUTH_COOKIE is ${SUBSCRIBER_AUTH_COOKIE}; + +NODE_ENV="AUTH_COOKIE_NAME=${AUTH_COOKIE_NAME} \ + SUBSCRIBER_AUTH_COOKIE='${SUBSCRIBER_AUTH_COOKIE}' \ + SUBSCRIBER_USER_ID=${SUBSCRIBER_USER_ID} \ + SUBSCRIBER_RESTAPI_NONCE=${SUBSCRIBER_RESTAPI_NONCE} \ +"; +echo NODE_ENV is ${NODE_ENV}; + +echo "Running e2e tests"; +if [ "${WPCOMSH_DEVMODE}" = "1" ]; then + su -l node -c "${NODE_ENV} npm --prefix /e2e run test:watch" +else + su -l node -c "${NODE_ENV} npm --prefix /e2e run test"; +fi diff --git a/projects/plugins/wpcomsh/bin/create-github-release.sh b/projects/plugins/wpcomsh/bin/create-github-release.sh new file mode 100755 index 0000000000000..0786d39b2c1ec --- /dev/null +++ b/projects/plugins/wpcomsh/bin/create-github-release.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +echo "Do not use this script anymore. Use the monorepo release tooling." >&2 +exit 1 +# Enable nicer messaging for build status. +BLUE_BOLD='\033[1;34m'; +GREEN_BOLD='\033[1;32m'; +RED_BOLD='\033[1;31m'; +YELLOW_BOLD='\033[1;33m'; +COLOR_RESET='\033[0m'; + +error () { + printf "\n🤯 ${RED_BOLD}$1${COLOR_RESET}\n" + exit 1 +} +status () { + printf "\n👩‍💻 ${BLUE_BOLD}$1${COLOR_RESET}\n" +} +success () { + printf "\n✅ ${GREEN_BOLD}$1${COLOR_RESET}\n" +} +warning () { + printf "\n${YELLOW_BOLD}$1${COLOR_RESET}\n" +} + +# We want to be in the root `wpcomsh` dir +cd `dirname "$0"` && cd .. + +# Test whether we're logged in to GitHub. +gh auth status --hostname github.com || exit 1 + +status "Creating GitHub release" + +CURRENTBRANCH=`git rev-parse --abbrev-ref HEAD` + +status "Reading the current version from wpcomsh.php" +VERSION=`awk '/[^[:graph:]]Version/{print $NF}' wpcomsh.php` +echo "Version that will be built and released is ${VERSION}" + +status "Making the build artifact" +make build + +ZIP_FILE="build/wpcomsh.${VERSION}.zip" + +if [ ! -r $ZIP_FILE ]; then + error "The build artifact could not be found at ${ZIP_FILE}" +fi + +status "Creating the release and attaching the build artifact" + +BRANCH="build/${VERSION}" +git checkout -b $BRANCH +gh release create --title "$VERSION" --notes "Release of version $VERSION. See README.md for details." "v${VERSION}" "${ZIP_FILE}" +STATUS=$? +git checkout $CURRENTBRANCH +git branch -D $BRANCH + +if [ "$STATUS" != 0 ]; then + error "Failed creating a release for ${VERSION}." +fi + +success "GitHub release complete." diff --git a/projects/plugins/wpcomsh/bin/i18n/generate-pot.sh b/projects/plugins/wpcomsh/bin/i18n/generate-pot.sh new file mode 100755 index 0000000000000..c7fda46c5895b --- /dev/null +++ b/projects/plugins/wpcomsh/bin/i18n/generate-pot.sh @@ -0,0 +1,62 @@ +#!/bin/bash +cd $(dirname "$(dirname "$(dirname "$0")")") + +rm -f languages/wpcomsh.pot + +find . -name '*.php' -and ! -path './build/*' -and ! -path './custom-colors/*' \ + -and \( \ + ! -path './vendor/*' \ + -or -path './vendor/automattic/jetpack-mu-wpcom/*' \ + -or -path './vendor/automattic/at-pressable-podcasting/*' \ + -or -path './vendor/automattic/custom-fonts-typekit/*' \ + -or -path './vendor/automattic/custom-fonts/*' \ + -or -path './vendor/automattic/jetpack-assets/*' \ + -or -path './vendor/automattic/jetpack-config/*' \ + -or -path './vendor/automattic/jetpack-post-list/*' \ + \) \ + -print \ +| sed -e 's,^\./,,' \ +| sort \ +| xargs xgettext \ + --keyword=__ \ + --keyword=_e \ + --keyword=_c \ + --keyword=esc_attr__ \ + --keyword=esc_attr_e \ + --keyword=__ngettext:1,2 \ + --keyword=__ngettext_noop:1,2 \ + --keyword=_n:1,2 \ + --keyword=_n_noop:1,2 \ + --keyword=_nc:1,2 \ + --keyword=_x:1,2c \ + --keyword=_ex:1,2c \ + --keyword=esc_attr_x:1,2c \ + --keyword=esc_html__ \ + --keyword=esc_html_e \ + --keyword=esc_html_x:1,2c \ + --keyword=_nx:1,2,4c \ + --keyword=_nx_noop:1,2,3c \ + --language=php \ + --output=languages/wpcomsh.pot \ + --from-code utf-8 \ + --add-comments=translators \ + --copyright-holder=Automattic \ + --msgid-bugs-address=help@wordpress.com \ + --package-name="WP.com Site Helper" \ + +echo ' + +#. Plugin Name of the plugin +msgid "WP.com Site Helper" +msgstr "" + +#. Description of the plugin +msgid "WordPress.com provided functionality & tools pre-installed and activated on all Atomic Sites" +msgstr "" +' >> languages/wpcomsh.pot + +if [ -e languages/wpcomsh.pot ]; then + echo 'languages/wpcomsh.pot file was generated.' +else + echo 'The file languages/wpcomsh.pot was not generated. Please check the output above.' +fi diff --git a/projects/plugins/wpcomsh/bin/i18n/update-translations.sh b/projects/plugins/wpcomsh/bin/i18n/update-translations.sh new file mode 100644 index 0000000000000..48862330076f0 --- /dev/null +++ b/projects/plugins/wpcomsh/bin/i18n/update-translations.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +cd $(dirname "$(dirname "$(dirname "$0")")") + +curl -Ls https://translate.wordpress.com/api/projects/wpcom/wpcomsh | jq -c '.translation_sets[]' | while IFS= read -r lang; do + LANG_NAME=$(echo $lang | jq -r '.name') + LANG_LOCALE=$(echo $lang | jq -r '.locale') + LANG_SLUG=$(echo $lang | jq -r '.slug') + + LANG_WP_LOCALE=$(echo $lang | jq -r '.wp_locale') + + if [[ "$LANG_WP_LOCALE" == "null" ]]; then + LANG_WP_LOCALE=${LANG_LOCALE} + fi + + if [[ "$LANG_WP_LOCALE" == "de_DE" ]] && [[ "$LANG_SLUG" == "formal" ]]; then + LANG_WP_LOCALE="de_DE_formal" + fi + + LANG_FILENAME=languages/wpcomsh-$LANG_WP_LOCALE + + echo "Downloading $LANG_NAME ($LANG_LOCALE/$LANG_SLUG → $LANG_WP_LOCALE)" + curl -so $LANG_FILENAME.po https://translate.wordpress.com/projects/wpcom/wpcomsh/$LANG_LOCALE/$LANG_SLUG/export-translations/?format=po + [[ -e $LANG_FILENAME.po ]] && msgfmt $LANG_FILENAME.po -o $LANG_FILENAME.mo +done diff --git a/projects/plugins/wpcomsh/bin/install-wp-tests.sh b/projects/plugins/wpcomsh/bin/install-wp-tests.sh new file mode 100755 index 0000000000000..c401896c91322 --- /dev/null +++ b/projects/plugins/wpcomsh/bin/install-wp-tests.sh @@ -0,0 +1,155 @@ +#!/usr/bin/env bash + +if [ $# -lt 3 ]; then + echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" + exit 1 +fi + +DB_NAME=$1 +DB_USER=$2 +DB_PASS=$3 +DB_HOST=${4-localhost} +WP_VERSION=${5-latest} +SKIP_DB_CREATE=${6-false} + +TMPDIR=${TMPDIR-/tmp} +TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//") +WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib} +WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress/} + +download() { + if [ `which curl` ]; then + curl -s "$1" > "$2"; + elif [ `which wget` ]; then + wget -nv -O "$2" "$1" + fi +} + +if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then + WP_BRANCH=${WP_VERSION%\-*} + WP_TESTS_TAG="branches/$WP_BRANCH" + +elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then + WP_TESTS_TAG="branches/$WP_VERSION" +elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then + if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then + # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x + WP_TESTS_TAG="tags/${WP_VERSION%??}" + else + WP_TESTS_TAG="tags/$WP_VERSION" + fi +elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + WP_TESTS_TAG="trunk" +else + # http serves a single offer, whereas https serves multiple. we only want one + download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json + grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json + LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') + if [[ -z "$LATEST_VERSION" ]]; then + echo "Latest WordPress version could not be found" + exit 1 + fi + WP_TESTS_TAG="tags/$LATEST_VERSION" +fi +set -ex + +install_wp() { + + if [ -d $WP_CORE_DIR ]; then + return; + fi + + mkdir -p $WP_CORE_DIR + + if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + mkdir -p $TMPDIR/wordpress-nightly + download https://wordpress.org/nightly-builds/wordpress-latest.zip $TMPDIR/wordpress-nightly/wordpress-nightly.zip + unzip -q $TMPDIR/wordpress-nightly/wordpress-nightly.zip -d $TMPDIR/wordpress-nightly/ + mv $TMPDIR/wordpress-nightly/wordpress/* $WP_CORE_DIR + else + if [ $WP_VERSION == 'latest' ]; then + local ARCHIVE_NAME='latest' + elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then + # https serves multiple offers, whereas http serves single. + download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json + if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then + # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x + LATEST_VERSION=${WP_VERSION%??} + else + # otherwise, scan the releases and get the most up to date minor version of the major release + local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'` + LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1) + fi + if [[ -z "$LATEST_VERSION" ]]; then + local ARCHIVE_NAME="wordpress-$WP_VERSION" + else + local ARCHIVE_NAME="wordpress-$LATEST_VERSION" + fi + else + local ARCHIVE_NAME="wordpress-$WP_VERSION" + fi + download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz + tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR + fi + + download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php +} + +install_test_suite() { + # portable in-place argument for both GNU sed and Mac OSX sed + if [[ $(uname -s) == 'Darwin' ]]; then + local ioption='-i.bak' + else + local ioption='-i' + fi + + # set up testing suite if it doesn't yet exist + if [ ! -d $WP_TESTS_DIR ]; then + # set up testing suite + mkdir -p $WP_TESTS_DIR + svn co https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes + svn co --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data + fi + + if [ ! -f wp-tests-config.php ]; then + download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php + # remove all forward slashes in the end + WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") + sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php + fi + +} + +install_db() { + + if [ ${SKIP_DB_CREATE} = "true" ]; then + return 0 + fi + + # parse DB_HOST for port or socket references + local PARTS=(${DB_HOST//\:/ }) + local DB_HOSTNAME=${PARTS[0]}; + local DB_SOCK_OR_PORT=${PARTS[1]}; + local EXTRA="" + + if ! [ -z $DB_HOSTNAME ] ; then + if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then + EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" + elif ! [ -z $DB_SOCK_OR_PORT ] ; then + EXTRA=" --socket=$DB_SOCK_OR_PORT" + elif ! [ -z $DB_HOSTNAME ] ; then + EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" + fi + fi + + # create database + mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA +} + +install_wp +install_test_suite +install_db diff --git a/projects/plugins/wpcomsh/bin/update-version.php b/projects/plugins/wpcomsh/bin/update-version.php new file mode 100644 index 0000000000000..1ef220e1782ca --- /dev/null +++ b/projects/plugins/wpcomsh/bin/update-version.php @@ -0,0 +1,36 @@ +version; + + $lines = array(); + $file = file( $filename ); + + foreach ( $file as $line ) { + if ( stripos( $line, ' * Version: ' ) !== false ) { + $line = " * Version: {$version}\n"; + } + if ( stripos( $line, "define( 'WPCOMSH_VERSION'," ) !== false ) { + $line = "define( 'WPCOMSH_VERSION', '{$version}' );\n"; + } + + $lines[] = $line; + } + + file_put_contents( $filename, $lines ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents +} + +replace_version( 'wpcomsh.php' ); diff --git a/projects/plugins/wpcomsh/bin/update-version.sh b/projects/plugins/wpcomsh/bin/update-version.sh new file mode 100755 index 0000000000000..a306f9b5809da --- /dev/null +++ b/projects/plugins/wpcomsh/bin/update-version.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +set -euo pipefail + +# Enable nicer messaging for build status. +BLUE_BOLD='\033[1;34m'; +GREEN_BOLD='\033[1;32m'; +RED_BOLD='\033[1;31m'; +YELLOW_BOLD='\033[1;33m'; +COLOR_RESET='\033[0m'; + +error () { + printf "\n🤯 ${RED_BOLD}$1${COLOR_RESET}\n" +} +status () { + printf "\n👩‍💻 ${BLUE_BOLD}$1${COLOR_RESET}\n" +} +success () { + printf "\n✅ ${GREEN_BOLD}$1${COLOR_RESET}\n" +} +warning () { + printf "\n${YELLOW_BOLD}$1${COLOR_RESET}\n" +} + +SCRIPT_DIR=$(dirname "$(realpath "$0")" || true) +PACKAGE_JSON="$SCRIPT_DIR/../package.json" +CURRENT_VERSION=$(grep '"version":' "$PACKAGE_JSON" | awk -F '"' '{print $4}' || true) +if [ -n "$CURRENT_VERSION" ]; then + status "Current version is $CURRENT_VERSION" +fi + +status "Update version" + +echo "Enter the version number to update to, for example 1.0.0: " +read -r VERSION + +status "Bump version in package.json" +npm --no-git-tag-version version $VERSION || { + error "ERROR: Invalid version number." + exit 1 +} + +status "Bump version in other files" +php bin/update-version.php || { + error "Failed." + exit 1 +} + +status "Make sure the following changes have been made, then commit them and push them to a new PR." +echo "- package.json: new version number" +echo "- wpcomsh.php: new version numbers" +echo "\n" +git status diff --git a/projects/plugins/wpcomsh/bin/wait-for b/projects/plugins/wpcomsh/bin/wait-for new file mode 100644 index 0000000000000..ca724b854ccee --- /dev/null +++ b/projects/plugins/wpcomsh/bin/wait-for @@ -0,0 +1,80 @@ +#!/bin/sh +# Copied from https://github.com/eficode/wait-for/blob/828386460d138e418c31a1ebf87d9a40f5cedc32/wait-for + +TIMEOUT=15 +QUIET=0 + +echoerr() { + if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi +} + +usage() { + exitcode="$1" + cat << USAGE >&2 +Usage: + $cmdname host:port [-t timeout] [-- command args] + -q | --quiet Do not output any status messages + -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit "$exitcode" +} + +wait_for() { + for i in `seq $TIMEOUT` ; do + nc -z "$HOST" "$PORT" > /dev/null 2>&1 + + result=$? + if [ $result -eq 0 ] ; then + if [ $# -gt 0 ] ; then + exec "$@" + fi + exit 0 + fi + sleep 1 + done + echo "Operation timed out" >&2 + exit 1 +} + +while [ $# -gt 0 ] +do + case "$1" in + *:* ) + HOST=$(printf "%s\n" "$1"| cut -d : -f 1) + PORT=$(printf "%s\n" "$1"| cut -d : -f 2) + shift 1 + ;; + -q | --quiet) + QUIET=1 + shift 1 + ;; + -t) + TIMEOUT="$2" + if [ "$TIMEOUT" = "" ]; then break; fi + shift 2 + ;; + --timeout=*) + TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + break + ;; + --help) + usage 0 + ;; + *) + echoerr "Unknown argument: $1" + usage 1 + ;; + esac +done + +if [ "$HOST" = "" -o "$PORT" = "" ]; then + echoerr "Error: you need to provide a host and port to test." + usage 2 +fi + +wait_for "$@" diff --git a/projects/plugins/wpcomsh/block-theme-footer-credits/block-theme-footer-credits.php b/projects/plugins/wpcomsh/block-theme-footer-credits/block-theme-footer-credits.php new file mode 100644 index 0000000000000..0e3c20561a5d4 --- /dev/null +++ b/projects/plugins/wpcomsh/block-theme-footer-credits/block-theme-footer-credits.php @@ -0,0 +1,25 @@ +should_update_footer_credits( $blog_id ) ) { + return; + } + + // Filter render blocks and find the footer. + add_filter( 'render_block', array( $this, 'maybe_add_markup_to_footer_template' ), 10, 2 ); + } + + /** + * Filters the blocks being rendered in render_block(), before it's processed, + * locate the footer template and update its content. + * + * @param string $block_content Block HTML content. + * @param array $block The block being rendered, as a single parsed block object. + * @return string The updated block HTML content. + */ + public function maybe_add_markup_to_footer_template( $block_content, $block ) { + + if ( $block['blockName'] !== 'core/template-part' || $block['attrs']['slug'] !== 'footer' ) { + return $block_content; + } + + // Try to find link to either WordPress.com or .org. + // Here we're expecting something along the lines of `

Powered by WordPress

` + $credit_regex = '/[^>]*)]*href="(http|https):\/\/(www\.)?wordpress.(org|com)(\/)?(\?\w+\=\w+)?"(\s?\w+\="[\w\-]+")*>.*<\/a>[^<]*/'; // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText + $credit_matches = preg_match( $credit_regex, $block_content ); + + // If there's a successful match, replace with our content. + if ( $credit_matches ) { + $new_value = $this->get_credit_link(); + return preg_replace( $credit_regex, $new_value, $block_content ); + } + + // If there is not a sucessful match append additional markup. + return $block_content . $this->get_credit_html(); + } + + /** + * Returns an HTML snipper with the WordPress.com link embedded. + * + * The innerHTML of the P tag may vary depending on which content the user has + * selected in the Customizer, if any. + * + * @return string The footer credit HTML with WordPress.com link. + */ + public function get_credit_html() { + $credit_link = $this->get_credit_link(); + if ( empty( $credit_link ) ) { + return ''; + } + return '
' . + '

' . + $credit_link . '

'; + } + + /** + * Returns the WordPress.com footer credit link HTML. + * + * @return string The footer link HTML + */ + public function get_credit_link() { + // Set any globals so the JS can access them. + $lang = get_bloginfo( 'language' ); + $credit_url = apply_filters( 'wpcom_better_footer_credit_url', 'https://wordpress.com/?ref=footer_blog', $lang ); + if ( ! empty( $credit_url ) ) { + $credit_link = sprintf( '%s.', esc_url( $credit_url ), __( 'Blog at WordPress.com', 'wpcomsh' ) ); + } else { + $credit_link = ''; + } + return apply_filters( 'wpcom_better_footer_credit_link', $credit_link, $lang ); + } + + /** + * Determines whether the footer credits bar should be updated for the current site. + * Should return as early as possible. + * Can be overridden with the WordPress filter: `wpcom_should_show_block_theme_footer_credits` + * + * @param int $blog_id current blog ID. + * @return boolean true if the credits should show, false otherwise. + */ + public function should_update_footer_credits( $blog_id ) { + // Reject empty argument. + if ( empty( $blog_id ) ) { + return false; + } + + // If we're not using the site editor-capable theme, don't show. + if ( ! $this->is_block_theme() ) { + return false; + } + + // If the current request is an API request, don't show. + if ( $this->is_api_request() ) { + return false; + } + + // Are we in Coming Soon mode? Don't show. + // Site members don't see the Coming Soon page when they're logged in. + // So we should also show the footer banner for those who can see the site. + // For all logged out coming soon page hits, we'll hide the banner. + if ( $this->is_coming_soon( $blog_id ) && ! $this->is_site_member_logged_in() ) { + return false; + } + + return apply_filters( 'wpcom_should_show_block_theme_footer_credits', true ); + } + + /** + * Determine if the current theme is a block theme. + * + * @return bool True if the theme is a block theme. False otherwise. + */ + public function is_block_theme() { + return function_exists( 'wp_is_block_theme' ) && wp_is_block_theme(); + } + + /** + * Determine if the current request is an API request + * + * @return bool True if the request is an API request, false otherwise. + */ + private function is_api_request() { + $is_api_request = defined( 'REST_API_REQUEST' ) && REST_API_REQUEST; + if ( $is_api_request ) { + return true; + } + + return false; + } + + /** + * Determine if the current site is in coming soon mode. + * + * @param int $blog_id The blog_id. + * @return bool True if the request is in coming soon mode, false otherwise. + */ + private function is_coming_soon( $blog_id ) { + if ( function_exists( 'is_wpcom_public_coming_soon_enabled' ) ) { + if ( is_wpcom_public_coming_soon_enabled( $blog_id ) ) { + return true; + } + } + return false; + } + + /** + * Checks if a site member is logged in while visiting the site. + * + * @return bool True if logged in, false otherwise. + */ + private function is_site_member_logged_in() { + if ( is_user_logged_in() && current_user_can( 'read' ) ) { + return true; + } + + return false; + } +} diff --git a/projects/plugins/wpcomsh/changelog/.gitkeep b/projects/plugins/wpcomsh/changelog/.gitkeep new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/projects/plugins/wpcomsh/changelog/add-converge-wpcomsh b/projects/plugins/wpcomsh/changelog/add-converge-wpcomsh new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/plugins/wpcomsh/changelog/add-converge-wpcomsh @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/wpcomsh/changelog/add-converge-wpcomsh#2 b/projects/plugins/wpcomsh/changelog/add-converge-wpcomsh#2 new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/wpcomsh/changelog/add-converge-wpcomsh#2 @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/wpcomsh/changelog/initial-version b/projects/plugins/wpcomsh/changelog/initial-version new file mode 100644 index 0000000000000..fb1837c901e51 --- /dev/null +++ b/projects/plugins/wpcomsh/changelog/initial-version @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Initial version. diff --git a/projects/plugins/wpcomsh/class-atomic-persistent-data.php b/projects/plugins/wpcomsh/class-atomic-persistent-data.php new file mode 100644 index 0000000000000..cd1d41c6737fa --- /dev/null +++ b/projects/plugins/wpcomsh/class-atomic-persistent-data.php @@ -0,0 +1,26 @@ + 'example-plugin interferes with Jetpack sync and has been disabled.' + * ), + * The html messaging is presented as a dismissible error admin notice when an unsupported plugin is deactivated. + * + * @var string[] + */ + public $incompatible_plugins = array( + // "reset" - break/interfere with provided functionality. + 'advanced-database-cleaner/advanced-db-cleaner.php' => '"advanced-database-cleaner" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'advanced-reset-wp/advanced-reset-wp.php' => '"advanced-reset-wp" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'advanced-wp-reset/advanced-wp-reset.php' => '"advanced-wp-reset" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'backup/backup.php' => '"backup" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'better-wp-security/better-wp-security.php' => '"better-wp-security" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'cf7-pipedrive-integration/class-cf7-pipedrive.php' => '"cf7-pipedrive-integration" has been deactivated, it interferes with site operation and is not supported on WordPress.com.', + 'database-browser/database-browser.php' => '"database-browser" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'duplicator/duplicator.php' => '"duplicator" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'extended-wp-reset/extended-wp-reset.php' => '"extended-wp-reset" has been deactivated, it interferes with site operation and is not supported on WordPress.com.', + 'file-manager-advanced/file_manager_advanced.php' => '"file-manager-advanced" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'file-manager/file-manager.php' => '"file-manager" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'hide-my-wp/index.php' => '"hide-my-wp" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'plugins-garbage-collector/plugins-garbage-collector.php' => '"plugins-garbage-collector" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'post-type-switcher/post-type-switcher.php' => '"post-type-switcher" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'reset-wp/reset-wp.php' => '"reset-wp" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'reset/data_reset.php' => '"reset" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'secure-file-manager/secure-file-manager.php' => '"secure-file-manager" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'ultimate-reset/ultimate-reset.php' => '"ultimate-reset" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'ultimate-wp-reset/ultimate-wordpress-reset.php' => '"ultimate-wp-reset" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'username-changer/username-changer.php' => '"username-changer" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'username-updater/username-updater.php' => '"username-updater" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'vamtam-offline-jetpack/vamtam-offline-jetpack.php' => '"vamtam-offline-jetpack" has been deactivated, an active Jetpack Connection is required for your site to operate properly on WordPress.com.', + 'wd-youtube/wd-youtube.php' => '"wd-youtube" has been deactivated, it interferes with site operation and is not supported on WordPress.com.', + 'wordpress-database-reset/wp-reset.php' => '"wordpress-database-reset" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'wordpress-reset/wordpress-reset.php' => '"wordpress-reset" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'wp-automatic/wp-automatic.php' => '"wp-automatic" has been deactivated, it interferes with site operation and is not supported on WordPress.com.', + 'wp-clone-by-wp-academy/wpclone.php' => '"wp-clone-by-wp-academy" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'wp-config-file-editor/wp-config-file-editor.php' => '"wp-config-file-editor" has been deactivated, it messes up data necessary to manage your site and is not supported on WordPress.com.', + 'wp-dbmanager/wp-dbmanager.php' => '"wp-dbmanager" has been deactivated, it messes up data necessary to manage your site and is not supported on WordPress.com.', + 'wp-file-manager/file_folder_manager.php' => '"wp-file-manager" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'wp-phpmyadmin-extension/index.php' => '"wp-phpmyadmin-extension" has been deactivated, it interferes with site operation. You can access phpMyAdmin under Settings > Hosting Config', + 'wp-prefix-changer/index.php' => '"wp-prefix-changer" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'wp-reset/wp-reset.php' => '"wp-reset" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'wp-uninstaller-by-azed/wp-uninstaller-by-azed.php' => '"wp-uninstaller-by-azed" is not supported on WordPress.com.', + 'wpmu-database-reset/wpmu-database-reset.php' => '"wpmu-database-reset" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'wps-hide-login/wps-hide-login.php' => '"wps-hide-login" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + 'z-inventory-manager/z-inventory-manager.php' => '"z-inventory-manager" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + + // Backup. + 'backup-wd/backup-wd.php' => '"backup-wd" has been deactivated, WordPress.com handles managing your site backups for you.', + 'backupwordpress/backupwordpress.php' => '"backupwordpress" has been deactivated, WordPress.com handles managing your site backups for you.', + 'backwpup/backwpup.php' => '"backwpup" has been deactivated, WordPress.com handles managing your site backups for you.', + 'backwpup-pro/backwpup.php' => '"backwpup-pro" has been deactivated, WordPress.com handles managing your site backups for you.', + 'jetpack-backup/jetpack-backup.php' => '"jetpack-backup" has been deactivated, WordPress.com handles managing your site backups for you.', + 'wp-db-backup/wp-db-backup.php' => '"wp-db-backup" has been deactivated, WordPress.com handles managing your site backups for you.', + + // Caching/performance. + 'breeze/breeze.php' => '"breeze" has been deactivated, WordPress.com automatically handles caching for your site.', + 'cache-enabler/cache-enabler.php' => '"cache-enabler" has been deactivated, WordPress.com automatically handles caching for your site.', + 'comet-cache/comet-cache.php' => '"comet-cache" has been deactivated, WordPress.com automatically handles caching for your site.', + 'hyper-cache/plugin.php' => '"hyper-cache" has been deactivated, WordPress.com automatically handles caching for your site.', + 'jch-optimize/jch-optimize.php' => '"jch-optimize" has been deactivated, WordPress.com automatically handles caching for your site.', + 'performance-lab/load.php' => '"performance-lab" has been deactivated, WordPress.com automatically handles caching and database optimization for your site.', + 'powered-cache/powered-cache.php' => '"powered-cache" has been deactivated, WordPress.com automatically handles caching for your site.', + 'quick-cache/quick-cache.php' => '"quick-cache" has been deactivated, WordPress.com automatically handles caching for your site.', + 'redis-cache/redis-cache.php' => '"redis-cache" has been deactivated, WordPress.com automatically handles caching for your site.', + 'sg-cachepress/sg-cachepress.php' => '"sg-cachepress" has been deactivated, WordPress.com automatically handles caching for your site.', + 'w3-total-cache/w3-total-cache.php' => '"w3-total-cache" has been deactivated, WordPress.com automatically handles caching for your site.', + 'wp-cache/wp-cache.php' => '"wp-cache" has been deactivated, WordPress.com automatically handles caching for your site.', + 'wp-fastest-cache/wpFastestCache.php' => '"wp-fastest-cache" has been deactivated, WordPress.com automatically handles caching for your site.', + 'wp-optimizer/wp-optizer.php' => '"wp-optimizer" has been deactivated, "performance" related plugins may break your site or cause issues and are not supported on WordPress.com.', // p9F6qB-66o-p2 + 'wp-speed-of-light/wp-speed-of-light.php' => '"wp-speed-of-light" has been deactivated, WordPress.com automatically handles caching for your site.', + 'wp-super-cache/wp-cache.php' => '"wp-super-cache" has been deactivated, WordPress.com automatically handles caching for your site.', + + // SQL heavy. + 'another-wordpress-classifieds-plugin/awpcp.php' => '"another-wordpress-classifieds-plugin" has been deactivated, it is known to cause severe database performance issues and is not supported.', + 'broken-link-checker/broken-link-checker.php' => '"broken-link-checker" has been deactivated, it is known to cause severe database performance issues and is not supported.', + 'leads/leads.php' => '"leads" has been deactivated, it is known to cause severe database performance issues and is not supported.', + 'native-ads-adnow/adnow-widget.php' => '"native-ads-now" has been deactivated, it is known to cause severe database performance issues and is not supported.', + 'ol_scrapes/ol_scrapes.php' => '"ol_scrapes" has been deactivated, it is known to cause severe database performance issues and is not supported.', + 'page-visit-counter/page-visit-counter.php' => '"page-visit-counter" has been deactivated, plugins that insert or update the database on page load can cause severe performance issues for your site and are not supported.', + 'post-views-counter/post-views-counter.php' => '"post-views-counter" has been deactivated, plugins that insert or update the database on page load can cause severe performance issues for your site and are not supported.', + 'tokenad/token-ad.php' => '"tokenad" has been deactivated, it is known to cause severe database performance issues and is not supported.', + 'top-10/top-10.php' => '"top-10" has been deactivated, it is known to cause severe database performance issues and is not supported.', + 'userpro/index.php' => '"userpro" has been deactivated, it is known to cause severe database performance issues and is not supported.', + 'wordpress-popular-posts/wordpress-popular-posts.php' => '"wordpress-popular-posts" has been deactivated, it is known to cause severe database performance issues and is not supported.', + 'wp-cerber/wp-cerber.php' => '"wp-cerber" has been deactivated, it is known to cause severe database performance issues and is not supported.', + 'wp-inject/wpinject.php' => '"wp-inject" has been deactivated, it is known to cause severe database performance issues and is not supported.', + 'wp-postviews/wp-postviews.php' => '"wp-postviews" has been deactivated, plugins that insert or update the database on page load can cause severe performance issues for your site and are not supported.', + 'wp-rss-aggregator/wp-rss-aggregator.php' => '"wp-rss-aggregator" has been deactivated, it is known to cause severe database performance issues and is not supported.', + 'wp-rss-feed-to-post/wp-rss-feed-to-post.php' => '"wp-rss-feed-to-post" has been deactivated, it is known to cause severe database performance issues and is not supported.', + 'wp-rss-wordai/wp-rss-wordai.php' => '"wp-rss-wordai" has been deactivated, it is known to cause severe database performance issues and is not supported.', + 'wp-session-manager/wp-session-manager.php' => '"wp-session-manager" has been deactivated, it is known to cause severe database performance issues and is not supported.', + 'wp-slimstat/wp-slimstat.php' => '"wp-slimstat" has been deactivated, plugins that insert or update the database on page load can cause severe performance issues for your site and are not supported.', + 'wp-statistics/wp-statistics.php' => '"wp-statistics" has been deactivated, plugins that insert or update the database on page load can cause severe performance issues for your site and are not supported.', + 'wp-ulike/wp-ulike.php' => '"wp-ulike" has been deactivated, plugins that insert or update the database on page load can cause severe performance issues for your site and are not supported.', + 'WPRobot5/wprobot.php' => '"WPRobot5" has been deactivated, plugins that insert or update the database on page load can cause severe performance issues for your site and are not supported.', + + // Security. + 'antihacker/antihacker.php' => '"antihacker" has been deactivated, "security" related plugins may break your site or cause performance issues for your site and are not supported on WordPress.com.', + 'deactivate-xml-rpc-service/deactivate-xml-rpc-service.php' => '"deactivate-xml-rpc-service" has been deactivated, XML-RPC is required for your Jetpack Connection on WordPress.com.', + 'disable-xml-rpc-api/disable-xml-rpc-api.php' => '"disable-xml-rpc-api" has been deactivated, XML-RPC is required for your Jetpack Connection on WordPress.com.', + 'disable-xml-rpc-fully/disable-xml-rpc-fully.php' => '"disable-xml-rpc-fully" has been deactivated, XML-RPC is required for your Jetpack Connection on WordPress.com.', + 'disable-xml-rpc-unset-x-pingback/index.php' => '"disable-xml-rpc-unset-x-pingback" has been deactivated, XML-RPC is required for your Jetpack Connection on WordPress.com.', + 'disable-xml-rpc/disable-xml-rpc.php' => '"disable-xml-rpc" has been deactivated, XML-RPC is required for your Jetpack Connection on WordPress.com.', + 'manage-xml-rpc/manage-xml-rpc.php' => '"manage-xml-rpc" has been deactivated, XML-RPC is required for your Jetpack Connection on WordPress.com.', + 'really-simple-ssl-pro/really-simple-ssl-pro.php' => '"really-simple-ssl-pro" is not supported on WordPress.com.', + 'sg-security/sg-security.php' => '"sg-security" has been deactivated, "security" related plugins may break your site or cause performance issues for your site and are not supported on WordPress.com.', + 'simple-xml-rpc-disabler/simple-xml-rpc-disabler.php' => '"simple-xml-rpc-disabler" has been deactivated, XML-RPC is required for your Jetpack Connection on WordPress.com.', + 'stopbadbots/stopbadbots.php' => '"stopbadbots" has been deactivated, "security" related plugins may break your site or cause performance issues for your site and are not supported on WordPress.com.', + 'wee-remove-xmlrpc-methods/wee-remove-xmlrpc-methods.php' => '"wee-remove-xmlrpc-methods" has been deactivated, XML-RPC is required for your Jetpack Connection on WordPress.com.', + 'wordfence/wordfence.php' => '"wordfence" has been deactivated, "security" related plugins may break your site or cause performance issues for your site and are not supported on WordPress.com.', + 'wp-hide-security-enhancer/wp-hide.php' => '"wp-hide-security-enhancer" has been deactivated, "security" related plugins may break your site or cause performance issues for your site and are not supported on WordPress.com.', + 'wp-security-hardening/wp-hardening.php' => '"wp-security-hardening" has been deactivated. It breaks WordPress.com required plugins.', // p9F6qB-66o-p2 + 'wp-simple-firewall/wp-simple-firewall.php' => '"wp-simple-firewall" has been deactivated, it deletes data necessary to manage your site and is not supported on WordPress.com.', + + // Spam. + 'e-mail-broadcasting/e-mail-broadcasting.php' => '"e-mail-broadcasting" has been deactivated, plugins that support sending e-mails in bulk are not supported on WordPress.com.', + 'mailit/mailit.php' => '"mailit" has been deactivated, plugins that support sending e-mails in bulk are not supported on WordPress.com.', + 'send-email-from-admin/send-email-from-admin.php' => '"send-email-from-admin" has been deactivated, plugins that support sending e-mails in bulk are not supported on WordPress.com.', + + // Cloning/staging. + 'flo-launch/flo-launch.php' => 'Staging plugins delete data necessary to manage your site and are not supported on WordPress.com. flo-launch has been deactivated.', + 'wp-staging/wp-staging.php' => 'Staging plugins delete data necessary to manage your site and are not supported on WordPress.com. wp-staging has been deactivated.', + + // Misc. + 'adult-mass-photos-downloader/adult-mass-photos-downloader.php' => '"adult-mass-photos-downloader" is not supported on WordPress.com.', + 'adult-mass-videos-embedder/adult-mass-videos-embedder.php' => '"adult-mass-videos-embedder" is not supported on WordPress.com.', + 'ari-adminer/ari-adminer.php' => '"ari-adminer" is not supported on WordPress.com.', + 'automatic-video-posts' => '"automatic-video-posts" is not supported on WordPress.com.', + 'blogmatic-poster/index.php' => '"blogmatic-poster" is not supported on WordPress.com.', + 'blogmatic/index.php' => '"blogmatic" is not supported on WordPress.com.', + 'bwp-minify/bwp-minify.php' => '"bwp-minify" is not supported on WordPress.com.', + 'clearfy/clearfy.php' => '"clearfy" is not supported on WordPress.com.', + 'cornerstone/main.php' => '"cornerstone" is not supported on WordPress.com.', + 'cryptocurrency-pricing-list/cryptocurrency-pricing-list-and-ticker.php' => '"cryptocurrency-pricing-list" is not supported on WordPress.com.', + 'db-access-adminer/db-access-adminer.php' => '"db-access-adminer" is not supported on WordPress.com.', + 'event-espresso-decaf/espresso.php' => '"event-espresso-decaf" is not supported on WordPress.com.', + 'facetwp-manipulator/facetwp-manipulator.php' => '"facetwp-manipulator" is not supported on WordPress.com.', + 'fast-velocity-minify/fvm.php' => '"fast-velocity-minify" is not supported on WordPress.com.', + 'nginx-helper/nginx-helper.php' => '"nginx-helper" is not supported on WordPress.com.', + 'p3/p3.php' => '"p3" is not supported on WordPress.com.', + 'pexlechris-adminer/pexlechris-adminer.php' => '"pexlechris-adminer" is not supported on WordPress.com.', + 'plugin-detective/plugin-detective.php' => '"plugin-detective" is not supported on WordPress.com.', + 'porn-embed/Porn-Embed.php' => '"porn-embed" is not supported on WordPress.com.', + 'propellerads-official/propeller-ads.php' => '"propellerads-official" is not supported on WordPress.com.', + 'really-simple-ssl/rlrsssl-really-simple-ssl.php' => '"really-simple-ssl" is not supported on WordPress.com.', + 'speed-contact-bar/speed-contact-bar.php' => '"speed-contact-bar" is not supported on WordPress.com.', + 'trafficzion/trafficzion.php' => '"trafficzion" is not supported on WordPress.com.', + 'tubeace/tubeace.php' => '"tubeace" is not supported on WordPress.com.', + 'unplug-jetpack/unplug-jetpack.php' => '"unplug-jetpack" is not supported on WordPress.com.', + 'video-importer/video-importer.php' => '"video-importer" is not supported on WordPress.com.', + 'woozone/plugin.php' => '"woozone" is not supported on WordPress.com.', + 'wp-cleanfix/index.php' => '"wp-cleanfix" is not supported on WordPress.com.', + 'wp-file-upload/wordpress_file_upload.php' => '"wp-file-upload" is not supported on WordPress.com.', + 'wp-monero-miner-pro/monero-miner-pro.php' => '"wp-monero-miner-pro" is not supported on WordPress.com.', + 'wp-monero-miner-using-coin-hive/wp-coin-hive.php' => '"wp-monero-miner-using-coin-hive" is not supported on WordPress.com.', + 'wp-optimize-by-xtraffic/wp-optimize-by-xtraffic.php' => '"wp-optimize-by-xtraffic" is not supported on WordPress.com.', + 'wpcom-migration/wpcom-migration.php' => '"wpcom-migration" is not supported on WordPress.com.', + 'wpematico/wpematico.php' => '"wpematico" is not supported on WordPress.com.', + 'wpstagecoach/wpstagecoach.php' => '"wpstagecoach" is not supported on WordPress.com.', // p9F6qB-66o-p2 + 'yuzo-related-post/yuzo_related_post.php' => '"yuzo-related-post" is not supported on WordPress.com.', + 'zapp-proxy-server/zapp-proxy-server.php' => '"zapp-proxy-server" is not supported on WordPress.com.', + + // CRM. + 'civicrm/civicrm.php' => '"civicrm" is not supported on WordPress.com.', // p9F6qB-66o-p2 + ); + + /** + * Admin notices. + * + * @var array + */ + protected $admin_notices = array(); + + /** + * Jetpack_Plugin_Compatibility constructor. + */ + protected function __construct() { + // Disable plugin activation for unsupported plugins. + add_action( 'load-plugins.php', array( $this, 'check_plugin_compatibility' ) ); + // Replace "Activate" plugin link for plugins that should not be activated (plugins.php). + add_filter( 'plugin_action_links', array( $this, 'disable_plugin_activate_link' ), 10, 2 ); + add_filter( 'network_admin_plugin_action_links', array( $this, 'disable_plugin_activate_link' ), 10, 2 ); + // Replace "Install" plugin link for plugins that not should not be activated (plugin-install.php). + add_filter( 'plugin_install_action_links', array( $this, 'disable_plugin_install_link' ), 10, 2 ); + // Print any notices about plugin deactivation. + add_action( 'admin_notices', array( $this, 'incompatible_plugin_notices' ) ); + // Disable My Jetpack page. + add_filter( 'jetpack_my_jetpack_should_initialize', 'wpcom_is_nav_redesign_enabled' ); + } + + /** + * Public getter to return a singleton instance of Jetpack_Plugin_Compatibility. + */ + public static function get_instance(): Jetpack_Plugin_Compatibility { + static $instance = null; + + if ( null === $instance ) { + $instance = new static(); + } + + return $instance; + } + + /** + * Deactivates incompatible plugins. + */ + public function check_plugin_compatibility() { + foreach ( $this->incompatible_plugins as $plugin => $message ) { + if ( ! is_plugin_active( $plugin ) ) { + continue; + } + + deactivate_plugins( $plugin ); + + $this->admin_notices[] = '

' . esc_html( $message ) . '

'; + unset( $_GET['activate'] ); // phpcs:ignore WordPress.Security.NonceVerification + } + } + + /** + * Displays admin notices. + */ + public function incompatible_plugin_notices() { + foreach ( $this->admin_notices as $notice ) { + echo wp_kses_post( $notice ); + } + } + + /** + * Disables plugin activations links for incompatible plugins. + * + * @param array $actions Plugin actions. + * @param string $plugin_file Plugin file. + * + * @return array Filtered array of plugin actions. + */ + public function disable_plugin_activate_link( $actions, $plugin_file ) { + if ( ! empty( $this->incompatible_plugins[ $plugin_file ] ) ) { + $actions['activate'] = 'Disabled'; + unset( $actions['edit'] ); + } + return $actions; + } + + /** + * Disables plugin install links for incompatible plugins. + * + * @param array $action_links Plugin actions. + * @param array $plugin Plugin information. + * + * @return string[] + */ + public function disable_plugin_install_link( $action_links, $plugin ) { + $needle = "{$plugin['slug']}/"; + foreach ( $this->incompatible_plugins as $disallowed_plugin => $message ) { + /* + * The naming convention of $disallowed_plugin is /.php so we are checking if + * the string $needle is included into $disallowed_plugin from the `0` position. + */ + if ( strpos( $disallowed_plugin, $needle ) === 0 ) { + $action_links = array( 'Not Supported' ); + break; + } + } + + return $action_links; + } + + /** + * Find the incompatible plugins on the site. + * + * @return array + */ + public function find_incompatible_plugins(): array { + // We don't apply the standard Core 'all_plugins' filter, so we are truly looking at all standard plugins. + $standard_plugins = get_plugins(); + + $disallowed_plugins = $this->get_disallowed_plugins(); + + $incompatible_plugins_on_site = array(); + + foreach ( $standard_plugins as $plugin_file => $plugin_details ) { + if ( ! array_key_exists( $plugin_file, $disallowed_plugins ) ) { + continue; + } + + $incompatible_plugins_on_site[ $plugin_file ] = array( + 'message' => $disallowed_plugins[ $plugin_file ], + 'details' => $plugin_details, + 'status' => $this->get_plugin_status( $plugin_file ), + ); + } + + $mu_plugins = get_mu_plugins(); + + foreach ( $mu_plugins as $mu_plugin_file => $mu_plugin_details ) { + if ( ! array_key_exists( $mu_plugin_file, $disallowed_plugins ) ) { + continue; + } + + $incompatible_plugins_on_site[ $mu_plugin_file ] = array( + 'message' => $disallowed_plugins[ $mu_plugin_file ], + 'details' => $mu_plugin_details, + 'status' => 'must-use', + ); + } + + return $incompatible_plugins_on_site; + } + + /** + * Helper function to return disallowed plugins. + * When possible, this function will include platform-level plugins. + * + * @return string[] + */ + protected function get_disallowed_plugins(): array { + if ( ! class_exists( 'Atomic_Platform_Mu_Plugin' ) || ! method_exists( 'Atomic_Platform_Mu_Plugin', 'get_disallowed_plugins' ) ) { + return $this->incompatible_plugins; + } + + $platform_mu_plugin = new Atomic_Platform_Mu_Plugin(); + + // We prefer product-level messages to platform messages when there are conflicts. + return array_merge( $platform_mu_plugin->get_disallowed_plugins(), $this->incompatible_plugins ); + } + + /** + * Helper function to determine the status of a standard plugin. + * + * @param string $plugin_file The full plugin filename. + * @return 'active-network'|'active'|'inactive' + */ + protected function get_plugin_status( string $plugin_file ): string { + if ( is_plugin_active_for_network( $plugin_file ) ) { + return 'active-network'; + } + + if ( is_plugin_active( $plugin_file ) ) { + return 'active'; + } + + return 'inactive'; + } +} diff --git a/projects/plugins/wpcomsh/class-wpcomsh-cli-commands.php b/projects/plugins/wpcomsh/class-wpcomsh-cli-commands.php new file mode 100644 index 0000000000000..ea2c5bf497adc --- /dev/null +++ b/projects/plugins/wpcomsh/class-wpcomsh-cli-commands.php @@ -0,0 +1,1143 @@ +arguments, 0, count( $disabled_command ) ) === $disabled_command ) { + WP_CLI::error( + sprintf( + 'The \'%s\' command is disabled on this platform.', + implode( ' ', $disabled_command ) + ) + ); + } + } + } +); + +/** + * Ask the user to confirm a yes/no question. + * + * @param string $question The yes/no question to ask the user. + * @return boolean Whether the user confirmed or not. + */ +function wpcomsh_cli_confirm( $question ) { + fwrite( STDOUT, $question . ' [Y/n] ' ); // phpcs:ignore WordPress.WP.AlternativeFunctions + $answer = strtolower( trim( fgets( STDIN ) ) ); + return 'y' === $answer || ! $answer; +} + +/** + * Get the names of plugins with the specified status. + * + * @param string $status The plugin status to match. + * + * @return string[]|false An array of plugin names. `false` if there is an error. + */ +function wpcomsh_cli_get_plugins_with_status( $status ) { + $list_result = WP_CLI::runcommand( + "--skip-plugins --skip-themes plugin list --format=json --status=$status", + array( + 'launch' => false, + 'return' => 'all', + 'exit_error' => false, + ) + ); + if ( 0 !== $list_result->return_code ) { + return false; + } + + $decoded_result = json_decode( $list_result->stdout ); + if ( null === $decoded_result ) { + return false; + } + if ( ! is_array( $decoded_result ) ) { + return false; + } + + return array_map( + function ( $plugin ) { + return $plugin->name; }, + $decoded_result + ); +} + +/** + * Save the latest record of deactivated plugins. + * + * @param array $deactivated_plugins Plugins to deactivate. + */ +function wpcomsh_cli_save_deactivated_plugins_record( $deactivated_plugins ) { + if ( empty( $deactivated_plugins ) ) { + delete_option( WPCOMSH_CLI_OPTION_DEACTIVATED_USER_PLUGINS ); + return; + } + + $updated = update_option( WPCOMSH_CLI_OPTION_DEACTIVATED_USER_PLUGINS, $deactivated_plugins, false /* don't autoload */ ); + if ( + false === $updated && + // Make sure the update didn't fail because the option is already set to the desired value. + get_option( WPCOMSH_CLI_OPTION_DEACTIVATED_USER_PLUGINS ) !== $deactivated_plugins + ) { + WP_CLI::warning( 'Failed to update deactivated plugins list.' ); + } +} + +/** + * Removes expired deactivations from the deactivation record. + */ +function wpcomsh_cli_remove_expired_from_deactivation_record() { + $deactivated_plugins = get_option( WPCOMSH_CLI_OPTION_DEACTIVATED_USER_PLUGINS, array() ); + $deactivated_plugins_to_remember = array(); + $current_time = time(); + + foreach ( $deactivated_plugins as $plugin_name => $timestamp ) { + if ( ( $current_time - $timestamp ) < WPCOMSH_CLI_PLUGIN_REACTIVATION_MAX_AGE ) { + $deactivated_plugins_to_remember[ $plugin_name ] = $timestamp; + } + } + + wpcomsh_cli_save_deactivated_plugins_record( $deactivated_plugins_to_remember ); +} + +/** + * Keeps a single event scheduled to clean up the deactivated user plugin record. + * + * @return boolean Whether the scheduling update succeeded. + */ +function wpcomsh_cli_reschedule_deactivated_list_cleanup() { + static $rescheduled_cleanup = false; + + // Avoid unnecessarily rescheduling multiple times within the same CLI command. + if ( ! $rescheduled_cleanup ) { + if ( + false !== wp_next_scheduled( WPCOMSH_CLI_DEACTIVATED_PLUGIN_RECORD_CLEANUP_JOB ) && + false === wp_unschedule_hook( WPCOMSH_CLI_DEACTIVATED_PLUGIN_RECORD_CLEANUP_JOB ) + ) { + // Avoid scheduling cleanup if we can't unschedule existing cleanup because scheduled jobs could accumulate. + return false; + } + + if ( false === get_option( WPCOMSH_CLI_OPTION_DEACTIVATED_USER_PLUGINS ) ) { + // No need to clean up a nonexistent option. + return true; + } + + $rescheduled_cleanup = wp_schedule_single_event( + // Pad scheduled time to give everything time to expire. + time() + WPCOMSH_CLI_PLUGIN_REACTIVATION_MAX_AGE + 15 * MINUTE_IN_SECONDS, + WPCOMSH_CLI_DEACTIVATED_PLUGIN_RECORD_CLEANUP_JOB + ); + } + + return $rescheduled_cleanup; +} + +/** + * Action hook for updating the deactivated plugin record when a plugin is deactivated. + * + * This allows us to maintain the deactivated plugin record in response to both + * the `wp plugin deactivate` and `wp wpcomsh deactivate-user-plugins` commands. + * + * @param string $file Plugin file. + */ +function wpcomsh_cli_remember_plugin_deactivation( $file ) { + $deactivated_plugins = get_option( WPCOMSH_CLI_OPTION_DEACTIVATED_USER_PLUGINS ); + $plugin_name = WP_CLI\Utils\get_plugin_name( $file ); + $deactivated_plugins[ $plugin_name ] = time(); + wpcomsh_cli_save_deactivated_plugins_record( $deactivated_plugins ); + wpcomsh_cli_reschedule_deactivated_list_cleanup(); +} + +/** + * Action hook for pruning the deactivated plugin record when a plugin is activated. + * + * This allows us to neatly maintain the deactivated plugin record in response to both + * the `wp plugin activate` and `wp wpcomsh reactivate-user-plugins` commands. + * + * @param string $file Plugin file. + */ +function wpcomsh_cli_forget_plugin_deactivation( $file ) { + $deactivated_plugins = get_option( WPCOMSH_CLI_OPTION_DEACTIVATED_USER_PLUGINS ); + $plugin_name = WP_CLI\Utils\get_plugin_name( $file ); + unset( $deactivated_plugins[ $plugin_name ] ); + wpcomsh_cli_save_deactivated_plugins_record( $deactivated_plugins ); +} + +// phpcs:disable Squiz.Commenting.FunctionComment.MissingParamTag +if ( class_exists( 'WP_CLI_Command' ) ) { + /** + * WPCOMSH-specific CLI commands + */ + class WPCOMSH_CLI_Commands extends WP_CLI_Command { + /** + * Bulk deactivate user installed plugins + * + * Deactivate all user installed plugins except for important ones for Atomic. + * + * ## OPTIONS + * + * [--interactive] + * : Ask for each active plugin whether to deactivate + * + * @subcommand deactivate-user-plugins + */ + public function deactivate_user_installed_plugins( $args, $assoc_args = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter + $active_plugins = wpcomsh_cli_get_plugins_with_status( 'active' ); + if ( false === $active_plugins ) { + WP_CLI::log( 'Failed to list active plugins.' ); + } + + $plugins_to_skip = WPCOMSH_CLI_DONT_DEACTIVATE_PLUGINS; + if ( wpcom_site_has_feature( WPCOM_Features::ECOMMERCE_MANAGED_PLUGINS ) ) { + // This site has access to the e-commerce plugin bundle, so we don't want to deactivate them. + $plugins_to_skip = array_unique( array_merge( $plugins_to_skip, WPCOMSH_CLI_ECOMMERCE_PLAN_PLUGINS ) ); + } + + foreach ( array_intersect( $active_plugins, $plugins_to_skip ) as $skipped ) { + WP_CLI::log( WP_CLI::colorize( " %b- skipping '$skipped'%n" ) ); + } + + $plugins_to_deactivate = array_diff( $active_plugins, $plugins_to_skip ); + if ( empty( $plugins_to_deactivate ) ) { + WP_CLI::warning( 'No active user-installed plugins found.' ); + return; + } + + $interactive = WP_CLI\Utils\get_flag_value( $assoc_args, 'interactive', false ); + $green_check_mark = WP_CLI::colorize( "%G\xE2\x9C\x94%n" ); + $red_x = WP_CLI::colorize( '%Rx%n' ); + foreach ( $plugins_to_deactivate as $plugin ) { + $deactivate = true; + if ( $interactive ) { + $deactivate = wpcomsh_cli_confirm( 'Deactivate plugin "' . $plugin . '"?' ); + } + + if ( $deactivate ) { + // Deactivate and print success/failure + $result = WP_CLI::runcommand( + "--skip-plugins --skip-themes plugin deactivate $plugin", + array( + 'launch' => false, + 'return' => 'all', + 'exit_error' => false, + ) + ); + if ( 0 === $result->return_code ) { + WP_CLI::log( " $green_check_mark deactivated '$plugin'" ); + } else { + WP_CLI::log( " $red_x failed to deactivate '$plugin'" ); + if ( ! empty( $result->stderr ) ) { + WP_CLI::log( $result->stderr ); + } + } + } + } + } + + /** + * Bulk re-activate user installed plugins. + * + * If previously user installed plugins had been deactivated, this re-activates these plugins. + * + * ## OPTIONS + * + * [--interactive] + * : Ask for each previously deactivated plugin whether to activate. + * + * @subcommand reactivate-user-plugins + */ + public function reactivate_user_installed_plugins( $args, $assoc_args = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter + // Clean up before getting the deactivation list so there are only current entries. + wpcomsh_cli_remove_expired_from_deactivation_record(); + + $inactive_plugins = wpcomsh_cli_get_plugins_with_status( 'inactive' ); + if ( false === $inactive_plugins ) { + WP_CLI::error( 'Failed to list inactive plugins for reactivation.' ); + return; + } + + $deactivation_records = get_option( WPCOMSH_CLI_OPTION_DEACTIVATED_USER_PLUGINS ); + if ( false === $deactivation_records ) { + WP_CLI::warning( "Can't find any previously deactivated plugins to activate." ); + return; + } + + // TODO: Should we reactivate these in the reverse order that they were deactivated? + // Only try to reactivate plugins that exist and are inactive. + $plugins_to_reactivate = array_keys( $deactivation_records ); + $plugins_to_reactivate = array_intersect( $plugins_to_reactivate, $inactive_plugins ); + + if ( empty( $plugins_to_reactivate ) ) { + WP_CLI::warning( "Can't find any previously deactivated plugins to activate." ); + return; + } + + $interactive = WP_CLI\Utils\get_flag_value( $assoc_args, 'interactive', false ); + if ( ! $interactive ) { + // Since we're not confirming one-by-one, we'll confirm once for all. + WP_CLI::log( 'The following will be reactivated:' ); + WP_CLI::log( ' - ' . implode( "\n - ", $plugins_to_reactivate ) ); + if ( ! wpcomsh_cli_confirm( 'Do you wish to proceed?' ) ) { + return; + } + } + + $green_check_mark = WP_CLI::colorize( "%G\xE2\x9C\x94%n" ); + $red_x = WP_CLI::colorize( '%Rx%n' ); + foreach ( $plugins_to_reactivate as $plugin ) { + $reactivate = true; + if ( $interactive ) { + $reactivate = wpcomsh_cli_confirm( 'Reactivate plugin "' . $plugin . '"?' ); + } + + if ( $reactivate ) { + $result = WP_CLI::runcommand( + "--skip-plugins --skip-themes plugin activate $plugin", + array( + 'launch' => false, + 'return' => 'all', + 'exit_error' => false, + ) + ); + if ( 0 === $result->return_code ) { + WP_CLI::log( " $green_check_mark activated '$plugin'" ); + } else { + WP_CLI::log( " $red_x failed to activate '$plugin'" ); + if ( ! empty( $result->stderr ) ) { + WP_CLI::log( $result->stderr ); + } + } + } + } + } + + /** + * Fire the update_option_home action for domain change. + * + * This is necessary for some plugins such as Yoast that looks for this action when a domain is updated, + * and since the Atomic platform uses direct SQL queries to update the URL when it's changed in wpcom, + * this action never fires. + * + * ## OPTIONS + * + * [--old_url=] + * : The URL that the domain was changed from + * + * [--new_url=] + * : The URL that the domain was changed to + * + * @subcommand domain-name-changed + */ + public function domain_name_changed( $args, $assoc_args = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter + $old_domain = WP_CLI\Utils\get_flag_value( $assoc_args, 'old_url', false ); + if ( false === $old_domain ) { + WP_CLI::error( 'Missing required --old_url=url value.' ); + } + + $new_domain = WP_CLI\Utils\get_flag_value( $assoc_args, 'new_url', false ); + if ( false === $new_domain ) { + WP_CLI::error( 'Missing required --new_url=url value.' ); + } + + // Bail if we're getting a value that does not match reality of what's current. + if ( get_home_url() !== $new_domain ) { + WP_CLI::warning( 'Did not send action. New domain does not match current get_home_url value.' ); + return; + } + + if ( ! defined( 'WP_HOME' ) || WP_HOME !== $new_domain ) { + WP_CLI::warning( 'Did not send action. New domain does not match current WP_HOME value.' ); + return; + } + + do_action( 'update_option_home', $old_domain, $new_domain ); + WP_CLI::success( 'Sent the update_option_home action successfully.' ); + } + + /** + * This is a post clone command that is called after a site is cloned. + * + * This is necessary for some plugins that need to perform certain actions after + * a site is cloned, such as WooCommerce Payments that needs to clear its cache. + * + * Note: This command should only be executed from WPCOM as part of an atomic transfer. + * + * @subcommand post-clone + */ + public function post_clone( $args, $assoc_args = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter, VariableAnalysis.CodeAnalysis.VariableAnalysis + $plugins = array( + 'woocommerce-payments' => function () { + $account = \WC_Payments::get_account_service(); + $account->clear_cache(); + }, + ); + + foreach ( $plugins as $plugin => $callback ) { + $result = WP_CLI::runcommand( + sprintf( '--skip-plugins --skip-themes plugin is-active %s', $plugin ), + array( + 'launch' => false, + 'return' => 'all', + 'exit_error' => false, + ) + ); + if ( 0 !== $result->return_code ) { + WP_CLI::log( sprintf( 'Skipping inactive plugin: %s', $plugin ) ); + continue; + } + + $callback(); + WP_CLI::log( sprintf( 'Callback executed for %s', $plugin ) ); + } + WP_CLI::success( 'Post clone completed successfully.' ); + } + + /** + * Proxies wp language plugin install --all using the active site language. + * + * After switching the site language, language packs for plugins are not automatically downloaded and the user + * has to manually check for and install updates, this command installs language packs for all plugins, + * using the active site language. + * + * @subcommand install-plugin-language-packs + */ + public function install_plugin_language_packs() { + /* + * Query the database directly as we previously hooked into pre_option_WPLANG to always return en_US, + * but now we need the actual site language to figure out what language packs to install. + */ + global $wpdb; + // phpcs:ignore WordPress.DB.DirectDatabaseQuery + $lang = $wpdb->get_var( 'SELECT option_value FROM ' . $wpdb->options . " WHERE option_name = 'WPLANG'" ); + if ( empty( $lang ) ) { + $lang = 'en_US'; + } + + $command = new Plugin_Language_Command(); + $command->install( + array( $lang ), + array( + 'all' => true, + ) + ); + } + + /** + * Retrieves an Atomic persistent data field. + * + * ## OPTIONS + * + * + * : The name of the data field to retrieve + * + * [--format=] + * : Render output in a particular format. + * --- + * default: list + * options: + * - list + * - json + * --- + * + * @subcommand persistent-data + */ + public function persistent_data( $args, $assoc_args ) { + if ( empty( $args[0] ) ) { + WP_CLI::error( 'Missing required field name.' ); + } + + $name = $args[0]; + $persistent_data = new Atomic_Persistent_Data(); + + $output = json_decode( $persistent_data->{ $name } ); + if ( null === $output ) { + $output = $persistent_data->{ $name }; + } + + if ( 'json' === $assoc_args['format'] ) { + $output = wp_json_encode( $output, JSON_PRETTY_PRINT ); + } + + WP_CLI::log( print_r( $output, true ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r + } + + /** + * Retrieves the WPCOM_PURCHASES field from Atomic Persistent Data. + * + * ## OPTIONS + * + * [--format=] + * : Render output in a particular format. + * --- + * default: list + * options: + * - list + * - json + * --- + * + * @subcommand purchases + */ + public function purchases( $args, $assoc_args ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter + WP_CLI::runcommand( 'wpcomsh persistent-data WPCOM_PURCHASES --format=' . $assoc_args['format'], array( 'launch' => false ) ); + } + + /** + * Apply terms and taxonomies from the current theme's annotation file. + * + * In the case of WooCommerce specific terms, they can only be applied + * after WooCommerce is installed, which might happen after a site's theme switch. + * So this is provided as a separate command which can be ran in a post-install job. + * + * @subcommand headstart-terms + */ + public function headstart_terms( $args, $assoc_args ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter, VariableAnalysis.CodeAnalysis.VariableAnalysis + $results = wpcomsh_apply_headstart_terms(); + $missing_taxonomies = $results['missing_taxonomies']; + $output = wp_json_encode( array( 'missing_taxonomies' => $missing_taxonomies ), JSON_PRETTY_PRINT ); + WP_CLI::log( $output ); + } + + /** + * Import a backup .zip file. + * + * ## OPTIONS + * + * [--source] + * : Source zip file path. + * + * [--dest] + * : destination file path to extract to. (required) + * + * [--skip-clean-up] + * : Skip cleaning up the temprary files. Defaults to false. + * + * [--skip-unpack] + * : Skip unpacking the zip file. Defaults to false. + * + * [--actions] + * : A comma-separated list of actions to perform. Defaults to all actions. + * + * [--dry-run] + * : Run the importer in dry run mode. Defaults to true. + * + * @subcommand backup-import + */ + public function backup_import( $args, $assoc_args ) { + $source = WP_CLI\Utils\get_flag_value( $assoc_args, 'source', '' ); + $dest = WP_CLI\Utils\get_flag_value( $assoc_args, 'dest' ); + $skip_clean_up = WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-clean-up', false ); + $skip_unpack = WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-unpack', false ); + $actions = WP_CLI\Utils\get_flag_value( $assoc_args, 'actions', '' ); + $dry_run = WP_CLI\Utils\get_flag_value( $assoc_args, 'dry-run', true ); + + $skip_unpack = filter_var( $skip_unpack, FILTER_VALIDATE_BOOLEAN ); + + if ( ! $skip_unpack && empty( $source ) ) { + WP_CLI::error( 'Missing file path passed to --source' ); + } + + if ( empty( $dest ) ) { + WP_CLI::error( 'Missing file path passed to --dest' ); + } + + $options = array( + 'skip_clean_up' => filter_var( $skip_clean_up, FILTER_VALIDATE_BOOLEAN ), + 'skip_unpack' => $skip_unpack, + 'actions' => $actions ? explode( ',', $actions ) : array(), + 'dry_run' => filter_var( $dry_run, FILTER_VALIDATE_BOOLEAN ), + ); + + $import_manager = new Imports\Backup_Import_Manager( $source, $dest, $options ); + $ret = $import_manager->import(); + + if ( is_wp_error( $ret ) ) { + WP_CLI::error( $ret->get_error_message() ); + } + + WP_CLI::success( 'Import completed successfully.' ); + } + + /** + * Manage user's global styles. + * + * ## OPTIONS + * + * + * : The action you want to run, e.g.: list, update, remove. + * + * [--field=] + * : The path of the data field to retrieve or remove. + * + * [--value=] + * : The value of the data field you want to set. + * + * [--dry-run] + * : Enable dry run mode + * + * @subcommand global-styles + */ + public function global_styles( $args, $assoc_args ) { + if ( empty( $args[0] ) ) { + WP_CLI::error( 'Missing the action.' ); + } + + $available_actions = array( 'list', 'update', 'remove' ); + $action = $args[0]; + if ( ! in_array( $action, $available_actions, true ) ) { + WP_CLI::error( 'The action is not supported yet' ); + } + + /** + * Get the global styles + */ + $active_global_styles_id = WP_Theme_JSON_Resolver::get_user_global_styles_post_id(); + $request = new \WP_REST_Request( 'GET', "/wp/v2/global-styles/$active_global_styles_id" ); + $request->set_query_params( + array( + 'context' => 'edit', + 'id' => $active_global_styles_id, + ) + ); + + $global_styles_controller = new WP_REST_Global_Styles_Controller(); + $response = $global_styles_controller->get_item( $request ); + if ( $response->is_error() ) { + WP_CLI::error( $response->as_error() ); + } + + $global_styles = $response->get_data(); + $field = $assoc_args['field'] ?? ''; + $field_path = ! empty( $field ) ? explode( '.', $field ) : array(); + if ( $action === 'list' ) { + $global_styles = $response->get_data(); + $global_styles = ! empty( $field_path ) ? _wp_array_get( $global_styles, $field_path ) : $global_styles; + WP_CLI::log( wp_json_encode( $global_styles, JSON_PRETTY_PRINT ) ); + return; + } + + $dry_run = isset( $assoc_args['dry-run'] ) ? filter_var( $assoc_args['dry-run'], FILTER_VALIDATE_BOOLEAN ) : false; + if ( $action === 'update' ) { + if ( empty( $field_path ) ) { + WP_CLI::error( 'Missing the data field you want to remove, e.g.: settings.typography.fontFamilies.theme' ); + } + + if ( ! isset( $assoc_args['value'] ) ) { + WP_CLI::error( 'Missing the value you want to set.' ); + } + + $value = json_decode( $assoc_args['value'], true ); + $json_decoding_error = json_last_error(); + if ( JSON_ERROR_NONE !== $json_decoding_error ) { + WP_CLI::error( 'The provided value is invalid.' ); + } + + _wp_array_set( $global_styles, $field_path, $value ); + + if ( $dry_run ) { + WP_CLI::log( wp_json_encode( $global_styles, JSON_PRETTY_PRINT ) ); + } else { + $request = new \WP_REST_Request( 'POST', "/wp/v2/global-styles/$active_global_styles_id" ); + $request->set_query_params( $global_styles ); + $response = $global_styles_controller->update_item( $request ); + if ( $response->is_error() ) { + WP_CLI::error( $response->as_error() ); + } + + WP_CLI::log( wp_json_encode( $response->get_data(), JSON_PRETTY_PRINT ) ); + } + + WP_CLI::success( "Update the data field `$field` successfully" ); + } + + if ( $action === 'remove' ) { + if ( empty( $field_path ) ) { + WP_CLI::error( 'Missing the data field you want to remove, e.g.: settings.typography.fontFamilies.theme' ); + } + + $length = count( $field_path ); + $current = &$global_styles; + for ( $i = 0; $i < $length - 1; ++$i ) { + $path = $field_path[ $i ]; + if ( ! array_key_exists( $path, $current ) || ! is_array( $current[ $path ] ) ) { + WP_CLI::error( "The data field `$field` doesn't exist" ); + } + + $current = &$current[ $path ]; + } + + unset( $current[ $field_path[ $i ] ] ); + + if ( $dry_run ) { + WP_CLI::log( wp_json_encode( $global_styles, JSON_PRETTY_PRINT ) ); + } else { + $request = new \WP_REST_Request( 'POST', "/wp/v2/global-styles/$active_global_styles_id" ); + $request->set_query_params( $global_styles ); + $response = $global_styles_controller->update_item( $request ); + if ( $response->is_error() ) { + WP_CLI::error( $response->as_error() ); + } + + WP_CLI::log( wp_json_encode( $response->get_data(), JSON_PRETTY_PRINT ) ); + } + + WP_CLI::success( "Removing the data field `$field` successfully" ); + } + } + + /** + * List incompatible plugins on the site. + * + * ## OPTIONS + * + * + * : The action you want to run. Only `list` supported at present. + * --- + * options: + * - list + * --- + * + * [--field=] + * : Prints the value of a single field for each incompatible plugin. + * + * [--fields=] + * : The fields to include in the output. + * + * [--format=] + * : The output format to use. + * --- + * default: table + * options: + * - table + * - csv + * - json + * --- + * + * [--status=] + * : Only return incompatible plugins with a specific status. + * --- + * options: + * - active + * - inactive + * - active-network + * - must-use + * + * ## AVAILABLE FIELDS + * + * These fields will be displayed by default for each plugin: + * + * * name + * * status + * * version + * + * These fields are optionally available: + * + * * message + * * title + * * description + * * file + * * author + * + * @subcommand incompatible-plugins + */ + public function incompatible_plugins( $args, $assoc_args ) { + if ( empty( $args[0] ) ) { + WP_CLI::error( 'No action specified.' ); + } + + $action = $args[0]; + + $supported_actions = array( 'list' ); + + if ( ! in_array( $action, $supported_actions, true ) ) { + WP_CLI::error( "Unsupported action: '{$action}'. Must be one of: " . implode( '|', $supported_actions ) ); + } + + $jetpack_plugin_compatibility = Jetpack_Plugin_Compatibility::get_instance(); + + $incompatible_plugins = $jetpack_plugin_compatibility->find_incompatible_plugins(); + + $status_to_filter = \WP_CLI\Utils\get_flag_value( $assoc_args, 'status' ); + if ( ! empty( $status_to_filter ) ) { + $incompatible_plugins = array_filter( + $incompatible_plugins, + function ( $incompatible_plugin_details ) use ( $status_to_filter ) { + return $status_to_filter === ( $incompatible_plugin_details['status'] ?? null ); + } + ); + } + + if ( empty( $incompatible_plugins ) ) { + WP_CLI::success( 'No incompatible plugins found.' ); + return; + } + + $refined_plugin_list = array(); + + foreach ( $incompatible_plugins as $plugin_filename => $plugin_details ) { + $refined_plugin_list[] = array( + 'name' => \WP_CLI\Utils\get_plugin_name( $plugin_filename ), + 'status' => $plugin_details['status'], + 'version' => $plugin_details['details']['Version'] ?? '', + 'message' => $plugin_details['message'], + 'title' => $plugin_details['details']['Name'] ?? '', + 'description' => $plugin_details['details']['Description'] ?? '', + 'file' => $plugin_filename, + 'author' => $plugin_details['details']['Author'] ?? '', + ); + } + + $formatter = new \WP_CLI\Formatter( $assoc_args, array( 'name', 'status', 'version' ), 'plugin' ); + + $formatter->display_items( $refined_plugin_list ); + } + } +} + +if ( class_exists( 'Checksum_Plugin_Command' ) ) { + /** + * This works just like plugin verify-checksums except it filters language translation files. + * Language files are not part of WordPress.org's checksums so they are listed as added and + * they obfuscate the output. This makes it hard to spot actual checksum verification errors. + */ + class Checksum_Plugin_Command_WPCOMSH extends Checksum_Plugin_Command { // phpcs:ignore Generic + /** + * Filters the passed file path. + * + * @param string $filepath File path. + * + * @return bool + */ + protected function filter_file( $filepath ) { + return ! preg_match( '#^(languages/)?[a-z0-9-]+-[a-z]{2}_[A-Z]{2}(_[a-z]+)?([.](mo|po)|-[a-f0-9]{32}[.]json)$#', $filepath ); + } + } +} + +/** + * Symlinks a managed plugin into the site's plugins directory. + * + * ## OPTIONS + * + * + * : The managed plugin to symlink. + * + * [--remove-unmanaged] + * : Deprecated. If there is an unmanaged directory in the way, remove it without asking. + * + * [--remove-existing] + * : If there is an existing directory or different symlink in the way, remove it without asking. + * + * [--activate] + * : Indicates that the symlinked plugin should be activated + * + * @return never + */ +function wpcomsh_cli_plugin_symlink( $args, $assoc_args = array() ) { + WP_CLI::warning( 'This command is deprecated. Please use the `wpcomsh plugin use-managed` command instead.' ); + + $plugin_to_symlink = $args[0]; + + if ( 'wpcomsh' === $plugin_to_symlink ) { + // wpcomsh is in the managed plugins directory, but it should not be symlinked into the plugins directory. + WP_CLI::error( 'Cannot symlink wpcomsh' ); + } + + if ( ! chdir( WP_PLUGIN_DIR ) ) { + WP_CLI::error( "Cannot switch to plugins directory '" . WP_PLUGIN_DIR . "'" ); + } + + $managed_plugin_relative_path = "../../../../wordpress/plugins/$plugin_to_symlink/latest"; + if ( false === realpath( $managed_plugin_relative_path ) ) { + WP_CLI::error( "'$plugin_to_symlink' is not a managed plugin" ); + } + + $already_symlinked = false; + if ( realpath( $plugin_to_symlink ) === realpath( $managed_plugin_relative_path ) ) { + $already_symlinked = true; + } elseif ( is_dir( $plugin_to_symlink ) ) { + $permission_to_remove = false; + if ( WP_CLI\Utils\get_flag_value( $assoc_args, 'remove-existing', false ) ) { + $permission_to_remove = true; + } elseif ( WP_CLI\Utils\get_flag_value( $assoc_args, 'remove-unmanaged', false ) ) { + $permission_to_remove = true; + } elseif ( wpcomsh_cli_confirm( "Plugin '$plugin_to_symlink' exists. Delete it and replace with symlink to managed plugin?" ) ) { + $permission_to_remove = true; + } + if ( ! $permission_to_remove ) { + exit( -1 ); + } + + if ( is_link( $plugin_to_symlink ) ) { + // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink + if ( ! unlink( $plugin_to_symlink ) ) { + WP_CLI::error( "Failed to remove conflicting symlink '$plugin_to_symlink'" ); + exit( -1 ); + } + } else { + WP_CLI::runcommand( + "--skip-plugins --skip-themes plugin delete '$plugin_to_symlink'", + array( + 'launch' => false, + 'exit_error' => true, + ) + ); + } + } + + if ( $already_symlinked ) { + WP_CLI::success( "Plugin '$plugin_to_symlink' is already symlinked" ); + } elseif ( symlink( $managed_plugin_relative_path, $plugin_to_symlink ) ) { + WP_CLI::success( "Symlinked '$plugin_to_symlink' plugin" ); + } else { + WP_CLI::error( "Failed to symlink '$plugin_to_symlink' plugin" ); + exit( -1 ); + } + + $activate = WP_CLI\Utils\get_flag_value( $assoc_args, 'activate', false ); + if ( $activate ) { + + // Invalidate cache so that the plugins can be read from the fs again. + if ( ! $already_symlinked ) { + wp_cache_delete( 'plugins', 'plugins' ); + } + + WP_CLI::runcommand( + "--skip-plugins --skip-themes plugin activate '$plugin_to_symlink'", + array( + 'launch' => false, + 'exit_error' => true, + ) + ); + } + + exit( 0 ); +} + +/** + * Symlinks a managed theme into the site's themes directory. + * + * ## OPTIONS + * + * + * : The managed theme to symlink. + * + * [--remove-unmanaged] + * : Deprecated. If there is an unmanaged directory in the way, remove it without asking. + * + * [--remove-existing] + * : If there is an existing directory or different symlink in the way, remove it without asking. + * + * [--activate] + * : Indicates that the symlinked theme should be activated + * + * @return never + */ +function wpcomsh_cli_theme_symlink( $args, $assoc_args = array() ) { + WP_CLI::warning( 'This command is deprecated. Please use the `wpcomsh theme use-managed` command instead.' ); + + $theme_to_symlink = $args[0]; + + $themes_dir = get_theme_root(); + if ( ! chdir( $themes_dir ) ) { + WP_CLI::error( "Cannot switch to themes directory '$themes_dir'" ); + } + + $candidate_managed_theme_paths = array( + // NOTE: pub and premium themes don't have nested `latest`and version directories. + "../../../../wordpress/themes/pub/$theme_to_symlink", + "../../../../wordpress/themes/premium/$theme_to_symlink", + // Consider root themes dir last because we want to favor WPCOM-managed things on WPCOM + // See p9o2xV-1LC-p2#comment-5417 + "../../../../wordpress/themes/$theme_to_symlink/latest", + ); + + $managed_theme_path = false; + foreach ( $candidate_managed_theme_paths as $candidate_path ) { + if ( false !== realpath( $candidate_path ) ) { + $managed_theme_path = $candidate_path; + break; + } + } + + if ( false === $managed_theme_path ) { + WP_CLI::error( "'$theme_to_symlink' is not a managed theme" ); + } + + $already_symlinked = false; + if ( realpath( $theme_to_symlink ) === realpath( $managed_theme_path ) ) { + $already_symlinked = true; + } elseif ( is_dir( $theme_to_symlink ) ) { + $permission_to_remove = false; + if ( WP_CLI\Utils\get_flag_value( $assoc_args, 'remove-existing', false ) ) { + $permission_to_remove = true; + } elseif ( WP_CLI\Utils\get_flag_value( $assoc_args, 'remove-unmanaged', false ) ) { + $permission_to_remove = true; + } elseif ( wpcomsh_cli_confirm( "Theme '$theme_to_symlink' exists. Delete it and replace with symlink to managed theme?" ) ) { + $permission_to_remove = true; + } + if ( ! $permission_to_remove ) { + exit( -1 ); + } + + if ( is_link( $theme_to_symlink ) ) { + // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink + if ( ! unlink( $theme_to_symlink ) ) { + WP_CLI::error( "Failed to remove conflicting symlink '$theme_to_symlink'" ); + exit( -1 ); + } + } else { + WP_CLI::runcommand( + "--skip-plugins --skip-themes theme delete '$theme_to_symlink'", + array( + 'launch' => false, + 'exit_error' => true, + ) + ); + } + } + + if ( $already_symlinked ) { + WP_CLI::success( "Theme '$theme_to_symlink' is already symlinked" ); + } elseif ( symlink( $managed_theme_path, $theme_to_symlink ) ) { + WP_CLI::success( "Symlinked '$theme_to_symlink' theme" ); + } else { + WP_CLI::error( "Failed to symlink '$theme_to_symlink' theme" ); + exit( -1 ); + } + + $activate = WP_CLI\Utils\get_flag_value( $assoc_args, 'activate', false ); + if ( $activate ) { + WP_CLI::runcommand( + "--skip-plugins --skip-themes theme activate '$theme_to_symlink'", + array( + 'launch' => false, + 'exit_error' => true, + ) + ); + } + + exit( 0 ); +} + +/** + * Makes the site live to the public. + */ +function wpcomsh_cli_launch_site() { + WP_CLI::success( "If you're reading this, you should visit automattic.com/jobs and apply to join the fun, mention this command." ); +} + +// Cleanup via WP-Cron event. +add_action( WPCOMSH_CLI_DEACTIVATED_PLUGIN_RECORD_CLEANUP_JOB, 'wpcomsh_cli_remove_expired_from_deactivation_record' ); + +if ( ! defined( 'WP_CLI' ) || true !== WP_CLI ) { + // We aren't running in a WP-CLI context, so there is nothing more to do. + return; +} + +// Force WordPress to always output English at the command line. +WP_CLI::add_wp_hook( + 'pre_option_WPLANG', + function () { + return 'en_US'; + } +); + +// Maintain a record of deactivated plugins so that they can be reactivated by the reactivate-user-plugins command. +add_action( 'deactivated_plugin', 'wpcomsh_cli_remember_plugin_deactivation' ); +add_action( 'activated_plugin', 'wpcomsh_cli_forget_plugin_deactivation' ); + +WP_CLI::add_command( 'wpcomsh', 'WPCOMSH_CLI_Commands' ); +WP_CLI::add_command( 'wpcomsh plugin verify-checksums', 'Checksum_Plugin_Command_WPCOMSH' ); +WP_CLI::add_command( 'plugin symlink', 'wpcomsh_cli_plugin_symlink' ); +WP_CLI::add_command( 'theme symlink', 'wpcomsh_cli_theme_symlink' ); +WP_CLI::add_command( 'launch-site', 'wpcomsh_cli_launch_site' ); + +add_action( + 'plugins_loaded', + function () { + if ( class_exists( 'Atomic_Platform_Managed_Software_Commands' ) ) { + WP_CLI::add_command( + 'wpcomsh plugin use-managed', + array( 'Atomic_Platform_Managed_Software_Commands', 'use_managed_plugin' ) + ); + WP_CLI::add_command( + 'wpcomsh plugin use-unmanaged', + array( 'Atomic_Platform_Managed_Software_Commands', 'use_unmanaged_plugin' ) + ); + WP_CLI::add_command( + 'wpcomsh theme use-managed', + array( 'Atomic_Platform_Managed_Software_Commands', 'use_managed_theme' ) + ); + WP_CLI::add_command( + 'wpcomsh theme use-unmanaged', + array( 'Atomic_Platform_Managed_Software_Commands', 'use_unmanaged_theme' ) + ); + } + } +); diff --git a/projects/plugins/wpcomsh/class-wpcomsh-log.php b/projects/plugins/wpcomsh/class-wpcomsh-log.php new file mode 100644 index 0000000000000..9df400b5da362 --- /dev/null +++ b/projects/plugins/wpcomsh/class-wpcomsh-log.php @@ -0,0 +1,139 @@ +add_hooks(); + } + + /** + * This method bypasses `at_options_logging_on` check. + * It is intended to be used when we are sure we want to send logs to logstash and + * we are sure that we don't fire it off frequently. Good example of when we want to use this + * is during the site setup process + * + * @param string $message Log message. + * @param array $extra Optional. Additional log data. Defaults to empty array. + */ + public static function unsafe_direct_log( $message, $extra = array() ) { + if ( ! self::$instance ) { + self::$instance = new self(); + } + self::$instance->log( $message, $extra ); + } + + /** + * Constructor. + */ + private function __construct() { + $this->siteurl = get_site_url(); + } + + /** + * Adds the log action. + */ + private function add_hooks() { + add_action( 'wpcomsh_log', array( $this, 'log' ), 1 ); + } + + /** + * Logs a log message. + * + * @param string $message Log message. + * @param array $extra Optional. Additional log data. Defaults to empty array. + */ + public function log( $message, $extra = array() ) { + $this->log_queue[] = array( + 'message' => $message, + 'extra' => $extra, + ); + if ( ! $this->has_shutdown_hook ) { + register_shutdown_function( array( $this, 'send_to_api' ) ); + $this->has_shutdown_hook = true; + } + } + + /** + * Sends log messages to the API endpoint. + */ + public function send_to_api() { + if ( count( $this->log_queue ) > 0 ) { + $payload = array( + 'siteurl' => $this->siteurl, + 'messages' => $this->log_queue, + ); + + wp_remote_post( self::$log_endpoint, array( 'body' => array( 'error' => wp_json_encode( $payload ) ) ) ); + } + } +} +add_action( 'init', array( 'WPCOMSH_Log', 'init' ) ); diff --git a/projects/plugins/wpcomsh/composer.json b/projects/plugins/wpcomsh/composer.json new file mode 100644 index 0000000000000..71e16225143b2 --- /dev/null +++ b/projects/plugins/wpcomsh/composer.json @@ -0,0 +1,144 @@ +{ + "name": "automattic/jetpack-wpcomsh", + "description": "A helper for connecting WordPress.com sites to external host infrastructure.", + "type": "wordpress-plugin", + "license": "GPL-2.0-or-later", + "require": { + "php": ">=7.4", + "automattic/at-pressable-podcasting": "^2.0", + "automattic/custom-fonts": "^3.0", + "automattic/custom-fonts-typekit": "^2.0", + "automattic/text-media-widget-styles": "^2.0", + "automattic/wc-calypso-bridge": "2.5.1", + "wordpress/classic-editor-plugin": "1.5", + "automattic/jetpack-config": "@dev", + "automattic/jetpack-post-list": "@dev", + "automattic/jetpack-mu-wpcom": "@dev", + "tubalmartin/cssmin": "^4.1" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "0.4.2", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "phpunit/phpunit": "^9", + "yoast/phpunit-polyfills": "1.1.0", + "wp-coding-standards/wpcs": "^3.0" + }, + "autoload": { + "classmap": [ + "block-theme-footer-credits", + "customizer-fixes", + "custom-colors", + "endpoints", + "feature-plugins", + "footer-credit", + "frontend-notices", + "imports", + "jetpack-require-connection-owner", + "jetpack-token-error-header", + "jetpack-token-resilience", + "lib", + "logo-tool", + "mailpoet", + "notices", + "privacy", + "private-site", + "safeguard", + "share-post", + "storage", + "storefront", + "widgets", + "wpcom-features", + "wpcom-migration-helpers", + "wpcom-plugins", + "wpcom-themes" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "post-install-cmd": "WorDBless\\Composer\\InstallDropin::copy", + "post-update-cmd": "WorDBless\\Composer\\InstallDropin::copy" + }, + "repositories": [ + { + "type": "path", + "url": "../../packages/*", + "options": { + "monorepo": true + } + }, + { + "type": "vcs", + "no-api": true, + "url": "https://github.com/Automattic/custom-fonts.git" + }, + { + "type": "vcs", + "no-api": true, + "url": "https://github.com/Automattic/custom-fonts-typekit.git" + }, + { + "type": "vcs", + "no-api": true, + "url": "https://github.com/automattic/at-pressable-podcasting.git" + }, + { + "type": "vcs", + "no-api": true, + "url": "https://github.com/Automattic/text-media-widget-styles.git" + }, + { + "type": "vcs", + "no-api": true, + "url": "https://github.com/Automattic/wc-calypso-bridge.git" + }, + { + "type": "package", + "package": { + "name": "wordpress/classic-editor-plugin", + "version": "1.5", + "dist": { + "url": "https://downloads.wordpress.org/plugin/classic-editor.1.5.zip", + "type": "zip" + }, + "source": { + "url": "https://plugins.svn.wordpress.org/classic-editor/", + "type": "svn", + "reference": "tags/1.5/" + }, + "autoload": { + "classmap": [] + } + } + } + ], + "minimum-stability": "dev", + "prefer-stable": true, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "composer/installers": true, + "roots/wordpress-core-installer": true + }, + "autoloader-suffix": "26841ac2064774301cbe06d174833bfc_wpcomshⓥ3_22_6_alpha" + }, + "extra": { + "mirror-repo": "Automattic/wpcom-site-helper", + "autorelease": true, + "autotagger": true, + "beta-plugin-slug": "wpcomsh", + "release-branch-prefix": "wpcomsh", + "installer-disable": true, + "changelogger": { + "versioning": "semver" + }, + "version-constants": { + "WPCOMSH_VERSION": "wpcomsh.php" + } + } +} diff --git a/projects/plugins/wpcomsh/composer.lock b/projects/plugins/wpcomsh/composer.lock new file mode 100644 index 0000000000000..f5883fd709b1a --- /dev/null +++ b/projects/plugins/wpcomsh/composer.lock @@ -0,0 +1,5343 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "f8abc07542e2835541aedabf0e46efce", + "packages": [ + { + "name": "automattic/at-pressable-podcasting", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/automattic/at-pressable-podcasting.git", + "reference": "113461a854d3d6551c173ca4c02dbc901bbefd27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/automattic/at-pressable-podcasting/zipball/113461a854d3d6551c173ca4c02dbc901bbefd27", + "reference": "113461a854d3d6551c173ca4c02dbc901bbefd27", + "shasum": "" + }, + "type": "wordpress-plugin", + "license": [ + "GPL-2.0-or-later" + ], + "time": "2024-06-06T16:58:27+00:00" + }, + { + "name": "automattic/custom-fonts", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/Automattic/custom-fonts.git", + "reference": "e9eb1ffe861baa5cd2c107d88338a23501aa82c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Automattic/custom-fonts/zipball/e9eb1ffe861baa5cd2c107d88338a23501aa82c6", + "reference": "e9eb1ffe861baa5cd2c107d88338a23501aa82c6", + "shasum": "" + }, + "require-dev": { + "10up/wp_mock": "0.4.2", + "phpunit/phpunit": "^7 || ^8" + }, + "type": "library", + "time": "2023-05-01T19:43:42+00:00" + }, + { + "name": "automattic/custom-fonts-typekit", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/Automattic/custom-fonts-typekit.git", + "reference": "0be6c997f3c576fc86844b5d57130d6ed81fe624" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Automattic/custom-fonts-typekit/zipball/0be6c997f3c576fc86844b5d57130d6ed81fe624", + "reference": "0be6c997f3c576fc86844b5d57130d6ed81fe624", + "shasum": "" + }, + "require-dev": { + "10up/wp_mock": "dev-master" + }, + "type": "library", + "time": "2023-05-01T20:00:28+00:00" + }, + { + "name": "automattic/jetpack-a8c-mc-stats", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/a8c-mc-stats", + "reference": "29e2de602fcb803984eed4229ffa60a2f96a53f9" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-a8c-mc-stats", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Used to record internal usage stats for Automattic. Not visible to site owners.", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-admin-ui", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/admin-ui", + "reference": "b191c34a0e21f625069eab0c054d8827b9542dfa" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/jetpack-logo": "@dev", + "automattic/wordbless": "dev-master", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-admin-ui", + "textdomain": "jetpack-admin-ui", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-admin-ui/compare/${old}...${new}" + }, + "branch-alias": { + "dev-trunk": "0.4.x-dev" + }, + "version-constants": { + "::PACKAGE_VERSION": "src/class-admin-menu.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Generic Jetpack wp-admin UI elements", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-assets", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/assets", + "reference": "d5648e0a4f0a8bffbbc805d0f6a5ed0f3cedd521" + }, + "require": { + "automattic/jetpack-constants": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "brain/monkey": "2.6.1", + "wikimedia/testing-access-wrapper": "^1.0 || ^2.0 || ^3.0", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-assets", + "textdomain": "jetpack-assets", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-assets/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "2.1.x-dev" + } + }, + "autoload": { + "files": [ + "actions.php" + ], + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-development": [ + "pnpm run build" + ], + "build-production": [ + "pnpm run build-production" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-js": [ + "pnpm run test" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Asset management utilities for Jetpack ecosystem packages", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-blocks", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/blocks", + "reference": "f2e9d1750729b4645c78cb1988112c3a838e1bce" + }, + "require": { + "automattic/jetpack-constants": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "dev-master", + "brain/monkey": "2.6.1", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-blocks", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-blocks/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Register and manage blocks within a plugin. Used to manage block registration, enqueues, and more.", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-calypsoify", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/calypsoify", + "reference": "10f1e08f3ac93e9c6f004d1d733443370e15334f" + }, + "require": { + "automattic/jetpack-assets": "@dev", + "automattic/jetpack-status": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.1.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-calypsoify/compare/v${old}...v${new}" + }, + "mirror-repo": "Automattic/jetpack-calypsoify", + "textdomain": "jetpack-calypsoify", + "version-constants": { + "::PACKAGE_VERSION": "src/class-jetpack-calypsoify.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-production": [ + "pnpm run build-production" + ], + "build-development": [ + "pnpm run build" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Calypsoify is designed to make sure specific wp-admin pages include navigation that prioritizes the Calypso navigation experience.", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-classic-theme-helper", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/classic-theme-helper", + "reference": "0a0b72c656b3949e67db20779c1f8bab8d8426fb" + }, + "require": { + "automattic/jetpack-assets": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "dev-master", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.3.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-classic-theme-helper/compare/v${old}...v${new}" + }, + "mirror-repo": "Automattic/jetpack-classic-theme-helper", + "textdomain": "jetpack-classic-theme-helper", + "version-constants": { + "::PACKAGE_VERSION": "src/class-main.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-production": [ + "pnpm run build-production" + ], + "build-development": [ + "pnpm run build" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Features used with classic themes", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-compat", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/compat", + "reference": "d602d0487adba4e922aed6279fe10a16714b1218" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-compat", + "textdomain": "jetpack-compat", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-compat/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "3.0.x-dev" + } + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Compatibility layer with previous versions of Jetpack", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-config", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/config", + "reference": "e4416daf21b3fe496a53235342255bcf0cb8faa4" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-import": "@dev", + "automattic/jetpack-jitm": "@dev", + "automattic/jetpack-post-list": "@dev", + "automattic/jetpack-publicize": "@dev", + "automattic/jetpack-search": "@dev", + "automattic/jetpack-stats": "@dev", + "automattic/jetpack-stats-admin": "@dev", + "automattic/jetpack-sync": "@dev", + "automattic/jetpack-videopress": "@dev", + "automattic/jetpack-waf": "@dev", + "automattic/jetpack-wordads": "@dev", + "automattic/jetpack-yoast-promo": "@dev" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-config", + "textdomain": "jetpack-config", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-config/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "2.0.x-dev" + }, + "dependencies": { + "test-only": [ + "packages/connection", + "packages/identity-crisis", + "packages/import", + "packages/jitm", + "packages/post-list", + "packages/publicize", + "packages/search", + "packages/stats", + "packages/stats-admin", + "packages/sync", + "packages/videopress", + "packages/waf", + "packages/wordads", + "packages/yoast-promo" + ] + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Jetpack configuration package that initializes other packages and configures Jetpack's functionality. Can be used as a base for all variants of Jetpack package usage.", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-connection", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/connection", + "reference": "34b11d946a049dbd9f35f94b99f32b0410eafb8b" + }, + "require": { + "automattic/jetpack-a8c-mc-stats": "@dev", + "automattic/jetpack-admin-ui": "@dev", + "automattic/jetpack-assets": "@dev", + "automattic/jetpack-constants": "@dev", + "automattic/jetpack-redirect": "@dev", + "automattic/jetpack-roles": "@dev", + "automattic/jetpack-status": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/jetpack-licensing": "@dev", + "automattic/jetpack-sync": "@dev", + "automattic/wordbless": "@dev", + "brain/monkey": "2.6.1", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-connection", + "textdomain": "jetpack-connection", + "version-constants": { + "::PACKAGE_VERSION": "src/class-package-version.php" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-connection/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "2.10.x-dev" + }, + "dependencies": { + "test-only": [ + "packages/licensing", + "packages/sync" + ] + } + }, + "autoload": { + "classmap": [ + "legacy", + "src/", + "src/webhooks", + "src/identity-crisis" + ] + }, + "scripts": { + "build-production": [ + "pnpm run build-production" + ], + "build-development": [ + "pnpm run build" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Everything needed to connect to the Jetpack infrastructure", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-constants", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/constants", + "reference": "3fd2bf1d1ba0bb374918e6b7dd670735ce554c2b" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "brain/monkey": "2.6.1", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-constants", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-constants/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "A wrapper for defining constants in a more testable way.", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-device-detection", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/device-detection", + "reference": "a6696f57f2f6f29f4a6930727ae5063c4e89fab4" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-device-detection", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-device-detection/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "2.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "A way to detect device types based on User-Agent header.", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-ip", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/ip", + "reference": "b696350993b7f42257788add260e0efa7c9934f4" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "brain/monkey": "2.6.1", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-ip", + "changelogger": { + "link-template": "https://github.com/automattic/jetpack-ip/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "0.2.x-dev" + }, + "textdomain": "jetpack-ip", + "version-constants": { + "::PACKAGE_VERSION": "src/class-utils.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Utilities for working with IP addresses.", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-jitm", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/jitm", + "reference": "c21ef5f64d44c453e7a7dddbe13202c41aecb942" + }, + "require": { + "automattic/jetpack-a8c-mc-stats": "@dev", + "automattic/jetpack-assets": "@dev", + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-device-detection": "@dev", + "automattic/jetpack-logo": "@dev", + "automattic/jetpack-redirect": "@dev", + "automattic/jetpack-status": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "brain/monkey": "2.6.1", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-jitm", + "textdomain": "jetpack-jitm", + "version-constants": { + "::PACKAGE_VERSION": "src/class-jitm.php" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-jitm/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-production": [ + "pnpm run build-production" + ], + "build-development": [ + "pnpm run build" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "watch": [ + "Composer\\Config::disableProcessTimeout", + "pnpm run watch" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Just in time messages for Jetpack", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-logo", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/logo", + "reference": "e152a4c83d1f952442d40260c559c4880757b298" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-logo", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-logo/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "A logo for Jetpack", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-mu-wpcom", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/jetpack-mu-wpcom", + "reference": "572557dc75d57d3d1a24aed00fc6cfbbf784f618" + }, + "require": { + "automattic/jetpack-assets": "@dev", + "automattic/jetpack-blocks": "@dev", + "automattic/jetpack-calypsoify": "@dev", + "automattic/jetpack-classic-theme-helper": "@dev", + "automattic/jetpack-compat": "@dev", + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-redirect": "@dev", + "automattic/jetpack-stats-admin": "@dev", + "automattic/jetpack-status": "@dev", + "automattic/scheduled-updates": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "0.4.2", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "mirror-repo": "Automattic/jetpack-mu-wpcom", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-mu-wpcom/compare/v${old}...v${new}" + }, + "autotagger": true, + "branch-alias": { + "dev-trunk": "5.35.x-dev" + }, + "textdomain": "jetpack-mu-wpcom", + "version-constants": { + "::PACKAGE_VERSION": "src/class-jetpack-mu-wpcom.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "build-production": [ + "pnpm run build-production-js" + ], + "build-development": [ + "pnpm run build-js" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Enhances your site with features powered by WordPress.com", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-password-checker", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/password-checker", + "reference": "16182898ae3faae3eb6ca9e5d2c490fd0b844243" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "@dev", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-password-checker", + "textdomain": "jetpack-password-checker", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-password-checker/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "0.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Password Checker.", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-plans", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/plans", + "reference": "572028d8755c1c303f0643b2d3663b555e5ce87b" + }, + "require": { + "automattic/jetpack-connection": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/jetpack-status": "@dev", + "automattic/wordbless": "@dev", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-plans", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-plans/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "0.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "build-production": [ + "echo 'Add your build step to composer.json, please!'" + ], + "build-development": [ + "echo 'Add your build step to composer.json, please!'" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Fetch information about Jetpack Plans from wpcom", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-post-list", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/post-list", + "reference": "d163b29e9f11af83ba2995fcec0106df86345c7e" + }, + "require": { + "automattic/jetpack-assets": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "@dev", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-post-list", + "textdomain": "jetpack-post-list", + "version-constants": { + "::PACKAGE_VERSION": "src/class-post-list.php" + }, + "changelogger": { + "link-template": "https://github.com/automattic/jetpack-post-list/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "0.6.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Enhance the classic view of the Admin section of your WordPress site", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-redirect", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/redirect", + "reference": "effd6fdea78e9c3cb1bebf479474b4a9262444a1" + }, + "require": { + "automattic/jetpack-status": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "brain/monkey": "2.6.1", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-redirect", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-redirect/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Utilities to build URLs to the jetpack.com/redirect/ service", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-roles", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/roles", + "reference": "0ac6d02e8ef2adb058f8f52e80a4924a33fa9b86" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "brain/monkey": "2.6.1", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-roles", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-roles/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Utilities, related with user roles and capabilities.", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-stats", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/stats", + "reference": "51130b6feb1e67769587bd503d52f17207303c6a" + }, + "require": { + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-constants": "@dev", + "automattic/jetpack-status": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "dev-master", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-stats", + "version-constants": { + "::PACKAGE_VERSION": "src/class-package-version.php" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-stats/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "0.13.x-dev" + }, + "textdomain": "jetpack-stats" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Collect valuable traffic stats and insights.", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-stats-admin", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/stats-admin", + "reference": "a17a4ebcea4f0a0c47916c0457fe23106ad6ae5c" + }, + "require": { + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-constants": "@dev", + "automattic/jetpack-jitm": "@dev", + "automattic/jetpack-plans": "@dev", + "automattic/jetpack-stats": "@dev", + "automattic/jetpack-status": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "dev-master", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-stats-admin", + "branch-alias": { + "dev-trunk": "0.19.x-dev" + }, + "textdomain": "jetpack-stats-admin", + "version-constants": { + "::VERSION": "src/class-main.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "build-production": [ + "echo 'Add your build step to composer.json, please!'" + ], + "build-development": [ + "echo 'Add your build step to composer.json, please!'" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Stats Dashboard", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-status", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/status", + "reference": "c588736555b0b718c18e944cfc0a809bfc63e0ad" + }, + "require": { + "automattic/jetpack-constants": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-ip": "@dev", + "automattic/jetpack-plans": "@dev", + "brain/monkey": "2.6.1", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-status", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-status/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "3.3.x-dev" + }, + "dependencies": { + "test-only": [ + "packages/connection", + "packages/identity-crisis", + "packages/plans" + ] + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Used to retrieve information about the current status of Jetpack and the site overall.", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-sync", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/sync", + "reference": "6b8af670d924432fb2457a8eb59cb32330bfc25a" + }, + "require": { + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-constants": "@dev", + "automattic/jetpack-ip": "@dev", + "automattic/jetpack-password-checker": "@dev", + "automattic/jetpack-roles": "@dev", + "automattic/jetpack-status": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/jetpack-search": "@dev", + "automattic/jetpack-waf": "@dev", + "automattic/wordbless": "@dev", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-sync", + "textdomain": "jetpack-sync", + "version-constants": { + "::PACKAGE_VERSION": "src/class-package-version.php" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-sync/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "3.1.x-dev" + }, + "dependencies": { + "test-only": [ + "packages/search", + "packages/waf" + ] + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Everything needed to allow syncing to the WP.com infrastructure.", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/scheduled-updates", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/scheduled-updates", + "reference": "8c675878023ed9657ebe6b80916afe241af62fc3" + }, + "require": { + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-constants": "@dev", + "automattic/jetpack-plans": "@dev", + "automattic/jetpack-status": "@dev", + "automattic/jetpack-sync": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "@dev", + "php-mock/php-mock-phpunit": "^2.10", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "mirror-repo": "Automattic/scheduled-updates", + "changelogger": { + "link-template": "https://github.com/Automattic/scheduled-updates/compare/v${old}...v${new}" + }, + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.13.x-dev" + }, + "textdomain": "jetpack-scheduled-updates", + "version-constants": { + "::PACKAGE_VERSION": "src/class-scheduled-updates.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Runs plugin and (eventually) theme updates on a set schedule.", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/text-media-widget-styles", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/Automattic/text-media-widget-styles.git", + "reference": "83d7572593cc77f7d6c72477d853a7d9c110f264" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Automattic/text-media-widget-styles/zipball/83d7572593cc77f7d6c72477d853a7d9c110f264", + "reference": "83d7572593cc77f7d6c72477d853a7d9c110f264", + "shasum": "" + }, + "type": "wordpress-plugin", + "license": [ + "GPL-2.0-or-later" + ], + "time": "2024-06-06T17:20:00+00:00" + }, + { + "name": "automattic/wc-calypso-bridge", + "version": "v2.5.1", + "source": { + "type": "git", + "url": "https://github.com/Automattic/wc-calypso-bridge.git", + "reference": "100cf99a31094475e0159c3769df9a878a4cb0f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Automattic/wc-calypso-bridge/zipball/100cf99a31094475e0159c3769df9a878a4cb0f1", + "reference": "100cf99a31094475e0159c3769df9a878a4cb0f1", + "shasum": "" + }, + "require": { + "composer/installers": "~1.2" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "phpunit/phpunit": "^8.0.0", + "sirbrillig/phpcs-changed": "^2.11", + "squizlabs/php_codesniffer": "*", + "wimg/php-compatibility": "9.0.0", + "woocommerce/woocommerce-sniffs": "*", + "wp-cli/wp-cli-bundle": "^2.8.1", + "wp-coding-standards/wpcs": "1.1.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "library", + "extra": { + "scripts-description": { + "test": "Run unit tests", + "phpcs": "Analyze code against the WordPress coding standards with PHP_CodeSniffer", + "phpcbf": "Fix coding standards warnings/errors automatically with PHP Code Beautifier" + } + }, + "autoload": { + "files": [ + "wc-calypso-bridge.php" + ] + }, + "scripts": { + "test": [ + "phpunit" + ], + "phpcs": [ + "phpcs -s -p" + ], + "phpcbf": [ + "phpcbf -p" + ] + }, + "time": "2024-05-29T08:30:00+00:00" + }, + { + "name": "composer/installers", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/composer/installers.git", + "reference": "d20a64ed3c94748397ff5973488761b22f6d3f19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/installers/zipball/d20a64ed3c94748397ff5973488761b22f6d3f19", + "reference": "d20a64ed3c94748397ff5973488761b22f6d3f19", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0" + }, + "replace": { + "roundcube/plugin-installer": "*", + "shama/baton": "*" + }, + "require-dev": { + "composer/composer": "1.6.* || ^2.0", + "composer/semver": "^1 || ^3", + "phpstan/phpstan": "^0.12.55", + "phpstan/phpstan-phpunit": "^0.12.16", + "symfony/phpunit-bridge": "^4.2 || ^5", + "symfony/process": "^2.3" + }, + "type": "composer-plugin", + "extra": { + "class": "Composer\\Installers\\Plugin", + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Installers\\": "src/Composer/Installers" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kyle Robinson Young", + "email": "kyle@dontkry.com", + "homepage": "https://github.com/shama" + } + ], + "description": "A multi-framework Composer library installer", + "homepage": "https://composer.github.io/installers/", + "keywords": [ + "Craft", + "Dolibarr", + "Eliasis", + "Hurad", + "ImageCMS", + "Kanboard", + "Lan Management System", + "MODX Evo", + "MantisBT", + "Mautic", + "Maya", + "OXID", + "Plentymarkets", + "Porto", + "RadPHP", + "SMF", + "Starbug", + "Thelia", + "Whmcs", + "WolfCMS", + "agl", + "aimeos", + "annotatecms", + "attogram", + "bitrix", + "cakephp", + "chef", + "cockpit", + "codeigniter", + "concrete5", + "croogo", + "dokuwiki", + "drupal", + "eZ Platform", + "elgg", + "expressionengine", + "fuelphp", + "grav", + "installer", + "itop", + "joomla", + "known", + "kohana", + "laravel", + "lavalite", + "lithium", + "magento", + "majima", + "mako", + "mediawiki", + "miaoxing", + "modulework", + "modx", + "moodle", + "osclass", + "pantheon", + "phpbb", + "piwik", + "ppi", + "processwire", + "puppet", + "pxcms", + "reindex", + "roundcube", + "shopware", + "silverstripe", + "sydes", + "sylius", + "symfony", + "tastyigniter", + "typo3", + "wordpress", + "yawik", + "zend", + "zikula" + ], + "support": { + "issues": "https://github.com/composer/installers/issues", + "source": "https://github.com/composer/installers/tree/v1.12.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-09-13T08:19:44+00:00" + }, + { + "name": "tubalmartin/cssmin", + "version": "v4.1.1", + "source": { + "type": "git", + "url": "https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port.git", + "reference": "3cbf557f4079d83a06f9c3ff9b957c022d7805cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tubalmartin/YUI-CSS-compressor-PHP-port/zipball/3cbf557f4079d83a06f9c3ff9b957c022d7805cf", + "reference": "3cbf557f4079d83a06f9c3ff9b957c022d7805cf", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "php": ">=5.3.2" + }, + "require-dev": { + "cogpowered/finediff": "0.3.*", + "phpunit/phpunit": "4.8.*" + }, + "bin": [ + "cssmin" + ], + "type": "library", + "autoload": { + "psr-4": { + "tubalmartin\\CssMin\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Túbal Martín", + "homepage": "http://tubalmartin.me/" + } + ], + "description": "A PHP port of the YUI CSS compressor", + "homepage": "https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port", + "keywords": [ + "compress", + "compressor", + "css", + "cssmin", + "minify", + "yui" + ], + "support": { + "issues": "https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port/issues", + "source": "https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port" + }, + "time": "2018-01-15T15:26:51+00:00" + }, + { + "name": "wordpress/classic-editor-plugin", + "version": "1.5", + "source": { + "type": "svn", + "url": "https://plugins.svn.wordpress.org/classic-editor/", + "reference": "tags/1.5/" + }, + "dist": { + "type": "zip", + "url": "https://downloads.wordpress.org/plugin/classic-editor.1.5.zip" + }, + "type": "library", + "autoload": { + "classmap": [] + } + } + ], + "packages-dev": [ + { + "name": "automattic/jetpack-changelogger", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/changelogger", + "reference": "d945e0cd8dec218ab24445d5ddc95894c9f24534" + }, + "require": { + "php": ">=7.0", + "symfony/console": "^3.4 || ^4.4 || ^5.2 || ^6.0 || ^7.0", + "symfony/process": "^3.4 || ^4.4 || ^5.2 || ^6.0 || ^7.0" + }, + "require-dev": { + "wikimedia/testing-access-wrapper": "^1.0 || ^2.0 || ^3.0", + "yoast/phpunit-polyfills": "1.1.0" + }, + "bin": [ + "bin/changelogger" + ], + "type": "project", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "4.2.x-dev" + }, + "mirror-repo": "Automattic/jetpack-changelogger", + "version-constants": { + "::VERSION": "src/Application.php" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-changelogger/compare/${old}...${new}" + } + }, + "autoload": { + "psr-4": { + "Automattic\\Jetpack\\Changelogger\\": "src", + "Automattic\\Jetpack\\Changelog\\": "lib" + } + }, + "autoload-dev": { + "psr-4": { + "Automattic\\Jetpack\\Changelogger\\Tests\\": "tests/php/includes/src", + "Automattic\\Jetpack\\Changelog\\Tests\\": "tests/php/includes/lib" + } + }, + "scripts": { + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "post-install-cmd": [ + "[ -e vendor/bin/changelogger ] || { cd vendor/bin && ln -s ../../bin/changelogger; }" + ], + "post-update-cmd": [ + "[ -e vendor/bin/changelogger ] || { cd vendor/bin && ln -s ../../bin/changelogger; }" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Jetpack Changelogger tool. Allows for managing changelogs by dropping change files into a changelog directory with each PR.", + "keywords": [ + "changelog", + "cli", + "dev", + "keepachangelog" + ], + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/wordbless", + "version": "0.4.2", + "source": { + "type": "git", + "url": "https://github.com/Automattic/wordbless.git", + "reference": "a1fe6376b81e6d037190aa1a5dc684d51eb674cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Automattic/wordbless/zipball/a1fe6376b81e6d037190aa1a5dc684d51eb674cd", + "reference": "a1fe6376b81e6d037190aa1a5dc684d51eb674cd", + "shasum": "" + }, + "require": { + "php": ">=5.6.20", + "roots/wordpress": "^6.0.2", + "yoast/phpunit-polyfills": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^9.5" + }, + "type": "wordpress-dropin", + "autoload": { + "psr-4": { + "WorDBless\\": "src/", + "WorDBless\\Composer\\": "src/Composer/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Automattic Inc." + } + ], + "description": "WorDBless allows you to use WordPress core functions in your PHPUnit tests without having to set up a database and the whole WordPress environment", + "support": { + "issues": "https://github.com/Automattic/wordbless/issues", + "source": "https://github.com/Automattic/wordbless/tree/0.4.2" + }, + "time": "2023-03-15T12:16:20+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "4be43904336affa5c2f70744a348312336afd0da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", + "reference": "4be43904336affa5c2f70744a348312336afd0da", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "time": "2023-01-05T11:28:13+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:15:36+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2023-03-08T13:26:56+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.0.2", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" + }, + "time": "2024-03-05T20:51:40+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpcsstandards/phpcsextra", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", + "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/11d387c6642b6e4acaf0bd9bf5203b8cca1ec489", + "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpcsstandards/phpcsutils": "^1.0.9", + "squizlabs/php_codesniffer": "^3.8.0" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "phpcsstandards/phpcsdevtools": "^1.2.1", + "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" + } + ], + "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", + "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSExtra" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2023-12-08T16:49:07+00:00" + }, + { + "name": "phpcsstandards/phpcsutils", + "version": "1.0.12", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", + "reference": "87b233b00daf83fb70f40c9a28692be017ea7c6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/87b233b00daf83fb70f40c9a28692be017ea7c6c", + "reference": "87b233b00daf83fb70f40c9a28692be017ea7c6c", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.10.0 || 4.0.x-dev@dev" + }, + "require-dev": { + "ext-filter": "*", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPCSUtils/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" + } + ], + "description": "A suite of utility functions for use with PHP_CodeSniffer", + "homepage": "https://phpcsutils.com/", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "phpcs3", + "standards", + "static analysis", + "tokens", + "utility" + ], + "support": { + "docs": "https://phpcsutils.com/", + "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", + "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSUtils" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-05-20T13:34:27+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.31", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:37:42+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.19", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.28", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2024-04-05T04:35:58+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "roots/wordpress", + "version": "6.5.4", + "source": { + "type": "git", + "url": "https://github.com/roots/wordpress.git", + "reference": "41ff6e23ccbc3a1691406d69fe8c211a225514e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/roots/wordpress/zipball/41ff6e23ccbc3a1691406d69fe8c211a225514e2", + "reference": "41ff6e23ccbc3a1691406d69fe8c211a225514e2", + "shasum": "" + }, + "require": { + "roots/wordpress-core-installer": "^1.0.0", + "roots/wordpress-no-content": "self.version" + }, + "type": "metapackage", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT", + "GPL-2.0-or-later" + ], + "description": "WordPress is open source software you can use to create a beautiful website, blog, or app.", + "homepage": "https://wordpress.org/", + "keywords": [ + "blog", + "cms", + "wordpress" + ], + "support": { + "issues": "https://github.com/roots/wordpress/issues", + "source": "https://github.com/roots/wordpress/tree/6.5.4" + }, + "funding": [ + { + "url": "https://github.com/roots", + "type": "github" + } + ], + "time": "2022-06-01T16:54:37+00:00" + }, + { + "name": "roots/wordpress-core-installer", + "version": "1.100.0", + "source": { + "type": "git", + "url": "https://github.com/roots/wordpress-core-installer.git", + "reference": "73f8488e5178c5d54234b919f823a9095e2b1847" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/roots/wordpress-core-installer/zipball/73f8488e5178c5d54234b919f823a9095e2b1847", + "reference": "73f8488e5178c5d54234b919f823a9095e2b1847", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.6.0" + }, + "conflict": { + "composer/installers": "<1.0.6" + }, + "replace": { + "johnpbloch/wordpress-core-installer": "*" + }, + "require-dev": { + "composer/composer": "^1.0 || ^2.0", + "phpunit/phpunit": ">=5.7.27" + }, + "type": "composer-plugin", + "extra": { + "class": "Roots\\Composer\\WordPressCorePlugin" + }, + "autoload": { + "psr-4": { + "Roots\\Composer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "John P. Bloch", + "email": "me@johnpbloch.com" + }, + { + "name": "Roots", + "email": "team@roots.io" + } + ], + "description": "A custom installer to handle deploying WordPress with composer", + "keywords": [ + "wordpress" + ], + "support": { + "issues": "https://github.com/roots/wordpress-core-installer/issues", + "source": "https://github.com/roots/wordpress-core-installer/tree/master" + }, + "funding": [ + { + "url": "https://github.com/roots", + "type": "github" + }, + { + "url": "https://www.patreon.com/rootsdev", + "type": "patreon" + } + ], + "time": "2020-08-20T00:27:30+00:00" + }, + { + "name": "roots/wordpress-no-content", + "version": "6.5.4", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress.git", + "reference": "6.5.4" + }, + "dist": { + "type": "zip", + "url": "https://downloads.wordpress.org/release/wordpress-6.5.4-no-content.zip", + "shasum": "bb4d48b2f6f47f4c06061460c83f8453e9809af5" + }, + "require": { + "php": ">= 7.0.0" + }, + "provide": { + "wordpress/core-implementation": "6.5.4" + }, + "suggest": { + "ext-curl": "Performs remote request operations.", + "ext-dom": "Used to validate Text Widget content and to automatically configuring IIS7+.", + "ext-exif": "Works with metadata stored in images.", + "ext-fileinfo": "Used to detect mimetype of file uploads.", + "ext-hash": "Used for hashing, including passwords and update packages.", + "ext-imagick": "Provides better image quality for media uploads.", + "ext-json": "Used for communications with other servers.", + "ext-libsodium": "Validates Signatures and provides securely random bytes.", + "ext-mbstring": "Used to properly handle UTF8 text.", + "ext-mysqli": "Connects to MySQL for database interactions.", + "ext-openssl": "Permits SSL-based connections to other hosts.", + "ext-pcre": "Increases performance of pattern matching in code searches.", + "ext-xml": "Used for XML parsing, such as from a third-party site.", + "ext-zip": "Used for decompressing Plugins, Themes, and WordPress update packages." + }, + "type": "wordpress-core", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "WordPress Community", + "homepage": "https://wordpress.org/about/" + } + ], + "description": "WordPress is open source software you can use to create a beautiful website, blog, or app.", + "homepage": "https://wordpress.org/", + "keywords": [ + "blog", + "cms", + "wordpress" + ], + "support": { + "docs": "https://developer.wordpress.org/", + "forum": "https://wordpress.org/support/", + "irc": "irc://irc.freenode.net/wordpress", + "issues": "https://core.trac.wordpress.org/", + "rss": "https://wordpress.org/news/feed/", + "source": "https://core.trac.wordpress.org/browser", + "wiki": "https://codex.wordpress.org/" + }, + "funding": [ + { + "url": "https://wordpressfoundation.org/donate/", + "type": "other" + } + ], + "time": "2024-06-06T12:30:39+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:27:43+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:41:17+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:19:30+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:30:58+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:33:00+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:35:11+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:20:34+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:07:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-14T16:00:52+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.10.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "8f90f7a53ce271935282967f53d0894f8f1ff877" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/8f90f7a53ce271935282967f53d0894f8f1ff877", + "reference": "8f90f7a53ce271935282967f53d0894f8f1ff877", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-05-22T21:24:41+00:00" + }, + { + "name": "symfony/console", + "version": "v5.4.40", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "aa73115c0c24220b523625bfcfa655d7d73662dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/aa73115c0c24220b523625bfcfa655d7d73662dd", + "reference": "aa73115c0c24220b523625bfcfa655d7d73662dd", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.4.40" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:33:22+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "80d075412b557d41002320b96a096ca65aa2c98d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/80d075412b557d41002320b96a096ca65aa2c98d", + "reference": "80d075412b557d41002320b96a096ca65aa2c98d", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-24T14:02:46+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "21bd091060673a1177ae842c0ef8fe30893114d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2", + "reference": "21bd091060673a1177ae842c0ef8fe30893114d2", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/process", + "version": "v5.4.40", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "deedcb3bb4669cae2148bc920eafd2b16dc7c046" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/deedcb3bb4669cae2148bc920eafd2b16dc7c046", + "reference": "deedcb3bb4669cae2148bc920eafd2b16dc7c046", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.4.40" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:33:22+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a2329596ddc8fd568900e3fc76cba42489ecc7f3", + "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-04-21T15:04:16+00:00" + }, + { + "name": "symfony/string", + "version": "v5.4.40", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "142877285aa974a6f7685e292ab5ba9aae86b143" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/142877285aa974a6f7685e292ab5ba9aae86b143", + "reference": "142877285aa974a6f7685e292ab5ba9aae86b143", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.40" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:33:22+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/9333efcbff231f10dfd9c56bb7b65818b4733ca7", + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-libxml": "*", + "ext-tokenizer": "*", + "ext-xmlreader": "*", + "php": ">=5.4", + "phpcsstandards/phpcsextra": "^1.2.1", + "phpcsstandards/phpcsutils": "^1.0.10", + "squizlabs/php_codesniffer": "^3.9.0" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.0", + "phpcsstandards/phpcsdevtools": "^1.2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "suggest": { + "ext-iconv": "For improved results", + "ext-mbstring": "For improved results" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "keywords": [ + "phpcs", + "standards", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", + "source": "https://github.com/WordPress/WordPress-Coding-Standards", + "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" + }, + "funding": [ + { + "url": "https://opencollective.com/php_codesniffer", + "type": "custom" + } + ], + "time": "2024-03-25T16:39:00+00:00" + }, + { + "name": "yoast/phpunit-polyfills", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", + "reference": "224e4a1329c03d8bad520e3fc4ec980034a4b212" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/224e4a1329c03d8bad520e3fc4ec980034a4b212", + "reference": "224e4a1329c03d8bad520e3fc4ec980034a4b212", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "require-dev": { + "yoast/yoastcs": "^2.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "phpunitpolyfills-autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Team Yoast", + "email": "support@yoast.com", + "homepage": "https://yoast.com" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Yoast/PHPUnit-Polyfills/graphs/contributors" + } + ], + "description": "Set of polyfills for changed PHPUnit functionality to allow for creating PHPUnit cross-version compatible tests", + "homepage": "https://github.com/Yoast/PHPUnit-Polyfills", + "keywords": [ + "phpunit", + "polyfill", + "testing" + ], + "support": { + "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", + "source": "https://github.com/Yoast/PHPUnit-Polyfills" + }, + "time": "2023-08-19T14:25:08+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": { + "automattic/jetpack-config": 20, + "automattic/jetpack-post-list": 20, + "automattic/jetpack-mu-wpcom": 20, + "automattic/jetpack-changelogger": 20 + }, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=7.4" + }, + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/projects/plugins/wpcomsh/constants.php b/projects/plugins/wpcomsh/constants.php new file mode 100644 index 0000000000000..ade9899584faf --- /dev/null +++ b/projects/plugins/wpcomsh/constants.php @@ -0,0 +1,35 @@ + + label ); ?> +
+ +
+ $value ) { + $this->{$key} = $value; + } + } + } + + /** + * Data setter method. + * + * @param string $member the data key. + * @param mixed $value the data value. + */ + public function __set( $member, $value ) { + $this->data[ $member ] = $value; + + if ( 'colors' === $member ) { + $this->format_colors(); + } + } + + /** + * Data getter method. + * + * @param string $member the data key. + * @return mixed $value + */ + public function __get( $member ) { + return $this->data[ $member ]; + } + + /** + * A search method for palettes. Specify an ID or a set of colors to find the matching palette. + * + * @param array{id?:int,colors?:array} $args initial arguments. + * @return Palette|false + */ + public static function get( $args = array() ) { + $defaults = array( + 'id' => 0, + 'colors' => array(), + ); + + $args = wp_parse_args( $args, $defaults ); + $args['id'] = intval( $args['id'] ); + $args['colors'] = (array) $args['colors']; + + if ( ! $args['id'] && ! empty( $args['colors'] ) ) { + foreach ( $args['colors'] as $color_index => $color_code ) { + $args['colors'][ $color_index ] = Colors_Manager::normalize_color( $color_code ); + } + + $args['colors'] = implode( ',', array_unique( $args['colors'] ) ); + + $palette_by_colors = Colors_API::call( 'palettes', array( 'colors' => $args['colors'] ) ); + if ( $palette_by_colors ) { + $args['id'] = $palette_by_colors['id']; + } + } + + if ( $args['id'] ) { + $palette_data = wp_cache_get( 'palette:' . $args['id'], 'colors' ); + + if ( false === $palette_data ) { + $palette_data = Colors_API::call( 'palettes', array(), $args['id'] ); + wp_cache_set( 'palette:' . $args['id'], $palette_data, 'colors' ); + } + + if ( $palette_data ) { + return new Palette( $palette_data ); + } + } + + return false; + } + + /** + * COLOURLovers formats color sets as arrays of [hex=>, width=>] pairs, but we only care about the hex. + * This function assigns the colors in the array to the five color roles. + */ + public function format_colors() { + if ( is_string( $this->data['colors'] ) ) { + $this->data['colors'] = json_decode( $this->data['colors'], true ); + } elseif ( is_array( $this->data['colors'] ) && isset( $this->data['colors']['bg'] ) ) { + return; + } + + $colors = array(); + + foreach ( array( 'bg', 'txt', 'link', 'fg1', 'fg2' ) as $color_index => $color_key ) { + if ( count( $this->data['colors'] ) === $color_index ) { + break; + } + + $colors[ $color_key ] = $this->data['colors'][ $color_index ]['hex']; + } + + $this->data['colors'] = $colors; + } +} diff --git a/projects/plugins/wpcomsh/custom-colors/colors-api.php b/projects/plugins/wpcomsh/custom-colors/colors-api.php new file mode 100644 index 0000000000000..0aabc7a17cb1c --- /dev/null +++ b/projects/plugins/wpcomsh/custom-colors/colors-api.php @@ -0,0 +1,146 @@ + 'GET' ), $args, 'wpcom' ); + if ( is_wp_error( $response ) || 200 !== $response['response']['code'] || ! isset( $response['body'] ) ) { + return array(); + } + return json_decode( $response['body'], true ); + } + + /** + * Check if route is valid. + * + * @param string|false $route Color API route. + * + * @return bool + */ + public static function is_valid_route( $route ) { + return in_array( $route, self::$valid_routes, true ); + } + + /** + * Validate args. + * + * @param array $args Color API args. + * + * @return bool|array + */ + public static function validate_args( $args ) { + if ( ! is_array( $args ) ) { + return false; + } + $valid_args = array(); + foreach ( $args as $arg => $value ) { + if ( in_array( $arg, self::$valid_args, true ) ) { + $valid_args[ $arg ] = $value; + } + } + return $valid_args; + } + + /** + * Query the WordPress.com REST API using the blog token + * + * Based on `wpcom_json_api_request_as_blog` in fbhepr%2Skers%2Swrgcnpx%2Spynff.wrgcnpx%2Qpyvrag.cuc-og + * Modified to work with v2 wpcom endpoints + * + * @param string $path Request path. + * @param int|string $version API version. + * @param array $args Request args. + * @param array|string|null $body Request body. + * @param string $base_api_path Determines the base API path for jetpack requests; defaults to 'rest'. + * + * @return array|WP_Error $response Data. + */ + public static function wpcom_json_api_request_as_blog( $path, $version = 1, $args = array(), $body = null, $base_api_path = 'rest' ) { + $filtered_args = array_intersect_key( + $args, + array( + 'method' => 'string', + 'timeout' => 'int', + 'redirection' => 'int', + 'stream' => 'boolean', + 'filename' => 'string', + 'sslverify' => 'boolean', + ) + ); + + /** + * Determines whether Jetpack can send outbound https requests to the WPCOM api. + * + * @since 3.6.0 + * + * @param bool $proto Defaults to true. + */ + $proto = apply_filters( 'jetpack_can_make_outbound_https', true ) ? 'https' : 'http'; + + // unprecedingslashit + $_path = preg_replace( '/^\//', '', $path ); + + // Use GET by default whereas `remote_request` uses POST + if ( isset( $filtered_args['method'] ) && strtoupper( $filtered_args['method'] ) === 'POST' ) { + $request_method = 'POST'; + } else { + $request_method = 'GET'; + } + + $validated_args = array_merge( + $filtered_args, + array( + 'url' => sprintf( '%s://%s/%s/v%s/%s', $proto, JETPACK__WPCOM_JSON_API_HOST, $base_api_path, $version, $_path ), + 'blog_id' => (int) Jetpack_Options::get_option( 'id' ), + 'method' => $request_method, + ) + ); + + return Automattic\Jetpack\Connection\Client::remote_request( $validated_args, $body ); + } +} + +new Colors_API(); diff --git a/projects/plugins/wpcomsh/custom-colors/colors.php b/projects/plugins/wpcomsh/custom-colors/colors.php new file mode 100644 index 0000000000000..572e789d12256 --- /dev/null +++ b/projects/plugins/wpcomsh/custom-colors/colors.php @@ -0,0 +1,2042 @@ + Background + if ( 'appearance_page_custom-background' !== $GLOBALS['page_hook'] ) { + return; + } + + require __DIR__ . '/core-bg-admin-notice.php'; + } + + /** + * A helper function to pick an unspecified theme based on the current context. + * + * @param ?boolean|string $theme A theme that, if false, the function will specify. + * @return string The theme. + */ + protected static function pick_theme( $theme = false ) { + if ( false !== $theme ) { + return $theme; + } + + $theme = get_option( 'stylesheet' ); + + // In an Ajax call from the Customizer, we might be previewing a separate theme. + // Detect that and use it if it's there. + + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + if ( ! isset( $_SERVER['HTTP_REFERER'] ) ) { + return $theme; + } + $parsed_url = wp_parse_url( sanitize_url( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) ); + if ( $parsed_url && ! isset( $parsed_url['query'] ) ) { + return $theme; + } + wp_parse_str( $parsed_url['query'] ?? '', $query_parts ); + if ( isset( $query_parts['theme'] ) ) { + return $query_parts['theme']; + } + } + return $theme; + } + + /** + * Does the theme have annotations? Will load them as well. + * + * @param ?boolean|string $theme A theme or false. + * @return boolean theme has annotations + */ + public static function has_annotations( $theme = false ) { + // if we're not gonna support it, avoid the filesys hit + if ( self::will_never_support( $theme ) ) { + return false; + } + // if $colors is populated, we've run some add_color_rule calls. + // but skip if we directly asked for a theme to avoid false positives + if ( ! $theme && ! empty( self::$colors ) ) { + return true; + } + // if we called a direct string, we probably don't want to actually load them + if ( $theme ) { + $file = get_template_directory() . '/inc/wpcom-colors.php'; + return is_readable( $file ); + } + // try to load annotations, which returns status of finding them. + return self::load_annotations( $theme ); + } + + /** + * Do we have colors to work with? + * + * @return boolean active state + */ + public static function theme_has_set_colors() { + $opts = get_theme_mod( 'colors_manager', array( 'colors' => false ) ); + + if ( ! isset( $opts['colors'] ) ) { + return false; + } + + $opts = $opts['colors']; + // need the softer non-equal on the last in case keys are in different order. + return self::has_annotations() && (bool) $opts && $opts !== self::get_default_colors(); + } + + /** + * Will the theme never support Custom Colors? + * + * @param boolean|string $theme Optional theme slug. Uses current theme by default. + * @return boolean + */ + public static function will_never_support( $theme = false ) { + $theme = self::pick_theme( $theme ); + return in_array( $theme, self::$never_support, true ); + } + + /** + * Admin Javascript and CSS + */ + public static function admin_scripts_and_css() { + wp_enqueue_style( 'colors-tool' ); + wp_enqueue_style( 'noticons' ); + wp_enqueue_script( 'colors-tool' ); + + $settings = array( + 'defaultColors' => self::get_default_colors(), + 'themeSupport' => array( 'customBackground' => current_theme_supports( 'custom-background' ) ), + 'defaultImage' => get_theme_support( 'custom-background', 'default-image' ), + 'topPatterns' => self::get_patterns( array( 'limit' => 30 ) ), + 'genPalette' => esc_js( __( 'Generating...', 'wpcomsh' ) ), + 'backgroundTitle' => esc_js( __( 'Background', 'wpcomsh' ) ), + 'colorsTitle' => esc_js( __( 'Colors', 'wpcomsh' ) ), + 'mediaTitle' => esc_js( __( 'Select background image', 'wpcomsh' ) ), + 'mediaSelectButton' => esc_js( __( 'Select', 'wpcomsh' ) ), + ); + + wp_localize_script( 'colors-tool', 'ColorsTool', $settings ); + } + + /** + * Registers scripts and styles. + */ + public static function register_scripts_and_styles() { + // register styles + wp_register_style( 'colors-tool', plugins_url( 'css/colors-control.css', __FILE__ ), array(), '20220727' ); + wp_register_style( 'noticons', '//s0.wp.com/i/noticons/noticons.css', array(), '20120621', 'all' ); + + // register scripts + wp_register_script( 'Color.js', plugins_url( 'js/color.js', __FILE__ ), array(), '20121210', true ); + wp_register_script( 'colors-instapreview', plugins_url( 'js/colors-theme-preview.js', __FILE__ ), array( 'customize-preview', 'jquery', 'Color.js' ), '20121210', true ); + wp_register_script( 'colors-tool', plugins_url( 'js/colors-control.js', __FILE__ ), array( 'customize-controls', 'iris' ), '20160726', true ); + wp_register_script( 'spin', plugins_url( 'js/spin.js', __FILE__ ), array(), '1.3', true ); + wp_register_script( 'jquery.spin', plugins_url( 'js/jquery.spin.js', __FILE__ ), array( 'spin' ), '20210111', true ); + } + + /** + * Add a 'custom-colors' body class to blogs with Custom Colors active. + * + * @param array $classes the array of classes to add custom class to. + */ + public static function body_class( $classes ) { + array_push( $classes, 'custom-colors' ); + return $classes; + } + + /** + * Enqueue WP.com spinner scripts. + */ + public static function spinner_scripts() { + wp_enqueue_script( 'spin' ); + wp_enqueue_script( 'jquery.spin' ); + } + + /** + * Constructs the color array + */ + public static function get_colors() { + $opts = get_theme_mod( 'colors_manager', array( 'colors' => false ) ); + $colors = ( $opts['colors'] ) ? $opts['colors'] : self::$default_colors; + unset( $colors['undefined'] ); + return $colors; + } + + /** + * Returns default colors. + */ + public static function get_default_colors() { + return self::$default_colors; + } + + /** + * Returns color slots. + */ + public static function get_color_slots() { + return array( 'bg', 'txt', 'link', 'fg1', 'fg2' ); + } + + /** + * The Color Grid + * + * This method outputs the core UI structure of the colors tool + * Includes color_palettes. + */ + public static function color_grid() { + ?> + + + + +
+
+
+
    + ', + esc_attr( $cat ), + esc_attr( $class ), + esc_attr( self::$labels[ $cat ] ) + ); + if ( 'bg' === $cat ) { + printf( + '%s', + esc_html__( 'Change', 'wpcomsh' ) + ); + } + printf( '' ); + } + ?> +
+ + + + + +
+ +

+
    +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
+

+ pick your own color.', 'wpcomsh' ), + array( + 'a' => array( + 'href' => array(), + 'id' => array(), + ), + ) + ); + ?> +

+ +
+ + +
+ +
    + $value ) { + $class = isset( self::$colors[ $cat ] ) ? $cat : "{$cat} unavailable"; + printf( + '
  • %s
  • ', + esc_attr( $class ), + esc_attr( $value ), + esc_attr( self::$labels[ $cat ] ), + esc_html( $value ) + ); + } + ?> +
+ $palettes ); + + header( 'Content-Type: text/javascript' ); + echo wp_json_encode( $response ); + die; + } + + /** + * Outputs generated color pallette for AJAX requests. + * + * @return never + */ + public static function ajax_generate_palette() { + $response = self::get_generated_palette( $_REQUEST ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- this is a GET request that doesn't change anything. + header( 'Content-Type: text/javascript' ); + echo wp_json_encode( $response ); + die; + } + + /** + * Outputs color recommendations for AJAX requests. + * + * @return never + */ + public static function ajax_color_recommendations() { + $colors = self::get_color_recommendations( $_REQUEST ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- this is a GET request that doesn't change anything. + + $response = array( 'colors' => $colors ); + + header( 'Content-Type: text/javascript' ); + echo wp_json_encode( $response ); + die; + } + + /** + * Outputs pattern recommendations for AJAX requests. + * + * @return never + */ + public static function ajax_pattern_recommendations() { + $patterns = self::get_pattern_recommendations( $_REQUEST ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- this is a GET request that doesn't change anything. + + $response = array( 'patterns' => $patterns ); + + header( 'Content-Type: text/javascript' ); + echo wp_json_encode( $response ); + die; + } + + /** + * Ensure that COLOURLovers URLs are saved without any imgpress stuff. + * + * @param array $new_theme_mods new theme mods. + * @return array + */ + public static function format_colourlovers_urls( $new_theme_mods ) { + if ( ! empty( $new_theme_mods['background_image'] ) && false !== strpos( $new_theme_mods['background_image'], '/imgpress?url=' . rawurlencode( self::COLOURLOVERS_HOST ) ) ) { + $parts = explode( '/imgpress?url=', $new_theme_mods['background_image'], 2 ); + $new_theme_mods['background_image'] = urldecode( array_pop( $parts ) ); + } + + return $new_theme_mods; + } + + /** + * When a user saves a COLOURLovers palette or pattern, save the CL metadata + * for attribution later on. + * + * Also used to track COLOURlovers asset usage. + * See https://mc.a8c.com/s/colourlovers-pattern/ and + * https://mc.a8c.com/s/colourlovers-palette/ + * + * 1. Which color palettes are chosen, and overall number of times a pattern is switched to. + * 2. Which background patterns are chosen, and the overall number of times a pattern is switched to. + * + * @param array $oldvalue old metadata. + * @param array $newvalue new metadata value. + */ + public static function save_colourlovers_metadata( $oldvalue, $newvalue ) { + $mods = $newvalue; + + if ( isset( $oldvalue['background_image'] ) && isset( $newvalue['background_image'] ) && $oldvalue['background_image'] !== $newvalue['background_image'] ) { + $using_colourlovers_pattern = false; + + if ( 0 === strpos( $mods['background_image'], self::COLOURLOVERS_HOST ) ) { + $matches = array(); + + if ( preg_match( '/\/([0-9]+)\.png$/i', $mods['background_image'], $matches ) ) { + $using_colourlovers_pattern = true; + + $pattern_id = $matches[1]; + + if ( empty( $mods['background_image_metadata'] ) || $pattern_id !== $mods['background_image_metadata']['pattern_id'] ) { + $pattern = Colors_API::call( 'patterns', array(), (int) $pattern_id ); + if ( ! is_wp_error( $pattern ) && is_array( $pattern ) ) { + set_theme_mod( + 'background_image_metadata', + array( + 'pattern_id' => $pattern_id, + 'username' => $pattern['username'], + 'title' => $pattern['title'], + ) + ); + } + } + } + } + + if ( ! $using_colourlovers_pattern && ! empty( $mods['background_image_metadata'] ) ) { + remove_theme_mod( 'background_image_metadata' ); + } + } + + if ( isset( $newvalue['background_image'] ) && 0 === strpos( $newvalue['background_image'], self::COLOURLOVERS_HOST ) && $newvalue['background_image'] !== $newvalue['background_image_thumb'] ) { + /** + * Due to a bug with percent signs in background_image URLs, we need to make sure that + * our background image is also saved as the background_image_thumb value. We need to + * do this any time theme_mods is updated, because there is other code aggressively + * trying to delete background_image_thumb completely. + */ + set_theme_mod( 'background_image_thumb', $newvalue['background_image'] ); + } + + if ( isset( $oldvalue['colors_manager'] ) && isset( $newvalue['colors_manager'] ) && $newvalue['colors_manager']['colors'] !== $oldvalue['colors_manager']['colors'] ) { + if ( empty( $newvalue['colors_manager']['colors'] ) && $newvalue['color_palette_metadata'] ) { + remove_theme_mod( 'color_palette_metadata' ); + } else { + require_once __DIR__ . '/class-palette.php'; + + $palette = Palette::get( array( 'colors' => $newvalue['colors_manager']['colors'] ) ); + + if ( $palette ) { + if ( empty( $newvalue['color_palette_metadata'] ) || $palette->id !== $newvalue['color_palette_metadata']['palette_id'] ) { + set_theme_mod( + 'color_palette_metadata', + array( + 'palette_id' => $palette->id, + 'username' => $palette->username, + 'title' => $palette->title, + ) + ); + } + } else { + remove_theme_mod( 'color_palette_metadata' ); + } + } + } + } + + /** + * Are colors the same? + * + * @param string $a color A. + * @param string $b color B. + * @return boolean + */ + public static function is_same_color( $a, $b ) { + $a = trim( strtolower( $a ), ' #' ); + $b = trim( strtolower( $b ), ' #' ); + return $a === $b; + } + + /** + * Are we on the default pallette? + * + * @param array $colors tested colors. + * @return boolean + */ + public static function is_default_palette( $colors ) { + // a saved palette may have more colors than the default palette. So, + // iterate over the default palette + foreach ( self::$default_colors as $id => $default_color ) { + if ( ! isset( $colors[ $id ] ) ) { + return false; + } + if ( ! self::is_same_color( $default_color, $colors[ $id ] ) ) { + return false; + } + } + return true; + } + + /** + * Are we on the featured pallette? + * + * @param array $colors tested colors. + * @return boolean + */ + public static function is_featured_palette( $colors ) { + + $featured_palettes = self::$color_palettes; + + foreach ( $colors as $c ) { + $c = strtolower( $c ); + } + + // look for our palette in featured palettes + foreach ( $featured_palettes as $p ) { + $p = $p['palette']; + $found = true; + // for each color of the featured palette + foreach ( $p as $i => $c ) { + // we don't care about the background color; non-CD users are + // free to change it + if ( 0 === $i ) { + continue; + } + + $c = strtolower( $c ); + // if that color isn't in our palette + if ( ! empty( $c ) && ! in_array( $c, $colors, true ) ) { + // try another featured palette + $found = false; + break; + } + } + if ( $found ) { + return true; + } + } + return false; + } + + /** + * Should we enable custom colors? + */ + public static function should_enable_colors() { + $opts = get_theme_mod( 'colors_manager', array( 'colors' => false ) ); + if ( ! $opts['colors'] ) { + return false; + } + + $colors = $opts['colors']; + + // If we managed to save the default palette, bail. It does not actually render + // the same thing as the theme's default style + if ( self::is_default_palette( $colors ) ) { + return false; + } + + return apply_filters( 'custom_colors_enable', true ); + } + + /** + * Query and return palette data. + * + * @param array{color?:string,limit?:int,offset?:int} $args initial color settings. + * @return array An array of color palettes. + */ + public static function get_color_palettes( $args = array() ) { + $defaults = array( + 'color' => false, + 'limit' => 6, + 'offset' => 0, + ); + + $args = wp_parse_args( $args, $defaults ); + + if ( $args['color'] ) { + $args['color'] = self::normalize_color( $args['color'] ); + + $palettes = wp_cache_get( 'color-palettes-from-' . $args['color'], 'colors' ); + + if ( false === $palettes ) { + $palettes = Colors_API::call( 'palettes', array( 'color' => $args['color'] ) ); + if ( ! is_wp_error( $palettes ) ) { + wp_cache_set( 'color-palettes-from-' . $args['color'], $palettes, 'colors', MONTH_IN_SECONDS ); + } + } + } else { + $palettes = wp_cache_get( 'color-palettes-top', 'colors' ); + + if ( false === $palettes ) { + $palettes = Colors_API::call( 'palettes' ); + if ( ! is_wp_error( $palettes ) ) { + wp_cache_set( 'color-palettes-top', $palettes, 'colors', MONTH_IN_SECONDS ); + } + } + } + + $palettes = array_slice( $palettes, $args['offset'], $args['limit'] ); + + if ( ! empty( $palettes ) ) { + foreach ( $palettes as $palette_index => $palette ) { + $colors = array(); + + foreach ( self::get_color_slots() as $color_index => $color_key ) { + if ( count( $palette['colors'] ) === $color_index ) { + break; + } + + $colors[ $color_key ] = $palette['colors'][ $color_index ]['hex']; + } + + $palettes[ $palette_index ]['colors'] = $colors; + } + } + + // Shuffle palettes to make them less repetitive + shuffle( $palettes ); + + // Prepend theme-defined palettes to the first set of palettes + if ( 0 === (int) $args['offset'] ) { + $palettes = array_merge( self::get_theme_color_palettes(), $palettes ); + $palettes = array_slice( $palettes, 0, (int) $args['limit'] ); + } + + return $palettes; + } + + /** + * Return an image URL based on Gravatar URL. + * + * @param string $image_url URL to be transformed. + * @return string + */ + public static function gravatar_image_url( $image_url ) { + $prefix_http = preg_quote( 'http://www.gravatar.com/avatar/', '/' ); + $prefix_https = preg_quote( 'https://secure.gravatar.com/avatar/', '/' ); + $gravatar_prefix = sprintf( '/^(%s|%s)/', $prefix_http, $prefix_https ); + $is_gravatar_url = preg_match( $gravatar_prefix, $image_url ); + + if ( $is_gravatar_url ) { + $image_url = preg_replace( '#/([0-9a-f]+)/#', '/$1.jpg', $image_url ); + } + + return $image_url; + } + + /** + * Returns a color palette matching a given image thanks to the Tonesque + * lib. + * + * @param array{image?:string} $args an image URL in the form of an array. + * @return array A single color palette + */ + public static function get_generated_palette( $args = array() ) { + // Some themes, like Ryu, include an older version of Tonesque, which is loaded instead of the version in `/wp-content/lib/`. + // For now, only load the shared library if Tonesque isn't already present. See #5557. + if ( ! class_exists( 'Tonesque' ) ) { + require_lib( 'tonesque' ); + } + + // If the loaded version doesn't have the method needed to support palette generation, abort for now until the themes are updated. See #5557. + if ( ! method_exists( 'Tonesque', 'grab_points' ) ) { + return array(); + } + + $defaults = array( + 'image' => false, + ); + + $args = wp_parse_args( $args, $defaults ); + extract( $args, EXTR_SKIP ); // phpcs:ignore + + if ( ! $image ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- extract adds this to the scope. + return array(); + } + + $tonesque = new Tonesque( $image ); // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable + $points = $tonesque->grab_points( 'hex' ); + + $roles = self::get_color_slots(); + shuffle( $roles ); + + if ( ! is_array( $points ) ) { + return array(); + } + + $colors = array_combine( $roles, $points ); + + $palette = array( + 'id' => 'generated-palette', + 'colors' => $colors, + ); + + return $palette; + } + + /** + * Returns theme color pallettes. + */ + public static function get_theme_color_palettes() { + if ( empty( self::$color_palettes ) ) { + return array(); + } + + $map = self::get_color_slots(); + $formatted_palettes = array(); + foreach ( self::$color_palettes as $id => $palette ) { + $formatted_palette = array( + 'id' => $id, + 'colors' => array(), + ); + foreach ( $map as $index => $key ) { + if ( ! isset( $palette['palette'][ $index ] ) ) { + continue; + } + $formatted_palette['colors'][ $key ] = str_replace( '#', '', $palette['palette'][ $index ] ); + } + + $formatted_palettes[] = $formatted_palette; + } + + return $formatted_palettes; + } + + /** + * Query and return pattern data. + * + * @param array{color?:string,limit?:int,offset?:int} $args initial settings. + * @return array An array of patterns. + */ + public static function get_patterns( $args = array() ) { + $defaults = array( + 'color' => false, + 'limit' => 4, + 'offset' => 0, + ); + + $args = wp_parse_args( $args, $defaults ); + + if ( $args['color'] ) { + $args['color'] = self::normalize_color( $args['color'] ); + + $patterns = wp_cache_get( 'patterns-from-' . $args['color'], 'colors' ); + + if ( false === $patterns ) { + $patterns = Colors_API::call( 'patterns', array( 'color' => $args['color'] ) ); + if ( ! is_wp_error( $patterns ) ) { + wp_cache_set( 'patterns-from-' . $args['color'], $patterns, 'colors', MONTH_IN_SECONDS ); + } + } + } else { + $patterns = wp_cache_get( 'patterns-top', 'colors' ); + + if ( false === $patterns ) { + $patterns = Colors_API::call( 'patterns' ); + if ( ! is_wp_error( $patterns ) ) { + wp_cache_set( 'patterns-top', $patterns, 'colors', MONTH_IN_SECONDS ); + } + } + } + + $patterns = array_slice( $patterns, $args['offset'], $args['limit'] ); + + if ( ! empty( $patterns ) ) { + foreach ( $patterns as $pattern_index => $pattern ) { + $colors = array(); + + foreach ( self::get_color_slots() as $color_index => $color_key ) { + if ( count( $pattern['colors'] ) === $color_index ) { + break; + } + + $colors[ $color_key ] = $pattern['colors'][ $color_index ]['hex']; + } + + $patterns[ $pattern_index ]['colors'] = $colors; + $patterns[ $pattern_index ]['preview_image_url'] = apply_filters( 'jetpack_photon_url', $pattern['preview_image_url'], array(), 'network_path' ); + } + } + + return $patterns; + } + + /** + * Converts rgb() or hex color codes to the AABBCC format: + * + * @param string $color An rgb or hex color code. + * @return string + */ + public static function normalize_color( $color ) { + if ( false !== strpos( $color, 'rgb' ) ) { + $color_data = preg_replace( '/[^0-9\.,]/', '', $color ); + $color_components = explode( ',', $color_data ); + + $hex_color = ''; + + for ( $i = 0; $i < 3; $i++ ) { + $hex_equivalent = dechex( intval( $color_components[ $i ] ) ); + if ( strlen( $hex_equivalent ) < 2 ) { + $hex_color .= '0'; + } + $hex_color .= $hex_equivalent; + } + + return strtoupper( $hex_color ); + } else { + $hex = strtoupper( substr( preg_replace( '/[^0-9A-Z]/i', '', $color ), 0, 6 ) ); + + if ( strlen( $hex ) === 3 ) { + $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; + } else { + for ( $i = strlen( $hex ); $i < 6; $i++ ) { + $hex = '0' . $hex; + } + } + + return $hex; + } + } + + /** + * Finds colors that could be suitable complement to a given set of colors. + * + * @param array{color?:string,role?:string,colors?:array,limit?:int} $args initial settings. + * @return array An array of color codes. + */ + public static function get_color_recommendations( $args ) { + $defaults = array( + 'color' => false, + 'role' => false, + 'colors' => false, + 'limit' => 8, + ); + + $args = wp_parse_args( $args, $defaults ); + + if ( $args['color'] ) { + $args['color'] = self::normalize_color( $args['color'] ); + } + + $colors = array(); + + foreach ( $args['colors'] as $role => $color ) { + $color = self::normalize_color( $color ); + $args['colors'][ $role ] = $color; + + $palettes = Colors_API::call( + 'palettes', + array( + 'color' => $color, + 'limit' => 8, + ) + ); + + if ( is_array( $palettes ) ) { + foreach ( $palettes as $palette ) { + $multiplier = 0; + + foreach ( $palette['colors'] as $_color ) { + if ( ! $_color ) { + continue; + } + + // If this palette contains more than one of the guide colors, + // give it more weight. + if ( in_array( $_color, $args['colors'], true ) ) { + ++$multiplier; + } + } + + foreach ( $palette['colors'] as $palette_role => $_color ) { + if ( ! $_color ) { + continue; + } + + $colors[ $_color ] += ( 1 * $multiplier ); + + if ( $palette_role === $args['role'] ) { + $colors[ $_color ] += ( 1 * $multiplier ); + } + } + } + } + } + + foreach ( $args['colors'] as $color ) { + unset( $colors[ $color ] ); + } + + if ( $args['color'] ) { + unset( $colors[ $args['color'] ] ); + } + + arsort( $colors ); + $colors = array_keys( $colors ); + + if ( count( $colors ) < 8 ) { + $more_suggestions = self::color_suggestions( $args['colors'], $args['role'] ); + $colors = array_merge( $colors, $more_suggestions ); + + foreach ( $args['colors'] as $color ) { + unset( $colors[ $color ] ); + } + + if ( $args['color'] ) { + unset( $colors[ $args['color'] ] ); + } + } + + $colors = array_slice( $colors, 0, $args['limit'] ); + + return $colors; + } + + /** + * Finds patterns that could be suitable complement to a given set of colors. + * + * @param array{colors?:array,limit?:int} $args initial settings. + * @return array An array of patterns. + */ + public static function get_pattern_recommendations( $args ) { + $defaults = array( + 'colors' => false, + 'limit' => 4, + ); + + $args = wp_parse_args( $args, $defaults ); + + $patterns_by_id = array(); + $pattern_ids = array(); + + foreach ( $args['colors'] as $role => $color ) { + $color = self::normalize_color( $color ); + $args['colors'][ $role ] = $color; + + $color_patterns = Colors_API::call( + 'patterns', + array( + 'color' => $color, + 'limit' => 5, + ) + ); + + if ( is_array( $color_patterns ) ) { + foreach ( $color_patterns as $pattern ) { + $patterns_by_id[ $pattern['id'] ] = $pattern; + + if ( ! isset( $pattern_ids[ $pattern['id'] ] ) ) { + $pattern_ids[ $pattern['id'] ] = 0; + } + $pattern_ids[ $pattern['id'] ] += 1; + + foreach ( $pattern['colors'] as $value ) { + if ( in_array( $value, $args['colors'], true ) ) { + $pattern_ids[ $pattern['id'] ] += 1; + } + } + } + } + } + + arsort( $pattern_ids ); + $pattern_ids = array_keys( $pattern_ids ); + $pattern_ids = array_slice( $pattern_ids, 0, $args['limit'] ); + + $patterns = array(); + + foreach ( $pattern_ids as $pattern_id ) { + unset( $patterns_by_id[ $pattern_id ]['colors'] ); + $patterns[] = $patterns_by_id[ $pattern_id ]; + } + + return $patterns; + } + + /** + * Renders the color palettes + */ + public static function color_palettes() { + ?> +
+

+
+
+ + + +
+
+ + + add_section( + 'colors_manager_tool', + array( + 'title' => __( 'Colors & Backgrounds', 'wpcomsh' ), + 'priority' => 35, + ) + ); + + $setting_opts = array( + 'default' => self::get_colors(), + 'capability' => 'edit_theme_options', + 'transport' => 'postMessage', + 'type' => 'theme_mod', + ); + + if ( is_admin() ) { + $setting_opts = array_merge( + $setting_opts, + array( + 'sanitize_callback' => array( __CLASS__, 'sanitize_colors_on_save' ), + 'sanitize_js_callback' => array( __CLASS__, 'sanitize_colors' ), + ) + ); + } + + $wp_customize->add_setting( 'colors_manager[colors]', $setting_opts ); + + $wp_customize->add_control( + new Colors_Manager_Control( + $wp_customize, + 'colors-tool', + array( + 'label' => __( 'Colors', 'wpcomsh' ), + 'section' => 'colors_manager_tool', + 'settings' => 'colors_manager[colors]', + ) + ) + ); + } + + /** + * Sanitizes colors on save. + * + * @param array $set_colors saved colors. + * @return array + */ + public static function sanitize_colors_on_save( $set_colors ) { + // since this function only gets called if the colors changed, + // we can safely invalidate without further checks + add_action( 'shutdown', array( __CLASS__, 'delete_cached_css_on_shutdown_because_reasons' ) ); + return self::sanitize_colors( $set_colors ); + } + + /** + * Sanitizes colors. + * + * @param array $set_colors saved colors. + * @return array + */ + public static function sanitize_colors( $set_colors ) { + // let's make sure all of our keys/values are proper + $colors_wanted = array(); + $cats = self::get_color_slots(); + if ( ! class_exists( 'Jetpack_color' ) ) { + require_lib( 'class.color' ); + } + foreach ( $set_colors as $key => $color ) { + if ( ! in_array( $key, $cats, true ) || ! $color ) { + continue; + } + try { + $color_object = new Jetpack_Color( $color ); + $colors_wanted[ $key ] = '#' . $color_object->toHex(); + } catch ( Exception $e ) { // phpcs:ignore + // Exception not handled to avoid it propagating further, apparently. + } + } + return $colors_wanted; + } + + /** + * Goodbye, cache! + */ + private static function delete_cached_css() { + $colors_manager = (array) get_theme_mod( 'colors_manager' ); + $colors_manager['cached'] = false; + set_theme_mod( 'colors_manager', $colors_manager ); + } + + /** + * Goodbye, cache. Because reasons. + */ + public static function delete_cached_css_on_shutdown_because_reasons() { + remove_all_filters( 'theme_mod_colors_manager' ); + self::delete_cached_css(); + } + + /** + * Overriding theme colors. + */ + public static function override_themecolors() { + global $themecolors; + + if ( ! self::should_enable_colors() ) { + return; + } + + $opts = get_theme_mod( 'colors_manager', array( 'colors' => false ) ); + if ( ! isset( $opts ) ) { + return; + } + + $colors = $opts['colors']; + + $colors['border'] = $colors['fg1']; + $colors['url'] = $colors['link']; + $colors['text'] = $colors['txt']; + + unset( $colors['fg1'] ); + unset( $colors['fg2'] ); + unset( $colors['txt'] ); + + foreach ( $colors as $role => $color ) { + if ( $color ) { + $themecolors[ $role ] = substr( $color, 1 ); + } + } + } + + /** + * Injects our postMessage listener scripts into the theme + * + * @param WP_Customize_Manager $wp_customize the customizer manager instance. + */ + public static function theme_colors_js( $wp_customize ) { + if ( $wp_customize->is_preview() && ! is_admin() ) { + wp_enqueue_script( 'colors-instapreview' ); + $js_data = array( + 'colors' => self::$colors, + 'defaultColors' => self::get_default_colors(), + 'extraCss' => self::get_extra_css( true ), + 'extraColors' => self::$extra_colors, + ); + wp_localize_script( 'colors-instapreview', 'ColorsTool', $js_data ); + } + } + + /** + * Prints theme CSS. + */ + public static function print_theme_css() { + if ( ! self::should_enable_colors() ) { + return; + } + $css = self::get_theme_css(); + printf( + '%s', + wp_strip_all_tags( $css ), // phpcs:ignore -- CSS can't be properly escaped with esc_html + "\n" + ); + } + + /** + * Print block editor CSS. + */ + public static function print_block_editor_css() { + if ( ! self::should_enable_colors() ) { + return; + } + $css = self::get_theme_css(); + + wp_register_style( 'custom-colors-editor-css', false, array(), '20210311' ); // Register an empty stylesheet to append custom CSS to. + wp_enqueue_style( 'custom-colors-editor-css' ); + wp_add_inline_style( 'custom-colors-editor-css', $css ); // Append inline style to our new stylesheet + } + + /** + * Return theme CSS. + */ + public static function get_theme_css() { + $opts = get_theme_mod( + 'colors_manager', + array( + 'colors' => false, + 'cached' => false, + ) + ); + $has_cached = isset( $opts['cached'] ) && $opts['cached']; + + if ( $has_cached && WPCOM_USE_CACHED_COLORS ) { + return $opts['cached']; + } + + $colors = $opts['colors']; + + // extra colors/CSS: always on + $css = self::get_extra_css(); + + // user colors + foreach ( self::$colors as $cat => $rules ) { + if ( ! isset( $colors[ $cat ] ) ) { + continue; + } + + $color = $colors[ $cat ]; + foreach ( $rules as $rule ) { + $css .= self::css_rule( $rule, $color ); + } + } + + // Minify & cache for future use. + $minifier = new tubalmartin\CssMin\Minifier(); + $css = $minifier->run( $css ); + + $opts['cached'] = $css; + set_theme_mod( 'colors_manager', $opts ); + + return $css; + } + + /** + * Get CSS rule. + * + * @todo possibly combine all of this into a keyed array to prevent selector duplication bloat + * @param array $rule the CSS rule. + * @param string $color the color string. + * @return string + */ + public static function css_rule( $rule, $color ) { + $css = ''; + + if ( isset( $rule[2] ) ) { + // we'll need it in either case + if ( ! class_exists( 'Jetpack_color' ) ) { + require_lib( 'class.color' ); + } + + try { + $working_color = new Jetpack_Color( $color ); + } catch ( RangeException $e ) { + $message = 'rule: ' . print_r( $rule, 1 ) . "\n"; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r + $message .= 'call: $working_color = new Jetpack_Color( ' . $color . ' );' . "\n"; + self::exception_mailer( $message ); + return ''; + } + + $number = (float) $rule[2]; + // ensure contrast or darken/lighten + if ( is_string( $rule[2] ) ) { + $first_char = substr( $rule[2], 0, 1 ); + // darken/lighten + if ( '+' === $first_char || '-' === $first_char ) { + $modify = 10 * $number; + $color = $working_color->incrementLightness( intval( $modify ) )->toString(); + } else { + // hex bg for contrast + if ( '#' === $first_char ) { + try { + $bg_color = new Jetpack_Color( $rule[2] ); + } catch ( RangeException $e ) { + $message = 'function: ' . __FUNCTION__ . "\n"; + $message .= 'call: $bg_color = new Jetpack_Color( ' . $rule[2] . ' );' . "\n"; + self::exception_mailer( $message ); + return ''; + } + } elseif ( isset( self::$colors[ $rule[2] ] ) ) { // set color bg for contrast + + $set_colors = self::get_colors(); + try { + $bg_color = new Jetpack_Color( $set_colors[ $rule[2] ] ?? null ); + } catch ( RangeException $e ) { + $message = 'function: ' . __FUNCTION__ . "\n"; + $message .= 'call: $bg_color = new Jetpack_Color( ' . $set_colors[ $rule[2] ] . ' );' . "\n"; + self::exception_mailer( $message ); + return ''; + } + } + + // we have a bg color to contrast + if ( isset( $bg_color ) && is_a( $bg_color, 'Jetpack_Color' ) ) { + // default contrast of 5, can be overridden with 4th arg. + $contrast = $rule[3] ?? 5; + $color = $working_color->getReadableContrastingColor( $bg_color, $contrast )->toString(); + } + } + } elseif ( $rule[2] < 1 ) { // alpha + unset( $rule[2] ); + // back compat for non-rgba browsers + $css .= self::css_rule( $rule, $color ); + $color = $working_color->toCSS( 'rgba', intval( $number ) ); + } + } + $css .= "{$rule[0]} { {$rule[1]}: {$color};}\n"; + return $css; + } + + /** + * Get extra CSS. + * + * @param boolean $only_callback no processing, just callback. + */ + public static function get_extra_css( $only_callback = false ) { + $css = ''; + $extra_cb = get_theme_support( 'custom_colors_extra_css' ); + + if ( is_array( $extra_cb ) && is_callable( $extra_cb[0] ) ) { + // will work with return values or straight printing + ob_start(); + $css = call_user_func( $extra_cb[0] ); + $css .= ob_get_clean(); + } + + if ( $only_callback ) { + return $css; + } + + foreach ( self::$extra_colors as $extra ) { + if ( ! isset( $extra['rules'] ) || ! is_array( $extra['rules'] ) ) { + continue; + } + $color = $extra['color']; + foreach ( $extra['rules'] as $rule ) { + $css .= self::css_rule( $rule, (string) $color ); + } + } + return $css; + } + + /** + * Function for making theme annotations. + * + * @param string $category The color category. One of bg, txt, link, fg1, fg2. + * @param string $default_color The default color for this category. + * @param array $rules Array of rule arrays. $rule: array( selector, property, opacity );. + * @param bool|string $label Optional. A UI helper label for identifying what a particular color will change in the theme. + */ + public static function add_color_rule( $category, $default_color, $rules, $label = false ) { + // extra rules + if ( 'extra' === $category ) { + self::$extra_colors[] = array( + 'color' => $default_color, + 'rules' => $rules, + ); + return; + } + // prime it + if ( ! isset( self::$colors[ $category ] ) ) { + self::$colors[ $category ] = array(); + } + self::$colors[ $category ] = array_merge( self::$colors[ $category ], $rules ); + + self::$default_colors[ $category ] = $default_color; + if ( $label ) { + self::$labels[ $category ] = $label; + } + } + + /** + * Allow a theme to declare its own color palettes. + * + * @param array $palette An array with 5 colors. + * @param bool|string $title optional title string. + */ + public static function add_color_palette( $palette, $title = false ) { + if ( ! $title ) { + $theme = wp_get_theme(); + $title = sprintf( + // translators: %1$s is a theme name, %2$s is its custom color scheme number. + __( '%1$s Alternative Scheme %2$s', 'wpcomsh' ), + $theme->display( 'Name' ), + count( self::$color_palettes ) + 1 + ); + } + + $id = sanitize_title_with_dashes( $title ); + + self::$color_palettes[ $id ] = compact( 'title', 'palette' ); + } + + /** + * Loads theme annotations, and filter them if loaded. + * + * @param boolean $theme Which theme to check for annotations on. Defaults to current theme. + * @return boolean Theme has annotations. + */ + protected static function load_annotations( $theme = false ) { + $theme_name = 'pub/' . self::pick_theme( $theme ); + $annotations_file = get_stylesheet_directory() . '/inc/wpcom-colors.php'; + self::prime_color_labels(); + if ( is_readable( $annotations_file ) ) { + require_once $annotations_file; + self::$colors = apply_filters( 'custom_colors_rules', self::$colors, $theme_name ); + self::handle_unset_colors(); + return true; + } + return false; + } + + /** + * Unset colors that need to be unset. + */ + protected static function handle_unset_colors() { + foreach ( self::$colors as $key => $value ) { + if ( empty( $value ) ) { + // set Label to Unused + self::$labels[ $key ] = __( 'Unused', 'wpcomsh' ); + unset( self::$colors[ $key ] ); + } + } + } + + /** + * Sets default, i10n-ized default color labels that can be overridden in annotations. + */ + protected static function prime_color_labels() { + if ( ! empty( self::$labels ) ) { + return; + } + + self::$labels = array( + 'bg' => __( 'Background', 'wpcomsh' ), + 'txt' => __( 'Headings', 'wpcomsh' ), + 'link' => __( 'Links', 'wpcomsh' ), + 'fg1' => __( 'Accent #1', 'wpcomsh' ), + 'fg2' => __( 'Accent #2', 'wpcomsh' ), + ); + } + + /** + * Generate color suggestions for a given role from a set of colors. + * + * @param array $colors color array. + * @param string $role (bg|fg1|fg2|txt|link). + * @return array + */ + public static function color_suggestions( $colors, $role ) { + if ( ! class_exists( 'Jetpack_color' ) ) { + require_lib( 'class.color' ); + } + + $suggestions = array(); + + $suggestions = array_merge( $suggestions, self::color_suggestions_from_palette( $colors, $role ) ); + $suggestions = array_merge( $suggestions, self::color_suggestions_from_math( $colors, $role ) ); + + shuffle( $suggestions ); + + return $suggestions; + } + + /** + * Generate color suggestions by grabbing a popular palette and applying + * it as a transformation to the colors we're using as a guide. + * + * @param array $colors color array. + * @param string $role (bg|fg1|fg2|txt|link). + * @return array + */ + public static function color_suggestions_from_palette( $colors, $role ) { + $suggestions = array(); + + $top_palette = self::get_color_palettes( + array( + 'limit' => 1, + 'offset' => wp_rand( + 0, + 100 + ), + ) + ); + $top_palette = $top_palette[0]; + + $equivalent_color_hex = $top_palette['colors'][ $role ]; + + foreach ( $top_palette['colors'] as $palette_role => $palette_color_hex ) { + $base_color_hex = $colors[ $palette_role ]; + try { + // phpcs:ignore -- $base_color:$new_color :: $palette_color:$equivalent_color + $base_color = new Jetpack_Color( $base_color_hex ); + $palette_color = new Jetpack_Color( $palette_color_hex ); + $equivalent_color = new Jetpack_Color( $equivalent_color_hex ); + + $palette_hsl = $palette_color->toHsl(); + $equivalent_hsl = $equivalent_color->toHsl(); + + $base_color->incrementHue( $equivalent_hsl['h'] - $palette_hsl['h'] ); + $base_color->saturate( $equivalent_hsl['s'] - $palette_hsl['s'] ); + $base_color->lighten( $equivalent_hsl['l'] - $palette_hsl['l'] ); + + $suggestions[] = self::normalize_color( $base_color->toHex() ); + } catch ( RangeException $e ) { + $message = "Color exception!\n\n"; + $message .= "role: $role\n"; + $message .= "base: $base_color_hex\n"; + $message .= "palette: $palette_color_hex\n"; + $message .= "equiv: $equivalent_color_hex\n"; + $message .= 'colors arg: ' . print_r( $colors, 1 ); // phpcs:ignore + self::exception_mailer( $message ); + continue; + } + } + + return $suggestions; + } + + // phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + /** + * Mail the exception. + * + * @param string $message the exception private. + */ + public static function exception_mailer( $message = 'Needs a message' ) { + $message .= "\n\nblog: " . home_url() . "\n"; + $message .= 'backtrace: ' . wp_debug_backtrace_summary() . "\n"; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_wp_debug_backtrace_summary + // phpcs:ignore -- wp_mail( 'wiebe@automattic.com', 'Color Exception on WordPress.com', $message ); + } + // phpcs:enable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + + /** + * Use a set of predefined transformations to generate color suggestions + * based on roles. + * + * @param array $colors color array. + * @param string $role (bg|fg1|fg2|txt|link). + * @return array + */ + public static function color_suggestions_from_math( $colors, $role ) { + $suggestions = array(); + + // These are the result of a couple of hours of playing around. + // Nothing here is set in stone. + $relations = array( + 'bg:fg1' => array( 'brighter', 'saturate' ), + 'bg:fg2' => array( 'darker', 'desaturate' ), + 'bg:txt' => array( '+triad' ), + 'bg:link' => array( '-triad' ), + 'fg1:bg' => array( 'desaturate', 'darker' ), + 'fg1:fg2' => array( '+analog' ), + 'fg1:txt' => array( '-tetrad' ), + 'fg1:link' => array( 'darker', 'saturate' ), + 'fg2:bg' => array( 'saturate', 'brighter' ), + 'fg2:fg1' => array( '-analog' ), + 'fg2:txt' => array( '-tetrad' ), + 'fg2:link' => array( 'darker', 'saturate' ), + 'txt:bg' => array( '+triad' ), + 'txt:fg1' => array( '+tetrad' ), + 'txt:fg2' => array( '+tetrad' ), + 'txt:link' => array( '-split-complement', 'saturate' ), + 'link:bg' => array( '-triad' ), + 'link:fg1' => array( 'desaturate', 'brighter' ), + 'link:fg2' => array( 'desaturate', 'brighter' ), + 'link:txt' => array( 'darker', 'saturate' ), + ); + + foreach ( $colors as $known_role => $color_code ) { + if ( $known_role === $role ) { + continue; + } + + $transforms = $relations[ $known_role . ':' . $role ]; + try { + $new_color = new Jetpack_Color( self::normalize_color( $color_code ) ); + } catch ( RangeException $e ) { + $message = 'function: ' . __FUNCTION__ . "\n"; + $message .= 'call: $new_color = new Jetpack_Color( self::normalize_color( ' . $color_code . ' ) );' . "\n"; + $message .= 'normalized color: ' . self::normalize_color( $color_code ); + self::exception_mailer( $message ); + continue; + } + + foreach ( $transforms as $transform ) { + switch ( $transform ) { + case 'complement': + $new_color->getComplement(); + break; + case 'brighter': + $new_color->lighten( 25 ); + break; + case 'darker': + $new_color->darken( 25 ); + break; + case 'grayscale': + $new_color->toGrayscale(); + break; + case '+split-complement': + $new_color->getSplitComplement( 1 ); + break; + case '-split-complement': + $new_color->getSplitComplement( -1 ); + break; + case '+triad': + $new_color->getTriad( 1 ); + break; + case '-triad': + $new_color->getTriad( -1 ); + break; + case 'saturate': + $new_color->saturate( 25 ); + break; + case 'desaturate': + $new_color->desaturate( 25 ); + break; + case '+analog': + $new_color->getAnalog( 1 ); + break; + case '-analog': + $new_color->getAnalog( -1 ); + break; + case '+tetrad': + $new_color->getTetrad( 1 ); + break; + case '-tetrad': + $new_color->getTetrad( -1 ); + break; + } + } + + $suggestions[] = self::normalize_color( $new_color->toHex() ); + } + + return $suggestions; + } +} + +/** + * Nothing to override + */ +class Colors_Manager extends Colors_Manager_Common {} + +/** + * Adds a color rule. + * + * @param string $category The color category. One of bg, txt, link, fg1, fg2. + * @param string $default_color The default color for this category. + * @param array $rules Array of rule arrays. $rule: array( selector, property, opacity );. + * @param bool|string $label Optional. A UI helper label for identifying what a particular color will change in the theme. + */ +function add_color_rule( $category, $default_color, $rules, $label = false ) { + Colors_Manager::add_color_rule( $category, $default_color, $rules, $label ); +} + +/** + * Adds color palette. + * + * @param array $palette An array with 5 colors. + * @param bool|string $title optional title string. + */ +function add_color_palette( $palette, $title = false ) { + return Colors_Manager::add_color_palette( $palette, $title ); +} + +/** + * This is a quick and easy function to track usage of COLOURlovers patterns across Dotcom. + * We intend to run it for a few weeks and then remove it. + * + * We also maintain a WPCOM version for Simple sites. + * + * @see pdKhl6-3qP-p2 + */ +function wpcomsh_temporarily_maybe_track_colourlovers_pattern_usage() { + $cache_key = 'stats_tmp_colourlovers_pattern'; + $found = false; + wp_cache_get( $cache_key, 'stats', false, $found ); + + // We don't need to track all page views, just once per site. + if ( $found ) { + return; + } + + $bg = get_theme_mod( 'background_image' ); + wp_cache_set( $cache_key, $bg, 'stats' ); + + if ( ! empty( $bg ) && str_contains( $bg, 'colourlovers' ) ) { + $event_properties = array( + 'siteid' => (int) Jetpack_Options::get_option( 'id' ), + 'pattern' => pathinfo( $bg, PATHINFO_FILENAME ), + 'theme' => get_stylesheet(), + ); + wpcomsh_record_tracks_event( 'wpcom_tmp_cl_pattern', $event_properties ); + } +} +add_action( 'wp_footer', 'wpcomsh_temporarily_maybe_track_colourlovers_pattern_usage', 101 ); + +/** + * Gutenberg color manager. + */ +class Colors_Manager_Gutenberg extends Colors_Manager_Common { + + /** + * Whether we're in Gutenberg. + * + * @var boolean + */ + protected static $is_gutenberg = true; + + /** + * Annotations file path. + * + * @var string + */ + protected static $annotations_file = 'wpcom-editor-colors.php'; +} + +/** + * Load Gutenberg's color manager. + */ +function colors_manager_gutenberg_load() { + if ( get_current_screen()->is_block_editor() ) { + Colors_Manager_Gutenberg::init(); // Gutenberg + } +} + +/** + * Load corresponding color manager. + */ +function load_corresponding_color_manager() { + global $pagenow; + if ( is_admin() && 'customize.php' !== $pagenow && ! defined( 'DOING_AJAX' ) ) { + add_action( 'current_screen', 'colors_manager_gutenberg_load' ); + } else { + Colors_Manager::init(); + } +} + +add_action( 'init', 'load_corresponding_color_manager' ); diff --git a/projects/plugins/wpcomsh/custom-colors/contrast.html b/projects/plugins/wpcomsh/custom-colors/contrast.html new file mode 100644 index 0000000000000..bc6c751f0112f --- /dev/null +++ b/projects/plugins/wpcomsh/custom-colors/contrast.html @@ -0,0 +1,77 @@ + + + + + + Contrast Playtime + + + + + + +

+

+ + + + + + + + + +
ContrastResult
+ + + + + + diff --git a/projects/plugins/wpcomsh/custom-colors/core-bg-admin-notice.php b/projects/plugins/wpcomsh/custom-colors/core-bg-admin-notice.php new file mode 100644 index 0000000000000..0e536a23010f4 --- /dev/null +++ b/projects/plugins/wpcomsh/custom-colors/core-bg-admin-notice.php @@ -0,0 +1,50 @@ + 'custom-design-preview', + 'state' => 'on', + 'source' => 'custom-background', + '_wpnonce' => wp_create_nonce( 'custom-design-preview' ), + ), + $url + ); +} +// add hash to open colors section +$url .= '#colors'; + +$img = sprintf( + '%s', + esc_url( $url ), + esc_url( plugins_url( '/images/colors-screenshot.png', __FILE__ ) ), + esc_attr__( 'Custom Colors interface', 'wpcomsh' ) +); + +if ( CustomDesign::is_upgrade_active() ) { + $message = esc_html__( 'You can do far more than just customize your background color and image as part of your Custom Design upgrade. Choose from great palettes and background patterns.', 'wpcomsh' ); + $message .= ' ' . esc_html__( 'Check it out!', 'wpcomsh' ) . ''; +} else { + $message = __( 'Did you know that you can do far more than just set your background color and image in the Colors tool in our Custom Design, part of the Premium Plan?', 'wpcomsh' ); + $message .= ' ' . esc_html__( 'Preview it now!', 'wpcomsh' ) . ''; +} + +?> + + diff --git a/projects/plugins/wpcomsh/custom-colors/core-bg-override.php b/projects/plugins/wpcomsh/custom-colors/core-bg-override.php new file mode 100644 index 0000000000000..03d2e4e4a61b0 --- /dev/null +++ b/projects/plugins/wpcomsh/custom-colors/core-bg-override.php @@ -0,0 +1,140 @@ +mods = get_theme_mods(); + + if ( $save_overrides ) { + add_action( 'shutdown', array( $this, 'restore_theme_mods' ) ); + } else { + foreach ( $this->props as $prop ) { + add_filter( "theme_mod_$prop", array( $this, $prop ) ); + } + } + } + + /** + * Called when we want the user upgrades to Custom Design. Permanently sets the parity + * we were heretofore achieving with filters. + * + * @return void + */ + public function do_upgrade() { + // first, let's ditch those filters that we don't need, however this was invoked. + remove_action( 'shutdown', array( $this, 'restore_theme_mods' ) ); + foreach ( $this->props as $prop ) { + remove_filter( "theme_mod_$prop", array( $this, $prop ) ); + } + + // now, time for parity + $previewed_bg_mods = get_theme_mod( $this->post_upgrade_parity_mod, false ); + // nothing set? bail. + if ( ! is_array( $previewed_bg_mods ) ) { + return; + } + + // if the preview mod had it active, set it. otherwise, remove it + foreach ( $this->props as $prop ) { + if ( isset( $previewed_bg_mods[ $prop ] ) ) { + set_theme_mod( $prop, $previewed_bg_mods[ $prop ] ); + } else { + remove_theme_mod( $prop ); + } + } + + // background_image is a special case - we need to set it to a blank string + // even if we didn't have something - otherwise we might get stuck with a default bg image + if ( ! isset( $previewed_bg_mods['background_image'] ) ) { + set_theme_mod( 'background_image', '' ); + } + + // remove our preview mod + remove_theme_mod( $this->post_upgrade_parity_mod ); + } + + /** + * This is where we restore the background theme mods that were saved + * while previewing Colors and save the previewed mods for future display parity. + * + * @return void + */ + public function restore_theme_mods() { + $pre_save = $this->mods; + $post_save = get_theme_mods(); + $post_upgrade_parity = array(); + + foreach ( $this->props as $prop ) { + + // we're going to store the saved value for usage after the upgrade has been purchased. + if ( isset( $post_save[ $prop ] ) ) { + $post_upgrade_parity[ $prop ] = $post_save[ $prop ]; + } + + if ( ! isset( $pre_save[ $prop ] ) ) { + // case 1: it wasn't set originally. unset it again. + remove_theme_mod( $prop ); + } elseif ( $post_save[ $prop ] !== $pre_save[ $prop ] ) { + // case 2: the value has changed. change it back. + set_theme_mod( $prop, $pre_save[ $prop ] ); + } + } + + set_theme_mod( $this->post_upgrade_parity_mod, $post_upgrade_parity ); + } + + /** + * Magic method for our preview filters for all core bg theme_mods. DRY FTW. + * + * @param string $name The method we're faking. Conveniently also the property we're overriding. + * We do this for all members of $this->props. + * @param array $args The arguments called on the method. + * @return mixed string|bool The overridden value, if one is applicable, otherwise false. + */ + public function __call( $name, $args ) { + $value = $args[0]; + $overrides = get_theme_mod( $this->post_upgrade_parity_mod, false ); + // we don't have any overrides. Just return + if ( ! is_array( $overrides ) ) { + return $value; + } + return $overrides[ $name ] ?? $value; + } +} diff --git a/projects/plugins/wpcomsh/custom-colors/css/colors-control-beta.css b/projects/plugins/wpcomsh/custom-colors/css/colors-control-beta.css new file mode 100644 index 0000000000000..f53709825fdde --- /dev/null +++ b/projects/plugins/wpcomsh/custom-colors/css/colors-control-beta.css @@ -0,0 +1,699 @@ +/** +* The Theme Chooser Styles +*/ + + +/* =Structure +----------------------------------------------- */ +.custom-design-feature { + width: 33% !important; +} +.customize-control-colorsTool { + position: relative; +} +.customize-control-colorsTool .customize-control-title { + font-size: 14px; + line-height: 34px; +} +#customize-control-colors-tool .customize-control-title { + margin: 5px 0 2px; +} +#customize-section-colors_manager_tool .customize-section-content, +#accordion-section-colors_manager_tool .accordion-section-content { + padding-top: 0; +} + + +/* =Color Picker +----------------------------------------------- */ +.color-grid { + display: block; + margin: 0; + padding: 0; +} +.color-grid.main li { + height: 55px; + width: 55px; + background-image: -moz-radial-gradient( center top, circle farthest-side, rgba(255, 255, 255, .35), rgba(255, 255, 255, 0) 80% ); + background-image: -webkit-radial-gradient( center top, circle farthest-side, rgba(255, 255, 255, .35), rgba(255, 255, 255, 0) 80% ); + background-image: radial-gradient( center top, circle farthest-side, rgba(255, 255, 255, .35), rgba(255, 255, 255, 0) 80% ); + background-position: center center; + background-size: cover; + border: 2px solid #fff; + border-radius: 50%; + box-shadow: inset 0 -1px 4px rgba(0,0,0,0.1); +} + +.disable-background .color-grid.main li, +.enable-background .color-grid.main li { + display: inline-block; + float: none; +} + +.color-grid.main li:not(:last-child) { + margin-right: 7px; +} +.enable-background .color-grid.main li.bg { + position: relative; + background-size: 100% 100%; + border-radius: 2%; + box-shadow: inset 0 -1px 4px rgba(0,0,0,0.1); + height: 55px; + margin-bottom: 0; + width: 100%; +} +.enable-background .color-grid.main .text-placeholder { + background: transparent; + border: none; + -moz-box-sizing: inherit; + -webkit-box-sizing: inherit; + box-sizing: inherit; + display: block; + list-style-type: none; + margin: 0; + width: 100%; + box-shadow: none; + height: 50px; + text-align: left; +} +.color-grid.main { + border-bottom: 2px solid #fff; + background: #eee; + height: auto; + text-align: center; + width: 100%; + padding-top: 20px +} + +.color-grid li { + background: #f1f1f1; + border: 5px solid rgba(0,0,0,0.1); + border-width: 1px 1px 5px; + font-size: 0px; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + display: block; + float: left; + list-style-type: none; + height: 50px; + width: 80px; + margin: 0px 13px 12px; +} + +.color-grid li a { + font-size: 0px; +} +.color-grid li:hover { + cursor: pointer; + background-image: none; +} +/* + This will be dynamic: get form setting (if there is one) + Or pull themecolors array +*/ +.color-grid .bg { + height: 100%; +} + +.color-grid .fg2 { + clear: right; +} +.color-grid.main .unavailable { + background: #353535 !important; + box-shadow: inset 0 5px 16px rgba(0, 0, 0, 0.4), inset 0 -1px 1px rgba(255, 255, 255, 0.2) !important; + opacity: 0.5; +} +.color-grid.main .unavailable.selected { + border: none; +} +.color-grid .unavailable:before { + color: #fff; + content: '\f406'; + font-family: "Noticons"; + font-size: 40px; + opacity: 0.4; + float: left; + position: relative; + left: 14px; + top: 20px; + text-shadow: 0 0 2px rgba(0,0,0,0.4); +} +.color-grid.main .selected { + border: 3px solid #fff; +} +.color-grid .ui-draggable-dragging:hover { + opacity: 1; +} +.enable-background .action-button-wrap + .customize-control-title { + padding-top: 30px; +} +.action-button-wrap { + float: right; +} +a.revert { + font-size: 11px !important; + z-index: 1; + float: left; +} +a.button.left-pill { + border-radius: 3px 0 0 3px; + display: none; + padding-right: 8px; +} +a.button.left-pill:hover, +a.button.left-pill:focus { + z-index: 3; +} +a.button.right-pill { + border-radius: 3px; + padding-left: 10px; + margin-left: -1px; +} +.color-grid .bg:not(:hover) .change-background, +.color-grid .bg.bg-change-disable .change-background { + display: none; +} +.color-grid .bg .change-background { + position: absolute; + bottom: 12px; + right: 5px; +} +.color-grid .bg .change-background sup { + font-size: 9px; +} +.action-button-wrap.undo a.button.left-pill { + display: block; +} +.action-button-wrap.undo a.button.right-pill { + border-radius: 0 3px 3px 0; + padding-left: 8px; +} + + +/* =Colors Suggestions and Picker +----------------------------------------------- */ +.customize-control-colorsTool a { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.the-picker { + background: #fff; + border: 2px solid #eee; + border-top-style: dashed; + border: none; + display: none; + padding: 50px 20px 20px; + position: relative; + left: -20px; + width: 260px; + z-index: 2; +} +/* override new-customizer.css */ +.color-picker .the-picker { + border: none; + margin-top: 20px; +} +.the-picker .color-label { + background: #eee; + border-radius: 2px; + color: #666; + display: block; + font-size: 15px; + line-height: 15px; + padding: 8px 18px; + position: relative; + top: -10px; + text-align: center; +} +.the-picker .color-label span { + background: #fff; + border: 1px solid #ddd; + border-radius: 5px; + color: #888; + display: inline-block; + font-family: Consolas, Monaco, monospace; + margin-left: 12px; + padding: 2px 6px; +} +.the-picker p { + clear: both; + color: #646464; + font-size: 13px; + margin: 10px 0 10px; +} +/* override new-customizer.css */ +.color-picker .the-picker p:not(.iris-launch) { + margin-top: 0; +} +.color-suggestions { + display: block; + margin: 20px 0; + padding: 0; + height: 50px; +} +.color-suggestions li { + background: #f1f1f1; + border: 3px solid rgba(0,0,0,0.1); + font-size: 0px; + display: block; + margin: 0 10px 15px; + float: left; + list-style-type: none; + height: 30px; + width: 40px; + display: none; +} +.color-suggestions li:first-child { + margin-left: 0; +} +.color-suggestions li:nth-of-type(5) { + margin-left: 0; +} +.color-suggestions li:hover { + cursor: pointer; + opacity: 1; +} +/* Background Choices */ +#the-bg-picker-prompt { + border-top: 2px dashed #eee; + padding: 10px 0; +} +#the-bg-picker-prompt .customize-control-title { + margin-bottom: 10px; +} +#the-bg-picker-prompt div { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + float: left; + width: 50%; + text-align: center; +} +#the-bg-picker-prompt:after { + content: '.'; + display: block; + height: 0; + clear: both; + visibility: hidden; + zoom: 1; +} +#the-bg-picker-prompt h4 { + font-weight: 400; +} +#the-bg-picker-prompt h4 strong { + font-weight: bold; +} +.choose-color, +.choose-pattern { + border: 3px solid #fff; + border-radius: 50%; + font-size: 0px; + box-shadow: 0 1px 2px #aaa; + display: block; + height: 70px; + width: 70px; + margin: 0 auto; +} +.choose-pattern { + background-image: url('../images/pattern-default.png'); + background-size: 70px 70px; +} + + +/* =Color Palettes +----------------------------------------------- */ +.colour-lovers { + border-radius: 4px; + float: left; + margin: 0px 18px 18px 0; + height: 50px; + width: 72px; + position: relative; +} +.colour-lovers:nth-of-type(3) { + margin-right: 0; +} +.colour-lovers li { + width: 20%; + height: 100%; +} +/* Displays theme color palettes of < five colors */ +.colour-lovers.items-1 li { + width: 100%; +} +.colour-lovers.items-2 li { + width: 50%; +} +.colour-lovers.items-3 li { + width: 33.3333%; +} +.colour-lovers.items-4 li { + width: 25%; +} +.colour-lovers:hover { + cursor: pointer; +} +.colour-lovers li:hover { + opacity: 1; +} +.colour-lovers .ui-draggable-dragging { + border-radius: 2px; + width: 25px; + height: 25px; +} +.colour-lovers:active { + opacity: 0.5; +} +#colourlovers-palettes-container { + clear: both; + padding: 5px 0 20px; + margin-right: -30px; +} +#colourlovers-palettes-container h3 { + font-size: 14px; +} +.color-picker .button:hover { + cursor: pointer; +} +.color-picker .button:active { + padding-top: 3px; +} +.color-picker #colourlovers-palettes-container .previous, .color-picker #colourlovers-palettes-container .next { + margin-top: 10px; +} +/*.color-picker .previous { + margin-left: 10px; +}*/ +/* Theme featured palettes */ +.colour-lovers.featured:before { + content: 'Featured'; + background: #000; + color: #fff; + display: block; + font-size: 10px; + opacity: 0.7; + position: absolute; + bottom: 5px; + right: 0; + left: 0; + text-indent: 5px; + line-height: 1.5; + padding-bottom: 1px; +} + +/* Generate from header image */ +#generate-palette { + display: block; + margin-right: 30px; + margin-top: 15px; + text-align: center; +} + + +/* =Color Patterns +----------------------------------------------- */ +#the-pattern-picker { + margin: 0 -20px; + padding: 0 20px 10px; +} +#colourlovers-patterns { + border-bottom: none; + margin: 0 -18px 10px; + min-height: 50px; +} +#colourlovers-patterns li { + float: left; + border-radius: 2px; + height: 50px; + margin-bottom: 10px; + margin-left: 18px; + overflow: hidden; + padding: 0; + width: 72px; +} +#colourlovers-patterns img { + cursor: pointer; + width: 72px; +} +#colourlovers-patterns:after { + content: '.'; + display: block; + height: 0; + clear: both; + visibility: hidden; + zoom: 1; +} +#more-patterns, #less-patterns { + margin-bottom: 10px; +} +#customize-control-background_image .pagination { + clear: both; + padding-top: 20px; + min-height: 30px; +} +#customize-control-background_image .pagination a.button { + display: inline; +} +#the-pattern-picker .customize-control-title a { + float: right; + font-size: smaller; +} +.customize-image-picker { + margin: 0px -20px 10px; + padding: 0 20px 20px; +} + +/* Iris */ +#iris { + width: 33%; +} +#iris-container { + padding-bottom: 40px; + position: relative; +} +input#iris { + position: absolute; + bottom: 0; + left: 10px; +} + +/* + * Free Mode + */ +.free-mode #colourlovers-palettes .color-grid:not(.featured) { + opacity: 0.2; +} +.free-mode #the-pattern-picker { + display: none; +} +.free-mode .buy-custom-design { + background: #191919; + padding: 40px 20px; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 5; +} +.free-mode .buy-custom-design .back-to-colors { + margin-left: 20px; +} +.free-mode .buy-custom-design .button span { + font-size: 12px; + margin-left: 10px; +} +.free-mode .buy-custom-design ul { + list-style-type: disc; + font-size: 12px; + padding-left: 20px; +} +.free-mode.preview-mode #accordion-section-colors_manager_tool h3:after { + display: none; +} +.free-mode .more-features { + font-size: 11px; + margin-top: 20px; + padding-top: 20px; +} +.free-mode ul + .more-features { + border-top: none; + padding-top: 0; +} +.free-mode .buy-custom-design ul.accordion-section-content { + margin: 20px 0 0 0; + padding: 0; + padding-top: 20px; +} +.free-mode .accordion-section-colors { + margin: 20px -20px 0; + padding-top: 40px; +} + + +/* Header text color */ + +.customize-control-header-text-color { + margin: 0px -20px 10px; + padding: 0 20px 20px; +} + +.customize-control-header-text-color .customize-control-title { + margin: 1em 0; /* like if it were a h3 */ +} + +/* Background Change View */ + +#background-change { + display: none; + padding-top: 10px; + min-height: 100px; + font-size: 20px; +} +.background-rectangle { + position: relative; + background-position-y: 33%; + background-repeat: repeat; + background-size: 100% 100%; + border-radius: 2%; + box-shadow: inset 0 -1px 4px rgba(0,0,0,0.1); + width: 260px; + height: 55px; + margin-bottom: 8px; +} +.background-rectangle .done { + background: transparent; + display: block; + position: absolute; + text-align: center; + top: 14px; + width: 100%; +} +.button.select-image { + float: right; +} +#customize-controls .button.background-options { + float: left; + display: block; + text-align: center; + margin-bottom: -11px; +} +.sep { + clear: both; +} + +#customize-controls .button.pressed, +#customize-controls .button.pressed:hover { + /* FIXME add better style later */ + background-color: #333; + color: #777; +} + +.float-button { + background-color: #000; + border-radius: 2px; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: 13px; + line-height: 1.5; + opacity: 0.7; + padding: 5px 8px; +} +.float-button:hover { + background-color: #222; + color: #eee; +} + +/* Background Options View */ + +.view.background-options { + display: none; + background-color: rgb(51, 51, 51); + border-radius: 2px; + padding: 5px 8px; + margin-top: 8px; +} +.view.background-options input { + float: right; +} +.background-options p.radios input { + display: none; +} +.background-options p.radios label { + margin-left: 2px; + color: black; + float: right; + padding: 2px 4px 2px 4px; + display: inline-block; + background: grey; +} +.background-options p.radios input:checked + label { + font-weight: bold; + background: white; +} +.background-options p.bottom { + text-align: center; + margin-bottom: 4px; +} + +.noticon-tile-none, +.noticon-tile-horizontally, +.noticon-tile-vertically { + width: 16px; + height: 16px; + background-size: 8px; + background-repeat: no-repeat; + background-position: 4px 4px; +} + +.noticon-tile-none { + background-image: url('../images/tile-none.png'); +} + +.noticon-tile-horizontally { + background-image: url('../images/tile-horizontally.png'); +} + +.noticon-tile-vertically { + background-image: url('../images/tile-vertically.png'); +} +.background-options .iris-picker { + margin: auto; + background-color: rgb(51, 51, 51); + border-color: #000; +} +.background-options label.underlying-color:not(:hover) { + opacity: 0.8; +} + +/* =NUX +----------------------------------------------- */ + +.nux #color-grid, +.nux #colourlovers-palettes-container h3, +.nux #more-palettes { + display: none !important; +} +/* only show theme palettes */ +.nux .color-grid:not(.featured), +.nux .colour-lovers.featured:before { + display: none; +} +.nux #customize-control-background_color { + display: block !important; +} + + + +.media-modal { + z-index: 1001000 !important; +} + +#customize-control-colors-tool .cd-design { + clear: right; + margin-top: 50px; +} diff --git a/projects/plugins/wpcomsh/custom-colors/css/colors-control.css b/projects/plugins/wpcomsh/custom-colors/css/colors-control.css new file mode 100644 index 0000000000000..eb2453423ae73 --- /dev/null +++ b/projects/plugins/wpcomsh/custom-colors/css/colors-control.css @@ -0,0 +1,735 @@ +/** +* The Theme Chooser Styles +*/ + + +/* =Structure +----------------------------------------------- */ +.custom-design-feature { + width: 33% !important; +} +.customize-control-colorsTool { + position: relative; +} +.customize-control-colorsTool .customize-control-title { + font-size: 14px; + line-height: 34px; +} +#customize-control-colors-tool { + margin-top: 15px; +} +#customize-control-colors-tool .customize-control-title { + margin: 5px 0 2px; + position: relative; + margin-top: -7px; +} + + +/* =Color Picker +----------------------------------------------- */ +.color-grid { + display: block; + margin: 0; + padding: 0; +} +.color-grid.main li { + height: 55px; + width: 55px; + background-image: -moz-radial-gradient( center top, circle farthest-side, rgba(255, 255, 255, .35), rgba(255, 255, 255, 0) 80% ); + background-image: -webkit-radial-gradient( center top, circle farthest-side, rgba(255, 255, 255, .35), rgba(255, 255, 255, 0) 80% ); + background-image: radial-gradient( center top, circle farthest-side, rgba(255, 255, 255, .35), rgba(255, 255, 255, 0) 80% ); + background-position: center center; + background-size: cover; + border: 2px solid #fff; + border-radius: 50%; + box-shadow: inset 0 -1px 4px rgba(0,0,0,0.1); +} + +.disable-background .color-grid.main li, +.enable-background .color-grid.main li { + display: inline-block; + float: none; +} + +.color-grid.main li:not(:last-child) { + margin-right: 7px; +} +.enable-background .color-grid.main li.bg { + position: relative; + background-size: 100% 100%; + border-radius: 2%; + box-shadow: inset 0 -1px 4px rgba(0,0,0,0.1); + height: 55px; + margin-bottom: 20px; + width: 100%; +} +.enable-background .color-grid.main .text-placeholder { + background: transparent; + border: none; + -moz-box-sizing: inherit; + -webkit-box-sizing: inherit; + box-sizing: inherit; + display: block; + list-style-type: none; + margin: 0; + width: 100%; + box-shadow: none; + height: 50px; + text-align: left; +} + +.color-grid li { + background: #f1f1f1; + border: 5px solid rgba(0,0,0,0.1); + border-width: 1px 1px 5px; + font-size: 0px; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + display: block; + float: left; + list-style-type: none; + margin: 0; + height: 50px; + width: 80px; +} + +.color-grid li a { + font-size: 0px; +} +.color-grid li:hover { + cursor: pointer; + background-image: none; +} +/* + This will be dynamic: get form setting (if there is one) + Or pull themecolors array +*/ +.color-grid .bg { + height: 100%; +} + +.color-grid .fg2 { + clear: right; +} +.color-grid.main .unavailable { + background: #353535 !important; + box-shadow: inset 0 5px 16px rgba(0, 0, 0, 0.4), inset 0 -1px 1px rgba(255, 255, 255, 0.2) !important; + opacity: 0.2; +} +.color-grid.main .unavailable.selected { + border: none; +} +.color-grid .unavailable:before { + color: #fff; + content: '\f406'; + font-family: "Noticons"; + font-size: 21px; + opacity: 0.4; + float: left; + position: relative; + left: 15px; + top: 18px; + text-shadow: 0 0 2px rgba(0,0,0,0.4); +} +.color-grid.main .selected { + border: 3px solid #fff; +} + +.action-button-wrap { + float: right; + margin-top: -5px; + position: relative; + z-index: 100; +} +a.revert { + font-size: 11px !important; + z-index: 1; + float: left; +} +.color-grid .bg:not(:hover) .change-background, +.color-grid .bg.bg-change-disable .change-background { + display: none; +} +.color-grid .bg .change-background { + position: absolute; + bottom: 11px; + right: 15px; +} + + +/* =Colors Suggestions and Picker +----------------------------------------------- */ +.customize-control-colorsTool a { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.the-picker { + background: #fff; + border: 2px solid #eee; + border-top-style: dashed; + border: none; + display: none; + padding: 10px 20px; + position: relative; + left: -20px; + width: 260px; + z-index: 2; +} +/* override new-customizer.css */ +.color-picker .the-picker { + border: none; + margin-top: 20px; +} +.the-picker .color-label { + background: #eee; + border-radius: 2px; + color: #666; + display: block; + font-size: 15px; + line-height: 15px; + padding: 8px 18px; + position: relative; + top: -10px; + text-align: center; +} +.the-picker .color-label span { + background: #fff; + border: 1px solid #ddd; + border-radius: 5px; + color: #888; + display: inline-block; + font-family: Consolas, Monaco, monospace; + margin-left: 12px; + padding: 2px 6px; +} +.the-picker p { + clear: both; + color: #646464; + font-size: 13px; + margin: 10px 0 10px; +} +/* override new-customizer.css */ +.color-picker .the-picker p:not(.iris-launch) { + margin-top: 0; +} +.color-suggestions { + display: block; + margin: 20px 0; + padding: 0; + height: 50px; +} +.color-suggestions li { + background: #f1f1f1; + border: 3px solid rgba(0,0,0,0.1); + font-size: 0px; + display: block; + margin: 0 3px 15px; + float: left; + list-style-type: none; + height: 30px; + width: 40px; + display: none; +} +.color-suggestions li:first-child { + margin-left: 0; +} +.color-suggestions li:nth-of-type(6), .color-suggestions li:nth-of-type(11) { + margin-left: 0; +} +.color-suggestions li:hover { + cursor: pointer; + opacity: 1; +} +/* Background Choices */ +#the-bg-picker-prompt { + border-top: 2px dashed #eee; + padding: 10px 0; +} +#the-bg-picker-prompt .customize-control-title { + margin-bottom: 10px; +} +#the-bg-picker-prompt div { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + float: left; + width: 50%; + text-align: center; +} +#the-bg-picker-prompt:after { + content: '.'; + display: block; + height: 0; + clear: both; + visibility: hidden; + zoom: 1; +} +#the-bg-picker-prompt h4 { + font-weight: 400; +} +#the-bg-picker-prompt h4 strong { + font-weight: bold; +} +.choose-color, +.choose-pattern { + border: 3px solid #fff; + border-radius: 50%; + font-size: 0px; + box-shadow: 0 1px 2px #aaa; + display: block; + height: 70px; + width: 70px; + margin: 0 auto; +} +.choose-pattern { + background-image: url('../images/pattern-default.png'); + background-size: 70px 70px; +} + + +/* =Color Palettes +----------------------------------------------- */ +.colour-lovers { + border-radius: 4px; + float: left; + margin: 0px 18px 18px 0; + height: 50px; + width: 72px; + position: relative; +} +.colour-lovers li { + width: 20%; + height: 100%; +} +/* Displays theme color palettes of < five colors */ +.colour-lovers.items-1 li { + width: 100%; +} +.colour-lovers.items-2 li { + width: 50%; +} +.colour-lovers.items-3 li { + width: 33.3333%; +} +.colour-lovers.items-4 li { + width: 25%; +} +.colour-lovers:hover { + cursor: pointer; +} +.colour-lovers li:hover { + opacity: 1; +} +.colour-lovers:active { + opacity: 0.5; +} +#colourlovers-palettes-container { + clear: both; + padding: 20px 0 20px; + margin-right: -30px; +} +#colourlovers-palettes-container h3 { + font-size: 14px; +} +.color-picker .button:hover { + cursor: pointer; +} +.color-picker .button:active { + padding-top: 3px; +} +.color-picker #colourlovers-palettes-container .previous, .color-picker #colourlovers-palettes-container .next { + margin-top: 10px; +} +/*.color-picker .previous { + margin-left: 10px; +}*/ +/* Theme featured palettes */ +.colour-lovers.featured:before { + content: 'Featured'; + background: #000; + color: #fff; + display: block; + font-size: 10px; + opacity: 0.7; + position: absolute; + bottom: 5px; + right: 0; + left: 0; + text-indent: 5px; + line-height: 1.5; + padding-bottom: 1px; +} + +/* Generate from header image */ +#generate-palette { + display: block; + margin-right: 30px; + margin-top: 15px; + text-align: center; +} + + +/* =Color Patterns +----------------------------------------------- */ +#the-pattern-picker { + margin: 0 -20px; + padding: 0 20px 10px; +} +#colourlovers-patterns { + border-bottom: none; + margin: 0 -18px 10px; + min-height: 50px; +} +#colourlovers-patterns li { + float: left; + border-radius: 2px; + height: 50px; + margin-bottom: 10px; + margin-left: 18px; + overflow: hidden; + padding: 0; + width: 72px; +} +#colourlovers-patterns img { + cursor: pointer; + width: 72px; +} +#colourlovers-patterns:after { + content: '.'; + display: block; + height: 0; + clear: both; + visibility: hidden; + zoom: 1; +} +#more-patterns, #less-patterns { + margin-bottom: 10px; +} +#customize-control-background_image .pagination { + clear: both; + padding-top: 20px; + min-height: 30px; +} +#customize-control-background_image .pagination a.button { + display: inline; +} +#the-pattern-picker .customize-control-title a { + float: right; + font-size: smaller; +} +.customize-image-picker { + margin: 0px -20px 10px; + padding: 0 20px 20px; +} +.palette-buttons { + clear: both; +} + +/* Iris */ +#iris { + width: 33%; +} +#iris-container { + padding-bottom: 40px; + position: relative; +} +input#iris { + position: absolute; + bottom: -10px; + left: 85px; +} + +/* + * Free Mode + */ +.free-mode #colourlovers-palettes .color-grid:not(.featured) { + display: none; +} +.free-mode #the-pattern-picker { + display: none; +} +.free-mode .buy-custom-design { + background: #191919; + padding: 40px 20px; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 5; +} +.free-mode .buy-custom-design .back-to-colors { + margin-left: 20px; +} +.free-mode .buy-custom-design .button span { + font-size: 12px; + margin-left: 10px; +} +.free-mode .buy-custom-design ul { + list-style-type: disc; + font-size: 12px; + padding-left: 20px; +} +.free-mode.preview-mode #accordion-section-colors_manager_tool h3:after { + display: none; +} +.free-mode .more-features { + font-size: 11px; + margin-top: 20px; + padding-top: 20px; +} +.free-mode ul + .more-features { + border-top: none; + padding-top: 0; +} +.free-mode .buy-custom-design ul.accordion-section-content { + margin: 20px 0 0 0; + padding: 0; + padding-top: 20px; +} +.free-mode .accordion-section-colors { + margin: 20px -20px 0; + padding-top: 40px; +} + +.free-mode .color-grid.main { + height: auto; + padding-bottom: 0; + margin-bottom: 0; +} + +.free-mode .color-grid.main li, .free-mode .enable-background .color-grid.main .text-placeholder.clrs { + display: none; +} + +.free-mode .color-grid.main li.bg { + display: block; + margin-bottom: 0; +} + +.free-mode #more-palettes, .free-mode .no-free-palettes, .free-mode #generate-palette { + display: none !important; +} + +/* Header text color */ + +.customize-control-header-text-color { + margin: 0px -20px 10px; + padding: 0 20px 20px; +} + +.customize-control-header-text-color .customize-control-title { + margin: 1em 0; /* like if it were a h3 */ +} + +/* Background Change View */ + +#background-change { + display: none; + padding-top: 10px; + min-height: 100px; + font-size: 20px; +} +.background-rectangle { + position: relative; + background-position-y: 33%; + background-repeat: repeat; + background-size: 100% 100%; + border-radius: 2%; + box-shadow: inset 0 -1px 4px rgba(0,0,0,0.1); + width: 260px; + height: 55px; + margin-bottom: 8px; +} +.background-rectangle .done { + background: transparent; + display: block; + position: absolute; + text-align: center; + top: 14px; + min-width: 50px; + right: 15px; +} +.button.select-image { + float: right; +} +#customize-controls .button.background-options { + float: left; + display: block; + text-align: center; + margin-bottom: -11px; +} +.sep { + clear: both; +} + + +.float-button { + background-color: #fff; + border-radius: 2px; + color: #333; + cursor: pointer; + display: inline-block; + font-size: 13px; + line-height: 1.5; + padding: 5px 8px; + opacity: 0.85; +} + +/* Background Options View */ + +.view.background-options { + display: none; + border-radius: 2px; + padding: 5px 8px; + margin-top: 8px; +} +.view.background-options input { + float: right; +} +.background-options p.radios input { + display: none; +} +.background-options p.radios label { + margin-left: 2px; + color: #2e4453; /* $gray-dark */ + float: right; + padding: 2px 5px; + display: inline-block; + border: 1px solid #c8d7e1; /* $gray-lighten-20 */ + border-radius: 2px; +} +.background-options p.radios input:checked + label { + font-weight: bold; + color: #fff; + background: #87a6bc !important; /* $gray */ + border-color: #668eaa; /* $gray-darken-10 */ +} +.background-options p.bottom { + text-align: center; + margin-bottom: 4px; +} + +.noticon-tile-none, +.noticon-tile-horizontally, +.noticon-tile-vertically { + width: 16px; + height: 16px; + background-size: 8px; + background-repeat: no-repeat; + background-position: 4px 4px; +} + +.noticon-tile-none { + background-image: url('../images/tile-none.png'); +} + +.noticon-tile-horizontally { + background-image: url('../images/tile-horizontally.png'); +} + +.noticon-tile-vertically { + background-image: url('../images/tile-vertically.png'); +} +.background-options .iris-picker { + margin: auto; + background-color: rgb(51, 51, 51); + border-color: #000; +} +.background-options label.underlying-color:not(:hover) { + opacity: 0.8; +} + +/* Don't flash replaced sections on first load */ +#accordion-section-background_image, #accordion-section-colors { + display: none; +} + +/* =NUX +----------------------------------------------- */ + +.nux #color-grid, +.nux #colourlovers-palettes-container h3, +.nux #more-palettes { + display: none !important; +} +/* only show theme palettes */ +.nux .color-grid:not(.featured), +.nux .colour-lovers.featured:before { + display: none; +} +.nux #customize-control-background_color { + display: block !important; +} + + +.media-modal { + z-index: 1001000 !important; +} + +#customize-control-colors-tool .cd-design { + clear: right; + margin-top: 50px; +} + +/* Restored CSS from wp.com theme customizer updates */ + +.colour-lovers { + border-radius: 2px; + overflow: hidden; +} +.color-grid li { + border-width: 1px; +} +.color-grid.main { + height: auto; + text-align: center; + width: 100%; + border-bottom: none; + background-color: transparent; + padding-top: 20px +} +.color-grid.main li { + background-image: none; + border: none; + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.2), inset 0 -2px 0 rgba(255, 255, 255, 0.1); + margin: 0px 13px 12px; +} +.color-grid .unavailable:before { + font-size: 20px; + left: 17px; + top: 20px; +} +.the-picker { + background-color: transparent; + border-top: 1px solid #333; + margin-top: 40px; + padding-top: 10px; +} +.the-picker p { + margin-top: 30px; +} +.the-picker p a { + font-weight: bold; +} +.color-suggestions, +.color-spectrum { + height: auto; + margin: 0; + overflow: hidden; +} +.color-suggestions li, +.color-spectrum li { + border-width: 1px; + border-radius: 2px; + margin: 10px 10px 0 0; + width: 30px; + height: 30px; + float: left; +} +.color-spectrum li { + cursor: pointer; +} diff --git a/projects/plugins/wpcomsh/custom-colors/css/core-bg-notice.css b/projects/plugins/wpcomsh/custom-colors/css/core-bg-notice.css new file mode 100644 index 0000000000000..49b82515fd78e --- /dev/null +++ b/projects/plugins/wpcomsh/custom-colors/css/core-bg-notice.css @@ -0,0 +1,16 @@ +.color-admin-notice { + background-color: #78c8e6; + + padding: 1px 20px; + overflow: hidden; + border-radius: 2px; +} +.color-admin-notice a { + color: #005684; + white-space: nowrap; +} +.color-admin-notice img { + margin: 0 24px 12px 0; + float: left; + width: 88px; +} diff --git a/projects/plugins/wpcomsh/custom-colors/css/rtl/colors-control-rtl.css b/projects/plugins/wpcomsh/custom-colors/css/rtl/colors-control-rtl.css new file mode 100644 index 0000000000000..89b3a43d9f629 --- /dev/null +++ b/projects/plugins/wpcomsh/custom-colors/css/rtl/colors-control-rtl.css @@ -0,0 +1,740 @@ +/* This file was automatically generated on Aug 16 2016 02:58:02 */ + +/** +* The Theme Chooser Styles +*/ + + +/* =Structure +----------------------------------------------- */ +.custom-design-feature { + width: 33% !important; +} +.customize-control-colorsTool { + position: relative; +} +.customize-control-colorsTool .customize-control-title { + font-size: 14px; + line-height: 34px; +} +#customize-control-colors-tool { + margin-top: 15px; +} +#customize-control-colors-tool .customize-control-title { + margin: 5px 0 2px; + position: relative; + margin-top: -7px; +} + + +/* =Color Picker +----------------------------------------------- */ +.color-grid { + display: block; + margin: 0; + padding: 0; +} +.color-grid.main li { + height: 55px; + width: 55px; + background-image: -moz-radial-gradient( center top, circle farthest-side, rgba(255, 255, 255, .35), rgba(255, 255, 255, 0) 80% ); + background-image: -webkit-radial-gradient( center top, circle farthest-side, rgba(255, 255, 255, .35), rgba(255, 255, 255, 0) 80% ); + background-image: radial-gradient( center top, circle farthest-side, rgba(255, 255, 255, .35), rgba(255, 255, 255, 0) 80% ); + background-position: center center; + background-size: cover; + border: 2px solid #fff; + border-radius: 50%; + box-shadow: inset 0 -1px 4px rgba(0,0,0,0.1); +} + +.disable-background .color-grid.main li, +.enable-background .color-grid.main li { + display: inline-block; + float: none; +} + +.color-grid.main li:not(:last-child) { + margin-left: 7px; +} +.enable-background .color-grid.main li.bg { + position: relative; + background-size: 100% 100%; + border-radius: 2%; + box-shadow: inset 0 -1px 4px rgba(0,0,0,0.1); + height: 55px; + margin-bottom: 20px; + width: 100%; +} +.enable-background .color-grid.main .text-placeholder { + background: transparent; + border: none; + -moz-box-sizing: inherit; + -webkit-box-sizing: inherit; + box-sizing: inherit; + display: block; + list-style-type: none; + margin: 0; + width: 100%; + box-shadow: none; + height: 50px; + text-align: right; +} +.color-grid.main { + height: auto; + text-align: center; + width: 100%; +} + +.color-grid li { + background: #f1f1f1; + border: 5px solid rgba(0,0,0,0.1); + border-width: 1px 1px 5px; + font-size: 0px; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + display: block; + float: right; + list-style-type: none; + margin: 0; + height: 50px; + width: 80px; +} + +.color-grid li a { + font-size: 0px; +} +.color-grid li:hover { + cursor: pointer; + background-image: none; +} +/* + This will be dynamic: get form setting (if there is one) + Or pull themecolors array +*/ +.color-grid .bg { + height: 100%; +} + +.color-grid .fg2 { + clear: left; +} +.color-grid.main .unavailable { + background: #353535 !important; + box-shadow: inset 0 5px 16px rgba(0, 0, 0, 0.4), inset 0 -1px 1px rgba(255, 255, 255, 0.2) !important; + opacity: 0.2; +} +.color-grid.main .unavailable.selected { + border: none; +} +.color-grid .unavailable:before { + color: #fff; + content: '\f406'; + font-family: "Noticons"; + font-size: 21px; + opacity: 0.4; + float: right; + position: relative; + right: 15px; + top: 18px; + text-shadow: 0 0 2px rgba(0,0,0,0.4); +} +.color-grid.main .selected { + border: 3px solid #fff; +} + +.action-button-wrap { + float: left; + margin-top: -5px; + position: relative; + z-index: 100; +} +a.revert { + font-size: 11px !important; + z-index: 1; + float: right; +} +.color-grid .bg:not(:hover) .change-background, +.color-grid .bg.bg-change-disable .change-background { + display: none; +} +.color-grid .bg .change-background { + position: absolute; + bottom: 11px; + left: 15px; +} + + +/* =Colors Suggestions and Picker +----------------------------------------------- */ +.customize-control-colorsTool a { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.the-picker { + background: #fff; + border: 2px solid #eee; + border-top-style: dashed; + border: none; + display: none; + padding: 10px 20px; + position: relative; + right: -20px; + width: 260px; + z-index: 2; +} +/* override new-customizer.css */ +.color-picker .the-picker { + border: none; + margin-top: 20px; +} +.the-picker .color-label { + background: #eee; + border-radius: 2px; + color: #666; + display: block; + font-size: 15px; + line-height: 15px; + padding: 8px 18px; + position: relative; + top: -10px; + text-align: center; +} +.the-picker .color-label span { + background: #fff; + border: 1px solid #ddd; + border-radius: 5px; + color: #888; + display: inline-block; + font-family: Consolas, Monaco, monospace; + margin-right: 12px; + padding: 2px 6px; +} +.the-picker p { + clear: both; + color: #646464; + font-size: 13px; + margin: 10px 0 10px; +} +/* override new-customizer.css */ +.color-picker .the-picker p:not(.iris-launch) { + margin-top: 0; +} +.color-suggestions { + display: block; + margin: 20px 0; + padding: 0; + height: 50px; +} +.color-suggestions li { + background: #f1f1f1; + border: 3px solid rgba(0,0,0,0.1); + font-size: 0px; + display: block; + margin: 0 3px 15px; + float: right; + list-style-type: none; + height: 30px; + width: 40px; + display: none; +} +.color-suggestions li:first-child { + margin-right: 0; +} +.color-suggestions li:nth-of-type(6), .color-suggestions li:nth-of-type(11) { + margin-right: 0; +} +.color-suggestions li:hover { + cursor: pointer; + opacity: 1; +} +/* Background Choices */ +#the-bg-picker-prompt { + border-top: 2px dashed #eee; + padding: 10px 0; +} +#the-bg-picker-prompt .customize-control-title { + margin-bottom: 10px; +} +#the-bg-picker-prompt div { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + float: right; + width: 50%; + text-align: center; +} +#the-bg-picker-prompt:after { + content: '.'; + display: block; + height: 0; + clear: both; + visibility: hidden; + zoom: 1; +} +#the-bg-picker-prompt h4 { + font-weight: 400; +} +#the-bg-picker-prompt h4 strong { + font-weight: bold; +} +.choose-color, +.choose-pattern { + border: 3px solid #fff; + border-radius: 50%; + font-size: 0px; + box-shadow: 0 1px 2px #aaa; + display: block; + height: 70px; + width: 70px; + margin: 0 auto; +} +.choose-pattern { + background-image: url('../../images/pattern-default.png'); + background-size: 70px 70px; +} + + +/* =Color Palettes +----------------------------------------------- */ +.colour-lovers { + border-radius: 4px; + float: right; + margin: 0px 0 18px 18px; + height: 50px; + width: 72px; + position: relative; +} +.colour-lovers:nth-of-type(3) { + margin-left: 0; +} +.colour-lovers li { + width: 20%; + height: 100%; +} +/* Displays theme color palettes of < five colors */ +.colour-lovers.items-1 li { + width: 100%; +} +.colour-lovers.items-2 li { + width: 50%; +} +.colour-lovers.items-3 li { + width: 33.3333%; +} +.colour-lovers.items-4 li { + width: 25%; +} +.colour-lovers:hover { + cursor: pointer; +} +.colour-lovers li:hover { + opacity: 1; +} +.colour-lovers:active { + opacity: 0.5; +} +#colourlovers-palettes-container { + clear: both; + padding: 20px 0 20px; + margin-left: -30px; +} +#colourlovers-palettes-container h3 { + font-size: 14px; +} +.color-picker .button:hover { + cursor: pointer; +} +.color-picker .button:active { + padding-top: 3px; +} +.color-picker #colourlovers-palettes-container .previous, .color-picker #colourlovers-palettes-container .next { + margin-top: 10px; +} +/*.color-picker .previous { + margin-left: 10px; +}*/ +/* Theme featured palettes */ +.colour-lovers.featured:before { + content: 'Featured'; + background: #000; + color: #fff; + display: block; + font-size: 10px; + opacity: 0.7; + position: absolute; + bottom: 5px; + left: 0; + right: 0; + text-indent: 5px; + line-height: 1.5; + padding-bottom: 1px; +} + +/* Generate from header image */ +#generate-palette { + display: block; + margin-left: 30px; + margin-top: 15px; + text-align: center; +} + + +/* =Color Patterns +----------------------------------------------- */ +#the-pattern-picker { + margin: 0 -20px; + padding: 0 20px 10px; +} +#colourlovers-patterns { + border-bottom: none; + margin: 0 -18px 10px; + min-height: 50px; +} +#colourlovers-patterns li { + float: right; + border-radius: 2px; + height: 50px; + margin-bottom: 10px; + margin-right: 18px; + overflow: hidden; + padding: 0; + width: 72px; +} +#colourlovers-patterns img { + cursor: pointer; + width: 72px; +} +#colourlovers-patterns:after { + content: '.'; + display: block; + height: 0; + clear: both; + visibility: hidden; + zoom: 1; +} +#more-patterns, #less-patterns { + margin-bottom: 10px; +} +#customize-control-background_image .pagination { + clear: both; + padding-top: 20px; + min-height: 30px; +} +#customize-control-background_image .pagination a.button { + display: inline; +} +#the-pattern-picker .customize-control-title a { + float: left; + font-size: smaller; +} +.customize-image-picker { + margin: 0px -20px 10px; + padding: 0 20px 20px; +} +.palette-buttons { + clear: both; +} + +/* Iris */ +#iris { + width: 33%; +} +#iris-container { + padding-bottom: 40px; + position: relative; +} +input#iris { + position: absolute; + bottom: -10px; + right: 85px; +} + +/* + * Free Mode + */ +.free-mode #colourlovers-palettes .color-grid:not(.featured) { + display: none; +} +.free-mode #the-pattern-picker { + display: none; +} +.free-mode .buy-custom-design { + background: #191919; + padding: 40px 20px; + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + z-index: 5; +} +.free-mode .buy-custom-design .back-to-colors { + margin-right: 20px; +} +.free-mode .buy-custom-design .button span { + font-size: 12px; + margin-right: 10px; +} +.free-mode .buy-custom-design ul { + list-style-type: disc; + font-size: 12px; + padding-right: 20px; +} +.free-mode.preview-mode #accordion-section-colors_manager_tool h3:after { + display: none; +} +.free-mode .more-features { + font-size: 11px; + margin-top: 20px; + padding-top: 20px; +} +.free-mode ul + .more-features { + border-top: none; + padding-top: 0; +} +.free-mode .buy-custom-design ul.accordion-section-content { + margin: 20px 0 0 0; + padding: 0; + padding-top: 20px; +} +.free-mode .accordion-section-colors { + margin: 20px -20px 0; + padding-top: 40px; +} + +.free-mode .color-grid.main { + height: auto; + padding-bottom: 0; + margin-bottom: 0; +} + +.free-mode .color-grid.main li, .free-mode .enable-background .color-grid.main .text-placeholder.clrs { + display: none; +} + +.free-mode .color-grid.main li.bg { + display: block; + margin-bottom: 0; +} + +.free-mode #more-palettes, .free-mode .no-free-palettes, .free-mode #generate-palette { + display: none !important; +} + +/* Header text color */ + +.customize-control-header-text-color { + margin: 0px -20px 10px; + padding: 0 20px 20px; +} + +.customize-control-header-text-color .customize-control-title { + margin: 1em 0; /* like if it were a h3 */ +} + +/* Background Change View */ + +#background-change { + display: none; + padding-top: 10px; + min-height: 100px; + font-size: 20px; +} +.background-rectangle { + position: relative; + background-position-y: 33%; + background-repeat: repeat; + background-size: 100% 100%; + border-radius: 2%; + box-shadow: inset 0 -1px 4px rgba(0,0,0,0.1); + width: 260px; + height: 55px; + margin-bottom: 8px; +} +.background-rectangle .done { + background: transparent; + display: block; + position: absolute; + text-align: center; + top: 14px; + min-width: 50px; + left: 15px; +} +.button.select-image { + float: left; +} +#customize-controls .button.background-options { + float: right; + display: block; + text-align: center; + margin-bottom: -11px; +} +.sep { + clear: both; +} + + +.float-button { + background-color: #fff; + border-radius: 2px; + color: #333; + cursor: pointer; + display: inline-block; + font-size: 13px; + line-height: 1.5; + padding: 5px 8px; + opacity: 0.85; +} + +/* Background Options View */ + +.view.background-options { + display: none; + border-radius: 2px; + padding: 5px 8px; + margin-top: 8px; +} +.view.background-options input { + float: left; +} +.background-options p.radios input { + display: none; +} +.background-options p.radios label { + margin-right: 2px; + color: #2e4453; /* $gray-dark */ + float: left; + padding: 2px 5px; + display: inline-block; + border: 1px solid #c8d7e1; /* $gray-lighten-20 */ + border-radius: 2px; +} +.background-options p.radios input:checked + label { + font-weight: bold; + color: #fff; + background: #87a6bc !important; /* $gray */ + border-color: #668eaa; /* $gray-darken-10 */ +} +.background-options p.bottom { + text-align: center; + margin-bottom: 4px; +} + +.noticon-tile-none, +.noticon-tile-horizontally, +.noticon-tile-vertically { + width: 16px; + height: 16px; + background-size: 8px; + background-repeat: no-repeat; + background-position: 4px 4px; +} + +.noticon-tile-none { + background-image: url('../../images/tile-none.png'); +} + +.noticon-tile-horizontally { + background-image: url('../../images/tile-horizontally.png'); +} + +.noticon-tile-vertically { + background-image: url('../../images/tile-vertically.png'); +} +.background-options .iris-picker { + margin: auto; + background-color: rgb(51, 51, 51); + border-color: #000; +} +.background-options label.underlying-color:not(:hover) { + opacity: 0.8; +} + +/* Don't flash replaced sections on first load */ +#accordion-section-background_image, #accordion-section-colors { + display: none; +} + +/* =NUX +----------------------------------------------- */ + +.nux #color-grid, +.nux #colourlovers-palettes-container h3, +.nux #more-palettes { + display: none !important; +} +/* only show theme palettes */ +.nux .color-grid:not(.featured), +.nux .colour-lovers.featured:before { + display: none; +} +.nux #customize-control-background_color { + display: block !important; +} + + +.media-modal { + z-index: 1001000 !important; +} + +#customize-control-colors-tool .cd-design { + clear: left; + margin-top: 50px; +} + +/* Restored CSS from wp.com theme customizer updates */ + +.colour-lovers { + border-radius: 2px; + overflow: hidden; +} +.color-grid li { + border-width: 1px; +} +.color-grid.main { + border-bottom: none; + background-color: transparent; +} +.color-grid.main li { + background-image: none; + border: none; + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.2), inset 0 -2px 0 rgba(255, 255, 255, 0.1); +} +.color-grid .unavailable:before { + font-size: 20px; + right: 17px; + top: 20px; +} +.the-picker { + background-color: transparent; + border-top: 1px solid #333; + margin-top: 40px; + padding-top: 10px; +} +.the-picker p { + margin-top: 30px; +} +.the-picker p a { + font-weight: bold; +} +.color-suggestions, +.color-spectrum { + height: auto; + margin: 0; + overflow: hidden; +} +.color-suggestions li, +.color-spectrum li { + border-width: 1px; + border-radius: 2px; + margin: 10px 0 0 10px; + width: 30px; + height: 30px; + float: right; +} +.color-spectrum li { + cursor: pointer; +} diff --git a/projects/plugins/wpcomsh/custom-colors/css/rtl/core-bg-notice-rtl.css b/projects/plugins/wpcomsh/custom-colors/css/rtl/core-bg-notice-rtl.css new file mode 100644 index 0000000000000..f6a4685f37db9 --- /dev/null +++ b/projects/plugins/wpcomsh/custom-colors/css/rtl/core-bg-notice-rtl.css @@ -0,0 +1,18 @@ +/* This file was automatically generated on Jun 24 2013 17:01:51 */ + +.color-admin-notice { + background-color: #78c8e6; + + padding: 1px 20px; + overflow: hidden; + border-radius: 2px; +} +.color-admin-notice a { + color: #005684; + white-space: nowrap; +} +.color-admin-notice img { + margin: 0 0 12px 24px; + float: right; + width: 88px; +} diff --git a/projects/plugins/wpcomsh/custom-colors/images/colors-screenshot.png b/projects/plugins/wpcomsh/custom-colors/images/colors-screenshot.png new file mode 100644 index 0000000000000..1b9cd67f52bf4 Binary files /dev/null and b/projects/plugins/wpcomsh/custom-colors/images/colors-screenshot.png differ diff --git a/projects/plugins/wpcomsh/custom-colors/images/pattern-default.png b/projects/plugins/wpcomsh/custom-colors/images/pattern-default.png new file mode 100644 index 0000000000000..ba2c7cd979aee Binary files /dev/null and b/projects/plugins/wpcomsh/custom-colors/images/pattern-default.png differ diff --git a/projects/plugins/wpcomsh/custom-colors/images/selected.png b/projects/plugins/wpcomsh/custom-colors/images/selected.png new file mode 100644 index 0000000000000..e80cb3890f9bb Binary files /dev/null and b/projects/plugins/wpcomsh/custom-colors/images/selected.png differ diff --git a/projects/plugins/wpcomsh/custom-colors/images/tile-horizontally.png b/projects/plugins/wpcomsh/custom-colors/images/tile-horizontally.png new file mode 100644 index 0000000000000..3da469d92b015 Binary files /dev/null and b/projects/plugins/wpcomsh/custom-colors/images/tile-horizontally.png differ diff --git a/projects/plugins/wpcomsh/custom-colors/images/tile-none.png b/projects/plugins/wpcomsh/custom-colors/images/tile-none.png new file mode 100644 index 0000000000000..dd9765e9c0e7c Binary files /dev/null and b/projects/plugins/wpcomsh/custom-colors/images/tile-none.png differ diff --git a/projects/plugins/wpcomsh/custom-colors/images/tile-vertically.png b/projects/plugins/wpcomsh/custom-colors/images/tile-vertically.png new file mode 100644 index 0000000000000..f81c5be618ef6 Binary files /dev/null and b/projects/plugins/wpcomsh/custom-colors/images/tile-vertically.png differ diff --git a/projects/plugins/wpcomsh/custom-colors/images/unavailable.png b/projects/plugins/wpcomsh/custom-colors/images/unavailable.png new file mode 100644 index 0000000000000..7f337485a0340 Binary files /dev/null and b/projects/plugins/wpcomsh/custom-colors/images/unavailable.png differ diff --git a/projects/plugins/wpcomsh/custom-colors/js/classic-background-stats.js b/projects/plugins/wpcomsh/custom-colors/js/classic-background-stats.js new file mode 100644 index 0000000000000..e84fb7dc0c90a --- /dev/null +++ b/projects/plugins/wpcomsh/custom-colors/js/classic-background-stats.js @@ -0,0 +1,63 @@ +/* global jQuery, _ */ ( function ( $, _ ) { + var processEvent, + sendClientSideStat, + statBucket = 'classic-custom-background'; + + sendClientSideStat = function ( eventName ) { + var url = + document.location.protocol + + '//pixel.wp.com/g.gif?v=wpcom-no-pv&x_' + + statBucket + + '=' + + eventName + + '&baba=' + + Math.random(); + new Image().src = url; + }; + + processEvent = function ( e ) { + var target = $( e.target ), + eventName = target.data( 'event' ); + + return sendClientSideStat( eventName ); + }; + + $( function () { + var colorChange, oldColorChange; + + oldColorChange = $( '#background-color' ).iris( 'option', 'change' ); + + // Replace the Iris change handler + // Debounce so we don't get too many colour change events from users + // trying to find the perfect colour + colorChange = _.debounce( + function () { + $( '#background-color' ).trigger( 'custom-bg-color-change' ); + oldColorChange.apply( null, arguments ); + }, + 1000, + true + ); + + $( '#background-color' ).iris( 'option', 'change', colorChange ); + + // Bind event handlers + $( '#remove-background' ).data( 'event', 'remove-background' ).on( 'click', processEvent ); + $( '#upload' ).data( 'event', 'upload-file' ).on( 'click', processEvent ); + $( '#choose-from-library-link' ) + .data( 'event', 'choose-from-library' ) + .on( 'click', processEvent ); + $( 'input[name=background-position-x]' ) + .data( 'event', 'change-background-position' ) + .on( 'click', processEvent ); + $( 'input[name=background-repeat]' ) + .data( 'event', 'change-background-repeat' ) + .on( 'click', processEvent ); + $( 'input[name=background-attachment]' ) + .data( 'event', 'change-background-attachment' ) + .on( 'click', processEvent ); + $( '#background-color' ) + .data( 'event', 'change-background-color' ) + .on( 'custom-bg-color-change', processEvent ); + } ); +} )( jQuery, _ ); diff --git a/projects/plugins/wpcomsh/custom-colors/js/color.js b/projects/plugins/wpcomsh/custom-colors/js/color.js new file mode 100644 index 0000000000000..03a8f2279c0e0 --- /dev/null +++ b/projects/plugins/wpcomsh/custom-colors/js/color.js @@ -0,0 +1,663 @@ +/*! Color.js - v1.1.0 - 2015-12-17 + * https://github.com/Automattic/Color.js + * Copyright (c) 2015 Matt Wiebe; Licensed GPLv2 */ +( function ( global, undef ) { + var Color = function ( color, type ) { + if ( ! ( this instanceof Color ) ) { + return new Color( color, type ); + } + + return this._init( color, type ); + }; + + Color.fn = Color.prototype = { + _color: 0, + _alpha: 1, + error: false, + // for preserving hue/sat in fromHsl().toHsl() flows + _hsl: { h: 0, s: 0, l: 0 }, + // for preserving hue/sat in fromHsv().toHsv() flows + _hsv: { h: 0, s: 0, v: 0 }, + // for setting hsl or hsv space - needed for .h() & .s() functions to function properly + _hSpace: 'hsl', + _init: function ( color ) { + var func = 'noop'; + switch ( typeof color ) { + case 'object': + // alpha? + if ( color.a !== undef ) { + this.a( color.a ); + } + func = + color.r !== undef + ? 'fromRgb' + : color.l !== undef + ? 'fromHsl' + : color.v !== undef + ? 'fromHsv' + : func; + return this[ func ]( color ); + case 'string': + return this.fromCSS( color ); + case 'number': + return this.fromInt( parseInt( color, 10 ) ); + } + return this; + }, + + _error: function () { + this.error = true; + return this; + }, + + clone: function () { + var newColor = new Color( this.toInt() ), + copy = [ '_alpha', '_hSpace', '_hsl', '_hsv', 'error' ]; + for ( var i = copy.length - 1; i >= 0; i-- ) { + newColor[ copy[ i ] ] = this[ copy[ i ] ]; + } + return newColor; + }, + + setHSpace: function ( space ) { + this._hSpace = space === 'hsv' ? space : 'hsl'; + return this; + }, + + noop: function () { + return this; + }, + + fromCSS: function ( color ) { + var list, + leadingRE = /^(rgb|hs(l|v))a?\(/; + this.error = false; + + // whitespace and semicolon trim + color = color.replace( /^\s+/, '' ).replace( /\s+$/, '' ).replace( /;$/, '' ); + + if ( color.match( leadingRE ) && color.match( /\)$/ ) ) { + list = color + .replace( /(\s|%)/g, '' ) + .replace( leadingRE, '' ) + .replace( /,?\);?$/, '' ) + .split( ',' ); + + if ( list.length < 3 ) { + return this._error(); + } + + if ( list.length === 4 ) { + this.a( parseFloat( list.pop() ) ); + // error state has been set to true in .a() if we passed NaN + if ( this.error ) { + return this; + } + } + + for ( var i = list.length - 1; i >= 0; i-- ) { + list[ i ] = parseInt( list[ i ], 10 ); + if ( isNaN( list[ i ] ) ) { + return this._error(); + } + } + + if ( color.match( /^rgb/ ) ) { + return this.fromRgb( { + r: list[ 0 ], + g: list[ 1 ], + b: list[ 2 ], + } ); + } else if ( color.match( /^hsv/ ) ) { + return this.fromHsv( { + h: list[ 0 ], + s: list[ 1 ], + v: list[ 2 ], + } ); + } else { + return this.fromHsl( { + h: list[ 0 ], + s: list[ 1 ], + l: list[ 2 ], + } ); + } + } else { + // must be hex amirite? + return this.fromHex( color ); + } + }, + + fromRgb: function ( rgb, preserve ) { + if ( typeof rgb !== 'object' || rgb.r === undef || rgb.g === undef || rgb.b === undef ) { + return this._error(); + } + + this.error = false; + return this.fromInt( parseInt( ( rgb.r << 16 ) + ( rgb.g << 8 ) + rgb.b, 10 ), preserve ); + }, + + fromHex: function ( color ) { + color = color.replace( /^#/, '' ).replace( /^0x/, '' ); + if ( color.length === 3 ) { + color = color[ 0 ] + color[ 0 ] + color[ 1 ] + color[ 1 ] + color[ 2 ] + color[ 2 ]; + } + + // rough error checking - this is where things go squirrely the most + this.error = ! /^[0-9A-F]{6}$/i.test( color ); + return this.fromInt( parseInt( color, 16 ) ); + }, + + fromHsl: function ( hsl ) { + var r, g, b, q, p, h, s, l; + + if ( typeof hsl !== 'object' || hsl.h === undef || hsl.s === undef || hsl.l === undef ) { + return this._error(); + } + + this._hsl = hsl; // store it + this._hSpace = 'hsl'; // implicit + h = hsl.h / 360; + s = hsl.s / 100; + l = hsl.l / 100; + if ( s === 0 ) { + r = g = b = l; // achromatic + } else { + q = l < 0.5 ? l * ( 1 + s ) : l + s - l * s; + p = 2 * l - q; + r = this.hue2rgb( p, q, h + 1 / 3 ); + g = this.hue2rgb( p, q, h ); + b = this.hue2rgb( p, q, h - 1 / 3 ); + } + return this.fromRgb( + { + r: r * 255, + g: g * 255, + b: b * 255, + }, + true + ); // true preserves hue/sat + }, + + fromHsv: function ( hsv ) { + var h, s, v, r, g, b, i, f, p, q, t; + if ( typeof hsv !== 'object' || hsv.h === undef || hsv.s === undef || hsv.v === undef ) { + return this._error(); + } + + this._hsv = hsv; // store it + this._hSpace = 'hsv'; // implicit + + h = hsv.h / 360; + s = hsv.s / 100; + v = hsv.v / 100; + i = Math.floor( h * 6 ); + f = h * 6 - i; + p = v * ( 1 - s ); + q = v * ( 1 - f * s ); + t = v * ( 1 - ( 1 - f ) * s ); + + switch ( i % 6 ) { + case 0: + r = v; + g = t; + b = p; + break; + case 1: + r = q; + g = v; + b = p; + break; + case 2: + r = p; + g = v; + b = t; + break; + case 3: + r = p; + g = q; + b = v; + break; + case 4: + r = t; + g = p; + b = v; + break; + case 5: + r = v; + g = p; + b = q; + break; + } + + return this.fromRgb( + { + r: r * 255, + g: g * 255, + b: b * 255, + }, + true + ); // true preserves hue/sat + }, + // everything comes down to fromInt + fromInt: function ( color, preserve ) { + this._color = parseInt( color, 10 ); + + if ( isNaN( this._color ) ) { + this._color = 0; + } + + // let's coerce things + if ( this._color > 16777215 ) { + this._color = 16777215; + } else if ( this._color < 0 ) { + this._color = 0; + } + + // let's not do weird things + if ( preserve === undef ) { + this._hsv.h = this._hsv.s = this._hsl.h = this._hsl.s = 0; + } + // EVENT GOES HERE + return this; + }, + + hue2rgb: function ( p, q, t ) { + if ( t < 0 ) { + t += 1; + } + if ( t > 1 ) { + t -= 1; + } + if ( t < 1 / 6 ) { + return p + ( q - p ) * 6 * t; + } + if ( t < 1 / 2 ) { + return q; + } + if ( t < 2 / 3 ) { + return p + ( q - p ) * ( 2 / 3 - t ) * 6; + } + return p; + }, + + toString: function () { + var hex = parseInt( this._color, 10 ).toString( 16 ); + if ( this.error ) { + return ''; + } + // maybe left pad it + if ( hex.length < 6 ) { + for ( var i = 6 - hex.length - 1; i >= 0; i-- ) { + hex = '0' + hex; + } + } + return '#' + hex; + }, + + toCSS: function ( type, alpha ) { + type = type || 'hex'; + alpha = parseFloat( alpha || this._alpha ); + switch ( type ) { + case 'rgb': + case 'rgba': + var rgb = this.toRgb(); + if ( alpha < 1 ) { + return 'rgba( ' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + alpha + ' )'; + } else { + return 'rgb( ' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ' )'; + } + break; + case 'hsl': + case 'hsla': + var hsl = this.toHsl(); + if ( alpha < 1 ) { + return 'hsla( ' + hsl.h + ', ' + hsl.s + '%, ' + hsl.l + '%, ' + alpha + ' )'; + } else { + return 'hsl( ' + hsl.h + ', ' + hsl.s + '%, ' + hsl.l + '% )'; + } + break; + default: + return this.toString(); + } + }, + + toRgb: function () { + return { + r: 255 & ( this._color >> 16 ), + g: 255 & ( this._color >> 8 ), + b: 255 & this._color, + }; + }, + + toHsl: function () { + var rgb = this.toRgb(); + var r = rgb.r / 255, + g = rgb.g / 255, + b = rgb.b / 255; + var max = Math.max( r, g, b ), + min = Math.min( r, g, b ); + var h, + s, + l = ( max + min ) / 2; + + if ( max === min ) { + h = s = 0; // achromatic + } else { + var d = max - min; + s = l > 0.5 ? d / ( 2 - max - min ) : d / ( max + min ); + switch ( max ) { + case r: + h = ( g - b ) / d + ( g < b ? 6 : 0 ); + break; + case g: + h = ( b - r ) / d + 2; + break; + case b: + h = ( r - g ) / d + 4; + break; + } + h /= 6; + } + + // maintain hue & sat if we've been manipulating things in the HSL space. + h = Math.round( h * 360 ); + if ( h === 0 && this._hsl.h !== h ) { + h = this._hsl.h; + } + s = Math.round( s * 100 ); + if ( s === 0 && this._hsl.s ) { + s = this._hsl.s; + } + + return { + h: h, + s: s, + l: Math.round( l * 100 ), + }; + }, + + toHsv: function () { + var rgb = this.toRgb(); + var r = rgb.r / 255, + g = rgb.g / 255, + b = rgb.b / 255; + var max = Math.max( r, g, b ), + min = Math.min( r, g, b ); + var h, + s, + v = max; + var d = max - min; + s = max === 0 ? 0 : d / max; + + if ( max === min ) { + h = s = 0; // achromatic + } else { + switch ( max ) { + case r: + h = ( g - b ) / d + ( g < b ? 6 : 0 ); + break; + case g: + h = ( b - r ) / d + 2; + break; + case b: + h = ( r - g ) / d + 4; + break; + } + h /= 6; + } + + // maintain hue & sat if we've been manipulating things in the HSV space. + h = Math.round( h * 360 ); + if ( h === 0 && this._hsv.h !== h ) { + h = this._hsv.h; + } + s = Math.round( s * 100 ); + if ( s === 0 && this._hsv.s ) { + s = this._hsv.s; + } + + return { + h: h, + s: s, + v: Math.round( v * 100 ), + }; + }, + + toInt: function () { + return this._color; + }, + + toIEOctoHex: function () { + // AARRBBGG + var hex = this.toString(); + var AA = parseInt( 255 * this._alpha, 10 ).toString( 16 ); + if ( AA.length === 1 ) { + AA = '0' + AA; + } + return '#' + AA + hex.replace( /^#/, '' ); + }, + + // http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef + toLuminosity: function () { + var rgb = this.toRgb(); + var lum = {}; + for ( var i in rgb ) { + if ( ! rgb.hasOwnProperty( i ) ) { + continue; + } + var chan = rgb[ i ] / 255; + lum[ i ] = chan <= 0.03928 ? chan / 12.92 : Math.pow( ( chan + 0.055 ) / 1.055, 2.4 ); + } + + return 0.2126 * lum.r + 0.7152 * lum.g + 0.0722 * lum.b; + }, + + // http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef + getDistanceLuminosityFrom: function ( color ) { + if ( ! ( color instanceof Color ) ) { + throw 'getDistanceLuminosityFrom requires a Color object'; + } + var lum1 = this.toLuminosity(); + var lum2 = color.toLuminosity(); + if ( lum1 > lum2 ) { + return ( lum1 + 0.05 ) / ( lum2 + 0.05 ); + } else { + return ( lum2 + 0.05 ) / ( lum1 + 0.05 ); + } + }, + + getMaxContrastColor: function () { + var withBlack = this.getDistanceLuminosityFrom( new Color( '#000' ) ); + var withWhite = this.getDistanceLuminosityFrom( new Color( '#fff' ) ); + var hex = withBlack >= withWhite ? '#000' : '#fff'; + return new Color( hex ); + }, + + getReadableContrastingColor: function ( bgColor, minContrast ) { + if ( ! ( bgColor instanceof Color ) ) { + return this; + } + + // you shouldn't use less than 5, but you might want to. + var targetContrast = minContrast === undef ? 5 : minContrast, + contrast = bgColor.getDistanceLuminosityFrom( this ), + maxContrastColor, + maxContrast, + incr; + + // if we have sufficient contrast already, cool + if ( contrast >= targetContrast ) { + return this; + } + + maxContrastColor = bgColor.getMaxContrastColor(); + maxContrast = maxContrastColor.getDistanceLuminosityFrom( bgColor ); + + // if current max contrast is less than the target contrast, we had wishful thinking. + // still, go max + if ( maxContrast <= targetContrast ) { + return maxContrastColor; + } + + incr = 0 === maxContrastColor.toInt() ? -1 : 1; + while ( contrast < targetContrast ) { + this.l( incr, true ); // 2nd arg turns this into an incrementer + contrast = this.getDistanceLuminosityFrom( bgColor ); + // infininite loop prevention: you never know. + if ( this._color === 0 || this._color === 16777215 ) { + break; + } + } + + return this; + }, + + a: function ( val ) { + if ( val === undef ) { + return this._alpha; + } + + var a = parseFloat( val ); + + if ( isNaN( a ) ) { + return this._error(); + } + + this._alpha = a; + return this; + }, + + // TRANSFORMS + + darken: function ( amount ) { + amount = amount || 5; + return this.l( -amount, true ); + }, + + lighten: function ( amount ) { + amount = amount || 5; + return this.l( amount, true ); + }, + + saturate: function ( amount ) { + amount = amount || 15; + return this.s( amount, true ); + }, + + desaturate: function ( amount ) { + amount = amount || 15; + return this.s( -amount, true ); + }, + + toGrayscale: function () { + return this.setHSpace( 'hsl' ).s( 0 ); + }, + + getComplement: function () { + return this.h( 180, true ); + }, + + getSplitComplement: function ( step ) { + step = step || 1; + var incr = 180 + step * 30; + return this.h( incr, true ); + }, + + getAnalog: function ( step ) { + step = step || 1; + var incr = step * 30; + return this.h( incr, true ); + }, + + getTetrad: function ( step ) { + step = step || 1; + var incr = step * 60; + return this.h( incr, true ); + }, + + getTriad: function ( step ) { + step = step || 1; + var incr = step * 120; + return this.h( incr, true ); + }, + + _partial: function ( key ) { + var prop = shortProps[ key ]; + return function ( val, incr ) { + var color = this._spaceFunc( 'to', prop.space ); + + // GETTER + if ( val === undef ) { + return color[ key ]; + } + + // INCREMENT + if ( incr === true ) { + val = color[ key ] + val; + } + + // MOD & RANGE + if ( prop.mod ) { + val = val % prop.mod; + } + if ( prop.range ) { + val = + val < prop.range[ 0 ] ? prop.range[ 0 ] : val > prop.range[ 1 ] ? prop.range[ 1 ] : val; + } + + // NEW VALUE + color[ key ] = val; + + return this._spaceFunc( 'from', prop.space, color ); + }; + }, + + _spaceFunc: function ( dir, s, val ) { + var space = s || this._hSpace, + funcName = dir + space.charAt( 0 ).toUpperCase() + space.substr( 1 ); + return this[ funcName ]( val ); + }, + }; + + var shortProps = { + h: { + mod: 360, + }, + s: { + range: [ 0, 100 ], + }, + l: { + space: 'hsl', + range: [ 0, 100 ], + }, + v: { + space: 'hsv', + range: [ 0, 100 ], + }, + r: { + space: 'rgb', + range: [ 0, 255 ], + }, + g: { + space: 'rgb', + range: [ 0, 255 ], + }, + b: { + space: 'rgb', + range: [ 0, 255 ], + }, + }; + + for ( var key in shortProps ) { + if ( shortProps.hasOwnProperty( key ) ) { + Color.fn[ key ] = Color.fn._partial( key ); + } + } + + // play nicely with Node + browser + if ( typeof exports === 'object' ) { + module.exports = Color; + } else { + global.Color = Color; + } +} )( this ); diff --git a/projects/plugins/wpcomsh/custom-colors/js/colors-control-beta.js b/projects/plugins/wpcomsh/custom-colors/js/colors-control-beta.js new file mode 100644 index 0000000000000..57ff3b9ad820d --- /dev/null +++ b/projects/plugins/wpcomsh/custom-colors/js/colors-control-beta.js @@ -0,0 +1,1312 @@ +/* jshint maxerr: 10000 */ +( function ( wp, $, _, undef ) { + // Open closure + + var api, fetchImage; + wp = wp || {}; + api = wp.customize; + + /** + * Our very own customizer handler + */ + api.ColorsTool = api.Control.extend( { + // some init vars + patternPageSize: 3, + palettes: [], + patterns: [], + paletteIndex: 0, + patternIndex: 0, + palettesAtATime: 6, + fetchingPatterns: false, + fetchingPalettes: false, + backgroundChangeView: {}, + + ready: function () { + var ct = this, + hex, + cat; + + // Some variables + ct.tool = $( '#customize-control-colors-tool' ); + ct.topLabel = $( '.customize-control-colorsTool .customize-control-title:first' ); + ct.grid = $( '#color-grid' ); + ct.color = this.grid.find( 'li' ); + ct.bgPrompt = $( '#the-bg-picker-prompt' ); + ct.picker = $( '#the-picker' ); + ct.patternPicker = $( '#the-pattern-picker' ); + ct.reference = $( '#color-reference' ); + ct.palette = $( '.colour-lovers' ); + ct.suggestions = this.picker.find( '.color-suggestions' ); + ct.opts = ColorsTool; // from wp_localize_script + ct.orig = ct.opts.colors; + ct.origBackground = api( 'background_image' ).get(); + ct.topPatterns = ct.opts.topPatterns; + + ct.backgroundChangeView = new api.ColorsTool.BackgroundChangeView( { + el: '#background-change', + controller: this, + } ); + + // The main functions + ct.colorPicker(); + ct.dragColor(); + ct.addChangeListener(); + ct.initPalettes(); + + if ( ! api.isNux ) { + ct.initPatterns(); + } + ct.iris = ct.irisPicker(); + ct.freeMode(); + ct.showHeaderTextColorControl(); + + // set up the color grid. + ct.color.each( function ( index ) { + cat = $( this ).data( 'role' ); + + if ( cat in ct.orig ) { + hex = ct.sanitizeHex( ct.orig[ cat ] ); + ct.setColor( this, hex ); + } + } ); + + if ( ! ct.opts.themeSupport.customBackground ) { + ct.grid.find( '.bg' ).addClass( 'bg-change-disable' ); + ct.tool.addClass( 'disable-background' ); + } else { + ct.tool.addClass( 'enable-background' ); + ct.breakSectionTitle(); + } + + $( '.action-button-wrap' ).insertBefore( ct.topLabel ); + + // Only show 'undo' button when a color change has occured + // Helps clear confusion between revert and undo + ct.grid.on( 'color-change', function () { + $( '.action-button-wrap' ).addClass( 'undo' ); + } ); + + // Revert action + // Updates the grid back to default colors + $( '.revert' ).on( 'click', function () { + // pick the colors to restore to. + var colors = $( this ).hasClass( 'revert-default' ) + ? ct.opts.defaultColors + : ct.opts.colors; + + ct.color.each( function ( index ) { + cat = $( this ).data( 'role' ); + + if ( cat in colors ) { + hex = ct.sanitizeHex( colors[ cat ] ); + ct.setColor( this, hex ); + } + } ); + + ct.grid.trigger( 'color-change' ); + + if ( $( this ).hasClass( 'revert-default' ) ) { + api( 'background_image' ).set( ct.opts.defaultImage ); + // Stat: 'revert-default' + new Image().src = + document.location.protocol + + '//stats.wordpress.com/g.gif?v=wpcom-no-pv&x_customizer-colors-actions=revert-default&baba=' + + Math.random(); + } else { + api( 'background_image' ).set( ct.origBackground ); + // Stat: 'revert-undo' + new Image().src = + document.location.protocol + + '//stats.wordpress.com/g.gif?v=wpcom-no-pv&x_customizer-colors-actions=revert-undo&baba=' + + Math.random(); + } + + $( '.action-button-wrap' ).removeClass( 'undo' ); + } ); + + // open it if we came here with a #colors hash + if ( window.location.hash === '#colors' ) { + // Bump a stat for each time the Color tool is loaded from the Custom Design page (uses the hash). + new Image().src = + document.location.protocol + + '//stats.wordpress.com/g.gif?v=wpcom-no-pv&x_theme-customizer-colors=1&baba=' + + Math.random(); + } + }, + + // Sets up free mode UX + freeMode: function () { + if ( this.opts.isFreeMode ) { + $( '#customize-controls' ).addClass( 'free-mode' ); + } + }, + + showHeaderTextColorControl: function () { + var ct = this, + toggleControl = function () { + var headerControl = $( '.customize-control-header-text-color' ); + + if ( ct.status() === 'default' ) { + headerControl.show(); + } else { + headerControl.hide(); + } + }; + + _.defer( function () { + // Grab core's control + $( '[data-id-from="accordion-section-colors"]' ) + .addClass( 'customize-control' ) + .addClass( 'customize-control-header-text-color' ) + .appendTo( '[data-id-from="accordion-section-colors_manager_tool"]' ) + .show() + .removeClass( 'accordion-section-content accordion-section-colors' ) + .children( ':not(#customize-control-header_textcolor)' ) + .hide(); + + toggleControl(); + } ); + + api.bind( 'change', function ( control ) { + if ( 'colors_manager[colors]' == control.id ) { + toggleControl(); + + // reset regardless of ct.status() + ct.resetHeaderTextColor(); + } + } ); + }, + + resetHeaderTextColor: function () { + var picker = $( '#customize-control-header_textcolor' ).find( 'input.wp-color-picker' ), + color = picker.wpColorPicker( 'defaultColor' ); + + if ( api( 'header_textcolor' ).get() !== 'blank' ) { + picker.wpColorPicker( 'color', color ); + api( 'header_textcolor' ).set( '' ); + } + }, + + breakSectionTitle: function () { + this.topLabel.text( this.opts.backgroundTitle ); + this.grid + .find( '.bg' ) + .after( + '
  • ' + + this.opts.colorsTitle + + '
  • ' + ); + }, + + overrideCoreBg: function () { + var ct = this; + + function bgCallback( to, from ) { + if ( to ) { + ct.grid.find( '.bg' ).css( 'background-image', 'url(' + to + ')' ); + ct.bgPrompt.find( '.choose-pattern' ).css( 'background-image', 'url(' + to + ')' ); + + if ( -1 !== to.indexOf( 'colourlovers' ) ) { + api( 'background_repeat' ).set( 'repeat' ); + // api( 'background_position_x' ).set( '' ); + // api( 'background_attachment' ).set( '' ); + } + } else { + ct.grid.find( '.bg' ).css( 'background-image', '' ); + ct.bgPrompt.find( '.choose-pattern' ).css( 'background-image', '' ); + } + } + + api( 'background_image' ).bind( bgCallback ); + bgCallback( _wpCustomizeSettings.settings.background_image.value ); + + ct.bgPrompt + .find( '.choose-color' ) + .css( 'background-color', ct.getColor( ct.grid.find( '.bg' ) ) ); + }, + + initPatterns: function () { + var ct = this; + if ( + ! ct.opts.themeSupport.customBackground || + ct.grid.find( '.bg' ).hasClass( 'unavailable' ) + ) { + return $( '#the-pattern-picker' ).hide(); + } + + ct.colorPatterns(); + + // Apply patterns to background element + // Binds to click event on each pattern anchor + ct.patternPicker + .on( 'click', '.pattern a', function ( e ) { + e.preventDefault(); + + api( 'background_image' ).set( $( this ).data( 'customizeImageValue' ) ); + + if ( ct.backgroundChangeView.optionsView ) { + ct.backgroundChangeView.optionsView.render(); + } + + // Stat: 'patterns' + new Image().src = + document.location.protocol + + '//stats.wordpress.com/g.gif?v=wpcom-no-pv&x_customizer-colors-actions=patterns&baba=' + + Math.random(); + } ) + .on( 'click', '#less-patterns', function ( e ) { + e.preventDefault(); + + ct.showPatterns( ct.patternIndex - ct.patternPageSize * 2 ); + } ) + .on( 'click', '#more-patterns', function ( e ) { + e.preventDefault(); + + // Stat: 'more-patterns' + new Image().src = + document.location.protocol + + '//stats.wordpress.com/g.gif?v=wpcom-no-pv&x_customizer-colors-actions=more-patterns&baba=' + + Math.random(); + + if ( ct.patternIndex < ct.patterns.length ) { + ct.showPatterns( ct.patternIndex ); + } + + if ( + ( ! ct.fetchingPatterns && 0 == ct.patterns.length ) || + ct.patternIndex == ct.patterns.length - 5 + ) { + ct.fetchingPatterns = true; + + var query_arguments = { + action: 'pattern_recommendations', + limit: '30', + offset: ct.patterns.length, + }; + + ct.color.each( function () { + query_arguments[ 'colors[' + $( this ).data( 'role' ) + ']' ] = ct.getColor( this ); + } ); + + $.get( + '/wp-admin/admin-ajax.php', + query_arguments, + function ( data ) { + if ( ! data.patterns.length ) { + $( '#more-patterns' ).hide(); + } + + $.merge( ct.patterns, data.patterns ); + + ct.fetchingPatterns = false; + + if ( 0 == ct.patternIndex ) { + ct.showPatterns(); + } + }, + 'json' + ); + } + } ); + + ct.overrideCoreBg(); + }, + + initPalettes: function () { + var ct = this; + + ct.colorPalettes(); + + // hide core Colors + $( '#accordion-section-colors' ).hide(); + + $( '#more-palettes' ) + .on( 'click', function () { + // Load palettes into a client-side cache 40 at a time + // and refresh that cache one page before it's necessary. + if ( ct.paletteIndex <= ct.palettes.length - ct.palettesAtATime ) { + ct.showPalettes( ct.paletteIndex ); + } + + // Stat: 'more-palettes' + new Image().src = + document.location.protocol + + '//stats.wordpress.com/g.gif?v=wpcom-no-pv&x_customizer-colors-actions=more-palettes&baba=' + + Math.random(); + + if ( + ! ct.fetchingPalettes && + ct.paletteIndex >= ct.palettes.length - ct.palettesAtATime * 2 + ) { + ct.fetchingPalettes = true; + $.get( + '/wp-admin/admin-ajax.php', + { + action: 'color_palettes', + limit: '40', + offset: ct.palettes.length, + }, + function ( data ) { + if ( ! data.palettes.length ) { + return $( '#more-palettes' ).hide(); + } + + $.merge( ct.palettes, data.palettes ); + if ( + 0 == ct.paletteIndex || + ct.paletteIndex >= ct.palettes.length - data.palettes.length - ct.palettesAtATime + ) { + $( '#more-palettes' ).click(); + } + ct.fetchingPalettes = false; + }, + 'json' + ); + } + } ) + .click(); + + $( '#less-palettes' ).click( function () { + ct.showPalettes( ct.paletteIndex - 2 * ct.palettesAtATime ); + } ); + + // Calls generate palette code + ct.generatePaletteFromHeader(); + }, + + /** + * Generate a palette based on the current header image + * Uses the tonesque library for color sampling + */ + generatePaletteFromHeader: function () { + var ct = this, + text, + colors, + cat, + generatePalette = $( '#generate-palette' ), + checkValidImage = function ( value ) { + var badValues = [ 'remove-header', 'random-uploaded-image', 'random-default-image' ]; + if ( value && ! _.contains( badValues, value ) ) { + ct.opts.headerImage = value; + generatePalette.show(); + return true; + } + generatePalette.hide(); + return false; + }; + + // Initialize from API + ct.opts.headerImage = api.settings.settings.header_image.value; + + // Actions for the "Match header image" button + api.bind( 'change', function ( control ) { + if ( 'header_image' == control.id ) { + checkValidImage( control._value ); + } + } ); + + // Check that we have a header image + // otherwise hide the button + checkValidImage( ct.opts.headerImage ); + + // Store button text + text = generatePalette.text(); + + generatePalette.on( 'click', function () { + // Don't do this if it's free mode + if ( ct.opts.isFreeMode ) { + return ct.buyNotice(); + } + + // Show processing message + $( this ).text( ct.opts.genPalette ); + + $.get( + '/wp-admin/admin-ajax.php', + { + action: 'generate_palette', + image: ct.opts.headerImage, + }, + function ( data ) { + colors = data[ 'colors' ]; + + if ( colors ) { + ct.color.each( function ( index ) { + cat = $( this ).data( 'role' ); + + if ( cat in colors ) { + hex = ct.sanitizeHex( colors[ cat ] ); + ct.setColor( this, hex ); + } + } ); + } + + ct.grid.trigger( 'color-change' ); + + // Restore button text + generatePalette.text( text ); + }, + 'json' + ); + // Stat: 'generate-palette' + new Image().src = + document.location.protocol + + '//stats.wordpress.com/g.gif?v=wpcom-no-pv&x_customizer-colors-actions=generate-palette&baba=' + + Math.random(); + } ); + }, + + /** + * Set up Iris Color Picker + */ + irisPicker: function () { + var ct = this, + container = $( '#iris' ); + + if ( ! ( 'iris' in container ) ) { + $( '.iris-launch' ).hide(); + return null; + } + + // first bind click handlers + $( '#pick-your-nose' ).click( function ( e ) { + e.preventDefault(); + $( '#iris-container' ).show(); + + // Stat: 'color-picker' + new Image().src = + document.location.protocol + + '//stats.wordpress.com/g.gif?v=wpcom-no-pv&x_customizer-colors-actions=color-picker&baba=' + + Math.random(); + } ); + + return container.iris( { + hide: false, + width: 260, + change: function ( event, ui ) { + if ( ct.getColor( ct.activeColor ).toUpperCase() != ui.color.toString().toUpperCase() ) { + ct.setColor( ct.activeColor, ui.color.toString() ); + ct.grid.trigger( 'color-change', $( ct.activeColor ).data( 'role' ) ); + } + }, + } ); + }, + + status: function () { + var ct = this; + + for ( var i = 0, _len = ct.color.length; i < _len; i++ ) { + var $self = $( ct.color.get( i ) ); + + if ( ct.getColor( $self ) != ct.opts.defaultColors[ $self.data( 'role' ) ] ) { + return 'saved'; + } + } + + return 'default'; + }, + getColor: function ( el ) { + var color = $( el ).data( 'color' ); + + if ( typeof color === 'undefined' ) { + color = $( el ).text(); + $( el ).data( 'color', color ); + } + return color; + }, + setColor: function ( el, color ) { + $( el ).data( 'color', color ).css( 'background-color', color ); + }, + sanitizeHex: function ( hex ) { + // @todo make more betterer at sanitizing. or just call it formatter. + return '#' + hex.replace( /^#/, '' ); + }, + /** + * Color Grid & Picker + */ + colorPicker: function () { + var ct = this, + tooltip = $( '#color-tooltip' ), + other_colors, + selected_color, + label, + bubble, + width; + + // Bind to click event on each color li + ct.grid.on( 'click', 'li:not(.text-placeholder)', function () { + if ( $( this ).hasClass( 'unavailable' ) ) { + return; + } + + // If it's free mode and the theme supports background + // allow users to use color suggestions and the picker + if ( ct.opts.isFreeMode ) { + if ( ! $( this ).hasClass( 'bg' ) || ! ct.opts.themeSupport.customBackground ) { + return ct.buyNotice(); + } + } + + ct.picker.hide(); + var self = $( this ); + + // Check to see if the clicked element was already active + if ( $( this ).hasClass( 'selected' ) ) { + $( this ).removeClass( 'selected' ); + ct.picker.hide(); + } + + // Does the main work + else { + if ( ct.color.hasClass( 'selected' ) ) { + // Remove class from other items + ct.color.removeClass( 'selected' ); + } else { + ct.picker.hide(); + } + self.addClass( 'selected' ); + // Displays the color picker box + ct.showColorChangeOptions( self ); + + if ( + $( this ).hasClass( 'bg' ) === true && + $( this ).hasClass( 'bg-change-disable' ) === false + ) { + ct.backgroundChangeView.open(); + $( '#colourlovers-palettes-container' ).hide(); + } + } + } ); + + // Apply a color suggestions to main grid + ct.picker.find( '.color-suggestions' ).on( 'click', 'li', function () { + var selected = ct.grid.find( '.selected' ), + color = ct.getColor( this ); + + ct.setColor( selected.get( 0 ), color ); + // Update hex reference + $( '#hex-code' ).text( color ); + + if ( ct.iris ) { + $( '#iris-container' ).hide(); + ct.iris.iris( 'option', 'color', color ); + } + + // Trigger the color change event + ct.grid.trigger( 'color-change', selected.data( 'role' ) ); + + // Stat: 'suggestions' + new Image().src = + document.location.protocol + + '//stats.wordpress.com/g.gif?v=wpcom-no-pv&x_customizer-colors-actions=suggestions&baba=' + + Math.random(); + } ); + }, + + showColorChangeOptions: function ( activeColor ) { + var ct = this, + self = $( activeColor ), + other_colors = [], + selected_color = ct.getColor( self ); + + // ct.bgPrompt.hide(); + ct.picker.show(); + // store so Iris knows who to talk to + ct.activeColor = self.get( 0 ); + + $( '.color-suggestions li' ).hide(); + ct.suggestions.spin( 'medium' ); + + // send to Iris + if ( ct.iris ) { + ct.iris.iris( 'option', 'color', selected_color ); + } + + // Display which $cat we are editing + label = self.data( 'title' ); + if ( label !== undefined ) { + ct.reference.html( label ).show(); + } else { + ct.reference.hide(); + } + + var query_arguments = { + action: 'color_recommendations', + color: selected_color, + role: self.data( 'role' ), + limit: 14, + }; + + ct.color.each( function () { + if ( ct.getColor( this ) != selected_color ) { + query_arguments[ 'colors[' + $( this ).data( 'role' ) + ']' ] = ct.getColor( this ); + } + } ); + + $.get( + '/wp-admin/admin-ajax.php', + query_arguments, + function ( data ) { + var suggestions = $( '.color-suggestions li' ), + color; + + for ( var i = 0, _len = data.colors.length; i < _len; i++ ) { + color = '#' + data.colors[ i ]; + ct.setColor( suggestions.get( i ), color ); + suggestions.eq( i ).show(); + } + // Hide the spinner + ct.suggestions.spin( false ); + }, + 'json' + ); + }, + + showBackgroundColorChangeOptions: function () { + if ( this.opts.themeSupport.customBackground ) { + if ( 0 == this.patterns.length ) { + // Populates the patterns for the matching palette + $( '#more-patterns' ).click(); + } + } else { + // todo: clean up bgPrompt + this.bgPrompt.find( '.choose-color' ).click(); + } + }, + + showBackgroundPatternOptions: function () { + // this.bgPrompt.hide(); + this.patternPicker.show(); + this.showPatterns(); + }, + + /** + * Color Patterns + */ + colorPatterns: function () { + this.showBackgroundPatternOptions(); + }, + + /** + * Color Palettes + */ + colorPalettes: function () { + var ct = this, + palettes = {}, + color, + role; + + $( '#colourlovers-palettes' ).on( 'click', '.colour-lovers', function () { + // Don't apply non-featured palettes for free mode + if ( ct.opts.isFreeMode && ! $( this ).hasClass( 'featured' ) ) { + return ct.buyNotice(); + } + + // Populate an array with color values + // taken from clicked palette + $( this ) + .find( 'li' ) + .each( function () { + color = ct.getColor( this ); + role = $( this ).attr( 'data-role' ); + palettes[ role ] = color; + } ); + + // Apply colors to our main grid + ct.color.each( function ( index, item ) { + ct.setColor( this, palettes[ $( this ).attr( 'data-role' ) ] ); + } ); + + // Hide the hex reference on update + $( '#hex-reference' ).hide(); + + ct.grid.trigger( 'color-change' ); + // Clear the object free to be used again + palettes = {}; + + // Hide the picker if it's visible + ct.grid.find( '.selected' ).removeClass( 'selected' ); + ct.picker.hide(); + + // Track featured palettes separately + if ( $( this ).hasClass( 'featured' ) ) { + // Stat: 'featured-palettes' + new Image().src = + document.location.protocol + + '//stats.wordpress.com/g.gif?v=wpcom-no-pv&x_customizer-colors-actions=featured-palettes&baba=' + + Math.random(); + } else { + // Stat: 'palettes' + new Image().src = + document.location.protocol + + '//stats.wordpress.com/g.gif?v=wpcom-no-pv&x_customizer-colors-actions=palettes&baba=' + + Math.random(); + } + } ); + }, + /** + * Drag & Drop for main grid + */ + dragColor: function () { + var ct = this, + old, + color, + toSwap; + + if ( this.opts.isFreeMode ) { + return; + } + + // Allow colors on main grid to be draggable + ct.color + .draggable( { + revert: true, + revertDuration: 0, + zIndex: 1000, + cursor: 'move', + // On drag-stop do the color swap + stop: function ( event, ui ) { + if ( ct.color.hasClass( 'color-swap' ) ) { + // Store the original color + old = ct.getColor( this ); + toSwap = ct.grid.find( '.color-swap' ); + color = ct.getColor( toSwap.get( 0 ) ); + + ct.setColor( this, color ); + ct.setColor( toSwap.get( 0 ), old ); + // Clean the added class at the end + toSwap.removeClass( 'color-swap' ); + + ct.grid.trigger( 'color-change', toSwap.data( 'role' ) ); + + // Stat: 'drag-color' + new Image().src = + document.location.protocol + + '//stats.wordpress.com/g.gif?v=wpcom-no-pv&x_customizer-colors-actions=drag-color&baba=' + + Math.random(); + } + }, + } ) + .droppable( { + tolerance: 'pointer', + // If you drop a color on top of another, give that color a class + drop: function ( event, ui ) { + $( this ).addClass( 'color-swap' ); + }, + } ); + }, + addChangeListener: function () { + var ct = this; + + // Binds to color-change + ct.grid.on( 'color-change', function ( e, role ) { + ct.setting( ct.currentPalette() ); + + // Save the background color in the core custom background color setting too. + if ( ct.status() == 'default' ) { + api( 'background_color' ).set( '' ); + } else { + api( 'background_color' ).set( ct.getColor( ct.grid.find( '.bg' ) ) ); + } + + // If the entire palette or background color has changed, reset the background image. + if ( ! role || 'bg' == role ) { + var backgroundImage = api( 'background_image' ).get(); + if ( + backgroundImage && + ( backgroundImage.indexOf( 'colourlovers' ) != -1 || + backgroundImage === ct.opts.defaultImage || + backgroundImage === ct.origBackground ) + ) { + api( 'background_image' ).set( '' ); + } + + ct.bgPrompt + .find( '.choose-color' ) + .css( 'background-color', ct.getColor( ct.grid.find( '.bg' ) ) ); + } + + // Reset the suggested patterns. + ct.patterns = []; + ct.patternIndex = 0; + // Generates the matching patterns for the color changes + ct.showBackgroundColorChangeOptions(); + } ); + }, + currentPalette: function () { + var ct = this, + colors = {}; + + ct.grid.children().each( function ( i, val ) { + colors[ $( this ).data( 'role' ) ] = ct.getColor( this ); + } ); + + return colors; + }, + showPalettes: function ( paletteIndex ) { + var ct = this, + palette_container = $( '#colourlovers-palettes' ).html( '' ); + + // Construct the color palettes + for ( var i = paletteIndex, _len = paletteIndex + 6; i < _len; i++ ) { + var new_palette = $( '