diff --git a/composer.json b/composer.json index dd49d8b1..1740d150 100644 --- a/composer.json +++ b/composer.json @@ -1,39 +1,41 @@ { - "name": "bigbite/themer", - "description": "", - "type": "wordpress-plugin", - "version": "1.0.0", - "repositories": [ - { - "url": "git@github.com:bigbite/phpcs-config.git", - "type": "vcs" - } - ], - "require": { - "automattic/jetpack-autoloader": "^2.10.1" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "0.7.2", - "wp-coding-standards/wpcs": "2.3.0" - }, - "autoload": { - "files": [ - "inc/constants.php", - "inc/utils.php", - "inc/class-admin.php", - "inc/setup.php" - ], - "psr-4": { - "Big_Bite\\themer\\": "inc/" - }, - "classmap": [ "inc/" ] - }, - "scripts": {}, - "config": { - "preferred-install": "dist", - "allow-plugins": { - "automattic/jetpack-autoloader": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - } - } + "name": "bigbite/themer", + "description": "", + "type": "wordpress-plugin", + "version": "1.0.0", + "repositories": [ + { + "url": "git@github.com:bigbite/phpcs-config.git", + "type": "vcs" + } + ], + "require": { + "automattic/jetpack-autoloader": "^2.10.1" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "0.7.2", + "wp-coding-standards/wpcs": "3.1.0" + }, + "autoload": { + "files": [ + "inc/constants.php", + "inc/utils.php", + "inc/class-admin.php", + "inc/setup.php" + ], + "psr-4": { + "Big_Bite\\themer\\": "inc/" + }, + "classmap": [ + "inc/" + ] + }, + "scripts": {}, + "config": { + "preferred-install": "dist", + "allow-plugins": { + "automattic/jetpack-autoloader": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/composer.lock b/composer.lock index 7919f8f5..dabc20bf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f3b38fae00f385ffe87fe2b531179a7b", + "content-hash": "a3d2bb2e96d50660f0a136afb06b72b9", "packages": [ { "name": "automattic/jetpack-autoloader", @@ -145,6 +145,172 @@ }, "time": "2022-02-04T12:51:07+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": "squizlabs/php_codesniffer", "version": "3.10.1", @@ -227,30 +393,38 @@ }, { "name": "wp-coding-standards/wpcs", - "version": "2.3.0", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", - "reference": "7da1894633f168fe244afc6de00d141f27517b62" + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7da1894633f168fe244afc6de00d141f27517b62", - "reference": "7da1894633f168fe244afc6de00d141f27517b62", + "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", - "squizlabs/php_codesniffer": "^3.3.1" + "phpcsstandards/phpcsextra": "^1.2.1", + "phpcsstandards/phpcsutils": "^1.0.10", + "squizlabs/php_codesniffer": "^3.9.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || ^0.6", + "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.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpcsstandards/phpcsdevtools": "^1.2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically." + "ext-iconv": "For improved results", + "ext-mbstring": "For improved results" }, "type": "phpcodesniffer-standard", "notification-url": "https://packagist.org/downloads/", @@ -267,6 +441,7 @@ "keywords": [ "phpcs", "standards", + "static analysis", "wordpress" ], "support": { @@ -274,15 +449,21 @@ "source": "https://github.com/WordPress/WordPress-Coding-Standards", "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" }, - "time": "2020-05-13T23:57:56+00:00" + "funding": [ + { + "url": "https://opencollective.com/php_codesniffer", + "type": "custom" + } + ], + "time": "2024-03-25T16:39:00+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [], - "plugin-api-version": "2.3.0" + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.6.0" } diff --git a/inc/class-admin.php b/inc/class-admin.php index 3c7267af..b4b96280 100644 --- a/inc/class-admin.php +++ b/inc/class-admin.php @@ -23,7 +23,7 @@ public function __construct() { * * @return void */ - public function enqueue_assets() : void { + public function enqueue_assets(): void { $plugin_name = basename( THEMER_DIR ); $asset_file = include THEMER_DIR . '/build/index.asset.php'; @@ -51,7 +51,7 @@ public function enqueue_assets() : void { * * @return void */ - public function create_admin_screen() : void { + public function create_admin_screen(): void { add_theme_page( __( 'Styles Editor' ), 'Styles Editor', diff --git a/inc/class-rest-api.php b/inc/class-rest-api.php index 386c6b74..bb54bed0 100644 --- a/inc/class-rest-api.php +++ b/inc/class-rest-api.php @@ -44,6 +44,16 @@ public function register_routes() { 'themer/v1', '/export', array( + 'args' => array( + 'include' => array( + 'description' => __( 'Array of theme.json data types to be merged', 'mediapress' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + 'enum' => array( 'core', 'block', 'theme', 'user' ), + ), + ), + ), 'methods' => 'GET', 'callback' => array( $this, 'get_theme_json' ), 'permission_callback' => fn() => is_user_logged_in() && current_user_can( 'edit_theme_options' ), @@ -117,19 +127,41 @@ public function can_load_theme_json() { /** * Returns an updated theme.json with merged and flattened layers * - * @return WP_REST_Response|WP_Error + * @param WP_REST_Request $request The request object. + * @return WP_REST_Response|WP_Error The theme.json data or an error if it cannot be located. */ - public function get_theme_json(): WP_REST_Response|WP_Error { - $all_theme_json_layers = WP_Theme_JSON_Resolver::get_merged_data(); + public function get_theme_json( $request ): WP_REST_Response|WP_Error { + $include = $request->get_param( 'include' ); + $include_core_data = in_array( 'core', $include, true ); + $include_block_data = in_array( 'block', $include, true ); - if ( ! $all_theme_json_layers instanceof WP_Theme_JSON ) { - return new WP_Error( 'no_theme_json', __( 'Unable to locate existing theme.json data', 'themer' ) ); + $include_theme_data = in_array( 'theme', $include, true ); + + $include_user_data = in_array( 'user', $include, true ); + + $theme_json = new WP_Theme_JSON(); + + if ( $include_core_data ) { + $theme_json->merge( WP_Theme_JSON_Resolver::get_core_data() ); + } + + if ( $include_block_data ) { + $theme_json->merge( WP_Theme_JSON_Resolver::get_block_data() ); } - $theme_json_raw_data = new WP_Theme_JSON( $all_theme_json_layers->get_raw_data() ); - $theme_json_flattened = $theme_json_raw_data->get_data(); + if ( $include_theme_data ) { + $theme_json->merge( WP_Theme_JSON_Resolver::get_theme_data() ); + } + + if ( $include_user_data ) { + $theme_json->merge( WP_Theme_JSON_Resolver::get_user_data() ); + } + + if ( ! $theme_json instanceof WP_Theme_JSON ) { + return new WP_Error( 'no_theme_json', __( 'Unable to locate existing theme.json data', 'themer' ) ); + } - return rest_ensure_response( $theme_json_flattened ); + return rest_ensure_response( $theme_json->get_data() ); } /** diff --git a/inc/constants.php b/inc/constants.php index 54bc0ef9..82b5db70 100644 --- a/inc/constants.php +++ b/inc/constants.php @@ -8,5 +8,5 @@ namespace Big_Bite\themer; if ( ! defined( 'THEMER_DIR' ) ) { - define( 'THEMER_DIR', rtrim( \dirname( __FILE__, 2 ), '/' ) ); + define( 'THEMER_DIR', rtrim( \dirname( __DIR__, 1 ), '/' ) ); } diff --git a/inc/setup.php b/inc/setup.php index 930b6a89..70ee8601 100644 --- a/inc/setup.php +++ b/inc/setup.php @@ -12,7 +12,7 @@ * * @return void */ -function setup() : void { +function setup(): void { new Admin(); new Rest_API(); } diff --git a/plugin.php b/plugin.php index 44db5eba..bfd4ad3a 100644 --- a/plugin.php +++ b/plugin.php @@ -22,6 +22,6 @@ exit; } -require_once rtrim( \dirname( __FILE__ ) ) . '/vendor/autoload_packages.php'; +require_once rtrim( __DIR__ ) . '/vendor/autoload_packages.php'; add_action( 'plugins_loaded', __NAMESPACE__ . '\\setup', 0 ); diff --git a/src/editor/components/ButtonExport.js b/src/editor/components/ButtonExport.js index 4e730338..b63d3809 100644 --- a/src/editor/components/ButtonExport.js +++ b/src/editor/components/ButtonExport.js @@ -1,8 +1,12 @@ import { __ } from '@wordpress/i18n'; -import apiFetch from '@wordpress/api-fetch'; import { MenuItem } from '@wordpress/components'; import { download } from '@wordpress/icons'; +import { + downloadThemeJSON, + downloadCustomisations, +} from '../../utils/download-json'; + /** * Renders the button to export theme.json */ @@ -10,48 +14,32 @@ const ButtonExport = () => { const isExportSupported = window.isSecureContext && 'showSaveFilePicker' in window; - /** - * Fetch theme JSON object - */ - const fetchThemeJSON = async () => { - try { - const response = await apiFetch( { path: '/themer/v1/export' } ); - saveThemeJSON( JSON.stringify( response, null, '\t' ) ); - } catch ( error ) { - console.error( error ); // eslint-disable-line no-console -- Output of caught error - } - }; - - /** - * Save JSON blob to a file - * - * @param {Object} data theme.json data - */ - const saveThemeJSON = async ( data ) => { - const blob = new Blob( [ data ], { type: 'application/json' } ); // eslint-disable-line no-undef -- Blob available in browser environment - - const handle = await window.showSaveFilePicker( { - suggestedName: 'theme.json', - } ); - const stream = await handle.createWritable(); - - await stream.write( blob ); - await stream.close(); - }; - if ( ! isExportSupported ) { return; } return ( - - { __( 'Export theme.json', 'themer' ) } - + <> + + { __( 'Export theme.json', 'themer' ) } + + + { __( 'Export customisations', 'themer' ) } + + ); }; diff --git a/src/utils/download-json.js b/src/utils/download-json.js new file mode 100644 index 00000000..8698ce8e --- /dev/null +++ b/src/utils/download-json.js @@ -0,0 +1,33 @@ +import apiFetch from '@wordpress/api-fetch'; + +import saveFile from './save-file'; + +/** + * Fetch theme JSON object + */ +const downloadThemeJSON = async () => { + try { + const response = await apiFetch( { + path: '/themer/v1/export?include[]=block&include[]=theme&include[]=user', + } ); + saveFile( 'theme.json', JSON.stringify( response, null, '\t' ) ); + } catch ( error ) { + console.error( error ); // eslint-disable-line no-console -- Output of caught error + } +}; + +/** + * Fetch theme JSON object + */ +const downloadCustomisations = async () => { + try { + const response = await apiFetch( { + path: '/themer/v1/export?include[]=user', + } ); + saveFile( 'style.json', JSON.stringify( response, null, '\t' ) ); + } catch ( error ) { + console.error( error ); // eslint-disable-line no-console -- Output of caught error + } +}; + +export { downloadThemeJSON, downloadCustomisations }; diff --git a/src/utils/save-file.js b/src/utils/save-file.js new file mode 100644 index 00000000..d1704418 --- /dev/null +++ b/src/utils/save-file.js @@ -0,0 +1,19 @@ +/** + * Save JSON blob to a file + * + * @param {string} filename filename + * @param {Object} data theme.json data + */ +const saveFile = async ( filename, data ) => { + const blob = new Blob( [ data ], { type: 'application/json' } ); // eslint-disable-line no-undef -- Blob available in browser environment + + const handle = await window.showSaveFilePicker( { + suggestedName: filename, + } ); + const stream = await handle.createWritable(); + + await stream.write( blob ); + await stream.close(); +}; + +export default saveFile;