From 5f81c988612739432822bf5965d8e24b38e3820d Mon Sep 17 00:00:00 2001 From: mricoul Date: Mon, 2 Feb 2026 10:03:03 +0100 Subject: [PATCH 01/60] feat: initializes Blockparty Icons plugin Sets up the basic structure for the Blockparty Icons plugin, including: - Core plugin files and directory structure - Build process and scripts - Initial blocks and components - Editor configuration and assets - CI workflows for PHP and JS code quality - Standard files such as .distignore, .editorconfig and README.md This commit lays the groundwork for adding custom SVG icons in the WordPress editor and enables streamlined development workflows. --- .distignore | 37 + .editorconfig | 22 + .github/workflows/quality-js.yml | 38 + .github/workflows/quality-php.yml | 36 + .github/workflows/release.yml | 78 + .gitignore | 41 + .plugin-data | 4 + .wp-env.json | 8 + CHANGELOG.md | 11 + README.md | 79 + blockparty-icons.php | 460 ++ composer.json | 58 + composer.lock | 5323 +++++++++++++++++ grumphp.yml | 19 + includes/Icon/Collection.php | 195 + includes/Icon/CollectionCreationException.php | 13 + includes/Icon/CollectionException.php | 7 + includes/Icon/CollectionItem.php | 54 + includes/Icon/CollectionItemsFactory.php | 200 + includes/Rest/CollectionsController.php | 118 + includes/Rest/IconsController.php | 213 + package.json | 35 + phpcs.xml.dist | 62 + psalm.xml.dist | 21 + readme.txt | 0 src/components/custom-appender.js | 106 + src/components/icon-content.js | 56 + src/components/icon-modal.js | 407 ++ src/components/icon-popover.js | 77 + src/components/icon-selector.js | 40 + src/components/link-url-popover.js | 66 + src/icon/block.json | 67 + src/icon/deprecated.js | 93 + src/icon/edit.js | 444 ++ src/icon/editor.scss | 49 + src/icon/index.js | 46 + src/icon/style.scss | 48 + src/icons/block.json | 49 + src/icons/deprecated.js | 467 ++ src/icons/edit.js | 232 + src/icons/editor.scss | 155 + src/icons/index.js | 52 + src/icons/save.js | 23 + src/icons/style.scss | 37 + src/utils.js | 536 ++ views/icon-item-raw.php | 99 + views/icon-item-sprite.php | 40 + views/icon-item.php | 54 + 48 files changed, 10375 insertions(+) create mode 100644 .distignore create mode 100644 .editorconfig create mode 100644 .github/workflows/quality-js.yml create mode 100644 .github/workflows/quality-php.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 .plugin-data create mode 100644 .wp-env.json create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 blockparty-icons.php create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 grumphp.yml create mode 100644 includes/Icon/Collection.php create mode 100644 includes/Icon/CollectionCreationException.php create mode 100644 includes/Icon/CollectionException.php create mode 100644 includes/Icon/CollectionItem.php create mode 100644 includes/Icon/CollectionItemsFactory.php create mode 100644 includes/Rest/CollectionsController.php create mode 100644 includes/Rest/IconsController.php create mode 100644 package.json create mode 100644 phpcs.xml.dist create mode 100644 psalm.xml.dist create mode 100644 readme.txt create mode 100644 src/components/custom-appender.js create mode 100644 src/components/icon-content.js create mode 100644 src/components/icon-modal.js create mode 100644 src/components/icon-popover.js create mode 100644 src/components/icon-selector.js create mode 100644 src/components/link-url-popover.js create mode 100644 src/icon/block.json create mode 100644 src/icon/deprecated.js create mode 100644 src/icon/edit.js create mode 100644 src/icon/editor.scss create mode 100644 src/icon/index.js create mode 100644 src/icon/style.scss create mode 100644 src/icons/block.json create mode 100644 src/icons/deprecated.js create mode 100644 src/icons/edit.js create mode 100644 src/icons/editor.scss create mode 100644 src/icons/index.js create mode 100644 src/icons/save.js create mode 100644 src/icons/style.scss create mode 100644 src/utils.js create mode 100644 views/icon-item-raw.php create mode 100644 views/icon-item-sprite.php create mode 100644 views/icon-item.php diff --git a/.distignore b/.distignore new file mode 100644 index 0000000..5d38460 --- /dev/null +++ b/.distignore @@ -0,0 +1,37 @@ +# Directories +/.git +/.github +/.vscode +/node_modules +/src +/tests + +# Configuration files +.distignore +.editorconfig +.gitattributes +.gitignore +.plugin-data +.wp-env.json +grumphp.yml +phpcs.xml.dist +phpunit.xml.dist +psalm.xml.dist + +# Dependency management +composer.json +composer.lock +package.json +package-lock.json + +# Documentation +CHANGELOG.md +CONTRIBUTING.md +README.md + +# Development files +webpack.config.js +yarn.lock +*.log +*.map +.DS_Store diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b3257e8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +# WordPress Coding Standards +# https://make.wordpress.org/core/handbook/coding-standards/ + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = tab + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 + +[composer.json] +indent_style = space +indent_size = 2 diff --git a/.github/workflows/quality-js.yml b/.github/workflows/quality-js.yml new file mode 100644 index 0000000..77bb2ed --- /dev/null +++ b/.github/workflows/quality-js.yml @@ -0,0 +1,38 @@ +name: Javascript Quality Checks + +on: + pull_request: + paths: + - 'src/**' + push: + paths: + - 'src/**' + branches: + - main + +# Cancels all previous workflow runs for pull requests that have not completed. +concurrency: + # The concurrency group contains the workflow name and the branch name for pull requests + # or the commit hash for any other events. + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + +jobs: + checks: + name: Lint JS + runs-on: ubuntu-latest + + steps: + - name: Checkout project + uses: actions/checkout@v3 + + - name: Setup NodeJS + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + + - name: Install npm dependencies + run: npm ci + + - name: Run wp-script lint:js on src folder + run: npm run lint:js src diff --git a/.github/workflows/quality-php.yml b/.github/workflows/quality-php.yml new file mode 100644 index 0000000..c9ae3e0 --- /dev/null +++ b/.github/workflows/quality-php.yml @@ -0,0 +1,36 @@ +name: Quality PHP + +on: + pull_request: + paths: + - '**.php' + +# Cancels all previous workflow runs for pull requests that have not completed. +concurrency: + # The concurrency group contains the workflow name and the branch name for pull requests + # or the commit hash for any other events. + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + name: PHP quality + steps: + - name: Checkout project + uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + extensions: mbstring, intl #optional, setup extensions + + - name: Validate composer file + run: composer validate + + - name: Install composer dependencies + run: composer install + + - name: Run codesniffer + run: composer cs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ee09dcf --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,78 @@ +name: "Release new TAG" +on: + push: + branches: + - main + +jobs: + build-and-release: + name: "Release new TAG" + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - id: set-vars + name: "Set variable from .plugin-data file" + run: | + # Get all data from .plugin-data file + content=`cat ./.plugin-data` + # the following lines are only required for multi line json + content="${content//'%'/'%25'}" + content="${content//$'\n'/'%0A'}" + content="${content//$'\r'/'%0D'}" + # end of optional handling for multi line json + echo "::set-output name=pluginData::$content" + + - id: check-version + name: "Check version does not exists" + run: | + # Get the version from .plugin-data file. + VERSION=${{fromJson(steps.set-vars.outputs.pluginData).version}} + + echo "Get Branch tag" + if git rev-parse "$VERSION" >/dev/null 2>&1; then + echo "Tag already exists, stop now"; + exit 1; + fi + + - id: build-node + name: "Build project CSS and JS" + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'npm' + - run: npm install + - run: npm run build + + - id: build-php + name: "Build project PHP" + uses: shivammathur/setup-php@v2 + with: + php-version: 7.2 + - run: composer install --prefer-dist --no-dev -o --ignore-platform-reqs + + - id: commit-and-push + name: "Commit and push new TAG" + run: | + # Get the version from .plugin-data file. + VERSION=${{fromJson(steps.set-vars.outputs.pluginData).version}} + + echo "Copy .distignore to .gitignore" + cp .distignore .gitignore + + echo "Configure git" + git config --local user.email "$(git log --format='%ae' HEAD^!)" + git config --local user.name "$(git log --format='%an' HEAD^!)" + + echo "Creating branch" + git checkout -b release/${VERSION} + + echo "Creating tag ${VERSION}" + git add . + git add -u + git commit -m "Release version ${VERSION}" + git tag ${VERSION} + git push --tags diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce77efd --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Coverage directory used by tools like istanbul +coverage + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +!includes/Icon/ +!src/icon/ +vendor/ +build/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Output of 'npm pack' +*.tgz + +# dotenv environment variables file +.env + +# WP Env +.wp-env/ + +# Editors and OS +.idea +.DS_Store +.nvmrc +package-lock.json +phpcs.xml diff --git a/.plugin-data b/.plugin-data new file mode 100644 index 0000000..0cbfe78 --- /dev/null +++ b/.plugin-data @@ -0,0 +1,4 @@ +{ + "version": "1.0.0", + "slug": "blockparty-icons" +} diff --git a/.wp-env.json b/.wp-env.json new file mode 100644 index 0000000..cf61ef0 --- /dev/null +++ b/.wp-env.json @@ -0,0 +1,8 @@ +{ + "plugins": [ + "." + ], + "themes": [ + "./.wp-env/themes/" + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ede8496 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Blockparty Icons + +## Note about SVG + +This plugin **doesn't run any sanitization on SVGs** before using them. Only use SVGs you know are safe. SVG sanitization should be handle beforehand. + +## Changelog + +### 1.0.0 - 2026-02-02 + +- Initial release. diff --git a/README.md b/README.md new file mode 100644 index 0000000..39c8cdf --- /dev/null +++ b/README.md @@ -0,0 +1,79 @@ +# Be API — Blockparty Icons + +Blockparty Icons enhances the WordPress editor by adding an extra block. This block enables users to integrate custom SVG icons directly from their theme or a WordPress plugin. Users can choose these icons from an SVG sprite or a folder containing multiple SVG files. This feature offers enhanced flexibility for customizing content, thereby improving the design and aesthetics of WordPress pages and posts. + +## Installation + +Install this plugin by placing it in your `plugins` directory and activating it through your WordPress dashboard. Afterward, incorporate custom icons for an enhanced experience. + +## How to add custom icons? + +In your `functions.php` file, add the following code: + +```php +function register_collection() { + \Blockparty\Icons\register_icon_collection( + 'my_collection', + [ + 'label' => 'My custom icons collection', + 'type' => 'sprite', + 'source' => get_stylesheet_directory() . '/dist/icons/sprite.svg', + ] + ); +} + +add_action( 'blockparty_icons_init', 'register_collection' ); +``` + +This is an example for adding a SVG sprite as a source. If you want to add icons from a folder, change the `type` value to `folder` and the path of your `source` to `get_stylesheet_directory() . '/dist/icons/'` for example. + +## Params + +| param | description | +|-----------|---------------------------| +| `name` | Name of the collection. | +| `options` | Options array. See below. | + +## Options + +| param | description | +|-----------|---------------------------| +| `label` | Label of the collection. | +| `source` | Path to the SVG sprite file or folder containing SVG files. | +| `type` | | + +## How to develop + +- Setup the working environment : + +```bash +npm run start:env +``` + +- Compile CSS/JS on edit : + +```bash +npm start +``` + +- Activate plugin and develop on (admin/password) + +## Icons collection does not display / Clear cache + +Icons collections are stored locally in session storage for better perfomances. + +When adding a new collection, if it does not appears in the block editor, clear your browser session storage. + +## How to generate the languages + +To generate the pot file + +```bash +npm run make:pot +``` + +To generate the JSON files for Gutenberg from the po files + +```bash +npm run make:json +``` diff --git a/blockparty-icons.php b/blockparty-icons.php new file mode 100644 index 0000000..cf2519a --- /dev/null +++ b/blockparty-icons.php @@ -0,0 +1,460 @@ + __NAMESPACE__ . '\\render_callback' ] ); + + // Load translations for JS + wp_set_script_translations( 'blockparty-icons-editor-script', 'blockparty-icons', BLOCKPARTY_ICONS_DIR . '/languages' ); + wp_set_script_translations( 'blockparty-icon-editor-script', 'blockparty-icons', BLOCKPARTY_ICONS_DIR . '/languages' ); + + // Expose sprite hashes to editor for cache busting in icon selector/modal. + $sprite_hashes = get_sprite_hashes_for_script(); + if ( ! empty( $sprite_hashes ) ) { + $config = [ 'spriteHashes' => $sprite_hashes ]; + wp_localize_script( 'blockparty-icons-editor-script', 'blockpartyIconsConfig', $config ); + wp_localize_script( 'blockparty-icon-editor-script', 'blockpartyIconsConfig', $config ); + } + + do_action( 'blockparty_icons_init' ); +} + +add_action( 'init', __NAMESPACE__ . '\\init' ); + +function rest() { + $endpoint = new CollectionsController(); + $endpoint->register_routes(); + + $collections = get_icon_collections(); + foreach ( $collections as $collection ) { + $endpoint = new IconsController( $collection ); + $endpoint->register_routes(); + } + unset( $endpoint ); +} + +add_action( 'rest_api_init', __NAMESPACE__ . '\\rest' ); + +/** + * Register new icon collection. + * + * @param string|Collection $name + * @param array $args + * + * @return Collection|bool + */ +function register_icon_collection( $name, array $args = [] ) { + /* @var Collection[] */ + global $blockparty_icon_collections; + + if ( $name instanceof Collection ) { + if ( icon_collection_exists( $name->name() ) ) { + return false; + } + + $blockparty_icon_collections[ $name->name() ] = $name; + + return $name; + } + + if ( icon_collection_exists( $name ) ) { + return false; + } + + $args = (array) wp_parse_args( + $args, + [ + 'label' => $name, + 'type' => '', + 'source' => '', + 'icon_map' => [], + ] + ); + + switch ( $args['type'] ) { + case 'folder': + try { + $collection = Collection::from_folder( $name, $args['source'], $args ); + } catch ( CollectionCreationException $e ) { + $collection = false; + } + break; + case 'sprite': + try { + $collection = Collection::from_sprite( $name, $args['source'], $args ); + } catch ( CollectionCreationException $e ) { + $collection = false; + } + break; + /*case 'file': + try { + $collection = new Collection( $name, $args['label'] ?? null ); + $collection->add( CollectionItem::from_file( $args['source'], [] ) ); + } catch ( CollectionCreationException $e ) { + $collection = false; + } + break;*/ + default: + $collection = new Collection( $name, $args['label'] ?? null ); + } + + $blockparty_icon_collections[ $name ] = $collection; + + return $blockparty_icon_collections[ $name ]; +} + +/** + * Add icons to an existing collection. + * + * @param string $collection_name + * @param string $type + * @param string $source + * @param array $args + * + * @return bool + */ +function add_icons( string $collection_name, string $type, string $source, array $args = [] ): bool { + if ( ! icon_collection_exists( $collection_name ) ) { + return false; + } + + $args = (array) wp_parse_args( + $args, + [ + 'label' => '', + 'icon_map' => [], + ] + ); + + $items = []; + switch ( $type ) { + case 'folder': + try { + $items = CollectionItemsFactory::from_folder( $source, $args['icon_map'] ); + } catch ( CollectionCreationException $e ) { + return false; + } + break; + case 'sprite': + try { + $items = CollectionItemsFactory::from_sprite( $source, $args['icon_map'] ); + } catch ( CollectionCreationException $e ) { + return false; + } + break; + case 'file': + try { + $items = CollectionItemsFactory::from_file( $source, $args ); + } catch ( CollectionCreationException $e ) { + return false; + } + break; + default: + return false; + } + + $collection = get_icon_collection( $collection_name ); + array_map( [ $collection, 'add' ], $items ); + + return true; +} + +/** + * Check if a collection exist with the provided name. + * + * @param string $name + * + * @return bool + */ +function icon_collection_exists( string $name ): bool { + global $blockparty_icon_collections; + + return isset( $blockparty_icon_collections[ $name ] ); +} + +/** + * Get a collection by name. + * + * @param string $name + * + * @return Collection|null + */ +function get_icon_collection( string $name ): ?Collection { + global $blockparty_icon_collections; + + return $blockparty_icon_collections[ $name ] ?? null; +} + +/** + * Get all collections. + * + * @return Collection[] + */ +function get_icon_collections(): array { + global $blockparty_icon_collections; + + return $blockparty_icon_collections ?? []; +} + +/** + * Allow additional HTML attributes in KSES. + * + * Without this, the attributes are stripped from the post content for users without the `unfiltered_html` capability. + * This break the block in the editor since the saved content doesn't match the output of the block's `save` function. + * + * @param array $tags + * @param string $context + * + * @return array + */ +function allow_aria_attributes( $tags, $context ) { + if ( 'post' !== $context ) { + return $tags; + } + + $tags['svg'] = [ + 'aria-hidden' => true, + 'class' => true, + 'fill' => true, + 'focusable' => true, + 'height' => true, + 'style' => true, + 'viewbox' => true, + 'width' => true, + 'version' => true, + 'xmlns' => true, + ]; + + $tags['path'] = [ + 'd' => true, + 'clip-path' => true, + 'clip-rule' => true, + 'color' => true, + 'cursor' => true, + 'fill' => true, + 'fill-opacity' => true, + 'fill-rule' => true, + 'filter' => true, + 'mask' => true, + 'opacity' => true, + 'stroke' => true, + 'transform' => true, + ]; + + $tags['use'] = [ + 'href' => true, + 'xmlns:xlink' => true, + 'xlink:href' => true, + ]; + + return $tags; +} + +add_filter( 'wp_kses_allowed_html', __NAMESPACE__ . '\\allow_aria_attributes', 10, 2 ); + +/** + * Allow additional CSS attributes in the `style` attribute. + * + * Without this, the CSS attributes are stripped from the `style` attribute for users without the `unfiltered_html` capability. + * This break the block in the editor since the saved content doesn't match the output of the block's `save` function. + * + * @param array $attr + * + * @return array + */ +function allow_css_attributes( $attr ) { + $attr[] = 'display'; + + return $attr; +} + +add_filter( 'safe_style_css', __NAMESPACE__ . '\\allow_css_attributes' ); + +/** + * Get sprite hashes array for use in wp_localize_script (editor icon selector). + * + * @return array Map of sprite path keys (e.g. "icons/social.svg") to hash values. + */ +function get_sprite_hashes_for_script(): array { + $hashes_file = get_sprite_hashes_file_path(); + if ( '' === $hashes_file || ! is_readable( $hashes_file ) ) { + return []; + } + + $json = @file_get_contents( $hashes_file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + if ( false === $json ) { + return []; + } + + $hashes = json_decode( $json, true ); + return is_array( $hashes ) ? $hashes : []; +} + +/** + * Get the path to the sprite hashes JSON file. + * + * @return string Path to sprite-hashes.json (empty if disabled or not set). + */ +function get_sprite_hashes_file_path(): string { + $default = ''; + if ( function_exists( 'get_theme_file_path' ) ) { + $default = get_theme_file_path( 'dist/sprite-hashes.json' ); + } + + /** + * Filter the path to the sprite hashes JSON file. + * + * @param string|false $path Absolute path to sprite-hashes.json. Return false or empty string to disable cache busting. + */ + $path = apply_filters( 'blockparty_icons_sprite_hashes_file', $default ); + if ( false === $path || '' === $path ) { + return ''; + } + return (string) $path; +} + +/** + * Append cache-busting hash to sprite URL when sprite-hashes.json exists and icon type is sprite. + * + * @param string $sprite_url Full sprite URL (e.g. https://example.com/.../dist/icons/social.svg#icon-id). + * @return string URL with ?v=hash query if hash found, unchanged otherwise. + */ +function get_sprite_url_with_hash( string $sprite_url ): string { + $hashes_file = get_sprite_hashes_file_path(); + if ( '' === $hashes_file || ! is_readable( $hashes_file ) ) { + return $sprite_url; + } + + // Local file path from filter; file_get_contents is appropriate here. + $json = @file_get_contents( $hashes_file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + if ( false === $json ) { + return $sprite_url; + } + + $hashes = json_decode( $json, true ); + if ( ! is_array( $hashes ) ) { + return $sprite_url; + } + + $parsed = wp_parse_url( $sprite_url ); + $path = isset( $parsed['path'] ) ? $parsed['path'] : ''; + $fragment = isset( $parsed['fragment'] ) ? '#' . $parsed['fragment'] : ''; + + // Match JSON keys like "icons/social.svg" - path segment after "dist/". + if ( preg_match( '#/dist/(.+)$#', $path, $m ) ) { + $key = $m[1]; + } else { + // Fallback: last two path segments (e.g. icons/social.svg). + $parts = array_filter( explode( '/', trim( $path, '/' ) ) ); + $key = implode( '/', array_slice( $parts, -2, 2 ) ); + } + + if ( empty( $hashes[ $key ] ) ) { + return $sprite_url; + } + + $hash = $hashes[ $key ]; + $base = $fragment ? strstr( $sprite_url, '#', true ) : $sprite_url; + $with_v = add_query_arg( 'v', $hash, $base ); + + return $with_v . $fragment; +} + +/** + * Render the icon item block. + * + * @param array $attributes + * @param string $content + * @param WP_Block $block + * + * @return string + */ +function render_callback( $attributes, $content, $block ) { + /** + * Filter block's template slug. + * + * Template must be located in the theme as it will be loaded via `get_template_part`. + * + * @param string $template_slug block's template slug + * @param array $attributes block's attributes + * @param \WP_Block $block block's \WP_Block instance + */ + $template_slug = apply_filters( 'blockparty_icons_template_slug', 'components/gutenberg/dynamic-block', $attributes, $block ); + + /** + * Filter block's template name. + * + * @param string $template_name block's template name + * @param string $template_slug block's template slug + * @param array $attributes block's attributes + * @param \WP_Block $block block's \WP_Block instance + */ + $template_name = apply_filters( 'blockparty_icons_template_name', '', $template_slug, $attributes, $block ); + + /** + * Filter block's template args. + * + * @param string $template_name block's template name + * @param string $template_slug block's template slug + * @param array $attributes block's attributes + * @param \WP_Block $block block's \WP_Block instance + */ + $template_args = apply_filters( + 'blockparty_icons_template_args', + [ + 'block_attributes' => $attributes, + 'block_wrapper_attributes' => get_block_wrapper_attributes(), + 'is_preview' => isset( $_GET['is_block_editor'] ), //phpcs:ignore WordPress.Security.NonceVerification.Recommended + ], + $template_slug, + $template_name, + $attributes, + $block + ); + + // Otherwise use default template. + ob_start(); + load_template( plugin_dir_path( __FILE__ ) . 'views/icon-item.php', false, $template_args ); + + return ob_get_clean(); +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..0b2e40b --- /dev/null +++ b/composer.json @@ -0,0 +1,58 @@ +{ + "name": "beapi/blockparty-icons", + "description": "Provides blocks in WordPress editor to add custom SVG icons from your theme.", + "type": "wordpress-plugin", + "license": "GPL-3.0-or-later", + "authors": [ + { + "name": "BeAPI", + "email": "technique@beapi.fr" + } + ], + "config": { + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "composer/installers": true, + "phpro/grumphp-shim": true + }, + "platform": { + "php": "8.1" + } + }, + "require": { + "php": "^8.1 | ^8.2 | ^8.3 | ^8.4", + "ext-json": "*", + "composer/installers": "^1.0 || ^2.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "humanmade/psalm-plugin-wordpress": "^3.0", + "overtrue/phplint": "^9.1", + "php-parallel-lint/php-parallel-lint": "^1.3", + "php-stubs/wordpress-seo-stubs": "^20.5", + "phpcompatibility/phpcompatibility-wp": "^2.1", + "phpro/grumphp-shim": "^1.5", + "roave/security-advisories": "dev-latest", + "roots/wordpress-no-content": "^6.0", + "vimeo/psalm": "^5.20", + "wp-coding-standards/wpcs": "^3.0" + }, + "autoload": { + "psr-4": { + "Blockparty\\Icons\\": "includes/" + } + }, + "scripts": { + "cs": "./vendor/bin/phpcs", + "cb": "./vendor/bin/phpcbf", + "psalm": "./vendor/bin/psalm" + }, + "scripts-descriptions": { + "cs": "Run PHP CodeSniffer on codebase using custom ruleset.", + "cb": "Run PHP Code Beautifier and Fixer on codebase using custom ruleset.", + "psalm": "Run psalm on codebase using custom ruleset." + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..3c6cab6 --- /dev/null +++ b/composer.lock @@ -0,0 +1,5323 @@ +{ + "_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": "133b9c8930af2e5def4599caf430e5ea", + "packages": [ + { + "name": "composer/installers", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/composer/installers.git", + "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/installers/zipball/12fb2dfe5e16183de69e784a7b84046c43d97e8e", + "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "composer/composer": "^1.10.27 || ^2.7", + "composer/semver": "^1.7.2 || ^3.4.0", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-phpunit": "^1", + "symfony/phpunit-bridge": "^7.1.1", + "symfony/process": "^5 || ^6 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "Composer\\Installers\\Plugin", + "branch-alias": { + "dev-main": "2.x-dev" + }, + "plugin-modifies-install-path": true + }, + "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": [ + "Dolibarr", + "Eliasis", + "Hurad", + "ImageCMS", + "Kanboard", + "Lan Management System", + "MODX Evo", + "MantisBT", + "Mautic", + "Maya", + "OXID", + "Plentymarkets", + "Porto", + "RadPHP", + "SMF", + "Starbug", + "Thelia", + "Whmcs", + "WolfCMS", + "agl", + "annotatecms", + "attogram", + "bitrix", + "cakephp", + "chef", + "cockpit", + "codeigniter", + "concrete5", + "concreteCMS", + "croogo", + "dokuwiki", + "drupal", + "eZ Platform", + "elgg", + "expressionengine", + "fuelphp", + "grav", + "installer", + "itop", + "known", + "kohana", + "laravel", + "lavalite", + "lithium", + "magento", + "majima", + "mako", + "matomo", + "mediawiki", + "miaoxing", + "modulework", + "modx", + "moodle", + "osclass", + "pantheon", + "phpbb", + "piwik", + "ppi", + "processwire", + "puppet", + "pxcms", + "reindex", + "roundcube", + "shopware", + "silverstripe", + "sydes", + "sylius", + "tastyigniter", + "wordpress", + "yawik", + "zend", + "zikula" + ], + "support": { + "issues": "https://github.com/composer/installers/issues", + "source": "https://github.com/composer/installers/tree/v2.3.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": "2024-06-24T20:46:46+00:00" + } + ], + "packages-dev": [ + { + "name": "amphp/amp", + "version": "v2.6.5", + "source": { + "type": "git", + "url": "https://github.com/amphp/amp.git", + "reference": "d7dda98dae26e56f3f6fcfbf1c1f819c9a993207" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/amp/zipball/d7dda98dae26e56f3f6fcfbf1c1f819c9a993207", + "reference": "d7dda98dae26e56f3f6fcfbf1c1f819c9a993207", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^7 | ^8 | ^9", + "react/promise": "^2", + "vimeo/psalm": "^3.12" + }, + "type": "library", + "autoload": { + "files": [ + "lib/functions.php", + "lib/Internal/functions.php" + ], + "psr-4": { + "Amp\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "https://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v2.6.5" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-09-03T19:41:28+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v1.8.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/4f0e968ba3798a423730f567b1b50d3441c16ddc", + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc", + "shasum": "" + }, + "require": { + "amphp/amp": "^2", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.4", + "friendsofphp/php-cs-fixer": "^2.3", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6 || ^7 || ^8", + "psalm/phar": "^3.11.4" + }, + "type": "library", + "autoload": { + "files": [ + "lib/functions.php" + ], + "psr-4": { + "Amp\\ByteStream\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "https://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v1.8.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-13T18:00:56+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "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": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "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": "2024-05-06T16:37:16+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "845eb62303d2ca9b289ef216356568ccc075ffd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/845eb62303d2ca9b289ef216356568ccc075ffd1", + "reference": "845eb62303d2ca9b289ef216356568ccc075ffd1", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.2", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "^2.2", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcompatibility/php-compatibility": "^9.0 || ^10.0.0@dev", + "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": "opensource@frenck.dev", + "homepage": "https://frenck.dev", + "role": "Open source developer" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "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", + "security": "https://github.com/PHPCSStandards/composer-installer/security/policy", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-11-11T04:32:07+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" + }, + "time": "2025-04-07T20:06:18+00:00" + }, + { + "name": "felixfbecker/advanced-json-rpc", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "php": "^7.1 || ^8.0", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "A more advanced JSONRPC implementation", + "support": { + "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", + "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" + }, + "time": "2021-06-11T22:34:44+00:00" + }, + { + "name": "felixfbecker/language-server-protocol", + "version": "v1.5.3", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/a9e113dbc7d849e35b8776da39edaf4313b7b6c9", + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1", + "vimeo/psalm": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "PHP classes for the Language Server Protocol", + "keywords": [ + "language", + "microsoft", + "php", + "server" + ], + "support": { + "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.3" + }, + "time": "2024-04-30T00:40:11+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" + }, + { + "name": "humanmade/psalm-plugin-wordpress", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/psalm/psalm-plugin-wordpress.git", + "reference": "3f4689ad5264eee7b37121053cec810a3754f7e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/psalm/psalm-plugin-wordpress/zipball/3f4689ad5264eee7b37121053cec810a3754f7e4", + "reference": "3f4689ad5264eee7b37121053cec810a3754f7e4", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "php-stubs/wordpress-globals": "^0.2.0", + "php-stubs/wordpress-stubs": "^6.0", + "php-stubs/wp-cli-stubs": "^2.7", + "vimeo/psalm": "^5 || ^6", + "wp-hooks/wordpress-core": "^1.3.0" + }, + "require-dev": { + "humanmade/coding-standards": "^1.2", + "phpunit/phpunit": "^9.0", + "psalm/plugin-phpunit": "^0.18.4" + }, + "type": "psalm-plugin", + "extra": { + "psalm": { + "pluginClass": "PsalmWordPress\\Plugin" + } + }, + "autoload": { + "psr-4": { + "PsalmWordPress\\": [ + "." + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "kkmuffme", + "role": "Maintainer" + }, + { + "name": "Joe Hoyle", + "role": "Creator" + } + ], + "description": "WordPress stubs and plugin for Psalm static analysis.", + "support": { + "issues": "https://github.com/psalm/psalm-plugin-wordpress/issues", + "source": "https://github.com/psalm/psalm-plugin-wordpress" + }, + "time": "2024-04-01T10:36:11+00:00" + }, + { + "name": "netresearch/jsonmapper", + "version": "v4.5.0", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8e76efb98ee8b6afc54687045e1b8dba55ac76e5", + "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v4.5.0" + }, + "time": "2024-09-08T10:13:13+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.19.5", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "51bd93cc741b7fc3d63d20b6bdcd99fdaa359837" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/51bd93cc741b7fc3d63d20b6bdcd99fdaa359837", + "reference": "51bd93cc741b7fc3d63d20b6bdcd99fdaa359837", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.1" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "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/v4.19.5" + }, + "time": "2025-12-06T11:45:25+00:00" + }, + { + "name": "overtrue/phplint", + "version": "9.6.3", + "source": { + "type": "git", + "url": "https://github.com/overtrue/phplint.git", + "reference": "b0ec1d07b37a37e7fc872c8bdbddacadcecbe047" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/overtrue/phplint/zipball/b0ec1d07b37a37e7fc872c8bdbddacadcecbe047", + "reference": "b0ec1d07b37a37e7fc872c8bdbddacadcecbe047", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0", + "ext-dom": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": "^8.1", + "symfony/cache": "^6.4 || ^7.0", + "symfony/console": "^6.4 || ^7.0", + "symfony/event-dispatcher": "^6.4 || ^7.0", + "symfony/finder": "^6.4 || ^7.0", + "symfony/options-resolver": "^6.4 || ^7.0", + "symfony/process": "^6.4 || ^7.0", + "symfony/yaml": "^6.4 || ^7.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4", + "brainmaestro/composer-git-hooks": "^3.0.0", + "jetbrains/phpstorm-stubs": "^2024.1", + "php-parallel-lint/php-console-highlighter": "^1.0" + }, + "bin": [ + "bin/phplint" + ], + "type": "library", + "extra": { + "hooks": { + "pre-commit": [ + "composer style:fix", + "composer code:check" + ] + }, + "bamarni-bin": { + "bin-links": true, + "forward-command": true, + "target-directory": "vendor-bin" + }, + "branch-alias": { + "dev-main": "9.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Overtrue\\PHPLint\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + }, + { + "name": "Laurent Laville", + "homepage": "https://github.com/llaville" + } + ], + "description": "`phplint` is a tool that can speed up linting of php files by running several lint processes at once.", + "keywords": [ + "check", + "lint", + "phplint", + "static analysis", + "syntax" + ], + "support": { + "issues": "https://github.com/overtrue/phplint/issues", + "source": "https://github.com/overtrue/phplint/tree/9.6.3" + }, + "funding": [ + { + "url": "https://github.com/overtrue", + "type": "github" + } + ], + "time": "2025-11-27T13:49:59+00:00" + }, + { + "name": "php-parallel-lint/php-parallel-lint", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/php-parallel-lint/PHP-Parallel-Lint.git", + "reference": "6db563514f27e19595a19f45a4bf757b6401194e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/6db563514f27e19595a19f45a4bf757b6401194e", + "reference": "6db563514f27e19595a19f45a4bf757b6401194e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=5.3.0" + }, + "replace": { + "grogy/php-parallel-lint": "*", + "jakub-onderka/php-parallel-lint": "*" + }, + "require-dev": { + "nette/tester": "^1.3 || ^2.0", + "php-parallel-lint/php-console-highlighter": "0.* || ^1.0", + "squizlabs/php_codesniffer": "^3.6" + }, + "suggest": { + "php-parallel-lint/php-console-highlighter": "Highlight syntax in code snippet" + }, + "bin": [ + "parallel-lint" + ], + "type": "library", + "autoload": { + "classmap": [ + "./src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "ahoj@jakubonderka.cz" + } + ], + "description": "This tool checks the syntax of PHP files about 20x faster than serial check.", + "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint", + "keywords": [ + "lint", + "static analysis" + ], + "support": { + "issues": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/issues", + "source": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/tree/v1.4.0" + }, + "time": "2024-03-27T12:14:49+00:00" + }, + { + "name": "php-stubs/wordpress-globals", + "version": "v0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-stubs/wordpress-globals.git", + "reference": "748a1fb2ae8fda94844bd0545935095dbf404b32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-stubs/wordpress-globals/zipball/748a1fb2ae8fda94844bd0545935095dbf404b32", + "reference": "748a1fb2ae8fda94844bd0545935095dbf404b32", + "shasum": "" + }, + "require-dev": { + "php": "~7.1" + }, + "suggest": { + "php-stubs/wordpress-stubs": "Up-to-date WordPress function and class declaration stubs", + "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Global variables and global constants from WordPress core.", + "homepage": "https://github.com/php-stubs/wordpress-globals", + "keywords": [ + "PHPStan", + "constants", + "globals", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/php-stubs/wordpress-globals/issues", + "source": "https://github.com/php-stubs/wordpress-globals/tree/master" + }, + "time": "2020-01-13T06:12:59+00:00" + }, + { + "name": "php-stubs/wordpress-seo-stubs", + "version": "v20.5.1", + "source": { + "type": "git", + "url": "https://github.com/php-stubs/wordpress-seo-stubs.git", + "reference": "67c69a1a6dfeabd600498b3f8239c5b531dc8156" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-stubs/wordpress-seo-stubs/zipball/67c69a1a6dfeabd600498b3f8239c5b531dc8156", + "reference": "67c69a1a6dfeabd600498b3f8239c5b531dc8156", + "shasum": "" + }, + "require": { + "php-stubs/wordpress-stubs": "^5.3.0 || ^6.0" + }, + "require-dev": { + "php": "~7.1", + "php-stubs/generator": "^0.8" + }, + "suggest": { + "symfony/polyfill-php73": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Yoas SEO function and class declaration stubs for static analysis.", + "homepage": "https://github.com/php-stubs/wordpress-seo-stubs", + "keywords": [ + "PHPStan", + "seo", + "static analysis", + "wordpress", + "yoast" + ], + "support": { + "issues": "https://github.com/php-stubs/wordpress-seo-stubs/issues", + "source": "https://github.com/php-stubs/wordpress-seo-stubs/tree/v20.5.1" + }, + "time": "2023-04-21T20:41:07+00:00" + }, + { + "name": "php-stubs/wordpress-stubs", + "version": "v6.9.0", + "source": { + "type": "git", + "url": "https://github.com/php-stubs/wordpress-stubs.git", + "reference": "5171cb6650e6c583a96943fd6ea0dfa3e1089a8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/5171cb6650e6c583a96943fd6ea0dfa3e1089a8a", + "reference": "5171cb6650e6c583a96943fd6ea0dfa3e1089a8a", + "shasum": "" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "5.6.1" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "nikic/php-parser": "^5.5", + "php": "^7.4 || ^8.0", + "php-stubs/generator": "^0.8.3", + "phpdocumentor/reflection-docblock": "^5.4.1", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^9.5", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.1.1", + "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" + }, + "suggest": { + "paragonie/sodium_compat": "Pure PHP implementation of libsodium", + "symfony/polyfill-php80": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WordPress function and class declaration stubs for static analysis.", + "homepage": "https://github.com/php-stubs/wordpress-stubs", + "keywords": [ + "PHPStan", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/php-stubs/wordpress-stubs/issues", + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.9.0" + }, + "time": "2025-12-03T23:06:24+00:00" + }, + { + "name": "php-stubs/wp-cli-stubs", + "version": "v2.12.0", + "source": { + "type": "git", + "url": "https://github.com/php-stubs/wp-cli-stubs.git", + "reference": "af16401e299a3fd2229bd0fa9a037638a4174a9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-stubs/wp-cli-stubs/zipball/af16401e299a3fd2229bd0fa9a037638a4174a9d", + "reference": "af16401e299a3fd2229bd0fa9a037638a4174a9d", + "shasum": "" + }, + "require": { + "php-stubs/wordpress-stubs": "^4.7 || ^5.0 || ^6.0" + }, + "require-dev": { + "php": "~7.3 || ~8.0", + "php-stubs/generator": "^0.8.0" + }, + "suggest": { + "symfony/polyfill-php73": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WP-CLI function and class declaration stubs for static analysis.", + "homepage": "https://github.com/php-stubs/wp-cli-stubs", + "keywords": [ + "PHPStan", + "static analysis", + "wordpress", + "wp-cli" + ], + "support": { + "issues": "https://github.com/php-stubs/wp-cli-stubs/issues", + "source": "https://github.com/php-stubs/wp-cli-stubs/tree/v2.12.0" + }, + "time": "2025-06-10T09:58:05+00:00" + }, + { + "name": "phpcompatibility/php-compatibility", + "version": "9.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "homepage": "https://github.com/wimg", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibility" + }, + "time": "2019-12-27T09:44:58+00:00" + }, + { + "name": "phpcompatibility/phpcompatibility-paragonie", + "version": "1.3.4", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", + "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/244d7b04fc4bc2117c15f5abe23eb933b5f02bbf", + "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "paragonie/random_compat": "dev-master", + "paragonie/sodium_compat": "dev-master" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "paragonie", + "phpcs", + "polyfill", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/security/policy", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" + }, + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcompatibility", + "type": "thanks_dev" + } + ], + "time": "2025-09-19T17:43:28+00:00" + }, + { + "name": "phpcompatibility/phpcompatibility-wp", + "version": "2.1.8", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", + "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/7c8d18b4d90dac9e86b0869a608fa09158e168fa", + "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0", + "phpcompatibility/phpcompatibility-paragonie": "^1.0", + "squizlabs/php_codesniffer": "^3.3" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "phpcs", + "standards", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityWP/security/policy", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" + }, + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcompatibility", + "type": "thanks_dev" + } + ], + "time": "2025-10-18T00:05:59+00:00" + }, + { + "name": "phpcsstandards/phpcsextra", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", + "reference": "b598aa890815b8df16363271b659d73280129101" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/b598aa890815b8df16363271b659d73280129101", + "reference": "b598aa890815b8df16363271b659d73280129101", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpcsstandards/phpcsutils": "^1.2.0", + "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcsstandards/phpcsdevcs": "^1.2.0", + "phpcsstandards/phpcsdevtools": "^1.2.1", + "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "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" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-11-12T23:06:57+00:00" + }, + { + "name": "phpcsstandards/phpcsutils", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", + "reference": "c216317e96c8b3f5932808f9b0f1f7a14e3bbf55" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/c216317e96c8b3f5932808f9b0f1f7a14e3bbf55", + "reference": "c216317e96c8b3f5932808f9b0f1f7a14e3bbf55", + "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.13.5 || ^4.0.1" + }, + "require-dev": { + "ext-filter": "*", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcsstandards/phpcsdevcs": "^1.2.0", + "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.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", + "phpcs4", + "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" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-12-08T14:27:58+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.6.6", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/5cee1d3dfc2d2aa6599834520911d246f656bcb8", + "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "webmozart/assert": "^1.9.1 || ^2" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.6" + }, + "time": "2025-12-22T21:13:58+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.12.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" + }, + "time": "2025-11-21T15:09:14+00:00" + }, + { + "name": "phpro/grumphp-shim", + "version": "v1.16.0", + "source": { + "type": "git", + "url": "https://github.com/phpro/grumphp-shim.git", + "reference": "98f7d27631785b48270af88e33abcc591ae022fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpro/grumphp-shim/zipball/98f7d27631785b48270af88e33abcc591ae022fc", + "reference": "98f7d27631785b48270af88e33abcc591ae022fc", + "shasum": "" + }, + "require": { + "composer-plugin-api": "~2.0", + "ext-json": "*", + "php": "^8.0" + }, + "replace": { + "phpro/grumphp": "self.version" + }, + "require-dev": { + "humbug/box": "^3.16" + }, + "bin": [ + "grumphp", + "grumphp.phar" + ], + "type": "composer-plugin", + "extra": { + "class": "GrumPHP\\Composer\\GrumPHPPlugin" + }, + "autoload": { + "psr-4": { + "GrumPHP\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Toon Verwerft", + "email": "toon.verwerft@phpro.be" + }, + { + "name": "Community", + "homepage": "https://github.com/phpro/grumphp/graphs/contributors" + } + ], + "description": "GrumPHP Phar distribution", + "support": { + "issues": "https://github.com/phpro/grumphp-shim/issues", + "source": "https://github.com/phpro/grumphp-shim/tree/v1.16.0" + }, + "time": "2023-04-27T11:06:59+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" + }, + "time": "2026-01-25T14:56:51+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "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/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "roave/security-advisories", + "version": "dev-latest", + "source": { + "type": "git", + "url": "https://github.com/Roave/SecurityAdvisories.git", + "reference": "8457f2008fc6396be788162c4e04228028306534" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/8457f2008fc6396be788162c4e04228028306534", + "reference": "8457f2008fc6396be788162c4e04228028306534", + "shasum": "" + }, + "conflict": { + "3f/pygmentize": "<1.2", + "adaptcms/adaptcms": "<=1.3", + "admidio/admidio": "<=4.3.16", + "adodb/adodb-php": "<=5.22.9", + "aheinze/cockpit": "<2.2", + "aimeos/ai-admin-graphql": ">=2022.04.1,<2022.10.10|>=2023.04.1,<2023.10.6|>=2024.04.1,<2024.07.2", + "aimeos/ai-admin-jsonadm": "<2020.10.13|>=2021.04.1,<2021.10.6|>=2022.04.1,<2022.10.3|>=2023.04.1,<2023.10.4|==2024.04.1", + "aimeos/ai-client-html": ">=2020.04.1,<2020.10.27|>=2021.04.1,<2021.10.22|>=2022.04.1,<2022.10.13|>=2023.04.1,<2023.10.15|>=2024.04.1,<2024.04.7", + "aimeos/ai-cms-grapesjs": ">=2021.04.1,<2021.10.8|>=2022.04.1,<2022.10.9|>=2023.04.1,<2023.10.15|>=2024.04.1,<2024.10.8|>=2025.04.1,<2025.10.2", + "aimeos/ai-controller-frontend": "<2020.10.15|>=2021.04.1,<2021.10.8|>=2022.04.1,<2022.10.8|>=2023.04.1,<2023.10.9|==2024.04.1", + "aimeos/aimeos-core": ">=2022.04.1,<2022.10.17|>=2023.04.1,<2023.10.17|>=2024.04.1,<2024.04.7", + "aimeos/aimeos-laravel": "==2021.10", + "aimeos/aimeos-typo3": "<19.10.12|>=20,<20.10.5", + "airesvsg/acf-to-rest-api": "<=3.1", + "akaunting/akaunting": "<2.1.13", + "akeneo/pim-community-dev": "<5.0.119|>=6,<6.0.53", + "alextselegidis/easyappointments": "<=1.5.2", + "alexusmai/laravel-file-manager": "<=3.3.1", + "algolia/algoliasearch-magento-2": "<=3.16.1|>=3.17.0.0-beta1,<=3.17.1", + "alt-design/alt-redirect": "<1.6.4", + "altcha-org/altcha": "<1.3.1", + "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", + "amazing/media2click": ">=1,<1.3.3", + "ameos/ameos_tarteaucitron": "<1.2.23", + "amphp/artax": "<1.0.6|>=2,<2.0.6", + "amphp/http": "<=1.7.2|>=2,<=2.1", + "amphp/http-client": ">=4,<4.4", + "anchorcms/anchor-cms": "<=0.12.7", + "andreapollastri/cipi": "<=3.1.15", + "andrewhaine/silverstripe-form-capture": ">=0.2,<=0.2.3|>=1,<1.0.2|>=2,<2.2.5", + "aoe/restler": "<1.7.1", + "apache-solr-for-typo3/solr": "<2.8.3", + "apereo/phpcas": "<1.6", + "api-platform/core": "<3.4.17|>=4,<4.0.22|>=4.1,<4.1.5", + "api-platform/graphql": "<3.4.17|>=4,<4.0.22|>=4.1,<4.1.5", + "appwrite/server-ce": "<=1.2.1", + "arc/web": "<3", + "area17/twill": "<1.2.5|>=2,<2.5.3", + "artesaos/seotools": "<0.17.2", + "asymmetricrypt/asymmetricrypt": "<9.9.99", + "athlon1600/php-proxy": "<=5.1", + "athlon1600/php-proxy-app": "<=3", + "athlon1600/youtube-downloader": "<=4", + "austintoddj/canvas": "<=3.4.2", + "auth0/auth0-php": ">=3.3,<8.18", + "auth0/login": "<7.20", + "auth0/symfony": "<=5.5", + "auth0/wordpress": "<=5.4", + "automad/automad": "<2.0.0.0-alpha5", + "automattic/jetpack": "<9.8", + "awesome-support/awesome-support": "<=6.0.7", + "aws/aws-sdk-php": "<3.368", + "azuracast/azuracast": "<=0.23.1", + "b13/seo_basics": "<0.8.2", + "backdrop/backdrop": "<=1.32", + "backpack/crud": "<3.4.9", + "backpack/filemanager": "<2.0.2|>=3,<3.0.9", + "bacula-web/bacula-web": "<9.7.1", + "badaso/core": "<=2.9.11", + "bagisto/bagisto": "<2.3.10", + "barrelstrength/sprout-base-email": "<1.2.7", + "barrelstrength/sprout-forms": "<3.9", + "barryvdh/laravel-translation-manager": "<0.6.8", + "barzahlen/barzahlen-php": "<2.0.1", + "baserproject/basercms": "<=5.1.1", + "bassjobsen/bootstrap-3-typeahead": ">4.0.2", + "bbpress/bbpress": "<2.6.5", + "bcit-ci/codeigniter": "<3.1.3", + "bcosca/fatfree": "<3.7.2", + "bedita/bedita": "<4", + "bednee/cooluri": "<1.0.30", + "bigfork/silverstripe-form-capture": ">=3,<3.1.1", + "billz/raspap-webgui": "<3.3.6", + "binarytorch/larecipe": "<2.8.1", + "bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3", + "blueimp/jquery-file-upload": "==6.4.4", + "bmarshall511/wordpress_zero_spam": "<5.2.13", + "bolt/bolt": "<3.7.2", + "bolt/core": "<=4.2", + "born05/craft-twofactorauthentication": "<3.3.4", + "bottelet/flarepoint": "<2.2.1", + "bref/bref": "<2.1.17", + "brightlocal/phpwhois": "<=4.2.5", + "brotkrueml/codehighlight": "<2.7", + "brotkrueml/schema": "<1.13.1|>=2,<2.5.1", + "brotkrueml/typo3-matomo-integration": "<1.3.2", + "buddypress/buddypress": "<7.2.1", + "bugsnag/bugsnag-laravel": ">=2,<2.0.2", + "bvbmedia/multishop": "<2.0.39", + "bytefury/crater": "<6.0.2", + "cachethq/cachet": "<2.5.1", + "cadmium-org/cadmium-cms": "<=0.4.9", + "cakephp/cakephp": "<3.10.3|>=4,<4.0.10|>=4.1,<4.1.4|>=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10|>=5.2.10,<5.2.12|==5.3", + "cakephp/database": ">=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", + "cardgate/magento2": "<2.0.33", + "cardgate/woocommerce": "<=3.1.15", + "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", + "cart2quote/module-quotation-encoded": ">=4.1.6,<=4.4.5|>=5,<5.4.4", + "cartalyst/sentry": "<=2.1.6", + "catfan/medoo": "<1.7.5", + "causal/oidc": "<4", + "cecil/cecil": "<7.47.1", + "centreon/centreon": "<22.10.15", + "cesnet/simplesamlphp-module-proxystatistics": "<3.1", + "chriskacerguis/codeigniter-restserver": "<=2.7.1", + "chrome-php/chrome": "<1.14", + "civicrm/civicrm-core": ">=4.2,<4.2.9|>=4.3,<4.3.3", + "ckeditor/ckeditor": "<4.25", + "clickstorm/cs-seo": ">=6,<6.8|>=7,<7.5|>=8,<8.4|>=9,<9.3", + "co-stack/fal_sftp": "<0.2.6", + "cockpit-hq/cockpit": "<2.11.4", + "code16/sharp": "<9.11.1", + "codeception/codeception": "<3.1.3|>=4,<4.1.22", + "codeigniter/framework": "<3.1.10", + "codeigniter4/framework": "<4.6.2", + "codeigniter4/shield": "<1.0.0.0-beta8", + "codiad/codiad": "<=2.8.4", + "codingms/additional-tca": ">=1.7,<1.15.17|>=1.16,<1.16.9", + "codingms/modules": "<4.3.11|>=5,<5.7.4|>=6,<6.4.2|>=7,<7.5.5", + "commerceteam/commerce": ">=0.9.6,<0.9.9", + "components/jquery": ">=1.0.3,<3.5", + "composer/composer": "<1.10.27|>=2,<2.2.26|>=2.3,<2.9.3", + "concrete5/concrete5": "<9.4.3", + "concrete5/core": "<8.5.8|>=9,<9.1", + "contao-components/mediaelement": ">=2.14.2,<2.21.1", + "contao/comments-bundle": ">=2,<4.13.40|>=5.0.0.0-RC1-dev,<5.3.4", + "contao/contao": ">=3,<3.5.37|>=4,<4.4.56|>=4.5,<4.13.56|>=5,<5.3.38|>=5.4.0.0-RC1-dev,<5.6.1", + "contao/core": "<3.5.39", + "contao/core-bundle": "<4.13.57|>=5,<5.3.42|>=5.4,<5.6.5", + "contao/listing-bundle": ">=3,<=3.5.30|>=4,<4.4.8", + "contao/managed-edition": "<=1.5", + "coreshop/core-shop": "<4.1.9", + "corveda/phpsandbox": "<1.3.5", + "cosenary/instagram": "<=2.3", + "couleurcitron/tarteaucitron-wp": "<0.3", + "cpsit/typo3-mailqueue": "<0.4.3|>=0.5,<0.5.1", + "craftcms/cms": "<=4.16.16|>=5,<=5.8.20", + "croogo/croogo": "<=4.0.7", + "cuyz/valinor": "<0.12", + "czim/file-handling": "<1.5|>=2,<2.3", + "czproject/git-php": "<4.0.3", + "damienharper/auditor-bundle": "<5.2.6", + "dapphp/securimage": "<3.6.6", + "darylldoyle/safe-svg": "<1.9.10", + "datadog/dd-trace": ">=0.30,<0.30.2", + "datahihi1/tiny-env": "<1.0.3|>=1.0.9,<1.0.11", + "datatables/datatables": "<1.10.10", + "david-garcia/phpwhois": "<=4.3.1", + "dbrisinajumi/d2files": "<1", + "dcat/laravel-admin": "<=2.1.3|==2.2.0.0-beta|==2.2.2.0-beta", + "derhansen/fe_change_pwd": "<2.0.5|>=3,<3.0.3", + "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1|>=7,<7.4", + "desperado/xml-bundle": "<=0.1.7", + "dev-lancer/minecraft-motd-parser": "<=1.0.5", + "devcode-it/openstamanager": "<=2.9.4", + "devgroup/dotplant": "<2020.09.14-dev", + "digimix/wp-svg-upload": "<=1", + "directmailteam/direct-mail": "<6.0.3|>=7,<7.0.3|>=8,<9.5.2", + "dl/yag": "<3.0.1", + "dmk/webkitpdf": "<1.1.4", + "dnadesign/silverstripe-elemental": "<5.3.12", + "doctrine/annotations": "<1.2.7", + "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", + "doctrine/common": "<2.4.3|>=2.5,<2.5.1", + "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2|>=3,<3.1.4", + "doctrine/doctrine-bundle": "<1.5.2", + "doctrine/doctrine-module": "<0.7.2", + "doctrine/mongodb-odm": "<1.0.2", + "doctrine/mongodb-odm-bundle": "<3.0.1", + "doctrine/orm": ">=1,<1.2.4|>=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4", + "dolibarr/dolibarr": "<21.0.3", + "dompdf/dompdf": "<2.0.4", + "doublethreedigital/guest-entries": "<3.1.2", + "drupal-pattern-lab/unified-twig-extensions": "<=0.1", + "drupal/access_code": "<2.0.5", + "drupal/acquia_dam": "<1.1.5", + "drupal/admin_audit_trail": "<1.0.5", + "drupal/ai": "<1.0.5", + "drupal/alogin": "<2.0.6", + "drupal/cache_utility": "<1.2.1", + "drupal/civictheme": "<1.12", + "drupal/commerce_alphabank_redirect": "<1.0.3", + "drupal/commerce_eurobank_redirect": "<2.1.1", + "drupal/config_split": "<1.10|>=2,<2.0.2", + "drupal/core": ">=6,<6.38|>=7,<7.103|>=8,<10.4.9|>=10.5,<10.5.6|>=11,<11.1.9|>=11.2,<11.2.8", + "drupal/core-recommended": ">=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8", + "drupal/currency": "<3.5", + "drupal/drupal": ">=5,<5.11|>=6,<6.38|>=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8", + "drupal/email_tfa": "<2.0.6", + "drupal/formatter_suite": "<2.1", + "drupal/gdpr": "<3.0.1|>=3.1,<3.1.2", + "drupal/google_tag": "<1.8|>=2,<2.0.8", + "drupal/ignition": "<1.0.4", + "drupal/json_field": "<1.5", + "drupal/lightgallery": "<1.6", + "drupal/link_field_display_mode_formatter": "<1.6", + "drupal/matomo": "<1.24", + "drupal/oauth2_client": "<4.1.3", + "drupal/oauth2_server": "<2.1", + "drupal/obfuscate": "<2.0.1", + "drupal/plausible_tracking": "<1.0.2", + "drupal/quick_node_block": "<2", + "drupal/rapidoc_elements_field_formatter": "<1.0.1", + "drupal/reverse_proxy_header": "<1.1.2", + "drupal/simple_multistep": "<2", + "drupal/simple_oauth": ">=6,<6.0.7", + "drupal/spamspan": "<3.2.1", + "drupal/tfa": "<1.10", + "drupal/umami_analytics": "<1.0.1", + "duncanmcclean/guest-entries": "<3.1.2", + "dweeves/magmi": "<=0.7.24", + "ec-cube/ec-cube": "<2.4.4|>=2.11,<=2.17.1|>=3,<=3.0.18.0-patch4|>=4,<=4.1.2", + "ecodev/newsletter": "<=4", + "ectouch/ectouch": "<=2.7.2", + "egroupware/egroupware": "<23.1.20260113|>=26.0.20251208,<26.0.20260113", + "elefant/cms": "<2.0.7", + "elgg/elgg": "<3.3.24|>=4,<4.0.5", + "elijaa/phpmemcacheadmin": "<=1.3", + "elmsln/haxcms": "<11.0.14", + "encore/laravel-admin": "<=1.8.19", + "endroid/qr-code-bundle": "<3.4.2", + "enhavo/enhavo-app": "<=0.13.1", + "enshrined/svg-sanitize": "<0.22", + "erusev/parsedown": "<1.7.2", + "ether/logs": "<3.0.4", + "evolutioncms/evolution": "<=3.2.3", + "exceedone/exment": "<4.4.3|>=5,<5.0.3", + "exceedone/laravel-admin": "<2.2.3|==3", + "ezsystems/demobundle": ">=5.4,<5.4.6.1-dev", + "ezsystems/ez-support-tools": ">=2.2,<2.2.3", + "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1-dev", + "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1-dev|>=5.4,<5.4.11.1-dev|>=2017.12,<2017.12.0.1-dev", + "ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24", + "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.39|>=3.3,<3.3.39", + "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1|>=5.3.0.0-beta1,<5.3.5", + "ezsystems/ezplatform-graphql": ">=1.0.0.0-RC1-dev,<1.0.13|>=2.0.0.0-beta1,<2.3.12", + "ezsystems/ezplatform-http-cache": "<2.3.16", + "ezsystems/ezplatform-kernel": "<1.2.5.1-dev|>=1.3,<1.3.35", + "ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8", + "ezsystems/ezplatform-richtext": ">=2.3,<2.3.26|>=3.3,<3.3.40", + "ezsystems/ezplatform-solr-search-engine": ">=1.7,<1.7.12|>=2,<2.0.2|>=3.3,<3.3.15", + "ezsystems/ezplatform-user": ">=1,<1.0.1", + "ezsystems/ezpublish-kernel": "<6.13.8.2-dev|>=7,<7.5.31", + "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.03.5.1", + "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", + "ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15", + "ezyang/htmlpurifier": "<=4.2", + "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", + "facturascripts/facturascripts": "<=2025.4|==2025.11|==2025.41|==2025.43", + "fastly/magento2": "<1.2.26", + "feehi/cms": "<=2.1.1", + "feehi/feehicms": "<=2.1.1", + "fenom/fenom": "<=2.12.1", + "filament/actions": ">=3.2,<3.2.123", + "filament/filament": ">=4,<4.3.1", + "filament/infolists": ">=3,<3.2.115", + "filament/tables": ">=3,<3.2.115", + "filegator/filegator": "<7.8", + "filp/whoops": "<2.1.13", + "fineuploader/php-traditional-server": "<=1.2.2", + "firebase/php-jwt": "<6", + "fisharebest/webtrees": "<=2.1.18", + "fixpunkt/fp-masterquiz": "<2.2.1|>=3,<3.5.2", + "fixpunkt/fp-newsletter": "<1.1.1|>=1.2,<2.1.2|>=2.2,<3.2.6", + "flarum/core": "<1.8.10", + "flarum/flarum": "<0.1.0.0-beta8", + "flarum/framework": "<1.8.10", + "flarum/mentions": "<1.6.3", + "flarum/sticky": ">=0.1.0.0-beta14,<=0.1.0.0-beta15", + "flarum/tags": "<=0.1.0.0-beta13", + "floriangaerber/magnesium": "<0.3.1", + "fluidtypo3/vhs": "<5.1.1", + "fof/byobu": ">=0.3.0.0-beta2,<1.1.7", + "fof/pretty-mail": "<=1.1.2", + "fof/upload": "<1.2.3", + "foodcoopshop/foodcoopshop": ">=3.2,<3.6.1", + "fooman/tcpdf": "<6.2.22", + "forkcms/forkcms": "<5.11.1", + "fossar/tcpdf-parser": "<6.2.22", + "francoisjacquet/rosariosis": "<=11.5.1", + "frappant/frp-form-answers": "<3.1.2|>=4,<4.0.2", + "friendsofsymfony/oauth2-php": "<1.3", + "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", + "friendsofsymfony/user-bundle": ">=1,<1.3.5", + "friendsofsymfony1/swiftmailer": ">=4,<5.4.13|>=6,<6.2.5", + "friendsofsymfony1/symfony1": ">=1.1,<1.5.19", + "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", + "friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6", + "froala/wysiwyg-editor": "<=4.3", + "froxlor/froxlor": "<=2.2.5", + "frozennode/administrator": "<=5.0.12", + "fuel/core": "<1.8.1", + "funadmin/funadmin": "<=5.0.2", + "gaoming13/wechat-php-sdk": "<=1.10.2", + "genix/cms": "<=1.1.11", + "georgringer/news": "<1.3.3", + "geshi/geshi": "<=1.0.9.1", + "getformwork/formwork": "<2.2", + "getgrav/grav": "<1.11.0.0-beta1", + "getkirby/cms": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1|>=5,<=5.2.1", + "getkirby/kirby": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1", + "getkirby/panel": "<2.5.14", + "getkirby/starterkit": "<=3.7.0.2", + "gilacms/gila": "<=1.15.4", + "gleez/cms": "<=1.3|==2", + "globalpayments/php-sdk": "<2", + "goalgorilla/open_social": "<12.3.11|>=12.4,<12.4.10|>=13.0.0.0-alpha1,<13.0.0.0-alpha11", + "gogentooss/samlbase": "<1.2.7", + "google/protobuf": "<3.4", + "gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3", + "gp247/core": "<1.1.24", + "gree/jose": "<2.2.1", + "gregwar/rst": "<1.0.3", + "grumpydictator/firefly-iii": "<6.1.17", + "gugoan/economizzer": "<=0.9.0.0-beta1", + "guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5", + "guzzlehttp/oauth-subscriber": "<0.8.1", + "guzzlehttp/psr7": "<1.9.1|>=2,<2.4.5", + "haffner/jh_captcha": "<=2.1.3|>=3,<=3.0.2", + "handcraftedinthealps/goodby-csv": "<1.4.3", + "harvesthq/chosen": "<1.8.7", + "helloxz/imgurl": "<=2.31", + "hhxsv5/laravel-s": "<3.7.36", + "hillelcoren/invoice-ninja": "<5.3.35", + "himiklab/yii2-jqgrid-widget": "<1.0.8", + "hjue/justwriting": "<=1", + "hov/jobfair": "<1.0.13|>=2,<2.0.2", + "httpsoft/http-message": "<1.0.12", + "hyn/multi-tenant": ">=5.6,<5.7.2", + "ibexa/admin-ui": ">=4.2,<4.2.3|>=4.6,<4.6.25|>=5,<5.0.3", + "ibexa/admin-ui-assets": ">=4.6.0.0-alpha1,<4.6.21", + "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4|>=4.2,<4.2.3|>=4.5,<4.5.6|>=4.6,<4.6.2", + "ibexa/fieldtype-richtext": ">=4.6,<4.6.25|>=5,<5.0.3", + "ibexa/graphql": ">=2.5,<2.5.31|>=3.3,<3.3.28|>=4.2,<4.2.3", + "ibexa/http-cache": ">=4.6,<4.6.14", + "ibexa/post-install": "<1.0.16|>=4.6,<4.6.14", + "ibexa/solr": ">=4.5,<4.5.4", + "ibexa/user": ">=4,<4.4.3|>=5,<5.0.4", + "icecoder/icecoder": "<=8.1", + "idno/known": "<=1.3.1", + "ilicmiljan/secure-props": ">=1.2,<1.2.2", + "illuminate/auth": "<5.5.10", + "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<6.18.31|>=7,<7.22.4", + "illuminate/database": "<6.20.26|>=7,<7.30.5|>=8,<8.40", + "illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15", + "illuminate/view": "<6.20.42|>=7,<7.30.6|>=8,<8.75", + "imdbphp/imdbphp": "<=5.1.1", + "impresscms/impresscms": "<=1.4.5", + "impresspages/impresspages": "<1.0.13", + "in2code/femanager": "<6.4.2|>=7,<7.5.3|>=8,<8.3.1", + "in2code/ipandlanguageredirect": "<5.1.2", + "in2code/lux": "<17.6.1|>=18,<24.0.2", + "in2code/powermail": "<7.5.1|>=8,<8.5.1|>=9,<10.9.1|>=11,<12.5.3|==13", + "innologi/typo3-appointments": "<2.0.6", + "intelliants/subrion": "<4.2.2", + "inter-mediator/inter-mediator": "==5.5", + "ipl/web": "<0.10.1", + "islandora/crayfish": "<4.1", + "islandora/islandora": ">=2,<2.4.1", + "ivankristianto/phpwhois": "<=4.3", + "jackalope/jackalope-doctrine-dbal": "<1.7.4", + "jambagecom/div2007": "<0.10.2", + "james-heinrich/getid3": "<1.9.21", + "james-heinrich/phpthumb": "<=1.7.23", + "jasig/phpcas": "<1.3.3", + "jbartels/wec-map": "<3.0.3", + "jcbrand/converse.js": "<3.3.3", + "joelbutcher/socialstream": "<5.6|>=6,<6.2", + "johnbillion/wp-crontrol": "<1.16.2|>=1.17,<1.19.2", + "joomla/application": "<1.0.13", + "joomla/archive": "<1.1.12|>=2,<2.0.1", + "joomla/database": ">=1,<2.2|>=3,<3.4", + "joomla/filesystem": "<1.6.2|>=2,<2.0.1", + "joomla/filter": "<2.0.6|>=3,<3.0.5|==4", + "joomla/framework": "<1.5.7|>=2.5.4,<=3.8.12", + "joomla/input": ">=2,<2.0.2", + "joomla/joomla-cms": "<3.9.12|>=4,<4.4.13|>=5,<5.2.6", + "joomla/joomla-platform": "<1.5.4", + "joomla/session": "<1.3.1", + "joyqi/hyper-down": "<=2.4.27", + "jsdecena/laracom": "<2.0.9", + "jsmitty12/phpwhois": "<5.1", + "juzaweb/cms": "<=3.4.2", + "jweiland/events2": "<8.3.8|>=9,<9.0.6", + "jweiland/kk-downloader": "<1.2.2", + "kazist/phpwhois": "<=4.2.6", + "kelvinmo/simplexrd": "<3.1.1", + "kevinpapst/kimai2": "<1.16.7", + "khodakhah/nodcms": "<=3", + "kimai/kimai": "<2.46", + "kitodo/presentation": "<3.2.3|>=3.3,<3.3.4", + "klaviyo/magento2-extension": ">=1,<3", + "knplabs/knp-snappy": "<=1.4.2", + "kohana/core": "<3.3.3", + "koillection/koillection": "<1.6.12", + "krayin/laravel-crm": "<=1.3", + "kreait/firebase-php": ">=3.2,<3.8.1", + "kumbiaphp/kumbiapp": "<=1.1.1", + "la-haute-societe/tcpdf": "<6.2.22", + "laminas/laminas-diactoros": "<2.18.1|==2.19|==2.20|==2.21|==2.22|==2.23|>=2.24,<2.24.2|>=2.25,<2.25.2", + "laminas/laminas-form": "<2.17.1|>=3,<3.0.2|>=3.1,<3.1.1", + "laminas/laminas-http": "<2.14.2", + "lara-zeus/artemis": ">=1,<=1.0.6", + "lara-zeus/dynamic-dashboard": ">=3,<=3.0.1", + "laravel/fortify": "<1.11.1", + "laravel/framework": "<10.48.29|>=11,<11.44.1|>=12,<12.1.1", + "laravel/laravel": ">=5.4,<5.4.22", + "laravel/pulse": "<1.3.1", + "laravel/reverb": "<1.7", + "laravel/socialite": ">=1,<2.0.10", + "latte/latte": "<2.10.8", + "lavalite/cms": "<=10.1", + "lavitto/typo3-form-to-database": "<2.2.5|>=3,<3.2.2|>=4,<4.2.3|>=5,<5.0.2", + "lcobucci/jwt": ">=3.4,<3.4.6|>=4,<4.0.4|>=4.1,<4.1.5", + "league/commonmark": "<2.7", + "league/flysystem": "<1.1.4|>=2,<2.1.1", + "league/oauth2-server": ">=8.3.2,<8.4.2|>=8.5,<8.5.3", + "leantime/leantime": "<3.3", + "lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3", + "libreform/libreform": ">=2,<=2.0.8", + "librenms/librenms": "<25.12", + "liftkit/database": "<2.13.2", + "lightsaml/lightsaml": "<1.3.5", + "limesurvey/limesurvey": "<6.5.12", + "livehelperchat/livehelperchat": "<=3.91", + "livewire-filemanager/filemanager": "<=1.0.4", + "livewire/livewire": "<2.12.7|>=3.0.0.0-beta1,<3.6.4", + "livewire/volt": "<1.7", + "lms/routes": "<2.1.1", + "localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2", + "lomkit/laravel-rest-api": "<2.13", + "luracast/restler": "<3.1", + "luyadev/yii-helpers": "<1.2.1", + "macropay-solutions/laravel-crud-wizard-free": "<3.4.17", + "maestroerror/php-heic-to-jpg": "<1.0.5", + "magento/community-edition": "<2.4.6.0-patch13|>=2.4.7.0-beta1,<2.4.7.0-patch8|>=2.4.8.0-beta1,<2.4.8.0-patch3|>=2.4.9.0-alpha1,<2.4.9.0-alpha3|==2.4.9", + "magento/core": "<=1.9.4.5", + "magento/magento1ce": "<1.9.4.3-dev", + "magento/magento1ee": ">=1,<1.14.4.3-dev", + "magento/product-community-edition": "<2.4.4.0-patch9|>=2.4.5,<2.4.5.0-patch8|>=2.4.6,<2.4.6.0-patch6|>=2.4.7,<2.4.7.0-patch1", + "magento/project-community-edition": "<=2.0.2", + "magneto/core": "<1.9.4.4-dev", + "mahocommerce/maho": "<25.9", + "maikuolan/phpmussel": ">=1,<1.6", + "mainwp/mainwp": "<=4.4.3.3", + "manogi/nova-tiptap": "<=3.2.6", + "mantisbt/mantisbt": "<2.27.2", + "marcwillmann/turn": "<0.3.3", + "marshmallow/nova-tiptap": "<5.7", + "matomo/matomo": "<1.11", + "matyhtf/framework": "<3.0.6", + "mautic/core": "<5.2.9|>=6,<6.0.7", + "mautic/core-lib": ">=1.0.0.0-beta,<4.4.13|>=5.0.0.0-alpha,<5.1.1", + "mautic/grapes-js-builder-bundle": ">=4,<4.4.18|>=5,<5.2.9|>=6,<6.0.7", + "maximebf/debugbar": "<1.19", + "mdanter/ecc": "<2", + "mediawiki/abuse-filter": "<1.39.9|>=1.40,<1.41.3|>=1.42,<1.42.2", + "mediawiki/cargo": "<3.8.3", + "mediawiki/core": "<1.39.5|==1.40", + "mediawiki/data-transfer": ">=1.39,<1.39.11|>=1.41,<1.41.3|>=1.42,<1.42.2", + "mediawiki/matomo": "<2.4.3", + "mediawiki/semantic-media-wiki": "<4.0.2", + "mehrwert/phpmyadmin": "<3.2", + "melisplatform/melis-asset-manager": "<5.0.1", + "melisplatform/melis-cms": "<5.3.4", + "melisplatform/melis-cms-slider": "<5.3.1", + "melisplatform/melis-core": "<5.3.11", + "melisplatform/melis-front": "<5.0.1", + "mezzio/mezzio-swoole": "<3.7|>=4,<4.3", + "mgallegos/laravel-jqgrid": "<=1.3", + "microsoft/microsoft-graph": ">=1.16,<1.109.1|>=2,<2.0.1", + "microsoft/microsoft-graph-beta": "<2.0.1", + "microsoft/microsoft-graph-core": "<2.0.2", + "microweber/microweber": "<=2.0.19", + "mikehaertl/php-shellcommand": "<1.6.1", + "mineadmin/mineadmin": "<=3.0.9", + "miniorange/miniorange-saml": "<1.4.3", + "mittwald/typo3_forum": "<1.2.1", + "mobiledetect/mobiledetectlib": "<2.8.32", + "modx/revolution": "<=3.1", + "mojo42/jirafeau": "<4.4", + "mongodb/mongodb": ">=1,<1.9.2", + "mongodb/mongodb-extension": "<1.21.2", + "monolog/monolog": ">=1.8,<1.12", + "moodle/moodle": "<4.4.12|>=4.5.0.0-beta,<4.5.8|>=5.0.0.0-beta,<5.0.4|>=5.1.0.0-beta,<5.1.1", + "moonshine/moonshine": "<=3.12.5", + "mos/cimage": "<0.7.19", + "movim/moxl": ">=0.8,<=0.10", + "movingbytes/social-network": "<=1.2.1", + "mpdf/mpdf": "<=7.1.7", + "munkireport/comment": "<4", + "munkireport/managedinstalls": "<2.6", + "munkireport/munki_facts": "<1.5", + "munkireport/reportdata": "<3.5", + "munkireport/softwareupdate": "<1.6", + "mustache/mustache": ">=2,<2.14.1", + "mwdelaney/wp-enable-svg": "<=0.2", + "namshi/jose": "<2.2", + "nasirkhan/laravel-starter": "<11.11", + "nategood/httpful": "<1", + "neoan3-apps/template": "<1.1.1", + "neorazorx/facturascripts": "<2022.04", + "neos/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", + "neos/form": ">=1.2,<4.3.3|>=5,<5.0.9|>=5.1,<5.1.3", + "neos/media-browser": "<7.3.19|>=8,<8.0.16|>=8.1,<8.1.11|>=8.2,<8.2.11|>=8.3,<8.3.9", + "neos/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<5.3.10|>=7,<7.0.9|>=7.1,<7.1.7|>=7.2,<7.2.6|>=7.3,<7.3.4|>=8,<8.0.2", + "neos/swiftmailer": "<5.4.5", + "nesbot/carbon": "<2.72.6|>=3,<3.8.4", + "netcarver/textile": "<=4.1.2", + "netgen/tagsbundle": ">=3.4,<3.4.11|>=4,<4.0.15", + "nette/application": ">=2,<2.0.19|>=2.1,<2.1.13|>=2.2,<2.2.10|>=2.3,<2.3.14|>=2.4,<2.4.16|>=3,<3.0.6", + "nette/nette": ">=2,<2.0.19|>=2.1,<2.1.13", + "neuron-core/neuron-ai": "<=2.8.11", + "nilsteampassnet/teampass": "<3.1.3.1-dev", + "nitsan/ns-backup": "<13.0.1", + "nonfiction/nterchange": "<4.1.1", + "notrinos/notrinos-erp": "<=0.7", + "noumo/easyii": "<=0.9", + "novaksolutions/infusionsoft-php-sdk": "<1", + "novosga/novosga": "<=2.2.12", + "nukeviet/nukeviet": "<4.5.02", + "nyholm/psr7": "<1.6.1", + "nystudio107/craft-seomatic": "<3.4.12", + "nzedb/nzedb": "<0.8", + "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", + "october/backend": "<1.1.2", + "october/cms": "<1.0.469|==1.0.469|==1.0.471|==1.1.1", + "october/october": "<3.7.5", + "october/rain": "<1.0.472|>=1.1,<1.1.2", + "october/system": "<=3.7.12|>=4,<=4.0.11", + "oliverklee/phpunit": "<3.5.15", + "omeka/omeka-s": "<4.0.3", + "onelogin/php-saml": "<2.21.1|>=3,<3.8.1|>=4,<4.3.1", + "oneup/uploader-bundle": ">=1,<1.9.3|>=2,<2.1.5", + "open-web-analytics/open-web-analytics": "<1.8.1", + "opencart/opencart": ">=0", + "openid/php-openid": "<2.3", + "openmage/magento-lts": "<20.16", + "opensolutions/vimbadmin": "<=3.0.15", + "opensource-workshop/connect-cms": "<1.8.7|>=2,<2.4.7", + "orchid/platform": ">=8,<14.43", + "oro/calendar-bundle": ">=4.2,<=4.2.6|>=5,<=5.0.6|>=5.1,<5.1.1", + "oro/commerce": ">=4.1,<5.0.11|>=5.1,<5.1.1", + "oro/crm": ">=1.7,<1.7.4|>=3.1,<4.1.17|>=4.2,<4.2.7", + "oro/crm-call-bundle": ">=4.2,<=4.2.5|>=5,<5.0.4|>=5.1,<5.1.1", + "oro/customer-portal": ">=4.1,<=4.1.13|>=4.2,<=4.2.10|>=5,<=5.0.11|>=5.1,<=5.1.3", + "oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<=4.2.10|>=5,<=5.0.12|>=5.1,<=5.1.3", + "oveleon/contao-cookiebar": "<1.16.3|>=2,<2.1.3", + "oxid-esales/oxideshop-ce": "<=7.0.5", + "oxid-esales/paymorrow-module": ">=1,<1.0.2|>=2,<2.0.1", + "packbackbooks/lti-1-3-php-library": "<5", + "padraic/humbug_get_contents": "<1.1.2", + "pagarme/pagarme-php": "<3", + "pagekit/pagekit": "<=1.0.18", + "paragonie/ecc": "<2.0.1", + "paragonie/random_compat": "<2", + "paragonie/sodium_compat": "<1.24|>=2,<2.5", + "passbolt/passbolt_api": "<4.6.2", + "paypal/adaptivepayments-sdk-php": "<=3.9.2", + "paypal/invoice-sdk-php": "<=3.9", + "paypal/merchant-sdk-php": "<3.12", + "paypal/permissions-sdk-php": "<=3.9.1", + "pear/archive_tar": "<1.4.14", + "pear/auth": "<1.2.4", + "pear/crypt_gpg": "<1.6.7", + "pear/http_request2": "<2.7", + "pear/pear": "<=1.10.1", + "pegasus/google-for-jobs": "<1.5.1|>=2,<2.1.1", + "personnummer/personnummer": "<3.0.2", + "ph7software/ph7builder": "<=17.9.1", + "phanan/koel": "<5.1.4", + "phenx/php-svg-lib": "<0.5.2", + "php-censor/php-censor": "<2.0.13|>=2.1,<2.1.5", + "php-mod/curl": "<2.3.2", + "phpbb/phpbb": "<3.3.11", + "phpems/phpems": ">=6,<=6.1.3", + "phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7", + "phpmailer/phpmailer": "<6.5", + "phpmussel/phpmussel": ">=1,<1.6", + "phpmyadmin/phpmyadmin": "<5.2.2", + "phpmyfaq/phpmyfaq": "<=4.0.16", + "phpoffice/common": "<0.2.9", + "phpoffice/math": "<=0.2", + "phpoffice/phpexcel": "<=1.8.2", + "phpoffice/phpspreadsheet": "<1.30|>=2,<2.1.12|>=2.2,<2.4|>=3,<3.10|>=4,<5", + "phppgadmin/phppgadmin": "<=7.13", + "phpseclib/phpseclib": "<2.0.47|>=3,<3.0.36", + "phpservermon/phpservermon": "<3.6", + "phpsysinfo/phpsysinfo": "<3.4.3", + "phpunit/phpunit": "<8.5.52|>=9,<9.6.33|>=10,<10.5.62|>=11,<11.5.50|>=12,<12.5.8", + "phpwhois/phpwhois": "<=4.2.5", + "phpxmlrpc/extras": "<0.6.1", + "phpxmlrpc/phpxmlrpc": "<4.9.2", + "pi/pi": "<=2.5", + "pimcore/admin-ui-classic-bundle": "<=1.7.15|>=2.0.0.0-RC1-dev,<=2.2.2", + "pimcore/customer-management-framework-bundle": "<4.2.1", + "pimcore/data-hub": "<1.2.4", + "pimcore/data-importer": "<1.8.9|>=1.9,<1.9.3", + "pimcore/demo": "<10.3", + "pimcore/ecommerce-framework-bundle": "<1.0.10", + "pimcore/perspective-editor": "<1.5.1", + "pimcore/pimcore": "<=11.5.13|>=12.0.0.0-RC1-dev,<12.3.1", + "pimcore/web2print-tools-bundle": "<=5.2.1|>=6.0.0.0-RC1-dev,<=6.1", + "piwik/piwik": "<1.11", + "pixelfed/pixelfed": "<0.12.5", + "plotly/plotly.js": "<2.25.2", + "pocketmine/bedrock-protocol": "<8.0.2", + "pocketmine/pocketmine-mp": "<5.32.1", + "pocketmine/raklib": ">=0.14,<0.14.6|>=0.15,<0.15.1", + "pressbooks/pressbooks": "<5.18", + "prestashop/autoupgrade": ">=4,<4.10.1", + "prestashop/blockreassurance": "<=5.1.3", + "prestashop/blockwishlist": ">=2,<2.1.1", + "prestashop/contactform": ">=1.0.1,<4.3", + "prestashop/gamification": "<2.3.2", + "prestashop/prestashop": "<8.2.3", + "prestashop/productcomments": "<5.0.2", + "prestashop/ps_checkout": "<4.4.1|>=5,<5.0.5", + "prestashop/ps_contactinfo": "<=3.3.2", + "prestashop/ps_emailsubscription": "<2.6.1", + "prestashop/ps_facetedsearch": "<3.4.1", + "prestashop/ps_linklist": "<3.1", + "privatebin/privatebin": "<1.4|>=1.5,<1.7.4|>=1.7.7,<2.0.3", + "processwire/processwire": "<=3.0.246", + "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7", + "propel/propel1": ">=1,<=1.7.1", + "psy/psysh": "<=0.11.22|>=0.12,<=0.12.18", + "pterodactyl/panel": "<1.12", + "ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2", + "ptrofimov/beanstalk_console": "<1.7.14", + "pubnub/pubnub": "<6.1", + "punktde/pt_extbase": "<1.5.1", + "pusher/pusher-php-server": "<2.2.1", + "pwweb/laravel-core": "<=0.3.6.0-beta", + "pxlrbt/filament-excel": "<1.1.14|>=2.0.0.0-alpha,<2.3.3", + "pyrocms/pyrocms": "<=3.9.1", + "qcubed/qcubed": "<=3.1.1", + "quickapps/cms": "<=2.0.0.0-beta2", + "rainlab/blog-plugin": "<1.4.1", + "rainlab/debugbar-plugin": "<3.1", + "rainlab/user-plugin": "<=1.4.5", + "rankmath/seo-by-rank-math": "<=1.0.95", + "rap2hpoutre/laravel-log-viewer": "<0.13", + "react/http": ">=0.7,<1.9", + "really-simple-plugins/complianz-gdpr": "<6.4.2", + "redaxo/source": "<=5.20.1", + "remdex/livehelperchat": "<4.29", + "renolit/reint-downloadmanager": "<4.0.2|>=5,<5.0.1", + "reportico-web/reportico": "<=8.1", + "rhukster/dom-sanitizer": "<1.0.7", + "rmccue/requests": ">=1.6,<1.8", + "robrichards/xmlseclibs": "<=3.1.3", + "roots/soil": "<4.1", + "roundcube/roundcubemail": "<1.5.10|>=1.6,<1.6.11", + "rudloff/alltube": "<3.0.3", + "rudloff/rtmpdump-bin": "<=2.3.1", + "s-cart/core": "<=9.0.5", + "s-cart/s-cart": "<6.9", + "sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1", + "sabre/dav": ">=1.6,<1.7.11|>=1.8,<1.8.9", + "samwilson/unlinked-wikibase": "<1.42", + "scheb/two-factor-bundle": "<3.26|>=4,<4.11", + "sensiolabs/connect": "<4.2.3", + "serluck/phpwhois": "<=4.2.6", + "setasign/fpdi": "<2.6.4", + "sfroemken/url_redirect": "<=1.2.1", + "sheng/yiicms": "<1.2.1", + "shopware/core": "<6.6.10.9-dev|>=6.7,<6.7.6.1-dev", + "shopware/platform": "<6.6.10.7-dev|>=6.7,<6.7.3.1-dev", + "shopware/production": "<=6.3.5.2", + "shopware/shopware": "<=5.7.17|>=6.4.6,<6.6.10.10-dev|>=6.7,<6.7.6.1-dev", + "shopware/storefront": "<6.6.10.10-dev|>=6.7,<6.7.5.1-dev", + "shopxo/shopxo": "<=6.4", + "showdoc/showdoc": "<2.10.4", + "shuchkin/simplexlsx": ">=1.0.12,<1.1.13", + "silverstripe-australia/advancedreports": ">=1,<=2", + "silverstripe/admin": "<1.13.19|>=2,<2.1.8", + "silverstripe/assets": ">=1,<1.11.1", + "silverstripe/cms": "<4.11.3", + "silverstripe/comments": ">=1.3,<3.1.1", + "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", + "silverstripe/framework": "<5.3.23", + "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.8.2|>=4,<4.3.7|>=5,<5.1.3", + "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", + "silverstripe/recipe-cms": ">=4.5,<4.5.3", + "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", + "silverstripe/reports": "<5.2.3", + "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4|>=2.1,<2.1.2", + "silverstripe/silverstripe-omnipay": "<2.5.2|>=3,<3.0.2|>=3.1,<3.1.4|>=3.2,<3.2.1", + "silverstripe/subsites": ">=2,<2.6.1", + "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1", + "silverstripe/userforms": "<3|>=5,<5.4.2", + "silverstripe/versioned-admin": ">=1,<1.11.1", + "simogeo/filemanager": "<=2.5", + "simple-updates/phpwhois": "<=1", + "simplesamlphp/saml2": "<=4.16.15|>=5.0.0.0-alpha1,<=5.0.0.0-alpha19", + "simplesamlphp/saml2-legacy": "<=4.16.15", + "simplesamlphp/simplesamlphp": "<1.18.6", + "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", + "simplesamlphp/simplesamlphp-module-openid": "<1", + "simplesamlphp/simplesamlphp-module-openidprovider": "<0.9", + "simplesamlphp/xml-common": "<1.20", + "simplesamlphp/xml-security": "==1.6.11", + "simplito/elliptic-php": "<1.0.6", + "sitegeist/fluid-components": "<3.5", + "sjbr/sr-feuser-register": "<2.6.2|>=5.1,<12.5", + "sjbr/sr-freecap": "<2.4.6|>=2.5,<2.5.3", + "sjbr/static-info-tables": "<2.3.1", + "slim/psr7": "<1.4.1|>=1.5,<1.5.1|>=1.6,<1.6.1", + "slim/slim": "<2.6", + "slub/slub-events": "<3.0.3", + "smarty/smarty": "<4.5.3|>=5,<5.1.1", + "snipe/snipe-it": "<=8.3.4", + "socalnick/scn-social-auth": "<1.15.2", + "socialiteproviders/steam": "<1.1", + "solspace/craft-freeform": "<4.1.29|>=5,<=5.14.6", + "soosyze/soosyze": "<=2", + "spatie/browsershot": "<5.0.5", + "spatie/image-optimizer": "<1.7.3", + "spencer14420/sp-php-email-handler": "<1", + "spipu/html2pdf": "<5.2.8", + "spiral/roadrunner": "<2025.1", + "spoon/library": "<1.4.1", + "spoonity/tcpdf": "<6.2.22", + "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", + "ssddanbrown/bookstack": "<24.05.1", + "starcitizentools/citizen-skin": ">=1.9.4,<3.9", + "starcitizentools/short-description": ">=4,<4.0.1", + "starcitizentools/tabber-neue": ">=1.9.1,<2.7.2|>=3,<3.1.1", + "starcitizenwiki/embedvideo": "<=4", + "statamic/cms": "<=5.22", + "stormpath/sdk": "<9.9.99", + "studio-42/elfinder": "<=2.1.64", + "studiomitte/friendlycaptcha": "<0.1.4", + "subhh/libconnect": "<7.0.8|>=8,<8.1", + "sukohi/surpass": "<1", + "sulu/form-bundle": ">=2,<2.5.3", + "sulu/sulu": "<1.6.44|>=2,<2.5.25|>=2.6,<2.6.9|>=3.0.0.0-alpha1,<3.0.0.0-alpha3", + "sumocoders/framework-user-bundle": "<1.4", + "superbig/craft-audit": "<3.0.2", + "svewap/a21glossary": "<=0.4.10", + "swag/paypal": "<5.4.4", + "swiftmailer/swiftmailer": "<6.2.5", + "swiftyedit/swiftyedit": "<1.2", + "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", + "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", + "sylius/grid-bundle": "<1.10.1", + "sylius/paypal-plugin": "<1.6.2|>=1.7,<1.7.2|>=2,<2.0.2", + "sylius/resource-bundle": ">=1,<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4", + "sylius/sylius": "<1.12.19|>=1.13.0.0-alpha1,<1.13.4", + "symbiote/silverstripe-multivaluefield": ">=3,<3.1", + "symbiote/silverstripe-queuedjobs": ">=3,<3.0.2|>=3.1,<3.1.4|>=4,<4.0.7|>=4.1,<4.1.2|>=4.2,<4.2.4|>=4.3,<4.3.3|>=4.4,<4.4.3|>=4.5,<4.5.1|>=4.6,<4.6.4", + "symbiote/silverstripe-seed": "<6.0.3", + "symbiote/silverstripe-versionedfiles": "<=2.0.3", + "symfont/process": ">=0", + "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", + "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", + "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", + "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<5.3.15|>=5.4.3,<5.4.4|>=6.0.3,<6.0.4", + "symfony/http-client": ">=4.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", + "symfony/http-foundation": "<5.4.50|>=6,<6.4.29|>=7,<7.3.7", + "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1", + "symfony/mime": ">=4.3,<4.3.8", + "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/polyfill": ">=1,<1.10", + "symfony/polyfill-php55": ">=1,<1.10", + "symfony/process": "<5.4.51|>=6,<6.4.33|>=7,<7.1.7|>=7.3,<7.3.11|>=7.4,<7.4.5|>=8,<8.0.5", + "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/routing": ">=2,<2.0.19", + "symfony/runtime": ">=5.3,<5.4.46|>=6,<6.4.14|>=7,<7.1.7", + "symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8", + "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.4.10|>=7,<7.0.10|>=7.1,<7.1.3", + "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9", + "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", + "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", + "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", + "symfony/symfony": "<5.4.51|>=6,<6.4.33|>=7,<7.3.11|>=7.4,<7.4.5|>=8,<8.0.5", + "symfony/translation": ">=2,<2.0.17", + "symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", + "symfony/ux-autocomplete": "<2.11.2", + "symfony/ux-live-component": "<2.25.1", + "symfony/ux-twig-component": "<2.25.1", + "symfony/validator": "<5.4.43|>=6,<6.4.11|>=7,<7.1.4", + "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", + "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", + "symfony/webhook": ">=6.3,<6.3.8", + "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7|>=2.2.0.0-beta1,<2.2.0.0-beta2", + "symphonycms/symphony-2": "<2.6.4", + "t3/dce": "<0.11.5|>=2.2,<2.6.2", + "t3g/svg-sanitizer": "<1.0.3", + "t3s/content-consent": "<1.0.3|>=2,<2.0.2", + "tastyigniter/tastyigniter": "<4", + "tcg/voyager": "<=1.8", + "tecnickcom/tc-lib-pdf-font": "<2.6.4", + "tecnickcom/tcpdf": "<6.8", + "terminal42/contao-tablelookupwizard": "<3.3.5", + "thelia/backoffice-default-template": ">=2.1,<2.1.2", + "thelia/thelia": ">=2.1,<2.1.3", + "theonedemon/phpwhois": "<=4.2.5", + "thinkcmf/thinkcmf": "<6.0.8", + "thorsten/phpmyfaq": "<=4.0.16|>=4.1.0.0-alpha,<=4.1.0.0-beta2", + "tikiwiki/tiki-manager": "<=17.1", + "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1", + "tinymce/tinymce": "<7.2", + "tinymighty/wiki-seo": "<1.2.2", + "titon/framework": "<9.9.99", + "tltneon/lgsl": "<7", + "tobiasbg/tablepress": "<=2.0.0.0-RC1", + "topthink/framework": "<6.0.17|>=6.1,<=8.0.4", + "topthink/think": "<=6.1.1", + "topthink/thinkphp": "<=3.2.3|>=6.1.3,<=8.0.4", + "torrentpier/torrentpier": "<=2.8.8", + "tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2", + "tribalsystems/zenario": "<=9.7.61188", + "truckersmp/phpwhois": "<=4.3.1", + "ttskch/pagination-service-provider": "<1", + "twbs/bootstrap": "<3.4.1|>=4,<4.3.1", + "twig/twig": "<3.11.2|>=3.12,<3.14.1|>=3.16,<3.19", + "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", + "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<9.5.55|>=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1", + "typo3/cms-belog": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-beuser": ">=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", + "typo3/cms-core": "<=8.7.56|>=9,<9.5.55|>=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1", + "typo3/cms-dashboard": ">=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", + "typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1", + "typo3/cms-extensionmanager": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-felogin": ">=4.2,<4.2.3", + "typo3/cms-fluid": "<4.3.4|>=4.4,<4.4.1", + "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-frontend": "<4.3.9|>=4.4,<4.4.5", + "typo3/cms-indexed-search": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-install": "<4.1.14|>=4.2,<4.2.16|>=4.3,<4.3.9|>=4.4,<4.4.5|>=12.2,<12.4.8|==13.4.2", + "typo3/cms-lowlevel": ">=11,<=11.5.41", + "typo3/cms-recordlist": ">=11,<11.5.48", + "typo3/cms-recycler": ">=9,<9.5.55|>=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1", + "typo3/cms-redirects": ">=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1", + "typo3/cms-rte-ckeditor": ">=9.5,<9.5.42|>=10,<10.4.39|>=11,<11.5.30", + "typo3/cms-scheduler": ">=11,<=11.5.41", + "typo3/cms-setup": ">=9,<=9.5.50|>=10,<=10.4.49|>=11,<=11.5.43|>=12,<=12.4.30|>=13,<=13.4.11", + "typo3/cms-webhooks": ">=12,<=12.4.30|>=13,<=13.4.11", + "typo3/cms-workspaces": ">=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", + "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", + "typo3/html-sanitizer": ">=1,<=1.5.2|>=2,<=2.1.3", + "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3", + "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", + "typo3/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", + "typo3fluid/fluid": ">=2,<2.0.8|>=2.1,<2.1.7|>=2.2,<2.2.4|>=2.3,<2.3.7|>=2.4,<2.4.4|>=2.5,<2.5.11|>=2.6,<2.6.10", + "ua-parser/uap-php": "<3.8", + "uasoft-indonesia/badaso": "<=2.9.7", + "unisharp/laravel-filemanager": "<2.9.1", + "universal-omega/dynamic-page-list3": "<3.6.4", + "unopim/unopim": "<=0.3", + "userfrosting/userfrosting": ">=0.3.1,<4.6.3", + "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2", + "uvdesk/community-skeleton": "<=1.1.1", + "uvdesk/core-framework": "<=1.1.1", + "vanilla/safecurl": "<0.9.2", + "verbb/comments": "<1.5.5", + "verbb/formie": "<=2.1.43", + "verbb/image-resizer": "<2.0.9", + "verbb/knock-knock": "<1.2.8", + "verot/class.upload.php": "<=2.1.6", + "vertexvaar/falsftp": "<0.2.6", + "villagedefrance/opencart-overclocked": "<=1.11.1", + "vova07/yii2-fileapi-widget": "<0.1.9", + "vrana/adminer": "<=4.8.1", + "vufind/vufind": ">=2,<9.1.1", + "waldhacker/hcaptcha": "<2.1.2", + "wallabag/tcpdf": "<6.2.22", + "wallabag/wallabag": "<2.6.11", + "wanglelecc/laracms": "<=1.0.3", + "wapplersystems/a21glossary": "<=0.4.10", + "web-auth/webauthn-framework": ">=3.3,<3.3.4|>=4.5,<4.9", + "web-auth/webauthn-lib": ">=4.5,<4.9", + "web-feet/coastercms": "==5.5", + "web-tp3/wec_map": "<3.0.3", + "webbuilders-group/silverstripe-kapost-bridge": "<0.4", + "webcoast/deferred-image-processing": "<1.0.2", + "webklex/laravel-imap": "<5.3", + "webklex/php-imap": "<5.3", + "webpa/webpa": "<3.1.2", + "webreinvent/vaahcms": "<=2.3.1", + "wikibase/wikibase": "<=1.39.3", + "wikimedia/parsoid": "<0.12.2", + "willdurand/js-translation-bundle": "<2.1.1", + "winter/wn-backend-module": "<1.2.4", + "winter/wn-cms-module": "<1.0.476|>=1.1,<1.1.11|>=1.2,<1.2.7", + "winter/wn-dusk-plugin": "<2.1", + "winter/wn-system-module": "<1.2.4", + "wintercms/winter": "<=1.2.3", + "wireui/wireui": "<1.19.3|>=2,<2.1.3", + "woocommerce/woocommerce": "<6.6|>=8.8,<8.8.5|>=8.9,<8.9.3", + "wp-cli/wp-cli": ">=0.12,<2.5", + "wp-graphql/wp-graphql": "<=1.14.5", + "wp-premium/gravityforms": "<2.4.21", + "wpanel/wpanel4-cms": "<=4.3.1", + "wpcloud/wp-stateless": "<3.2", + "wpglobus/wpglobus": "<=1.9.6", + "wwbn/avideo": "<14.3", + "xataface/xataface": "<3", + "xpressengine/xpressengine": "<3.0.15", + "yab/quarx": "<2.4.5", + "yeswiki/yeswiki": "<=4.5.4", + "yetiforce/yetiforce-crm": "<6.5", + "yidashi/yii2cmf": "<=2", + "yii2mod/yii2-cms": "<1.9.2", + "yiisoft/yii": "<1.1.31", + "yiisoft/yii2": "<2.0.52", + "yiisoft/yii2-authclient": "<2.2.15", + "yiisoft/yii2-bootstrap": "<2.0.4", + "yiisoft/yii2-dev": "<=2.0.45", + "yiisoft/yii2-elasticsearch": "<2.0.5", + "yiisoft/yii2-gii": "<=2.2.4", + "yiisoft/yii2-jui": "<2.0.4", + "yiisoft/yii2-redis": "<2.0.20", + "yikesinc/yikes-inc-easy-mailchimp-extender": "<6.8.6", + "yoast-seo-for-typo3/yoast_seo": "<7.2.3", + "yourls/yourls": "<=1.10.2", + "yuan1994/tpadmin": "<=1.3.12", + "yungifez/skuul": "<=2.6.5", + "z-push/z-push-dev": "<2.7.6", + "zencart/zencart": "<=1.5.7.0-beta", + "zendesk/zendesk_api_client_php": "<2.2.11", + "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3", + "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", + "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", + "zendframework/zend-db": "<2.2.10|>=2.3,<2.3.5", + "zendframework/zend-developer-tools": ">=1.2.2,<1.2.3", + "zendframework/zend-diactoros": "<1.8.4", + "zendframework/zend-feed": "<2.10.3", + "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-http": "<2.8.1", + "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6", + "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", + "zendframework/zend-mail": "<2.4.11|>=2.5,<2.7.2", + "zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-session": ">=2,<2.2.9|>=2.3,<2.3.4", + "zendframework/zend-validator": ">=2.3,<2.3.6", + "zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6", + "zendframework/zendframework": "<=3", + "zendframework/zendframework1": "<1.12.20", + "zendframework/zendopenid": "<2.0.2", + "zendframework/zendrest": "<2.0.2", + "zendframework/zendservice-amazon": "<2.0.3", + "zendframework/zendservice-api": "<1", + "zendframework/zendservice-audioscrobbler": "<2.0.2", + "zendframework/zendservice-nirvanix": "<2.0.2", + "zendframework/zendservice-slideshare": "<2.0.2", + "zendframework/zendservice-technorati": "<2.0.2", + "zendframework/zendservice-windowsazure": "<2.0.2", + "zendframework/zendxml": ">=1,<1.0.1", + "zenstruck/collection": "<0.2.1", + "zetacomponents/mail": "<1.8.2", + "zf-commons/zfc-user": "<1.2.2", + "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3", + "zfr/zfr-oauth2-server-module": "<0.1.2", + "zoujingli/thinkadmin": "<=6.1.53" + }, + "default-branch": true, + "type": "metapackage", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "role": "maintainer" + }, + { + "name": "Ilya Tribusean", + "email": "slash3b@gmail.com", + "role": "maintainer" + } + ], + "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", + "keywords": [ + "dev" + ], + "support": { + "issues": "https://github.com/Roave/SecurityAdvisories/issues", + "source": "https://github.com/Roave/SecurityAdvisories/tree/latest" + }, + "funding": [ + { + "url": "https://github.com/Ocramius", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/roave/security-advisories", + "type": "tidelift" + } + ], + "time": "2026-01-30T22:06:58+00:00" + }, + { + "name": "roots/wordpress-no-content", + "version": "6.9", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress.git", + "reference": "6.9" + }, + "dist": { + "type": "zip", + "url": "https://downloads.wordpress.org/release/wordpress-6.9-no-content.zip", + "reference": "6.9", + "shasum": "2d8cb4b253b690e255afde1d451e87380e6a5cb5" + }, + "require": { + "php": ">= 7.2.24" + }, + "provide": { + "wordpress/core-implementation": "6.9" + }, + "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": "2025-12-02T19:09:36+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-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", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:15:17+00:00" + }, + { + "name": "spatie/array-to-xml", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/spatie/array-to-xml.git", + "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/88b2f3852a922dd73177a68938f8eb2ec70c7224", + "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": "^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.2", + "pestphp/pest": "^1.21", + "spatie/pest-plugin-snapshots": "^1.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Spatie\\ArrayToXml\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://freek.dev", + "role": "Developer" + } + ], + "description": "Convert an array to xml", + "homepage": "https://github.com/spatie/array-to-xml", + "keywords": [ + "array", + "convert", + "xml" + ], + "support": { + "source": "https://github.com/spatie/array-to-xml/tree/3.4.4" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-12-15T09:00:41+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.13.5", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "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", + "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" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-11-04T16:30:35+00:00" + }, + { + "name": "symfony/cache", + "version": "v6.4.33", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "5b088fa41eb9568748dc255c45e4054c387ba73b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/5b088fa41eb9568748dc255c45e4054c387ba73b", + "reference": "5b088fa41eb9568748dc255c45e4054c387ba73b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.3.6|^7.0" + }, + "conflict": { + "doctrine/dbal": "<2.13.1", + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/var-dumper": "<5.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^2.13.1|^3|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "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 extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v6.4.33" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-27T15:05:20+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/5d68a57d66910405e5c0b63d6f0af941e66fc868", + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "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 caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.6.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": "2025-03-13T15:25:07+00:00" + }, + { + "name": "symfony/console", + "version": "v6.4.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3", + "reference": "0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "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/v6.4.32" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-13T08:45:59+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "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/v3.6.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-09-25T14:21:43+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v6.4.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "99d7e101826e6610606b9433248f80c1997cd20b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99d7e101826e6610606b9433248f80c1997cd20b", + "reference": "99d7e101826e6610606b9433248f80c1997cd20b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "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": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.32" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-05T11:13:48+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "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 dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.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-09-25T14:21:43+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v6.4.30", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "441c6b69f7222aadae7cbf5df588496d5ee37789" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/441c6b69f7222aadae7cbf5df588496d5ee37789", + "reference": "441c6b69f7222aadae7cbf5df588496d5ee37789", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^5.4|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "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": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v6.4.30" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-26T14:43:45+00:00" + }, + { + "name": "symfony/finder", + "version": "v6.4.33", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "24965ca011dac87431729640feef8bcf7b5523e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/24965ca011dac87431729640feef8bcf7b5523e0", + "reference": "24965ca011dac87431729640feef8bcf7b5523e0", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "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": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v6.4.33" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-26T13:03:48+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v6.4.30", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "eeaa8cabe54c7b3516938c72a4a161c0cc80a34f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/eeaa8cabe54c7b3516938c72a4a161c0cc80a34f", + "reference": "eeaa8cabe54c7b3516938c72a4a161c0cc80a34f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "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": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v6.4.30" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-12T13:06:53+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "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.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "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.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T09:58:17+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "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.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "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.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/process", + "version": "v6.4.33", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "c46e854e79b52d07666e43924a20cb6dc546644e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/c46e854e79b52d07666e43924a20cb6dc546644e", + "reference": "c46e854e79b52d07666e43924a20cb6dc546644e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "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/v6.4.33" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-23T16:02:12+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "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/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T11:30:57+00:00" + }, + { + "name": "symfony/string", + "version": "v6.4.30", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "50590a057841fa6bf69d12eceffce3465b9e32cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/50590a057841fa6bf69d12eceffce3465b9e32cb", + "reference": "50590a057841fa6bf69d12eceffce3465b9e32cb", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.0|^7.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/v6.4.30" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-21T18:03:05+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v6.4.26", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "466fcac5fa2e871f83d31173f80e9c2684743bfc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/466fcac5fa2e871f83d31173f80e9c2684743bfc", + "reference": "466fcac5fa2e871f83d31173f80e9c2684743bfc", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "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": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v6.4.26" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-09-11T09:57:09+00:00" + }, + { + "name": "symfony/yaml", + "version": "v6.4.30", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "8207ae83da19ee3748d6d4f567b4d9a7c656e331" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/8207ae83da19ee3748d6d4f567b4d9a7c656e331", + "reference": "8207ae83da19ee3748d6d4f567b4d9a7c656e331", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0|^7.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "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": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v6.4.30" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-02T11:50:18+00:00" + }, + { + "name": "vimeo/psalm", + "version": "5.26.1", + "source": { + "type": "git", + "url": "https://github.com/vimeo/psalm.git", + "reference": "d747f6500b38ac4f7dfc5edbcae6e4b637d7add0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/d747f6500b38ac4f7dfc5edbcae6e4b637d7add0", + "reference": "d747f6500b38ac4f7dfc5edbcae6e4b637d7add0", + "shasum": "" + }, + "require": { + "amphp/amp": "^2.4.2", + "amphp/byte-stream": "^1.5", + "composer-runtime-api": "^2", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^2.0 || ^3.0", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "ext-ctype": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "felixfbecker/advanced-json-rpc": "^3.1", + "felixfbecker/language-server-protocol": "^1.5.2", + "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "nikic/php-parser": "^4.17", + "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0", + "spatie/array-to-xml": "^2.17.0 || ^3.0", + "symfony/console": "^4.1.6 || ^5.0 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0" + }, + "conflict": { + "nikic/php-parser": "4.17.0" + }, + "provide": { + "psalm/psalm": "self.version" + }, + "require-dev": { + "amphp/phpunit-util": "^2.0", + "bamarni/composer-bin-plugin": "^1.4", + "brianium/paratest": "^6.9", + "ext-curl": "*", + "mockery/mockery": "^1.5", + "nunomaduro/mock-final-classes": "^1.1", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpdoc-parser": "^1.6", + "phpunit/phpunit": "^9.6", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.18", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.6", + "symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-curl": "In order to send data to shepherd", + "ext-igbinary": "^2.0.5 is required, used to serialize caching data" + }, + "bin": [ + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalter" + ], + "type": "project", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev", + "dev-3.x": "3.x-dev", + "dev-4.x": "4.x-dev", + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psalm\\": "src/Psalm/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Brown" + } + ], + "description": "A static analysis tool for finding errors in PHP applications", + "keywords": [ + "code", + "inspection", + "php", + "static analysis" + ], + "support": { + "docs": "https://psalm.dev/docs", + "issues": "https://github.com/vimeo/psalm/issues", + "source": "https://github.com/vimeo/psalm" + }, + "time": "2024-09-08T18:53:08+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.12.1", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^7.2 || ^8.0" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.12.1" + }, + "time": "2025-10-29T15:56:20+00:00" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "3.3.0", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", + "reference": "7795ec6fa05663d716a549d0b44e47ffc8b0d4a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7795ec6fa05663d716a549d0b44e47ffc8b0d4a6", + "reference": "7795ec6fa05663d716a549d0b44e47ffc8b0d4a6", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-libxml": "*", + "ext-tokenizer": "*", + "ext-xmlreader": "*", + "php": ">=7.2", + "phpcsstandards/phpcsextra": "^1.5.0", + "phpcsstandards/phpcsutils": "^1.1.0", + "squizlabs/php_codesniffer": "^3.13.4" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcompatibility/php-compatibility": "^10.0.0@dev", + "phpcsstandards/phpcsdevtools": "^1.2.0", + "phpunit/phpunit": "^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": "2025-11-25T12:08:04+00:00" + }, + { + "name": "wp-hooks/wordpress-core", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/wp-hooks/wordpress-core-hooks.git", + "reference": "610a3721216797daa72a16044c40938ac040551a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-hooks/wordpress-core-hooks/zipball/610a3721216797daa72a16044c40938ac040551a", + "reference": "610a3721216797daa72a16044c40938ac040551a", + "shasum": "" + }, + "replace": { + "johnbillion/wp-hooks": "*" + }, + "require-dev": { + "erusev/parsedown": "1.8.0-beta-7", + "oomphinc/composer-installers-extender": "^2", + "roots/wordpress-core-installer": "^1.0.0", + "roots/wordpress-full": "6.9", + "wp-hooks/generator": "1.0.0" + }, + "type": "library", + "extra": { + "wp-hooks": { + "ignore-files": [ + "wp-admin/includes/deprecated.php", + "wp-admin/includes/ms-deprecated.php", + "wp-content/", + "wp-includes/deprecated.php", + "wp-includes/ID3/", + "wp-includes/ms-deprecated.php", + "wp-includes/pomo/", + "wp-includes/random_compat/", + "wp-includes/Requests/", + "wp-includes/SimplePie/", + "wp-includes/sodium_compat/", + "wp-includes/Text/" + ], + "ignore-hooks": [ + "load-categories.php", + "load-edit-link-categories.php", + "load-edit-tags.php", + "load-page-new.php", + "load-page.php", + "option_enable_xmlrpc", + "edit_post_{$field}", + "pre_post_{$field}", + "post_{$field}", + "pre_option_enable_xmlrpc", + "$page_hook", + "$hook", + "$hook_name" + ] + }, + "wordpress-install-dir": "vendor/wordpress/wordpress" + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-3.0-or-later" + ], + "authors": [ + { + "name": "John Blackbourn", + "homepage": "https://johnblackbourn.com/" + } + ], + "description": "All the actions and filters from WordPress core in machine-readable JSON format.", + "support": { + "issues": "https://github.com/wp-hooks/wordpress-core-hooks/issues", + "source": "https://github.com/wp-hooks/wordpress-core-hooks/tree/1.11.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/johnbillion", + "type": "github" + } + ], + "time": "2025-12-03T22:11:59+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "roave/security-advisories": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1 | ^8.2 | ^8.3 | ^8.4", + "ext-json": "*" + }, + "platform-dev": {}, + "platform-overrides": { + "php": "8.1" + }, + "plugin-api-version": "2.6.0" +} diff --git a/grumphp.yml b/grumphp.yml new file mode 100644 index 0000000..86a9cf2 --- /dev/null +++ b/grumphp.yml @@ -0,0 +1,19 @@ +grumphp: + tasks: + git_blacklist: + keywords: + - 'var_dump(' + whitelist_patterns: [] + triggered_by: ['php'] + regexp_type: G + composer: + no_check_all: true + no_check_publish: true + no_local_repository: true + phplint: + exclude: ['vendor'] + jobs: ~ + triggered_by: ['php'] + phpcs: + standard: ['phpcs.xml'] + triggered_by: [php] diff --git a/includes/Icon/Collection.php b/includes/Icon/Collection.php new file mode 100644 index 0000000..845cc1c --- /dev/null +++ b/includes/Icon/Collection.php @@ -0,0 +1,195 @@ +name = $name; + $this->label = $label ?? $name; + } + + /** + * Get callection's name. + * + * @return string + */ + public function name(): string { + return $this->name; + } + + /** + * Get collection's label. + * + * @return string + */ + public function label(): string { + return $this->label; + } + + /** + * Add a new icon to the collection. + * + * @param CollectionItem $item + */ + public function add( CollectionItem $item ): void { + $this->items[ strtok( $item->name(), '.' ) ] = $item; + } + + /** + * Get a icon. + * + * @param string $name icon's name. + * + * @return CollectionItem|null + */ + public function get( string $name ): ?CollectionItem { + return $this->items[ $name ] ?? null; + } + + /** + * Get all icons in the collection. + * + * @return array + */ + public function all(): array { + return $this->items; + } + + /** + * Get icon count in the collection. + * + * @return int + */ + public function count(): int { + return count( $this->items ); + } + + /** + * Filter the collection. + * + * @param string $query search query. + * + * @return self + */ + public function search( string $query ): self { + $filtered_items = array_filter( + $this->items, + function ( CollectionItem $item ) use ( $query ) { + // Search in both label and name + $label_match = stripos( $item->label(), $query ) !== false; + $name_match = stripos( $item->name(), $query ) !== false; + + return $label_match || $name_match; + } + ); + + $collection = new self( $this->name, $this->label ); + foreach ( $filtered_items as $filtered_item ) { + $collection->add( $filtered_item ); + } + + return $collection; + } + + /** + * @inerhitDoc + */ + #[\ReturnTypeWillChange] + public function current() { + return $this->items[ $this->position ]; + } + + /** + * @inerhitDoc + */ + #[\ReturnTypeWillChange] + public function next() { + ++$this->position; + } + + /** + * @inerhitDoc + */ + #[\ReturnTypeWillChange] + public function key() { + return $this->position; + } + + /** + * @inerhitDoc + */ + #[\ReturnTypeWillChange] + public function valid() { + return isset( $this->items[ $this->position ] ); + } + + /** + * @inerhitDoc + */ + #[\ReturnTypeWillChange] + public function rewind() { + $this->position = 0; + } +} diff --git a/includes/Icon/CollectionCreationException.php b/includes/Icon/CollectionCreationException.php new file mode 100644 index 0000000..c474982 --- /dev/null +++ b/includes/Icon/CollectionCreationException.php @@ -0,0 +1,13 @@ +name = $name; + $this->type = $type; + $this->content = $content; + $this->label = $label ?? $name; + } + + /** + * Get icon's name. + * + * @return string + */ + public function name(): string { + return $this->name; + } + + /** + * Get icon's label. + * + * @return string + */ + public function label(): string { + return $this->label; + } + + /** + * Get icon's type. + * + * @return string + */ + public function type(): string { + return $this->type; + } + + /** + * Get icon's content. + * + * @return string + */ + public function content(): string { + return $this->content; + } +} diff --git a/includes/Icon/CollectionItemsFactory.php b/includes/Icon/CollectionItemsFactory.php new file mode 100644 index 0000000..92c6726 --- /dev/null +++ b/includes/Icon/CollectionItemsFactory.php @@ -0,0 +1,200 @@ +' ); + if ( ! preg_match_all( '/id="(\S+)"/m', strip_tags( $contents, $allowed_tags ), $svg ) ) { + wp_cache_set( $cache_key, [], self::CACHE_GROUP, DAY_IN_SECONDS ); + + return []; + } + + $items = []; + $base_url = str_replace( WP_CONTENT_DIR, WP_CONTENT_URL, $path ); + foreach ( $svg[1] as $name ) { + if ( empty( $name ) ) { + continue; + } + + $name = sanitize_title( $name ); + $item_content = sprintf( '%s#%s', $base_url, $name ); + + $label = $icon_map[ $name ] ?? self::format_svg_name( $name ); + $items[] = new CollectionItem( $name, 'sprite', $item_content, $label ); + } + + wp_cache_set( $cache_key, $items, self::CACHE_GROUP, DAY_IN_SECONDS ); + + return $items; + } + + /** + * Instantiate array of CollectionItem from a file. + * + * @param string $path + * @param array $args + * + * @return CollectionItem[] + * @throws CollectionCreationException + */ + public static function from_file( string $path, array $args = [] ): array { + $cache_key = md5( 'from_file' . $path . serialize( $args ) ); + + if ( ! self::is_cache_disabled() ) { + $found = null; + $items = wp_cache_get( $cache_key, self::CACHE_GROUP, false, $found ); + + if ( $found && is_array( $items ) ) { + return $items; + } + } + + if ( ! is_readable( $path ) ) { + //TODO: create dedicated exception + throw CollectionCreationException::unreadable_path( $path ); + } + + $args = (array) wp_parse_args( + $args, + [ + 'name' => '', + 'label' => '', + ] + ); + + $items = []; + $item_content = file_get_contents( $path ); + if ( ! $item_content ) { + wp_cache_set( $cache_key, $items, self::CACHE_GROUP, DAY_IN_SECONDS ); + + return $items; + } + + $name = $args['name'] ?? pathinfo( $path, PATHINFO_FILENAME ); + $label = $args['label'] ?? $name; + $items[] = new CollectionItem( $name, 'raw', $item_content, $label ); + + wp_cache_set( $cache_key, $items, self::CACHE_GROUP, DAY_IN_SECONDS ); + + return $items; + } + + /** + * Format SVG name from its id attribute. + * + * @param $id + * @param bool $delete_suffix + * + * @return string + */ + private static function format_svg_name( $id, $delete_suffix = true ): string { + // Split up the string based on the `-` character + $ex = explode( '-', $id ); + if ( empty( $ex ) ) { + return $id; + } + + // Delete the first value, as it has no real value for the icon name. + if ( $delete_suffix ) { + unset( $ex[0] ); + } + + // Remix values into one with spaces + $text = implode( ' ', $ex ); + + // Add uppercase to the first word + return ucfirst( $text ); + } +} diff --git a/includes/Rest/CollectionsController.php b/includes/Rest/CollectionsController.php new file mode 100644 index 0000000..d83f462 --- /dev/null +++ b/includes/Rest/CollectionsController.php @@ -0,0 +1,118 @@ + \WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_items' ], + 'permission_callback' => [ $this, 'get_items_permissions_check' ], + 'args' => $this->get_collection_params(), + ], + 'schema' => [ $this, 'get_public_item_schema' ], + ] + ); + } + + public function get_items_permissions_check( $request ) { + return current_user_can( 'edit_posts' ); + } + + /** + * Retrieves all icons collections. + * + * @param \WP_REST_Request $request Full details about the request. + * + * @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_items( $request ) { + $data = []; + $collections = get_icon_collections(); + + foreach ( $collections as $collection ) { + $response_item = $this->prepare_item_for_response( $collection, $request ); + $data[ $collection->name() ] = $this->prepare_response_for_collection( $response_item ); + } + + return rest_ensure_response( $data ); + } + + /** + * @param Collection $item + * @param \WP_REST_Request $request + * + * @return \WP_Error|\WP_REST_Response + */ + public function prepare_item_for_response( $item, $request ) { + $fields = $this->get_fields_for_response( $request ); + $data = []; + + if ( in_array( 'name', $fields, true ) ) { + $data['name'] = $item->name(); + } + + if ( in_array( 'label', $fields, true ) ) { + $data['label'] = $item->label(); + } + + if ( in_array( 'count', $fields, true ) ) { + $data['count'] = $item->count(); + } + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + + return rest_ensure_response( $data ); + } + + /** + * Retrieves the icons collection's schema, conforming to JSON Schema. + * + * @return array Item schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $schema = [ + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'collection', + 'type' => 'object', + 'properties' => [ + 'name' => [ + 'description' => __( 'Identifier of the collection', 'blockparty-icons' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + 'label' => [ + 'description' => __( 'Label of the collection', 'blockparty-icons' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + 'count' => [ + 'description' => __( 'Number of icons in the collection', 'blockparty-icons' ), + 'type' => 'integer', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + ], + ]; + + $this->schema = $schema; + + return $this->add_additional_fields_schema( $this->schema ); + } +} diff --git a/includes/Rest/IconsController.php b/includes/Rest/IconsController.php new file mode 100644 index 0000000..8f41ada --- /dev/null +++ b/includes/Rest/IconsController.php @@ -0,0 +1,213 @@ +collection = $collection; + } + + public function register_routes() { + register_rest_route( + 'icons/v1', + '/' . $this->collection->name(), + [ + [ + 'methods' => \WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_items' ], + 'permission_callback' => [ $this, 'get_permissions_check' ], + 'args' => $this->get_collection_params(), + ], + 'schema' => [ $this, 'get_public_item_schema' ], + ] + ); + + $get_item_args = [ + 'context' => $this->get_context_param( [ 'default' => 'view' ] ), + ]; + register_rest_route( + 'icons/v1', + '/' . $this->collection->name() . '/(?P[\w\-]+)', + [ + 'args' => [ + 'name' => [ + 'description' => __( "Icon's name.", 'blockparty-icons' ), + 'type' => 'string', + ], + ], + [ + 'methods' => \WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_item' ], + 'permission_callback' => [ $this, 'get_permissions_check' ], + 'args' => $get_item_args, + ], + 'schema' => [ $this, 'get_public_item_schema' ], + ] + ); + } + + public function get_permissions_check( $request ) { + return current_user_can( 'edit_posts' ); + } + + /** + * Retrieves all icons collections. + * + * @param \WP_REST_Request $request Full details about the request. + * + * @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_items( $request ) { + $paged = $request->get_param( 'page' ) ?? 1; + $per_page = $request->get_param( 'per_page' ) ?? 10; + $search = $request->get_param( 'search' ) ?? ''; + $paged = absint( $paged ); + $per_page = absint( $per_page ); + $search = trim( $search ); + $data = []; + + $cursor = ( $paged * $per_page ) - $per_page; + + // Get filtered or all items based on search + if ( ! empty( $search ) ) { + $filtered_collection = $this->collection->search( $search ); + $items = $filtered_collection->all(); + $total_count = $filtered_collection->count(); + } else { + $items = $this->collection->all(); + $total_count = $this->collection->count(); + } + + $icons = array_slice( $items, $cursor, $per_page ); + foreach ( $icons as $icon ) { + $response_item = $this->prepare_item_for_response( $icon, $request ); + $data[ $icon->name() ] = $this->prepare_response_for_collection( $response_item ); + } + + $response = rest_ensure_response( $data ); + + $response->header( 'X-WP-Total', $total_count ); + $response->header( 'X-WP-TotalPages', ceil( $total_count / $per_page ) ); + + return $response; + } + + /** + * + * + * @param \WP_REST_Request $request Full details about the request. + * + * @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_item( $request ) { + $icon = $this->collection->get( (string) $request->get_param( 'name' ) ); + if ( null === $icon ) { + return new \WP_Error( + 'rest_icon_invalid_name', + __( 'Invalid icon name.', 'blockparty-icons' ), + [ 'status' => 404 ] + ); + } + + $data = $this->prepare_item_for_response( $icon, $request ); + + return rest_ensure_response( $data ); + } + + /** + * @param CollectionItem $item + * @param \WP_REST_Request $request + * + * @return \WP_Error|\WP_REST_Response + */ + public function prepare_item_for_response( $item, $request ) { + $fields = $this->get_fields_for_response( $request ); + $data = []; + + if ( in_array( 'name', $fields, true ) ) { + $data['name'] = $item->name(); + } + + if ( in_array( 'label', $fields, true ) ) { + $data['label'] = $item->label(); + } + + if ( in_array( 'type', $fields, true ) ) { + $data['type'] = $item->type(); + } + + if ( in_array( 'content', $fields, true ) ) { + $data['content'] = $item->content(); + } + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + + return rest_ensure_response( $data ); + } + + public function get_collection_params() { + $params = parent::get_collection_params(); + $params['per_page']['maximum'] = 500; + + return $params; + } + + + /** + * Retrieves the icons collection's schema, conforming to JSON Schema. + * + * @return array Item schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $schema = [ + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'icon', + 'type' => 'object', + 'properties' => [ + 'name' => [ + 'description' => __( 'Identifier of the icon', 'blockparty-icons' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + 'label' => [ + 'description' => __( 'Label of the icon', 'blockparty-icons' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + 'type' => [ + 'description' => __( 'Type of the icon', 'blockparty-icons' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + 'content' => [ + 'description' => __( 'Content of the icon', 'blockparty-icons' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + ], + ]; + + $this->schema = $schema; + + return $this->add_additional_fields_schema( $this->schema ); + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8c589b7 --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "blockparty-icons", + "version": "1.0.0", + "description": "Provides blocks in WordPress editor to add custom SVG icons from your theme.", + "author": "Be API Technical team", + "license": "GPL-2.0-or-later", + "main": "build/index.js", + "scripts": { + "build": "wp-scripts build", + "format": "wp-scripts format", + "lint:css": "wp-scripts lint-style", + "lint:js": "wp-scripts lint-js", + "packages-update": "wp-scripts packages-update", + "plugin-zip": "wp-scripts plugin-zip", + "start": "wp-scripts start", + "start:env": "wp-env start --config=./wp-env.json", + "stop:env": "wp-env stop --config=./wp-env.json", + "make-pot": "wp i18n make-pot . languages/blockparty-icons.pot --exclude=\"src\" --domain=blockparty-icons", + "make-json": "wp i18n make-json languages/blockparty-icons-fr_FR.po languages/ --no-purge" + }, + "devDependencies": { + "@wordpress/env": "^10.39.0", + "@wordpress/icons": "^10.24.0", + "@wordpress/scripts": "^30.17.0 " + }, + "dependencies": { + "@beapi/icons": "^1.2.5", + "classnames": "^2.5.1", + "html-react-parser": "^5.2.5", + "react": "^18.0.0" + }, + "volta": { + "node": "22.16.0" + } +} diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..773689c --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,62 @@ + + + + Description + + + blockparty-icons.php + ./includes/ + ./views/ + + ./build/ + ./node_modules/ + ./src/ + ./tools/ + ./vendor/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/psalm.xml.dist b/psalm.xml.dist new file mode 100644 index 0000000..9678266 --- /dev/null +++ b/psalm.xml.dist @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/components/custom-appender.js b/src/components/custom-appender.js new file mode 100644 index 0000000..3d44593 --- /dev/null +++ b/src/components/custom-appender.js @@ -0,0 +1,106 @@ +import { __ } from '@wordpress/i18n'; +import { createBlock } from '@wordpress/blocks'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { store as blockEditorStore } from '@wordpress/block-editor'; +import { Icon, plus } from '@wordpress/icons'; +import { IconButton } from '@wordpress/components'; +import { useEffect, useState } from '@wordpress/element'; +import { getCollections, getAllIcons, setStoredIcons } from '../utils'; +import IconModal from '../components/icon-modal'; + +/** + * @param {Object} root0 + * @param {boolean} root0.disabled + * @param {string} root0.rootClientId + */ +function CustomInserter( { disabled, rootClientId } ) { + const [ icons, setIcons ] = useState( null ); + const [ collections, setCollections ] = useState( null ); + const [ isModalVisible, setIsModalVisible ] = useState( false ); + + const { insertBlocks } = useDispatch( blockEditorStore ); + const innerBlockCount = useSelect( + ( select ) => select( blockEditorStore ).getBlockCount( rootClientId ), + [ rootClientId ] + ); + + // Load available collections when the component is mount. + useEffect( () => { + getCollections( { context: 'edit' } ).then( ( items ) => { + setCollections( items ); + } ); + }, [] ); + + // Load available icons each time the collection change. + useEffect( () => { + if ( collections ) { + getAllIcons( { + context: 'edit', + } ).then( ( items ) => { + setIcons( + Object.keys( items ).map( ( item ) => items[ item ] ) + ); + } ); + } + }, [ collections ] ); + + /** + * Insert an icon-item block with the selected icon and close the modal. + * + * @typedef {Object} IconObject + * @property {string} content Icon SVG content + * @property {string} label Icon label + * @property {string} name Icon name + * @property {string} type Icon type + * + * @param {IconObject} selectedIcon Icon object to select + */ + function selectIcon( selectedIcon ) { + setStoredIcons( selectedIcon ); + const { + content: iconContent, + label: iconLabel, + name: iconName, + type: iconType, + } = selectedIcon; + const newBlock = createBlock( 'blockparty/icon', { + content: iconContent, + icon: { label: iconLabel, name: iconName, type: iconType }, + } ); + insertBlocks( newBlock, innerBlockCount, rootClientId ); + setIsModalVisible( false ); + } + + return ( + <> + { isModalVisible && ( + setIsModalVisible( false ) } + handleIconSelectButtonClick={ selectIcon } + /> + ) } + setIsModalVisible( true ) } + disabled={ disabled } + label={ __( 'Add an icon', 'blockparty-icons' ) } + icon={ } + /> + + ); +} + +/** + * @param {Object} root0 + * @param {string} root0.rootClientId Parent block client ID (blockparty/icons). + */ +export default function CustomAppender( { rootClientId } ) { + return ( + + ); +} diff --git a/src/components/icon-content.js b/src/components/icon-content.js new file mode 100644 index 0000000..42ddb86 --- /dev/null +++ b/src/components/icon-content.js @@ -0,0 +1,56 @@ +/** + * WordPress dependencies + */ +import { SVG } from '@wordpress/primitives'; +import { getSpriteUrlWithHash, SvgComponent } from '../utils'; + +export default function IconContent( { + iconData: { content, icon, iconColor, padding, size }, + type, +} ) { + const style = { + color: iconColor, + }; + + if ( padding ) { + style.padding = `${ padding.top } ${ padding.right } ${ padding.bottom } ${ padding.left }`; + } + + if ( type === 'raw' ) { + return ( + + + + ); + } + + let href = content; + + if ( typeof href === 'string' && href.startsWith( ' + + + ); +} diff --git a/src/components/icon-modal.js b/src/components/icon-modal.js new file mode 100644 index 0000000..49ad9e6 --- /dev/null +++ b/src/components/icon-modal.js @@ -0,0 +1,407 @@ +import { useEffect, useState, useCallback } from '@wordpress/element'; +import { + Modal, + RangeControl, + SearchControl, + Spinner, + Button, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { store as preferencesStore } from '@wordpress/preferences'; + +import { capitalize, getIcons } from '../utils'; +import IconSelector from './icon-selector'; + +const ICONS_PER_PAGE = 50; +const PREFERENCES_NAME = 'blockparty-icons'; +const DEFAULT_ICON_PREVIEW_SIZE = 24; + +function IconModal( { collections, onClose, handleIconSelectButtonClick } ) { + const [ isOpen, setOpen ] = useState( true ); + const [ collectionsArr, setCollectionsArr ] = useState( [] ); + const [ searchInput, setSearchInput ] = useState( '' ); + const [ debouncedSearchInput, setDebouncedSearchInput ] = useState( '' ); + const [ loading, setLoading ] = useState( false ); + const [ loadingMore, setLoadingMore ] = useState( false ); + + // Get icon preview size from preferences store + const iconPreviewSize = useSelect( + ( select ) => + select( preferencesStore ).get( + PREFERENCES_NAME, + 'iconPreviewSize' + ) ?? DEFAULT_ICON_PREVIEW_SIZE, + [] + ); + + // Get dispatcher to update preferences + const { set: setPreference } = useDispatch( preferencesStore ); + + /** + * Load icons for a collection with pagination support + * + * @param {string} collectionName Collection name + * @param {number} page Page number (1-indexed) + * @param {string} search Search query + * @return {Promise<{icons: Array, total: number, totalPages: number}>} Promise resolving to icon data + */ + const loadIcons = useCallback( + async ( collectionName, page = 1, search = '' ) => { + const args = { + context: 'edit', + per_page: ICONS_PER_PAGE, + page, + }; + + if ( search ) { + args.search = search; + } + + const result = await getIcons( collectionName, args ); + const items = result.data || result; // Handle both old and new format + const icons = Object.keys( items ).map( ( item ) => items[ item ] ); + + // Get pagination info from headers if available + const headers = result.headers || {}; + const currentPage = headers.totalPages ? page : 1; + const totalPages = headers.totalPages || 1; + const hasMore = + currentPage < totalPages || + ( ! headers.totalPages && icons.length === ICONS_PER_PAGE ); + + return { + icons, + hasMore, + totalPages, + currentPage, + }; + }, + [] + ); + + /** + * Store initial icons for reset when search is cleared + */ + const [ initialCollectionsArr, setInitialCollectionsArr ] = useState( [] ); + + /** + * Load initial icons for all collections + */ + useEffect( () => { + if ( collections ) { + setLoading( true ); + async function getCollectionsArr( collectionsData ) { + const collectionsArray = Object.keys( collectionsData ).map( + ( collection ) => collectionsData[ collection ] + ); + + for ( const collectionItem of collectionsArray ) { + const { icons, hasMore } = await loadIcons( + collectionItem.name, + 1, + '' + ); + + collectionItem.icons = icons; + collectionItem.currentPage = 1; + collectionItem.hasMore = hasMore; + collectionItem.loading = false; + } + + setCollectionsArr( collectionsArray ); + setInitialCollectionsArr( collectionsArray ); + setLoading( false ); + } + + getCollectionsArr( collections ); + } + }, [ collections, loadIcons ] ); + + /** + * Load more icons for a specific collection + */ + const loadMoreIcons = useCallback( + async ( collectionName ) => { + setLoadingMore( true ); + + setCollectionsArr( ( prevCollections ) => { + return prevCollections.map( ( collectionItem ) => { + if ( collectionItem.name !== collectionName ) { + return collectionItem; + } + + const nextPage = ( collectionItem.currentPage || 1 ) + 1; + collectionItem.loading = true; + + loadIcons( + collectionName, + nextPage, + debouncedSearchInput + ).then( ( { icons, hasMore } ) => { + collectionItem.icons = [ + ...collectionItem.icons, + ...icons, + ]; + collectionItem.currentPage = nextPage; + collectionItem.hasMore = hasMore; + collectionItem.loading = false; + setCollectionsArr( ( prev ) => [ ...prev ] ); + setLoadingMore( false ); + } ); + + return collectionItem; + } ); + } ); + }, + [ loadIcons, debouncedSearchInput ] + ); + + /** + * Debounce search input - delay before triggering actual search + */ + useEffect( () => { + const timeoutId = setTimeout( () => { + setDebouncedSearchInput( searchInput ); + }, 500 ); // 500ms delay + + return () => clearTimeout( timeoutId ); + }, [ searchInput ] ); + + /** + * Handle search - use API search instead of client-side filtering + */ + useEffect( () => { + // Only wait for collections metadata, not for initial icons to load + if ( ! collections ) { + return; + } + + // If search is empty, restore initial icons (or wait for them to load) + if ( ! debouncedSearchInput.trim() ) { + if ( initialCollectionsArr.length ) { + setCollectionsArr( initialCollectionsArr ); + } + return; + } + + // Allow search even if initial icons haven't loaded yet + setLoading( true ); + + async function searchIcons() { + const collectionsData = Object.keys( collections ).map( + ( collectionKey ) => collections[ collectionKey ] + ); + + const updatedCollections = await Promise.all( + collectionsData.map( async ( collectionItem ) => { + const { icons, hasMore } = await loadIcons( + collectionItem.name, + 1, + debouncedSearchInput + ); + + return { + ...collectionItem, + icons, + currentPage: 1, + hasMore, + loading: false, + }; + } ) + ); + + setCollectionsArr( updatedCollections ); + setLoading( false ); + } + + searchIcons(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ debouncedSearchInput ] ); + + return ( + <> + { isOpen && ( + { + setOpen( false ); + onClose(); + } } + className="blockparty-icons-modal" + > + { ! collectionsArr.length && } + { !! collectionsArr.length && ( + <> +
+ + { + setPreference( + PREFERENCES_NAME, + 'iconPreviewSize', + newSize + ); + } } + /> +
+
+ { loading && } + { ! loading && + ( () => { + const filteredCollections = + collectionsArr.filter( ( c ) => { + // During search, show all collections (even if empty) + // Otherwise, only show collections with icons or loading + if ( + debouncedSearchInput.trim() + ) { + return true; + } + return ( + c.icons?.length > 0 || + c.loading + ); + } ); + + // Check if any collection has icons + const hasAnyIcons = + filteredCollections.some( + ( c ) => c.icons?.length > 0 + ); + + // Show global "no results" message if searching and no icons found + if ( + debouncedSearchInput.trim() && + ! hasAnyIcons + ) { + return ( +
+

+ { __( + 'No icons found matching your search.', + 'blockparty-icons' + ) } +

+
+ ); + } + + return filteredCollections.map( + ( c ) => ( +
    +
  • +

    + { capitalize( + c.name + ) } +

    + { c.loading && ( + + ) } + { !! c.icons + ?.length && ( + <> +
      + { c.icons.map( + ( + i + ) => ( +
    • + { + handleIconSelectButtonClick( + i + ); + setOpen( + false + ); + onClose(); + } } + /> +
    • + ) + ) } +
    + { c.hasMore && ( +
    + +
    + ) } + + ) } +
  • +
+ ) + ); + } )() } +
+ + ) } +
+ ) } + + ); +} + +export default IconModal; diff --git a/src/components/icon-popover.js b/src/components/icon-popover.js new file mode 100644 index 0000000..07c4b0c --- /dev/null +++ b/src/components/icon-popover.js @@ -0,0 +1,77 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Button, Popover, SearchControl } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import IconSelector from './icon-selector'; + +import { useState } from '@wordpress/element'; +import { getStoredIcons } from '../utils'; + +export default function IconPopover( { + anchor, + handleBrowseAllClick, + handleIconSelectButtonClick, + icons, + placement = 'bottom-start', +} ) { + const [ searchInput, setSearchInput ] = useState( '' ); + const storedIcons = getStoredIcons(); + const popoverIconsList = storedIcons.concat( + icons + .filter( + ( icon ) => + ! storedIcons.some( ( sIcon ) => sIcon.name === icon.name ) + ) + .slice( 0, 9 - storedIcons.length ) + ); + + return ( + <> + + +
    + { popoverIconsList && + popoverIconsList + .filter( + ( icon ) => + ! searchInput.length || + icon.name + .toLowerCase() + .includes( searchInput.toLowerCase() ) + ) + .map( ( icon, index ) => ( +
  • + +
  • + ) ) } +
+ + +
+ + ); +} diff --git a/src/components/icon-selector.js b/src/components/icon-selector.js new file mode 100644 index 0000000..bcca523 --- /dev/null +++ b/src/components/icon-selector.js @@ -0,0 +1,40 @@ +/** + * WordPress dependencies + */ +import { Button } from '@wordpress/components'; + +import IconContent from './icon-content'; +import { capitalize } from '../utils'; + +export default function IconSelector( { + handleIconSelectButtonClick, + icon, + iconColorValue, + padding, + size, +} ) { + return ( + + ); +} diff --git a/src/components/link-url-popover.js b/src/components/link-url-popover.js new file mode 100644 index 0000000..20e24bd --- /dev/null +++ b/src/components/link-url-popover.js @@ -0,0 +1,66 @@ +/** + * WordPress dependencies + */ +import { DELETE, BACKSPACE } from '@wordpress/keycodes'; +import { useDispatch } from '@wordpress/data'; +import { + URLPopover, + URLInput, + store as blockEditorStore, +} from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; +import { Button } from '@wordpress/components'; +import { keyboardReturn } from '@wordpress/icons'; + +export default function SocialLinkURLPopover( { + url, + setAttributes, + setPopover, + popoverAnchor, + clientId, +} ) { + const { removeBlock } = useDispatch( blockEditorStore ); + return ( + setPopover( false ) } + > +
{ + event.preventDefault(); + setPopover( false ); + } } + > +
+ + setAttributes( { url: nextURL } ) + } + placeholder={ __( 'Enter address' ) } + disableSuggestions={ true } + onKeyDown={ ( event ) => { + if ( + !! url || + event.defaultPrevented || + ! [ BACKSPACE, DELETE ].includes( + event.keyCode + ) + ) { + return; + } + removeBlock( clientId ); + } } + /> +
+
+ + ), + orientation: 'horizontal', + directInsert: hasAncestor && innerBlockCount < maxIcons, + } ); + + const colorSettings = [ + { + // Use custom attribute as fallback to prevent loss of named color selection when + // switching themes to a new theme that does not have a matching named color. + value: iconColor, + onChange: ( colorValue ) => { + setAttributes( { iconColor: colorValue } ); + }, + label: __( 'Icons color', 'blockparty-icons' ), + resetAllFilter: () => { + setAttributes( { iconColor: undefined } ); + }, + }, + ]; + const colorGradientSettings = useMultipleOriginColorsAndGradients(); + + // Load available icons each time the collection change. + useEffect( () => { + getAllIcons( { + context: 'edit', + } ).then( ( items ) => { + setIcons( Object.keys( items ).map( ( item ) => items[ item ] ) ); + } ); + }, [] ); + + if ( icons && ! icons.length ) { + return ( + + ); + } + + return ( + <> + + + = maxIcons } + label={ __( 'Add icon', 'blockparty-icons' ) } + /> + + + { isIconModalOpen && ( + setIsIconModalOpen( false ) } + handleIconSelectButtonClick={ handleIconSelect } + /> + ) } + + { colorSettings.map( + ( { + onChange, + label: colorLabel, + value, + resetAllFilter, + } ) => ( + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis + + ) + ) } + +
+ + ); +} diff --git a/src/icons/editor.scss b/src/icons/editor.scss new file mode 100644 index 0000000..2fc8c7f --- /dev/null +++ b/src/icons/editor.scss @@ -0,0 +1,155 @@ +/** + * The following styles get applied inside the editor only. + * + * Replace them with your own styles or remove the file completely. + */ + +.wp-block-blockparty-icons { + &__popover { + .components-popover__content { + width: 350px; + } + + .components-search-control { + padding: 16px 16px 0; + } + } + + &__list-icon { + display: grid; + margin: 0; + padding: 16px; + grid-template-columns: repeat(3, 1fr); + + > li { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } +} + +.blockparty-icons-modal { + .components-modal__content { + padding-bottom: 0; + + &.has-scrolled-content { + .blockparty-icons-modal__toolbar { + margin-inline: -32px; + padding-inline: 32px; + } + + .components-base-control__help { + opacity: 0; + height: 0; + margin: 0; + pointer-events: none; + } + + .blockparty-icons-modal__collection-name { + top: 36px; + } + } + } + .components-spinner { + display: block; + margin: auto; + } + + &__toolbar, + &__collection-name { + background-color: #fff; + } + + &__toolbar { + position: absolute; + z-index: 2; + top: 72px; + left: 32px; + right: 32px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 24px; + transition: margin-inline 300ms ease-in-out, padding-inline 300ms ease-in-out; + + .components-search-control { + flex: 1; + min-width: 0; + } + + .components-range-control { + flex-shrink: 0; + width: 200px; + margin-bottom: 0; + .components-base-control__label{ + width: 1px; + height: 100%; + overflow: hidden; + background-clip: content-box; + position: absolute; + } + } + } + + .components-base-control__help { + transition: opacity 100ms ease-in-out; + } + + &__collection-name { + position: sticky; + z-index: 1; + top: 100px; + margin-inline: -32px; + padding: 10px 32px; + border-bottom: 1px solid #ddd; + transition: top 300ms ease-in-out; + } + + &__collections-list { + margin-top: 100px; + min-width: 621px; + } + + &__list-icon { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 12px; + + > li { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + .block-editor-block-icon { + width: 100%; + } + + svg { + max-height: none; + max-width: none; + min-height: 8px; + min-width: 8px; + } + + } + + &__load-more { + text-align: center; + } + + @media(min-width: 600px) { + // Override core style to avoid the modal to become too small during search and make it moving. + width: 1480px; + max-width: calc(100% - 32px); + min-height: 400px; + } + + @media(min-width: 1280px) { + &__list-icon { + grid-template-columns: repeat(6, 1fr); + } + } +} diff --git a/src/icons/index.js b/src/icons/index.js new file mode 100644 index 0000000..074196c --- /dev/null +++ b/src/icons/index.js @@ -0,0 +1,52 @@ +/** + * Registers a new block provided a unique name and an object defining its behavior. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/ + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files. + * All files containing `style` keyword are bundled together. The code used + * gets applied both to the front of your site and to the editor. + * + * @see https://www.npmjs.com/package/@wordpress/scripts#using-css + */ +import './style.scss'; + +/** + * Internal dependencies + */ +import save from './save'; +import edit from './edit'; +import deprecated from './deprecated'; +import metadata from './block.json'; + +/** + * External dependencies + */ +import { Icon, shapes } from '@beapi/icons'; + +/** + * Every block starts by registering a new block type definition. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/ + */ +registerBlockType( metadata, { + icon: , + + /** + * @see ./edit.js + */ + edit, + + /** + * @see ./save.js + */ + save, + + /** + * @see ./deprecated.js + */ + deprecated, +} ); diff --git a/src/icons/save.js b/src/icons/save.js new file mode 100644 index 0000000..b0f6341 --- /dev/null +++ b/src/icons/save.js @@ -0,0 +1,23 @@ +/** + * React hook that is used to mark the block wrapper element. + * It provides all the necessary props like the class name. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops + */ +import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor'; + +/** + * The save function defines the way in which the different attributes should + * be combined into the final markup, which is then serialized by the block + * editor into `post_content`. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#save + * + * @return {Element} Element to render. + */ +export default function save() { + const blockProps = useBlockProps.save(); + const innerBlocksProps = useInnerBlocksProps.save( blockProps ); + + return
; +} diff --git a/src/icons/style.scss b/src/icons/style.scss new file mode 100644 index 0000000..651ff03 --- /dev/null +++ b/src/icons/style.scss @@ -0,0 +1,37 @@ +/** + * The following styles get applied both on the front of your site + * and in the editor. + * + * Replace them with your own styles or remove the file completely. + */ + +:root { + --blockparty-icon-list-row-gap: 24px; + --blockparty-icon-list-column-gap: 24px; +} + +.wp-block-blockparty-icons { + display: flex; + justify-content: flex-start; + list-style: none !important; + gap: var(--blockparty-icon-list-row-gap, 24px) var(--blockparty-icon-list-column-gap, 24px); + padding-left: 0 !important; + + &__prompt { + cursor: default; + font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif; + font-size: 13px; + line-height: 24px; + list-style: none; + margin-bottom: auto; + margin-top: auto; + min-height: 24px; + order: 2; + padding-right: 8px; + } + + .wp-block-blockparty-icon { + margin-left: 0 !important; + margin-right: 0 !important; + } +} diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..c056cf7 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,536 @@ +import { + Circle, + G, + Line, + Path, + Polygon, + Rect, + Defs, + RadialGradient, + LinearGradient, + Stop, + SVG, +} from '@wordpress/primitives'; +import apiFetch from '@wordpress/api-fetch'; +import { addQueryArgs } from '@wordpress/url'; +import { select } from '@wordpress/data'; + +/** + * Cache configuration + */ +const CACHE_CONFIG = { + STORAGE_KEY: '@beapi/icons-block-cache', + TTL: 30 * 60 * 1000, // 30 minutes in milliseconds +}; + +/** + * Cache utilities + */ + +/** + * Get cache key for API requests + * + * @param {string} endpoint The API endpoint + * @param {Object} args Query arguments + * @return {string} Cache key + */ +function getCacheKey( endpoint, args = {} ) { + const argsString = JSON.stringify( args ); + return `${ endpoint }_${ argsString }`; +} + +/** + * Get cached data from sessionStorage + * + * @param {string} key Cache key + * @return {object|null} Cached data or null if not found/expired + */ +function getCachedData( key ) { + try { + if ( typeof window === 'undefined' || ! window.sessionStorage ) { + return null; + } + + const cached = window.sessionStorage.getItem( key ); + if ( ! cached ) { + return null; + } + + const { data, timestamp } = JSON.parse( cached ); + const now = Date.now(); + + // Check if cache is expired + if ( now - timestamp > CACHE_CONFIG.TTL ) { + window.sessionStorage.removeItem( key ); + return null; + } + + return data; + } catch ( error ) { + // Silently handle cache read errors + return null; + } +} + +/** + * Set data in sessionStorage cache + * + * @param {string} key Cache key + * @param {any} data Data to cache + */ +function setCachedData( key, data ) { + try { + if ( typeof window === 'undefined' || ! window.sessionStorage ) { + return; + } + + const cacheData = { + data, + timestamp: Date.now(), + }; + + window.sessionStorage.setItem( key, JSON.stringify( cacheData ) ); + } catch ( error ) { + // Silently handle cache write errors + } +} + +/** + * Clear all cached data + */ +export function clearIconCache() { + try { + if ( typeof window === 'undefined' || ! window.sessionStorage ) { + return; + } + + const keys = Object.keys( window.sessionStorage ); + keys.forEach( ( key ) => { + if ( key.startsWith( CACHE_CONFIG.STORAGE_KEY ) ) { + window.sessionStorage.removeItem( key ); + } + } ); + } catch ( error ) { + // Silently handle cache clear errors + } +} + +/** + * Get cache statistics + * + * @return {Object} Cache statistics + */ +export function getCacheStats() { + try { + if ( typeof window === 'undefined' || ! window.sessionStorage ) { + return { total: 0, expired: 0, valid: 0 }; + } + + const keys = Object.keys( window.sessionStorage ); + const cacheKeys = keys.filter( ( key ) => + key.startsWith( CACHE_CONFIG.STORAGE_KEY ) + ); + + let expired = 0; + let valid = 0; + const now = Date.now(); + + cacheKeys.forEach( ( key ) => { + try { + const cached = window.sessionStorage.getItem( key ); + if ( cached ) { + const { timestamp } = JSON.parse( cached ); + if ( now - timestamp > CACHE_CONFIG.TTL ) { + expired++; + } else { + valid++; + } + } + } catch ( error ) { + expired++; + } + } ); + + return { + total: cacheKeys.length, + expired, + valid, + }; + } catch ( error ) { + // Silently handle cache stats errors + return { total: 0, expired: 0, valid: 0 }; + } +} + +/** + * Capitalize string and replace all hyphens with blank space + * + * @param {string} str source + * + * @return {string} Capitalized string with hyphens replaced by spaces + */ +export function capitalize( str ) { + if ( typeof str === 'undefined' ) { + return ''; + } + + if ( ! str.length ) { + return str; + } + + return ( str[ 0 ].toUpperCase() + str.slice( 1 ) ).replaceAll( '-', ' ' ); +} + +/** + * Get icons collections with cache + * + * @param {Object} args Query arguments + * @return {Promise} Promise resolving to collections object + */ +export async function getCollections( args = {} ) { + const cacheKey = getCacheKey( 'collections', args ); + + // Try to get from cache first + const cached = getCachedData( cacheKey ); + if ( cached ) { + return cached; + } + + // If not in cache, fetch from API + const path = addQueryArgs( 'icons/v1/collections', args ); + const collections = await apiFetch( { path } ); + + // Store in cache + setCachedData( cacheKey, collections ); + + return collections; +} + +/** + * Get icon data with cache + * + * @param {string} collection icon collection + * @param {Object} args Query arguments + * @return {Promise<{data: object, headers: object}>} Promise resolving to icon data with pagination headers + */ +export async function getIcons( collection, args = {} ) { + const cacheKey = getCacheKey( `icons_${ collection }`, args ); + + // Try to get from cache first + const cached = getCachedData( cacheKey ); + if ( cached ) { + return cached; + } + + // If not in cache, fetch from API + const pathBase = `icons/v1/${ collection }`; + + // Use fetch to access response headers for pagination info + // WordPress REST API settings + const wpApiSettings = window.wpApiSettings || {}; + const restUrl = wpApiSettings.root || '/wp-json/'; + const restNonce = wpApiSettings.nonce || ''; + + // With index.php?rest_route= (e.g. WP Env), query params must use & not ? + // so we get ?rest_route=/icons/v1/Bootstrap&context=edit&per_page=50 + const isRestRouteFormat = restUrl.includes( 'rest_route' ); + const path = isRestRouteFormat + ? pathBase + : addQueryArgs( pathBase, args ); + const queryString = isRestRouteFormat ? addQueryArgs( '', args ).replace( /^\?/, '' ) : ''; + const url = + restUrl.replace( /\/$/, '' ) + + '/' + + path.replace( /^\//, '' ) + + ( queryString ? '&' + queryString : '' ); + + const response = await fetch( url, { + method: 'GET', + headers: { + 'X-WP-Nonce': restNonce, + 'Content-Type': 'application/json', + }, + credentials: 'include', + } ); + + if ( ! response.ok ) { + throw new Error( `API request failed: ${ response.status }` ); + } + + const icons = await response.json(); + + // Extract pagination headers + const headers = { + total: parseInt( response.headers.get( 'X-WP-Total' ) || '0', 10 ), + totalPages: parseInt( + response.headers.get( 'X-WP-TotalPages' ) || '0', + 10 + ), + }; + + const result = { + data: icons, + headers, + }; + + // Store in cache + setCachedData( cacheKey, result ); + + return result; +} + +/** + * Get all icon data with cache + * + * @param {Object} args Query arguments + * @return {Promise} Promise resolving to array of all icons + */ +export async function getAllIcons( args = {} ) { + const cacheKey = getCacheKey( 'all_icons', args ); + + // Try to get from cache first + const cached = getCachedData( cacheKey ); + if ( cached ) { + return cached; + } + + // If not in cache, fetch from API + const collections = await getCollections( args ); + const icons = []; + + for ( const key of Object.keys( collections ) ) { + const collection = collections[ key ]; + const result = await getIcons( collection.name, args ); + const nextIcons = result.data || result; // Handle both old and new format + + icons.push( + Object.keys( nextIcons ).map( ( icon ) => nextIcons[ icon ] ) + ); + } + + const flatIcons = icons.flat(); + + // Store in cache + setCachedData( cacheKey, flatIcons ); + + return flatIcons; +} + +export const SvgComponent = ( { svgText, size, style } ) => { + const parseSvg = ( svgTextContent ) => { + const regex = /<([a-z]+)([^>]*)\/?>/g; + const matches = svgTextContent.matchAll( regex ); + let svgAttributes = {}; + const svgElements = Array.from( matches, ( match, index ) => { + const [ , tagName, attributes ] = match; + const parsedAttributes = ( + attributes.match( /([a-zA-Z][a-zA-Z-]*)="([^"]*)"/g ) || [] + ) + .filter( + ( attr ) => + ! attr.includes( 'style' ) && ! attr.includes( 'class' ) + ) + .map( ( attr ) => { + const [ name, value ] = attr.split( '=' ); + + return { + name: + name.includes( '-' ) && ! name.includes( 'aria' ) + ? name.replace( /-([a-z])/g, ( _, letter ) => + letter.toUpperCase() + ) + : name, + value: value.slice( 1, -1 ), + }; + } ) + .reduce( + ( acc, { name, value } ) => ( { ...acc, [ name ]: value } ), + {} + ); + + if ( tagName.toLowerCase() === 'svg' ) { + // Si la balise est , conserve ses attributs pour l'utiliser comme attributs de la balise parente + svgAttributes = parsedAttributes; + return null; // Ignore la balise en tant que composant React + } + + // Convertissons les balises en composants React + const DynamicComponent = ( { + tagName: componentTagName, + ...props + } ) => { + switch ( componentTagName ) { + case 'circle': + return ; + case 'g': + return ; + case 'line': + return ; + case 'path': + return ; + case 'polygon': + return ; + case 'rect': + return ; + case 'defs': + return ; + case 'radialGradient': + return ; + case 'linearGradient': + return ; + case 'stop': + return ; + } + }; + + return ( + + ); + } ); + + return { svgAttributes, svgElements }; + }; + + const { svgAttributes, svgElements } = parseSvg( svgText ); + + svgAttributes.style = { ...svgAttributes.style, ...style }; + svgAttributes.width = size; + svgAttributes.height = size; + svgAttributes.viewBox = svgAttributes.viewBox || '0 0 24 24'; + + return { svgElements }; +}; + +/** + * Update icons localStorage + * + * @typedef {Object} IconObject + * @property {string} content Icon SVG content + * @property {string} label Icon label + * @property {string} name Icon name + * @property {string} type Icon type + * + * @param {IconObject} icon Icon object to store + */ +export function setStoredIcons( icon ) { + const STORAGE_KEY = `@beapi/icons-block`; + const storeIcons = JSON.parse( + window.localStorage.getItem( STORAGE_KEY ) || '[]' + ); + + if ( ! storeIcons.some( ( i ) => i.name === icon.name ) ) { + storeIcons.unshift( icon ); + } + + if ( storeIcons.length > 9 ) { + storeIcons.pop(); + } + + window.localStorage.setItem( STORAGE_KEY, JSON.stringify( storeIcons ) ); +} + +/** + * Get icons localStorage + * + * @return {IconObject[]} Array of stored icon objects + */ +export function getStoredIcons() { + try { + if ( typeof window === 'undefined' || ! window.localStorage ) { + return []; + } + const storedData = window.localStorage.getItem( `@beapi/icons-block` ); + if ( ! storedData ) { + return []; + } + const parsedData = JSON.parse( storedData ); + return Array.isArray( parsedData ) ? parsedData : []; + } catch ( error ) { + return []; + } +} + +/** + * Append cache-busting hash to sprite URL when sprite hashes are available (from PHP). + * Used in the icon selector/modal so previews use the same cache-busted URLs as the front. + * + * @param {string} spriteUrl Full sprite URL (e.g. https://example.com/.../dist/icons/social.svg#icon-id). + * @return {string} URL with ?v=hash if hash found, unchanged otherwise. + */ +export function getSpriteUrlWithHash( spriteUrl ) { + const hashes = + typeof window !== 'undefined' && + window.blockpartyIconsConfig?.spriteHashes; + if ( ! hashes || typeof spriteUrl !== 'string' ) { + return spriteUrl; + } + try { + const url = new URL( spriteUrl, window.location.origin ); + const path = url.pathname || ''; + const fragment = url.hash || ''; + // Match JSON keys like "icons/social.svg" - path segment after "dist/". + const distMatch = path.match( /\/dist\/(.+)$/ ); + const key = distMatch + ? distMatch[ 1 ] + : path.split( '/' ).filter( Boolean ).slice( -2 ).join( '/' ); + const hash = hashes[ key ]; + if ( ! hash ) { + return spriteUrl; + } + url.searchParams.set( 'v', hash ); + return url.toString(); + } catch { + return spriteUrl; + } +} + +/** + * Check if block icons has ancestors block registred in const ANCESTORS_BLOCKS + * + * @param {string} clientId block client id + * @param {('blockparty/icons'|'blockparty/icon')} name block name + * + * @return {boolean} True if block has ancestor blocks + */ +export function hasBlockAncestors( clientId, name ) { + if ( name !== 'blockparty/icons' && name !== 'blockparty/icon' ) { + return false; + } + + const ICON_BLOCK_NAME = 'blockparty/icons'; + const ANCESTORS_BLOCKS = [ + 'beapi/accordion-summary', + 'beapi/mega-menu-list-item-head', + 'beapi/tabs-nav-item', + ]; + const parentBlock = + name !== ICON_BLOCK_NAME + ? select( 'core/block-editor' ).getBlockParentsByBlockName( + clientId, + ICON_BLOCK_NAME + ) + : [ clientId ]; + const parentBlocks = select( 'core/block-editor' ).getBlockParents( + parentBlock[ 0 ] + ); + const parentAttributes = + select( 'core/block-editor' ).getBlocksByClientId( parentBlocks ); + + return parentAttributes + .map( ( parentAttribute ) => ( { + ...{ + blockName: parentAttribute.name, + innerBlocks: parentAttribute.innerBlocks, + }, + } ) ) + .some( + ( { blockName, innerBlocks } ) => + innerBlocks.some( + ( innerBlock ) => innerBlock.name === ICON_BLOCK_NAME + ) && ANCESTORS_BLOCKS.includes( blockName ) + ); +} diff --git a/views/icon-item-raw.php b/views/icon-item-raw.php new file mode 100644 index 0000000..3d100c4 --- /dev/null +++ b/views/icon-item-raw.php @@ -0,0 +1,99 @@ + array( + 'xmlns' => true, + 'width' => true, + 'height' => true, + 'viewbox' => true, + 'viewBox' => true, + 'class' => true, + 'style' => true, + 'fill' => true, + 'stroke' => true, + 'aria-hidden' => true, + 'focusable' => true, + 'role' => true, + ), + 'path' => array( + 'd' => true, + 'fill' => true, + 'stroke' => true, + 'class' => true, + ), + 'circle' => array( + 'cx' => true, + 'cy' => true, + 'r' => true, + 'fill' => true, + 'stroke' => true, + 'class' => true, + ), + 'rect' => array( + 'x' => true, + 'y' => true, + 'width' => true, + 'height' => true, + 'fill' => true, + 'stroke' => true, + 'class' => true, + ), + 'g' => array( 'class' => true ), + 'defs' => array(), + 'use' => array( 'href' => true, 'xlink:href' => true ), +); + +$content_safe = wp_kses( $content, $allowed_svg ); + +// Merge our class with existing class on the opening to avoid duplicate attribute. +$block_class = 'wp-block-blockparty-icons__icon-component wp-block-blockparty-icons__icon-component--raw'; +if ( ! empty( $icon['name'] ) ) { + $block_class .= ' ' . $icon['name']; +} +if ( preg_match( '/]*\bclass=(["\'])([^"\']*)\1/', $content_safe, $class_match ) ) { + $content_safe = preg_replace( '/]*)\bclass=(["\'])([^"\']*)\2/', ' tag. +$extra_attrs = ' aria-hidden="true" focusable="false"'; +if ( ! empty( $icon_style ) ) { + $extra_attrs .= ' style="' . esc_attr( $icon_style ) . '"'; +} +if ( is_string( $block_class ) && strpos( $block_class, '=' ) !== false ) { + $extra_attrs .= $block_class; +} +$content_safe = preg_replace( '/ + diff --git a/views/icon-item.php b/views/icon-item.php new file mode 100644 index 0000000..11b339b --- /dev/null +++ b/views/icon-item.php @@ -0,0 +1,54 @@ + +
> + + > + + + + + + +

+ +
From 14e7c1ef8e6155d87bb907d6be49369fa86c6694 Mon Sep 17 00:00:00 2001 From: mricoul Date: Mon, 2 Feb 2026 11:58:00 +0100 Subject: [PATCH 02/60] refactor: icon block to standalone block Removes the parent "icons" block and associated components. Changes the "icon" block to be a standalone block, allowing for more flexible usage. Updates the block editor to reflect these changes, removing unnecessary dependencies and code related to the parent "icons" block. Moves and renames editor styles to align with the new block structure. --- blockparty-icons.php | 3 - src/components/custom-appender.js | 106 ------- src/components/icon-popover.js | 77 ----- src/icon/block.json | 2 - src/icon/edit.js | 82 ++---- src/icon/editor.scss | 127 ++++++++ src/icon/style.scss | 42 +-- src/icons/block.json | 49 ---- src/icons/deprecated.js | 467 ------------------------------ src/icons/edit.js | 232 --------------- src/icons/editor.scss | 155 ---------- src/icons/index.js | 52 ---- src/icons/save.js | 23 -- src/icons/style.scss | 37 --- src/utils.js | 48 --- views/icon-item-sprite.php | 2 +- views/icon-item.php | 24 +- 17 files changed, 185 insertions(+), 1343 deletions(-) delete mode 100644 src/components/custom-appender.js delete mode 100644 src/components/icon-popover.js delete mode 100644 src/icons/block.json delete mode 100644 src/icons/deprecated.js delete mode 100644 src/icons/edit.js delete mode 100644 src/icons/editor.scss delete mode 100644 src/icons/index.js delete mode 100644 src/icons/save.js delete mode 100644 src/icons/style.scss diff --git a/blockparty-icons.php b/blockparty-icons.php index cf2519a..35bd893 100644 --- a/blockparty-icons.php +++ b/blockparty-icons.php @@ -41,18 +41,15 @@ function init() { // Load available translations. load_plugin_textdomain( 'blockparty-icons', false, dirname( BLOCKPARTY_ICONS_PLUGIN_BASENAME ) . '/languages' ); - register_block_type( __DIR__ . '/build/icons' ); register_block_type( __DIR__ . '/build/icon', [ 'render_callback' => __NAMESPACE__ . '\\render_callback' ] ); // Load translations for JS - wp_set_script_translations( 'blockparty-icons-editor-script', 'blockparty-icons', BLOCKPARTY_ICONS_DIR . '/languages' ); wp_set_script_translations( 'blockparty-icon-editor-script', 'blockparty-icons', BLOCKPARTY_ICONS_DIR . '/languages' ); // Expose sprite hashes to editor for cache busting in icon selector/modal. $sprite_hashes = get_sprite_hashes_for_script(); if ( ! empty( $sprite_hashes ) ) { $config = [ 'spriteHashes' => $sprite_hashes ]; - wp_localize_script( 'blockparty-icons-editor-script', 'blockpartyIconsConfig', $config ); wp_localize_script( 'blockparty-icon-editor-script', 'blockpartyIconsConfig', $config ); } diff --git a/src/components/custom-appender.js b/src/components/custom-appender.js deleted file mode 100644 index 3d44593..0000000 --- a/src/components/custom-appender.js +++ /dev/null @@ -1,106 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import { createBlock } from '@wordpress/blocks'; -import { useDispatch, useSelect } from '@wordpress/data'; -import { store as blockEditorStore } from '@wordpress/block-editor'; -import { Icon, plus } from '@wordpress/icons'; -import { IconButton } from '@wordpress/components'; -import { useEffect, useState } from '@wordpress/element'; -import { getCollections, getAllIcons, setStoredIcons } from '../utils'; -import IconModal from '../components/icon-modal'; - -/** - * @param {Object} root0 - * @param {boolean} root0.disabled - * @param {string} root0.rootClientId - */ -function CustomInserter( { disabled, rootClientId } ) { - const [ icons, setIcons ] = useState( null ); - const [ collections, setCollections ] = useState( null ); - const [ isModalVisible, setIsModalVisible ] = useState( false ); - - const { insertBlocks } = useDispatch( blockEditorStore ); - const innerBlockCount = useSelect( - ( select ) => select( blockEditorStore ).getBlockCount( rootClientId ), - [ rootClientId ] - ); - - // Load available collections when the component is mount. - useEffect( () => { - getCollections( { context: 'edit' } ).then( ( items ) => { - setCollections( items ); - } ); - }, [] ); - - // Load available icons each time the collection change. - useEffect( () => { - if ( collections ) { - getAllIcons( { - context: 'edit', - } ).then( ( items ) => { - setIcons( - Object.keys( items ).map( ( item ) => items[ item ] ) - ); - } ); - } - }, [ collections ] ); - - /** - * Insert an icon-item block with the selected icon and close the modal. - * - * @typedef {Object} IconObject - * @property {string} content Icon SVG content - * @property {string} label Icon label - * @property {string} name Icon name - * @property {string} type Icon type - * - * @param {IconObject} selectedIcon Icon object to select - */ - function selectIcon( selectedIcon ) { - setStoredIcons( selectedIcon ); - const { - content: iconContent, - label: iconLabel, - name: iconName, - type: iconType, - } = selectedIcon; - const newBlock = createBlock( 'blockparty/icon', { - content: iconContent, - icon: { label: iconLabel, name: iconName, type: iconType }, - } ); - insertBlocks( newBlock, innerBlockCount, rootClientId ); - setIsModalVisible( false ); - } - - return ( - <> - { isModalVisible && ( - setIsModalVisible( false ) } - handleIconSelectButtonClick={ selectIcon } - /> - ) } - setIsModalVisible( true ) } - disabled={ disabled } - label={ __( 'Add an icon', 'blockparty-icons' ) } - icon={ } - /> - - ); -} - -/** - * @param {Object} root0 - * @param {string} root0.rootClientId Parent block client ID (blockparty/icons). - */ -export default function CustomAppender( { rootClientId } ) { - return ( - - ); -} diff --git a/src/components/icon-popover.js b/src/components/icon-popover.js deleted file mode 100644 index 07c4b0c..0000000 --- a/src/components/icon-popover.js +++ /dev/null @@ -1,77 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { Button, Popover, SearchControl } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import IconSelector from './icon-selector'; - -import { useState } from '@wordpress/element'; -import { getStoredIcons } from '../utils'; - -export default function IconPopover( { - anchor, - handleBrowseAllClick, - handleIconSelectButtonClick, - icons, - placement = 'bottom-start', -} ) { - const [ searchInput, setSearchInput ] = useState( '' ); - const storedIcons = getStoredIcons(); - const popoverIconsList = storedIcons.concat( - icons - .filter( - ( icon ) => - ! storedIcons.some( ( sIcon ) => sIcon.name === icon.name ) - ) - .slice( 0, 9 - storedIcons.length ) - ); - - return ( - <> - - -
    - { popoverIconsList && - popoverIconsList - .filter( - ( icon ) => - ! searchInput.length || - icon.name - .toLowerCase() - .includes( searchInput.toLowerCase() ) - ) - .map( ( icon, index ) => ( -
  • - -
  • - ) ) } -
- - -
- - ); -} diff --git a/src/icon/block.json b/src/icon/block.json index 7dc9faf..f56538c 100644 --- a/src/icon/block.json +++ b/src/icon/block.json @@ -6,7 +6,6 @@ "title": "Icon", "category": "widgets", "description": "Easily add custom vector icon to your content.", - "parent": [ "blockparty/icons" ], "supports": { "reusable": false, "html": false, @@ -59,7 +58,6 @@ "type": "string" } }, - "usesContext": [ "icon", "iconColor" ], "textdomain": "blockparty-icons", "editorScript": "file:./index.js", "editorStyle": "file:./index.css", diff --git a/src/icon/edit.js b/src/icon/edit.js index 8bd73e1..2479322 100644 --- a/src/icon/edit.js +++ b/src/icon/edit.js @@ -54,7 +54,6 @@ import { capitalize, getAllIcons, getCollections, - hasBlockAncestors, setStoredIcons, } from '../utils'; import { link, linkOff, replace, trash } from '@wordpress/icons'; @@ -86,9 +85,7 @@ const MIN_SIZE = 8; * @param {string} root0.attributes.text * @param {string} root0.attributes.url * @param {string} root0.clientId - * @param {Object} root0.context * @param {boolean} root0.isSelected - * @param {string} root0.name * @param {Function} root0.setAttributes * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit * @@ -97,9 +94,7 @@ const MIN_SIZE = 8; export default function Edit( { attributes, clientId, - context, isSelected, - name, setAttributes, } ) { const { @@ -114,7 +109,6 @@ export default function Edit( { url, } = attributes; - const hasAncestor = hasBlockAncestors( clientId, name ); const blockRef = useRef( null ); const [ icons, setIcons ] = useState( null ); const [ iconColor, setIconColor ] = useState( iconColorValue ); @@ -185,24 +179,13 @@ export default function Edit( { const colorSettings = [ { - // Use custom attribute as fallback to prevent loss of named color selection when - // switching themes to a new theme that does not have a matching named color. - value: iconColorValue || context.iconColor, + value: iconColorValue, onChange: ( colorValue ) => { setIconColor( colorValue ); setAttributes( { iconColorValue: colorValue } ); }, label: __( 'Icon color', 'blockparty-icons' ), resetAllFilter: () => { - if ( context?.iconColor ) { - setIconColor( context?.iconColor ); - setAttributes( { - iconColorValue: context?.iconColor, - } ); - - return; - } - setIconColor( undefined ); setAttributes( { iconColorValue: undefined } ); }, @@ -211,26 +194,6 @@ export default function Edit( { const colorGradientSettings = useMultipleOriginColorsAndGradients(); const style = {}; - useEffect( () => { - if ( context?.icon && ( ! content || ! icon ) ) { - setAttributes( { - content: context?.icon?.content, - icon: { - label: context?.icon?.label, - name: context?.icon?.name, - type: context?.icon?.type, - }, - } ); - } - - if ( ! iconColor ) { - setAttributes( { - iconColorValue: context?.iconColor, - } ); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ context ] ); - // Load available collections when the component is mount. useEffect( () => { getCollections( { context: 'edit' } ).then( ( items ) => { @@ -265,7 +228,7 @@ export default function Edit( { } return ( -
+
{ isModalVisible && ( setAttributes( { text: value } ) @@ -396,26 +362,24 @@ export default function Edit( {
- { ! hasAncestor && ( - { - setAttributes( { size: newSize } ); - } } - initialPosition={ DEFAULT_SIZE } - min={ MIN_SIZE } - max={ MAX_SIZE } - allowReset={ true } - withInputField={ true } - renderTooltipContent={ ( value ) => - `${ value }px` - } - /> - ) } + { + setAttributes( { size: newSize } ); + } } + initialPosition={ DEFAULT_SIZE } + min={ MIN_SIZE } + max={ MAX_SIZE } + allowReset={ true } + withInputField={ true } + renderTooltipContent={ ( value ) => + `${ value }px` + } + />
diff --git a/src/icon/editor.scss b/src/icon/editor.scss index e8ba981..a1e2226 100644 --- a/src/icon/editor.scss +++ b/src/icon/editor.scss @@ -5,6 +5,8 @@ */ .wp-block-blockparty-icon { + overflow: hidden; + .components-button { width: 100%; height: auto; @@ -47,3 +49,128 @@ stroke: currentColor; } } + +.blockparty-icons-modal { + .components-modal__content { + padding-bottom: 0; + + &.has-scrolled-content { + .blockparty-icons-modal__toolbar { + margin-inline: -32px; + padding-inline: 32px; + } + + .components-base-control__help { + opacity: 0; + height: 0; + margin: 0; + pointer-events: none; + } + + .blockparty-icons-modal__collection-name { + top: 36px; + } + } + } + .components-spinner { + display: block; + margin: auto; + } + + &__toolbar, + &__collection-name { + background-color: #fff; + } + + &__toolbar { + position: absolute; + z-index: 2; + top: 72px; + left: 32px; + right: 32px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 24px; + transition: margin-inline 300ms ease-in-out, padding-inline 300ms ease-in-out; + + .components-search-control { + flex: 1; + min-width: 0; + } + + .components-range-control { + flex-shrink: 0; + width: 200px; + margin-bottom: 0; + .components-base-control__label{ + width: 1px; + height: 100%; + overflow: hidden; + background-clip: content-box; + position: absolute; + } + } + } + + .components-base-control__help { + transition: opacity 100ms ease-in-out; + } + + &__collection-name { + position: sticky; + z-index: 1; + top: 100px; + margin-inline: -32px; + padding: 10px 32px; + border-bottom: 1px solid #ddd; + transition: top 300ms ease-in-out; + } + + &__collections-list { + margin-top: 100px; + min-width: 621px; + } + + &__list-icon { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 12px; + + > li { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + .block-editor-block-icon { + width: 100%; + } + + svg { + max-height: none; + max-width: none; + min-height: 8px; + min-width: 8px; + } + + } + + &__load-more { + text-align: center; + } + + @media(min-width: 600px) { + // Override core style to avoid the modal to become too small during search and make it moving. + width: 1480px; + max-width: calc(100% - 32px); + min-height: 400px; + } + + @media(min-width: 1280px) { + &__list-icon { + grid-template-columns: repeat(6, 1fr); + } + } +} diff --git a/src/icon/style.scss b/src/icon/style.scss index ca561f5..75ddc6c 100644 --- a/src/icon/style.scss +++ b/src/icon/style.scss @@ -6,24 +6,28 @@ */ .wp-block-blockparty-icon { - // background-color: transparent !important; - // text-align: center; - - // > span, - // > a > span { - // display: inline-flex; - // margin: 0 auto var(--blockparty-icon-svg-margin-bottom, 0); - // } - - // svg { - // display: block; - // margin: 0 auto var(--blockparty-icon-svg-margin-bottom, 0); - // box-sizing: content-box; - // } + --wp-block-blockparty-icon-max-width: max-content; + --wp-block-blockparty-icon-flex-direction: column; + --wp-block-blockparty-icon-align-items: center; + --wp-block-blockparty-icon-justify-content: center; + --wp-block-blockparty-icon-gap: 8px; + + background-color: transparent !important; + text-align: center; + max-width: var(--wp-block-blockparty-icon-max-width, max-content); + + &:has(.wp-block-blockparty-icon__text):not(:has(.wp-block-blockparty-icon__link)), + .wp-block-blockparty-icon__link { + display: flex; + gap: var(--wp-block-blockparty-icon-gap, 0); + flex-direction: var(--wp-block-blockparty-icon-flex-direction, column); + align-items: var(--wp-block-blockparty-icon-align-items, center); + justify-content: var(--wp-block-blockparty-icon-justify-content, center); + } - // .block-list-appender__toggle { - // display: flex !important; - // } + .wp-block-blockparty-icon__text { + margin: 0; + } .wp-block-blockparty-icons__icon-component--raw svg { fill: none; @@ -41,8 +45,4 @@ stroke: currentColor; } } - - .wp-block-blockparty-icons__icon-component--sprite { - fill: currentColor; - } } diff --git a/src/icons/block.json b/src/icons/block.json deleted file mode 100644 index 5d7e32a..0000000 --- a/src/icons/block.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "$schema": "https://schemas.wp.org/trunk/block.json", - "apiVersion": 3, - "name": "blockparty/icons", - "version": "1.0.0", - "title": "Icons", - "category": "widgets", - "description": "Easily add custom vector icons to your content.", - "example": {}, - "attributes": { - "iconColor": { - "type": "string" - }, - "maxIcons": { - "type": "number", - "default": 999 - } - }, - "providesContext": { - "iconColor": "iconColor" - }, - "supports": { - "align": [ "left", "center", "right" ], - "anchor": true, - "layout": { - "allowSwitching": false, - "allowInheriting": false, - "allowVerticalAlignment": true, - "default": { - "type": "flex" - } - }, - "color": { - "enableContrastChecker": true, - "background": true, - "gradients": true, - "text": false - }, - "spacing": { - "blockGap": [ "horizontal" ], - "margin": true, - "padding": true - } - }, - "textdomain": "blockparty-icons", - "editorScript": "file:./index.js", - "editorStyle": "file:./index.css", - "style": "file:./style-index.css" -} diff --git a/src/icons/deprecated.js b/src/icons/deprecated.js deleted file mode 100644 index da1a004..0000000 --- a/src/icons/deprecated.js +++ /dev/null @@ -1,467 +0,0 @@ -/** - * External dependencies - */ -import parse from 'html-react-parser'; -import classNames from 'classnames'; - -/** - * WordPress dependencies - */ -import { Icon } from '@wordpress/components'; -import { createBlock } from '@wordpress/blocks'; -import { useBlockProps } from '@wordpress/block-editor'; - -function IconRender( { - attributes: { className, collection, icon, url, content, width }, - color, -} ) { - if ( ! collection || ! icon || ( ! url && ! content ) ) { - return <>; - } - - const iconStyle = { - width: '100%', - height: '100%', - }; - - const iconClassNames = classNames( - 'icon', - { [ `icon-${ icon.name }` ]: ! icon.name.startsWith( 'icon-' ) }, // css class generated if icon name doesn't start with icon- - { [ `${ icon.name }` ]: icon.name.startsWith( 'icon-' ) }, // css class generated if icon name start with icon- - className // combine with custom classnames - ); - - const blockStyles = { - width, - height: width, - backgroundColor: color && color.slug ? undefined : color.color, - }; - - let blockClassName = ''; - - blockClassName = classNames( - 'icon-container', - { - [ `has-${ color.slug }-background-color` ]: !! ( - color && color.slug - ), - } // css class generated is slug is set - ); - - switch ( icon.type ) { - case 'file': - case 'sprite': - return ( -
- -
- ); - default: - return ( -
- - parse( content, { - trim: true, - replace: ( domNode ) => { - // TODO: Very basic SVG sanitization, needs more refinement. - if ( - domNode.type !== 'tag' || - ( ! domNode.parent && - domNode.name !== 'svg' ) || - ! domNode.name - ) { - return <>; - } - }, - } ) - } - /> -
- ); - } -} - -const DEPRECATED_ATTRIBUTES_V1 = { - collection: { - type: 'object', - }, - icon: { - type: 'object', - }, - url: { - type: 'string', - }, - content: { - type: 'string', - source: 'html', - selector: '.icon-container', - default: '', - }, - width: { - type: 'integer', - default: 48, - }, - iconBackground: { - type: 'string', - }, - customIconBackground: { - type: 'string', - }, - layout: { - type: 'object', - }, -}; - -const v1 = { - attributes: DEPRECATED_ATTRIBUTES_V1, - supports: { - html: false, - }, - isEligible: ( { borderRadius, padding, size } ) => - !! borderRadius || !! padding || ! size, - migrate( attributes, innerBlocks ) { - const { - content, - customIconBackground, - icon, - iconBackground, - width, - ...restAttributes - } = attributes; - - // Check if icon exists before destructuring - if ( ! icon ) { - return [ attributes, innerBlocks ]; - } - - const { label, name, type } = icon; - - return [ - restAttributes, - [ - createBlock( 'blockparty/icon', { - content, - icon: { - label, - name, - type, - }, - size: width, - } ), - ...innerBlocks, - ], - ]; - }, - save( { attributes } ) { - const { customIconBackground, iconBackground } = attributes; - const colorObject = { - slug: iconBackground, - color: customIconBackground, - }; - - return ( -
- -
- ); - }, -}; - -const DEPRECATED_ATTRIBUTES_V2 = { - collection: { - type: 'object', - }, - icon: { - type: 'object', - }, - url: { - type: 'string', - }, - content: { - type: 'string', - source: 'html', - selector: '.icon-container', - default: '', - }, - size: { - type: 'integer', - default: 48, - }, - padding: { - type: 'object', - default: { - top: '0px', - left: '0px', - right: '0px', - bottom: '0px', - }, - }, - borderRadius: { - type: 'integer', - default: 0, - }, - iconColor: { - type: 'object', - default: {}, - }, - backgroundColor: { - type: 'object', - default: {}, - }, - mini: { - type: 'boolean', - default: false, - }, - layout: { - type: 'object', - }, -}; - -/** - * v2 single-icon format: block comment has collection + icon + url, no inner blocks. - * Must be tried first so validation uses this save (matches saved HTML) instead of v1. - */ -const v2SingleIcon = { - attributes: DEPRECATED_ATTRIBUTES_V2, - supports: { - html: false, - }, - isEligible( attributes, innerBlocks ) { - return ( - innerBlocks.length === 0 && - !! attributes.icon && - !! attributes.collection && - ( !! attributes.url || !! attributes.content ) - ); - }, - migrate( attributes, innerBlocks ) { - return v2.migrate( attributes, innerBlocks ); - }, - save( { attributes } ) { - return v2.save( { attributes } ); - }, -}; - -const v2 = { - attributes: DEPRECATED_ATTRIBUTES_V2, - supports: { - html: false, - }, - isEligible: ( { borderRadius, padding, size } ) => - !! borderRadius || !! padding || ! size, - migrate( attributes, innerBlocks ) { - const { - backgroundColor, - borderRadius, - content, - customIconBackground, - icon, - iconColor, - padding, - size, - ...restAttributes - } = attributes; - - // Check if icon exists before destructuring - if ( ! icon ) { - return [ attributes, innerBlocks ]; - } - - const { label, name, type } = icon; - - let newContent = content; - const matchedContent = content.match( /<\/use>/ ); - - if ( matchedContent ) { - newContent = matchedContent[ 1 ]; - } - - return [ - restAttributes, - [ - createBlock( 'blockparty/icon', { - borderRadius, - content: newContent, - icon: { - label, - name, - type, - }, - iconColorValue: iconColor.color, - padding, - text: '', - size, - } ), - ...innerBlocks, - ], - ]; - }, - save( { attributes } ) { - const { - backgroundColor, - borderRadius, - className, - collection, - content, - icon, - iconColor, - padding, - size, - url, - } = attributes; - - if ( icon && collection ) { - if ( ! collection || ! icon || ( ! url && ! content ) ) { - return <>; - } - - const iconStyle = { - width: size, - height: size, - }; - - const iconClassNames = classNames( - 'icon', - { - [ `icon-${ icon.name }` ]: - Boolean( icon.name ) && - ! icon.name.startsWith( 'icon-' ), - }, // css class generated if icon name doesn't start with icon- - { - [ `${ icon.name }` ]: - Boolean( icon.name ) && icon.name.startsWith( 'icon-' ), - }, // css class generated if icon name start with icon- - className // combine with custom classnames - ); - - const blockStyles = { - borderRadius: `${ borderRadius }%`, - display: 'inline-block', - backgroundColor: backgroundColor.color, - color: iconColor.color, - }; - - if ( Boolean( padding ) ) { - blockStyles.padding = `${ padding.top } ${ padding.right } ${ padding.bottom } ${ padding.left }`; - } - - // css class generated is slug is set - let blockClassName = `icon-container`; - - if ( Boolean( iconColor ) && Boolean( iconColor.length ) ) { - blockClassName += ` has-text-color`; - } - - if ( Boolean( iconColor.slug ) ) { - blockClassName += ` has-${ iconColor.slug }-text-color`; - } - - if ( - Boolean( backgroundColor ) && - Boolean( backgroundColor.length ) - ) { - blockClassName += ` has-background-color`; - } - - if ( Boolean( backgroundColor.slug ) ) { - blockClassName += ` has-${ backgroundColor.slug }-background-color`; - } - - switch ( icon.type ) { - case 'sprite': - return ( -
-
- -
-
- ); - default: - return ( -
-
- - parse( content, { - trim: true, - replace: ( domNode ) => { - if ( - domNode.type === 'tag' && - domNode.name === 'svg' - ) { - domNode.attribs.class = `${ icon.name } icon`; - domNode.attribs.focusable = - 'false'; - domNode.attribs[ - 'aria-hidden' - ] = 'true'; - domNode.attribs.style = `width:${ size }px;height:${ size }px;`; - } - if ( - domNode.type !== 'tag' || - ( ! domNode.parent && - domNode.name !== - 'svg' ) || - ! domNode.name - ) { - return <>; - } - }, - } ) - } - /> -
-
- ); - } - } - }, -}; - -const v3 = { - attributes: { - iconColor: { type: 'string' }, - maxIcons: { type: 'number', default: 999 }, - }, - supports: { - html: false, - }, - isEligible( attributes, innerBlocks ) { - return innerBlocks.length === 0; - }, - save() { - return