├── .nvmrc ├── .npmrc ├── .husky └── pre-commit ├── index.php ├── .gitignore ├── .wordpress-org ├── icon-128x128.png ├── icon-256x256.png ├── icon-256x256.psd ├── banner-1544x500.png ├── banner-1544x500.psd └── banner-772x250.png ├── tests ├── e2e │ ├── fixtures │ │ └── example.json │ ├── .eslintrc.json │ ├── support │ │ ├── index.js │ │ └── commands.js │ ├── cypress.config.js │ └── integration │ │ ├── wp-basic.cy.js │ │ └── cf7-repeatable-fields.cy.js ├── unit │ ├── test-class-cf7-repeatable-fields.php │ └── bootstrap.php └── plugins │ └── log-emails.php ├── .wp-env.json ├── .eslintrc.json ├── .editorconfig ├── phpunit.xml.dist ├── .distignore ├── .github ├── workflows │ ├── wordpress-latest.yml │ ├── push-asset-readme-update.yml │ ├── deploy.yml │ ├── linters.yml │ ├── no-response.yml │ ├── test.yml │ └── e2e-tests.yml └── ISSUE_TEMPLATE │ ├── 3-help.yml │ ├── 2-enhancement.yml │ └── 1-bug-report.yml ├── phpcs.xml ├── composer.json ├── CREDITS.md ├── dist └── scripts.js ├── languages └── cf7-repeatable-fields.pot ├── package.json ├── cf7-repeatable-fields.php ├── README.md ├── assets └── js │ └── scripts.js ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── bin └── install-wp-tests.sh ├── CHANGELOG.md ├── readme.txt ├── class-cf7-repeatable-fields.php ├── LICENSE.md └── composer.lock /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | ./tests/ 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.distignore: -------------------------------------------------------------------------------- 1 | .wordpress-org 2 | .git 3 | .github 4 | node_modules 5 | vendor 6 | 7 | .distignore 8 | .gitignore 9 | 10 | .editorconfig 11 | .eslintrc.json 12 | .npmrc 13 | .nvmrc 14 | Gruntfile.js 15 | composer.json 16 | composer.lock 17 | package.json 18 | package-lock.json 19 | phpcs.xml 20 | phpunit.xml.dist 21 | README.md 22 | CODE_OF_CONDUCT.md 23 | CONTRIBUTING.md 24 | CREDITS.md 25 | LICENSE.md 26 | 27 | .wp-env.json 28 | .husky 29 | tests 30 | bin 31 | -------------------------------------------------------------------------------- /.github/workflows/wordpress-latest.yml: -------------------------------------------------------------------------------- 1 | name: "WordPress version checker" 2 | on: 3 | push: 4 | branches: 5 | - trunk 6 | pull_request: 7 | branches: 8 | - trunk 9 | schedule: 10 | - cron: '0 0 * * 1' 11 | 12 | jobs: 13 | wordpress-version-checker: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: WordPress version checker 17 | uses: skaut/wordpress-version-checker@v1.2.0 18 | with: 19 | repo-token: ${{ secrets.GITHUB_TOKEN }} 20 | -------------------------------------------------------------------------------- /.github/workflows/push-asset-readme-update.yml: -------------------------------------------------------------------------------- 1 | name: Plugin asset/readme update 2 | on: 3 | push: 4 | branches: 5 | - trunk 6 | jobs: 7 | trunk: 8 | name: Push to trunk 9 | runs-on: ubuntu-latest 10 | steps: 11 | 12 | - name: Checkout code 13 | uses: actions/checkout@v3 14 | 15 | - name: WordPress.org plugin asset/readme update 16 | uses: 10up/action-wordpress-plugin-asset-update@stable 17 | env: 18 | SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} 19 | SVN_USERNAME: ${{ secrets.SVN_USERNAME }} -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to WordPress.org 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | jobs: 7 | tag: 8 | name: New tag 9 | runs-on: ubuntu-latest 10 | steps: 11 | 12 | - name: Checkout code 13 | uses: actions/checkout@v3 14 | 15 | - name: WordPress Plugin Deploy 16 | if: "! github.event.release.prerelease" 17 | id: deploy 18 | uses: 10up/action-wordpress-plugin-deploy@stable 19 | env: 20 | SVN_USERNAME: ${{ secrets.SVN_USERNAME }} 21 | SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} -------------------------------------------------------------------------------- /tests/unit/test-class-cf7-repeatable-fields.php: -------------------------------------------------------------------------------- 1 | shortcode_render( [ 'group-id' ], '' ); 17 | 18 | $this->assertSame( 1, did_filter( 'wpcf7_field_group_add_button_atts' ) ); 19 | $this->assertSame( 1, did_filter( 'wpcf7_field_group_add_button' ) ); 20 | $this->assertSame( 1, did_filter( 'wpcf7_field_group_remove_button_atts' ) ); 21 | $this->assertSame( 1, did_filter( 'wpcf7_field_group_remove_button' ) ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/e2e/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/e2e.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | 22 | import '@10up/cypress-wp-utils'; 23 | -------------------------------------------------------------------------------- /tests/e2e/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress'); 2 | 3 | module.exports = defineConfig({ 4 | fixturesFolder: 'tests/e2e/fixtures', 5 | screenshotsFolder: 'tests/e2e/screenshots', 6 | videosFolder: 'tests/e2e/videos', 7 | downloadsFolder: 'tests/e2e/downloads', 8 | video: false, 9 | retries: { 10 | runMode: 1, 11 | }, 12 | e2e: { 13 | async setupNodeEvents(on, config) { 14 | const { loadConfig } = require('@wordpress/env/lib/config'); 15 | 16 | const wpEnvConfig = await loadConfig('../..'); 17 | 18 | if (wpEnvConfig) { 19 | const port = wpEnvConfig.env.tests.port || null; 20 | 21 | if (port) { 22 | config.baseUrl = wpEnvConfig.env.tests.config.WP_SITEURL; 23 | } 24 | } 25 | 26 | return config; 27 | }, 28 | specPattern: 'tests/e2e/integration/**/*.cy.{js,jsx,ts,tsx}', 29 | supportFile: 'tests/e2e/support/index.js', 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | CF7 Repeatable Fields rules for PHP_CodeSniffer 4 | 5 | . 6 | 7 | 8 | 9 | index.php 10 | 11 | 12 | 13 | vendor/* 14 | node_modules/* 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3-help.yml: -------------------------------------------------------------------------------- 1 | name: "❓ Need help?" 2 | description: "Ask us a question, we are here to help!" 3 | labels: ["question"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | If you have a question that is neither a bug report nor an enhancement, then please post it here! Please fill in as much of the template below as you can. 9 | - type: textarea 10 | attributes: 11 | label: Describe your question 12 | description: A clear and concise description of what your question is. 13 | validations: 14 | required: true 15 | 16 | - type: checkboxes 17 | id: terms 18 | attributes: 19 | label: Code of Conduct 20 | description: By submitting this issue, you agree to follow our `Code of Conduct` (see the `CODE_OF_CONDUCT.md` file in the repo). 21 | options: 22 | - label: I agree to follow this project's Code of Conduct 23 | required: true -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "felipeelia/cf7-repeatable-fields", 3 | "description": "Contact Form 7 – Repeatable Fields", 4 | "type": "wordpress-plugin", 5 | "license": "GPL-2.0", 6 | "repositories": [ 7 | { 8 | "type": "composer", 9 | "url": "https://wpackagist.org" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=7.0", 14 | "composer/installers": "^1.0 || ^2.0" 15 | }, 16 | "require-dev": { 17 | "10up/phpcs-composer": "dev-master", 18 | "wpackagist-plugin/contact-form-7": "*", 19 | "phpcompatibility/phpcompatibility-wp": "*", 20 | "yoast/phpunit-polyfills": "^2.0" 21 | }, 22 | "config": { 23 | "allow-plugins": { 24 | "composer/installers": true, 25 | "dealerdirect/phpcodesniffer-composer-installer": true 26 | } 27 | }, 28 | "extra": { 29 | "installer-paths": { 30 | "vendor/{$name}/": [ 31 | "type:wordpress-plugin", 32 | "type:wordpress-theme" 33 | ] 34 | } 35 | }, 36 | "scripts": { 37 | "test": "phpunit", 38 | "setup-local-tests": "bash bin/install-wp-tests.sh cf7_rf_test root password 127.0.0.1 latest true" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /CREDITS.md: -------------------------------------------------------------------------------- 1 | # Credits 2 | 3 | The following acknowledges the Maintainers for this repository, those who have Contributed to this repository (via bug reports, code, design, ideas, project management, translation, testing, etc.), and any Libraries utilized. 4 | 5 | ## Maintainers 6 | 7 | The following individual is responsible for curating the list of issues, responding to pull requests, and ensuring regular releases happen: 8 | 9 | [Felipe Elia (@felipeelia)](https://github.com/felipeelia). 10 | 11 | ## Contributors 12 | 13 | Thank you to all the people who have already contributed to this repository via bug reports, code, design, ideas, project management, testing, etc. 14 | 15 | [Bertrand Guay-Paquet (@berniegp)](https://github.com/berniegp), 16 | [Matthew Harris (@rtpHarry)](https://github.com/rtpHarry), 17 | [Peter Thaleikis](https://peterthaleikis.com/), 18 | [Tessa Watkins](https://github.com/Tessachu), 19 | and 20 | [@sfdeveloper](https://profiles.wordpress.org/sfdeveloper/). 21 | 22 | ## Libraries 23 | 24 | The following software libraries are utilized in this repository. 25 | 26 | N/A. -------------------------------------------------------------------------------- /tests/e2e/integration/wp-basic.cy.js: -------------------------------------------------------------------------------- 1 | describe('WordPress basic actions', () => { 2 | it('Has tag', () => { 3 | cy.visit('/'); 4 | cy.get('title').should('exist'); 5 | }); 6 | 7 | it('Can login', () => { 8 | cy.login(); 9 | cy.visit('/'); 10 | cy.get('#wpadminbar').should('exist'); 11 | }); 12 | 13 | it('Can see admin bar on front end', () => { 14 | cy.login(); 15 | cy.visit('/'); 16 | cy.get('#wpadminbar').should('exist'); 17 | }); 18 | 19 | it('Can save own profile', () => { 20 | cy.login(); 21 | cy.visitAdminPage('profile.php'); 22 | cy.get('#first_name').clearThenType('Test Name'); 23 | cy.get('#submit').click(); 24 | cy.get('#first_name').should('have.value', 'Test Name'); 25 | }); 26 | 27 | it('Can change site title', () => { 28 | cy.login(); 29 | cy.visitAdminPage('options-general.php'); 30 | cy.get('#wpadminbar').should('be.visible'); 31 | cy.get('#blogname').clearThenType('Updated Title'); 32 | cy.get('#submit').click(); 33 | cy.get('#wp-admin-bar-site-name a') 34 | .first() 35 | .should('have.text', 'Updated Title'); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /.github/workflows/linters.yml: -------------------------------------------------------------------------------- 1 | name: Code Linters 2 | 3 | env: 4 | NODE_VERSION: "20" 5 | 6 | on: 7 | push: 8 | branches: 9 | - trunk 10 | pull_request: 11 | branches: 12 | - trunk 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v3 21 | 22 | - name: "Install node v${{ env.NODE_VERSION }}" 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: ${{ env.NODE_VERSION }} 26 | 27 | - name: npm ci 28 | run: npm ci 29 | 30 | - name: ES lint 31 | run: npm run lint:js 32 | 33 | - name: Set PHP version 34 | uses: shivammathur/setup-php@v2 35 | with: 36 | php-version: '8.2' 37 | tools: cs2pr 38 | coverage: none 39 | 40 | - name: composer install 41 | run: composer install 42 | 43 | - name: PHPCS check 44 | run: './vendor/bin/phpcs . -q --report=checkstyle --runtime-set testVersion 7.0- | cs2pr' 45 | -------------------------------------------------------------------------------- /tests/plugins/log-emails.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Plugin Name: Log Emails 4 | * Version: 1.0.0 5 | * Author: Felipe Elia 6 | * Author URI: https://felipeelia.dev/ 7 | * License: GPLv2 or later 8 | * 9 | * @package CF7_Repeatable_Fields_Tests 10 | */ 11 | 12 | /** 13 | * Create a CPT called "Emails". 14 | */ 15 | function create_post_type() { 16 | register_post_type( 17 | 'email', 18 | [ 19 | 'labels' => [ 20 | 'name' => __( 'Emails' ), 21 | 'singular_name' => __( 'Email' ), 22 | ], 23 | 'public' => true, 24 | 'has_archive' => true, 25 | ] 26 | ); 27 | } 28 | add_action( 'init', __NAMESPACE__ . '\create_post_type' ); 29 | 30 | /** 31 | * Log all emails 32 | * 33 | * @param null|bool $return Short-circuit return value. 34 | * @param array $atts Array of the `wp_mail()` arguments. 35 | * @return true 36 | */ 37 | function log_email( $return, $atts ) { 38 | wp_insert_post( 39 | [ 40 | 'post_type' => 'email', 41 | 'post_title' => $atts['subject'], 42 | 'post_content' => $atts['message'], 43 | ] 44 | ); 45 | return true; 46 | } 47 | add_filter( 'pre_wp_mail', __NAMESPACE__ . '\log_email', 10, 2 ); 48 | -------------------------------------------------------------------------------- /dist/scripts.js: -------------------------------------------------------------------------------- 1 | !function(e){"use strict";e((function(){const c=e(".wpcf7-field-groups");c.length&&(c.each((function(){e(this).data("group-model",e(this).find(".wpcf7-field-group").eq(0).clone())})),e("body").on("wpcf7-field-groups/change",".wpcf7-field-groups",(function(){const c=e(this).find(".wpcf7-field-group");c.each((function(c){e(this).find(".wpcf7-field-group-remove").toggle(c>0);const o=c+1;e(this).find("[name]").each((function(){const c=e(this),t=c.closest(".wpcf7-form-control-wrap"),n=c.attr("name"),i=n.indexOf("[]")>-1,f=n.replace("[]","");let p=f.replace(/__[0-9]*/,"")+"__"+o;t.attr("data-name",p),t.length&&!t.hasClass(p)&&t.removeClass(f).addClass(p),p+=i?"[]":"",c.attr("name",p)}))})),e(this).find(".wpcf7-field-group-count").val(c.length)})),c.trigger("wpcf7-field-groups/change"),e("body").on("click",".wpcf7-field-group-add, .wpcf7-field-group-remove",(function(){const c=e(this),o=c.closest(".wpcf7-field-groups");if(c.hasClass("wpcf7-field-group-add")){const e=o.data("group-model").clone(!0);o.append(e),c.trigger("wpcf7-field-groups/added",e)}else c.trigger("wpcf7-field-groups/removed"),c.closest(".wpcf7-field-group").remove();return o.trigger("wpcf7-field-groups/change"),!1})),c.on("click",".wpcf7-exclusive-checkbox input:checkbox",(function(){const o=e(this).attr("name");c.find('input:checkbox[name="'+o+'"]').not(this).prop("checked",!1)})))}))}(jQuery); -------------------------------------------------------------------------------- /tests/unit/bootstrap.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * PHPUnit bootstrap file. 4 | * 5 | * @package Cf7_Repeatable_Fields 6 | */ 7 | 8 | $_tests_dir = getenv( 'WP_TESTS_DIR' ); 9 | 10 | if ( ! $_tests_dir ) { 11 | $_tests_dir = rtrim( sys_get_temp_dir(), '/\\' ) . '/wordpress-tests-lib'; 12 | } 13 | 14 | // Forward custom PHPUnit Polyfills configuration to PHPUnit bootstrap file. 15 | $_phpunit_polyfills_path = getenv( 'WP_TESTS_PHPUNIT_POLYFILLS_PATH' ); 16 | if ( false !== $_phpunit_polyfills_path ) { 17 | define( 'WP_TESTS_PHPUNIT_POLYFILLS_PATH', $_phpunit_polyfills_path ); 18 | } 19 | 20 | if ( ! file_exists( "{$_tests_dir}/includes/functions.php" ) ) { 21 | echo "Could not find {$_tests_dir}/includes/functions.php, have you run bin/install-wp-tests.sh ?" . PHP_EOL; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 22 | exit( 1 ); 23 | } 24 | 25 | // Give access to tests_add_filter() function. 26 | require_once "{$_tests_dir}/includes/functions.php"; 27 | 28 | /** 29 | * Manually load the plugin being tested. 30 | */ 31 | function _manually_load_plugin() { 32 | require __DIR__ . '/../../vendor/contact-form-7/wp-contact-form-7.php'; 33 | require __DIR__ . '/../../cf7-repeatable-fields.php'; 34 | } 35 | 36 | tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' ); 37 | 38 | // Start up the WP testing environment. 39 | require "{$_tests_dir}/includes/bootstrap.php"; 40 | -------------------------------------------------------------------------------- /.github/workflows/no-response.yml: -------------------------------------------------------------------------------- 1 | name: No Response 2 | 3 | # **What it does**: Closes issues where the original author doesn't respond to a request for information. 4 | # **Why we have it**: To remove the need for maintainers to remember to check back on issues periodically to see if contributors have responded. 5 | # **Who does it impact**: Everyone that works on docs or docs-internal. 6 | 7 | on: 8 | issue_comment: 9 | types: [created] 10 | schedule: 11 | # Schedule for five minutes after the hour, every hour 12 | - cron: '5 * * * *' 13 | 14 | jobs: 15 | noResponse: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: lee-dohm/no-response@v0.5.0 19 | with: 20 | token: ${{ github.token }} 21 | daysUntilClose: 30 # Number of days of inactivity before an Issue is closed for lack of response 22 | responseRequiredLabel: "reporter feedback" # Label indicating that a response from the original author is required 23 | closeComment: > 24 | This issue has been automatically closed because there has been no response 25 | to our request for more information in the past 30 days. With only the 26 | information that is currently available, we are unable to take further action on this ticket. Please reach out if you have found or find the answers we need so 27 | that we can investigate further. When the information is ready, leave a comment and this issue will be automatically re-opened. -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | env: 4 | COMPOSER_VERSION: "2" 5 | COMPOSER_CACHE: "${{ github.workspace }}/.composer-cache" 6 | 7 | on: 8 | schedule: 9 | - cron: '0 0 * * *' 10 | push: 11 | branches: 12 | - trunk 13 | pull_request: 14 | branches: 15 | - trunk 16 | 17 | jobs: 18 | phpunit: 19 | name: PHP Unit 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v3 25 | 26 | - name: Start MySQL 27 | run: sudo systemctl start mysql.service 28 | 29 | - name: Configure sysctl limits 30 | run: | 31 | sudo swapoff -a 32 | sudo sysctl -w vm.swappiness=1 33 | sudo sysctl -w fs.file-max=262144 34 | sudo sysctl -w vm.max_map_count=262144 35 | 36 | - name: Set cache directories 37 | run: | 38 | composer config -g cache-dir "${{ env.COMPOSER_CACHE }}" 39 | 40 | - name: Prepare composer cache 41 | uses: actions/cache@v3 42 | with: 43 | path: ${{ env.COMPOSER_CACHE }} 44 | key: composer-${{ env.COMPOSER_VERSION }}-${{ hashFiles('**/composer.lock') }} 45 | restore-keys: | 46 | composer-${{ env.COMPOSER_VERSION }}- 47 | 48 | - name: Set PHP version 49 | uses: shivammathur/setup-php@v2 50 | with: 51 | php-version: '8.2' 52 | coverage: none 53 | 54 | - name: Install dependencies 55 | run: composer install 56 | 57 | - name: Setup WP Tests 58 | run: | 59 | bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1 60 | sleep 10 61 | 62 | - name: PHPUnit 63 | run: | 64 | composer run-script test 65 | -------------------------------------------------------------------------------- /languages/cf7-repeatable-fields.pot: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2024 Felipe Elia 2 | # This file is distributed under the GPLv2 or later. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Contact Form 7 - Repeatable Fields 2.0.2\n" 6 | "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/cf7-repeatable-fields\n" 7 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" 8 | "Language-Team: LANGUAGE <LL@li.org>\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "POT-Creation-Date: 2024-10-22T23:21:58+00:00\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "X-Generator: WP-CLI 2.8.1\n" 15 | "X-Domain: cf7-repeatable-fields\n" 16 | 17 | #. Plugin Name of the plugin 18 | msgid "Contact Form 7 - Repeatable Fields" 19 | msgstr "" 20 | 21 | #. Plugin URI of the plugin 22 | msgid "https://github.com/felipeelia/cf7-repeatable-fields" 23 | msgstr "" 24 | 25 | #. Description of the plugin 26 | msgid "An add-on plugin for Contact Form 7 that adds a repeatable group of fields functionality." 27 | msgstr "" 28 | 29 | #. Author of the plugin 30 | msgid "Felipe Elia" 31 | msgstr "" 32 | 33 | #. Author URI of the plugin 34 | msgid "https://felipeelia.dev/" 35 | msgstr "" 36 | 37 | #. translators: 1: Plugin name; 2: Contact Form 7 link 38 | #: cf7-repeatable-fields.php:54 39 | msgid "In order to %1$s work, %2$s needs to be installed and activated." 40 | msgstr "" 41 | 42 | #: cf7-repeatable-fields.php:56 43 | msgid "Contact Form 7" 44 | msgstr "" 45 | 46 | #: cf7-repeatable-fields.php:65 47 | msgid "Do it now?" 48 | msgstr "" 49 | 50 | #. translators: Format to use surrounded by code tag 51 | #: class-cf7-repeatable-fields.php:82 52 | msgid "You need to set an ID to this group. Use %s format." 53 | msgstr "" 54 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-enhancement.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F680 Enhancement" 2 | description: "Suggest an idea for this project." 3 | labels: ["enhancement"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you for suggesting an idea to make things better. Please fill in as much of the template below as you can. 9 | - type: textarea 10 | attributes: 11 | label: Is your enhancement related to a problem? Please describe. 12 | description: Please describe the problem you are trying to solve. 13 | placeholder: | 14 | I use this project as a `...` and I would like `...` so that `...describe benefit...`. 15 | validations: 16 | required: true 17 | 18 | - type: textarea 19 | attributes: 20 | label: Designs 21 | description: | 22 | If applicable, add mockups/screenshots/etc. to help explain your idea. 23 | Tip: You can attach images or videos by clicking this area to highlight it and then dragging files in. 24 | validations: 25 | required: false 26 | 27 | - type: textarea 28 | attributes: 29 | label: Describe alternatives you've considered 30 | description: | 31 | Please describe alternative solutions or features you have considered. 32 | placeholder: | 33 | I have also considered `...describe alternative...`, however I feel that my solution described above is better because of `...reason...`. 34 | validations: 35 | required: false 36 | 37 | - type: checkboxes 38 | id: terms 39 | attributes: 40 | label: Code of Conduct 41 | description: By submitting this issue, you agree to follow our `Code of Conduct` (see the `CODE_OF_CONDUCT.md` file in the repo). 42 | options: 43 | - label: I agree to follow this project's Code of Conduct 44 | required: true -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cf7-repeatable-fields", 3 | "version": "2.0.2", 4 | "description": "Repeatable Fields Add-on for Contact Form 7", 5 | "license": "GPL-3.0+", 6 | "main": "Gruntfile.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/felipeelia/cf7-repeatable-fields" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/felipeelia/cf7-repeatable-fields/issues" 13 | }, 14 | "author": "Felipe Elia", 15 | "homepage": "https://github.com/felipeelia/cf7-repeatable-fields#readme", 16 | "engines": { 17 | "node": ">=20.0.0", 18 | "npm": ">=9.0.0" 19 | }, 20 | "devDependencies": { 21 | "@10up/cypress-wp-utils": "^0.4.0", 22 | "@typescript-eslint/eslint-plugin": "^8.11.0", 23 | "@wordpress/env": "^10.10.0", 24 | "@wordpress/eslint-plugin": "^21.3.0", 25 | "@wordpress/scripts": "^30.3.0", 26 | "cypress": "^13.1.0", 27 | "eslint": "^8.57.1", 28 | "eslint-plugin-cypress": "^3.6.0", 29 | "eslint-plugin-prettier": "^5.0.0", 30 | "eslint-plugin-react-hooks": "^5.0.0", 31 | "husky": "^9.0.11", 32 | "lint-staged": "^15.2.2" 33 | }, 34 | "husky": { 35 | "hooks": { 36 | "pre-commit": "lint-staged" 37 | } 38 | }, 39 | "lint-staged": { 40 | "*.js": [ 41 | "wp-scripts lint-js" 42 | ], 43 | "*.php": [ 44 | "./vendor/bin/phpcs --extensions=php --runtime-set testVersion 7.0- -s" 45 | ] 46 | }, 47 | "scripts": { 48 | "prepare": "husky", 49 | "build": "wp-scripts build assets/js/scripts.js --webpack-no-externals --output-path=dist", 50 | "env": "wp-env", 51 | "format": "wp-scripts format ./assets", 52 | "lint:js": "wp-scripts lint-js", 53 | "lint:php": "./vendor/bin/phpcs --extensions=php --runtime-set testVersion 7.0- -s", 54 | "lint": "npm run lint:js && npm run lint:php", 55 | "start": "wp-scripts start assets/js/scripts.js --webpack-no-externals --output-path=dist", 56 | "e2e:open": "cypress open --config-file tests/e2e/cypress.config.js", 57 | "e2e:run": "cypress run --config-file tests/e2e/cypress.config.js" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.github/workflows/e2e-tests.yml: -------------------------------------------------------------------------------- 1 | name: E2E Test 2 | 3 | env: 4 | COMPOSER_VERSION: "2" 5 | COMPOSER_CACHE: "${{ github.workspace }}/.composer-cache" 6 | NODE_VERSION: "20" 7 | NODE_CACHE: "${{ github.workspace }}/node_modules_cache" 8 | 9 | on: 10 | schedule: 11 | - cron: '0 0 * * *' 12 | push: 13 | branches: 14 | - trunk 15 | pull_request: 16 | branches: 17 | - trunk 18 | - '[0-9].[0-9x]*' # Version branches: 4.x.x, 4.1.x, 5.x 19 | 20 | jobs: 21 | cypress_local: 22 | name: Cypress - Local 23 | runs-on: ubuntu-latest 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | 29 | - name: Prepare npm cache 30 | uses: actions/cache@v3 31 | with: 32 | path: ${{ env.NODE_CACHE }} 33 | key: npm-${{ env.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }} 34 | restore-keys: | 35 | npm-${{ env.NODE_VERSION }}- 36 | 37 | - name: Prepare composer cache 38 | uses: actions/cache@v3 39 | with: 40 | path: ${{ env.COMPOSER_CACHE }} 41 | key: composer-${{ env.COMPOSER_VERSION }}-${{ hashFiles('**/composer.lock') }} 42 | restore-keys: | 43 | composer-${{ env.COMPOSER_VERSION }}- 44 | 45 | - name: Set PHP version 46 | uses: shivammathur/setup-php@v2 47 | with: 48 | php-version: '8.2' 49 | tools: cs2pr 50 | coverage: none 51 | 52 | - name: composer install 53 | run: composer install 54 | 55 | - name: "Install node v${{ env.NODE_VERSION }}" 56 | uses: actions/setup-node@v3 57 | with: 58 | node-version: ${{ env.NODE_VERSION }} 59 | 60 | - name: Install dependencies 61 | run: npm ci --include=dev 62 | 63 | - name: Set up WP environment 64 | run: npm run env start 65 | 66 | - name: Build asset 67 | run: npm run build 68 | 69 | - name: Test 70 | run: npm run e2e:run 71 | 72 | - name: Make artifacts available 73 | uses: actions/upload-artifact@v3 74 | if: failure() 75 | with: 76 | name: e2e-artifact 77 | retention-days: 2 78 | path: | 79 | ${{ github.workspace }}/tests/e2e/screenshots/ 80 | ${{ github.workspace }}/tests/e2e/videos/ 81 | ${{ github.workspace }}/tests/e2e/logs/ 82 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-bug-report.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F41B Bug report" 2 | description: Create a report to help us improve 3 | title: 'BUG: ' 4 | labels: ["bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this bug report! Please fill in as much of the template below as you can. 10 | 11 | - type: textarea 12 | attributes: 13 | label: Describe the bug 14 | description: Please write a clear and concise description of the bug. 15 | placeholder: | 16 | Feature '...' is not working properly. I expect '...' to happen, but '...' happens instead 17 | validations: 18 | required: true 19 | 20 | - type: textarea 21 | attributes: 22 | label: Steps to Reproduce 23 | description: Please write the steps needed to reproduce the bug. 24 | placeholder: | 25 | 1. Go to '...' 26 | 2. Click on '...' 27 | 3. Scroll down to '...' 28 | 4. See error 29 | validations: 30 | required: true 31 | 32 | - type: textarea 33 | attributes: 34 | label: Form tab contents 35 | placeholder: The content of the "Form" tab of your form 36 | validations: 37 | required: true 38 | 39 | - type: textarea 40 | attributes: 41 | label: Mail tab contents 42 | placeholder: The content of the "Mail" tab of your form 43 | validations: 44 | required: true 45 | 46 | - type: textarea 47 | attributes: 48 | label: WordPress information 49 | placeholder: | 50 | <details><summary>Site Health info:</summary> 51 | <!-- Go to Tools > Site Health > Info tab, click "Copy site info to clipboard", and paste those details here. --> 52 | </details> 53 | validations: 54 | required: false 55 | 56 | - type: checkboxes 57 | id: terms 58 | attributes: 59 | label: Code of Conduct 60 | description: By submitting this issue, you agree to follow our `Code of Conduct` (see the `CODE_OF_CONDUCT.md` file in the repo). 61 | options: 62 | - label: I agree to follow this project's Code of Conduct 63 | required: true -------------------------------------------------------------------------------- /cf7-repeatable-fields.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Plugin Name: Contact Form 7 - Repeatable Fields 4 | * Plugin URI: https://github.com/felipeelia/cf7-repeatable-fields 5 | * Description: An add-on plugin for Contact Form 7 that adds a repeatable group of fields functionality. 6 | * Version: 2.0.2 7 | * Requires at least: 6.0 8 | * Requires PHP: 7.2 9 | * Requires Plugins: contact-form-7 10 | * Author: Felipe Elia 11 | * Author URI: https://felipeelia.dev/ 12 | * Text Domain: cf7-repeatable-fields 13 | * Domain Path: /languages 14 | * License: GPLv2 or later 15 | * 16 | * @package CF7_Repeatable_Fields 17 | */ 18 | 19 | defined( 'ABSPATH' ) || exit; 20 | 21 | define( 'CF7_REPEATABLE_FIELDS_VERSION', '2.0.2' ); 22 | define( 'CF7_REPEATABLE_FIELDS_FILE', __FILE__ ); 23 | define( 'CF7_REPEATABLE_FIELDS_DIR', __DIR__ ); 24 | 25 | /** 26 | * Run after all plugins are loaded. If CF7 is active loads the main class, otherwise show a notice. 27 | */ 28 | function wpcf7_repeatable_fields_init() { 29 | if ( class_exists( 'WPCF7_ContactForm' ) ) { 30 | require CF7_REPEATABLE_FIELDS_DIR . '/class-cf7-repeatable-fields.php'; 31 | CF7_Repeatable_Fields::get_instance(); 32 | } else { 33 | add_action( 'admin_notices', 'wpcf7_repeatable_fields_admin_notice' ); 34 | } 35 | } 36 | add_action( 'plugins_loaded', 'wpcf7_repeatable_fields_init' ); 37 | 38 | /** 39 | * Notice asking for CF7. 40 | */ 41 | function wpcf7_repeatable_fields_admin_notice() { 42 | if ( ! current_user_can( 'activate_plugins' ) ) { 43 | return; 44 | } 45 | 46 | $plugin_info = get_plugin_data( CF7_REPEATABLE_FIELDS_FILE ); 47 | 48 | ?> 49 | <div class="notice notice-error is-dismissible"> 50 | <p> 51 | <?php 52 | printf( 53 | /* translators: 1: Plugin name; 2: Contact Form 7 link */ 54 | esc_html__( 'In order to %1$s work, %2$s needs to be installed and activated.', 'cf7-repeatable-fields' ), 55 | '<strong>' . esc_html( $plugin_info['Name'] ) . '</strong>', 56 | '<a href="http://contactform7.com/" target="_blank">' . esc_html__( 'Contact Form 7', 'cf7-repeatable-fields' ) . '</a>' 57 | ); 58 | $screen = get_current_screen(); 59 | if ( 'plugins' !== $screen->id ) { 60 | if ( file_exists( WP_PLUGIN_DIR . '/contact-form-7/wp-contact-form-7.php' ) ) { 61 | $url = 'plugins.php'; 62 | } else { 63 | $url = 'plugin-install.php?tab=search&s=Contact+form+7'; 64 | } 65 | echo ' <a href="' . esc_url( admin_url( $url ) ) . '">' . esc_html__( 'Do it now?', 'cf7-repeatable-fields' ) . '</a>'; 66 | } 67 | ?> 68 | </p> 69 | </div> 70 | <?php 71 | } 72 | -------------------------------------------------------------------------------- /tests/e2e/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | 27 | Cypress.Commands.add('visitAdminPage', (page = 'index.php') => { 28 | cy.login(); 29 | if (page.includes('http')) { 30 | cy.visit(page); 31 | } else { 32 | cy.visit(`/wp-admin/${page.replace(/^\/|\/$/g, '')}`); 33 | } 34 | }); 35 | 36 | Cypress.Commands.add( 37 | 'clearThenType', 38 | { prevSubject: true }, 39 | (subject, text, force = false) => { 40 | /* eslint-disable-next-line cypress/unsafe-to-chain-command */ 41 | cy.wrap(subject).clear().type(text, { force }); 42 | } 43 | ); 44 | 45 | Cypress.Commands.add('wpCliEvalCustom', (command) => { 46 | const fileName = (Math.random() + 1).toString(36).substring(7); 47 | 48 | // this will be written "local" plugin directory 49 | const escapedCommand = command.replace(/^<\?php /, ''); 50 | cy.writeFile(fileName, `<?php ${escapedCommand}`); 51 | 52 | cy.exec('echo $(basename $(pwd))').then((result) => { 53 | const pluginName = result.stdout; 54 | 55 | // which is read from it's proper location in the plugins directory 56 | cy.exec( 57 | `npm --silent run env run tests-cli wp eval-file wp-content/plugins/${pluginName}/${fileName}` // eslint-disable-line @typescript-eslint/restrict-template-expressions 58 | ).then((commandResult) => { 59 | cy.exec(`rm ${fileName}`); 60 | cy.wrap(commandResult); 61 | }); 62 | }); 63 | }); 64 | 65 | Cypress.Commands.add('wpCliCustom', (command, ignoreFailures = false) => { 66 | const escapedCommand = command.replace(/"/g, '\\"').replace(/^wp /, ''); 67 | const options = { 68 | failOnNonZeroExit: !ignoreFailures, 69 | }; 70 | cy.exec( 71 | `npm --silent run env run tests-cli wp ${escapedCommand}`, 72 | options 73 | ).then((result) => { 74 | cy.wrap(result); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Contact Form 7 - Repeatable Fields 2 | 3 | > Adds repeatable groups of fields to Contact Form 7. 4 | 5 | [![Support Level](https://img.shields.io/badge/support-may_take_time-yellow.svg)](#support-level) [![Tests Status](https://github.com/felipeelia/cf7-repeatable-fields/actions/workflows/test.yml/badge.svg?branch=trunk)](https://github.com/felipeelia/cf7-repeatable-fields) [![Release Version](https://img.shields.io/github/release/felipeelia/cf7-repeatable-fields.svg)](https://github.com/felipeelia/cf7-repeatable-fields/releases/latest) ![WordPress tested up to version](https://img.shields.io/wordpress/plugin/tested/cf7-repeatable-fields?label=WordPress) [![GPLv2 License](https://img.shields.io/github/license/felipeelia/cf7-repeatable-fields.svg)](https://github.com/felipeelia/cf7-repeatable-fields/blob/trunk/LICENSE.md) 6 | 7 | ## Requirements 8 | 9 | This plugin requires these software with the following versions: 10 | 11 | * [WordPress](https://wordpress.org) 6.0+ 12 | * [PHP](https://php.net/) 7.2+ 13 | * [Contact Form 7](https://wordpress.org/plugins/contact-form-7/) 5.7+ 14 | 15 | ## Usage ## 16 | 17 | ### Form tab ### 18 | Wrap the desired fields with `[field_group your_group_id_here][/field_group]`. The shortcode accepts additional parameters, in WP shortcode format and in CF7 fields parameters format as well. 19 | 20 | Example: 21 | ```html 22 | [field_group emails id="emails-groups" tabindex:1] 23 | <label>Your Email (required)[email* your-email]</label> 24 | [radio your-radio use_label_element default:1 "radio 1" "radio 2" "radio 3"] 25 | [select* your-menu include_blank "option1" "option 2"] 26 | [checkbox* your-checkbox "check 1" "check 2"] 27 | [/field_group] 28 | ``` 29 | 30 | ### Mail tab ### 31 | In the mail settings, wrap the fields with your group id. You can use the `[group_index]` tag to print the group index and an additional `__<NUMBER>` to print a field at a specific index. 32 | 33 | Example: 34 | ```html 35 | The second email entered by the user was: [your-email__2] 36 | 37 | These were the groups: 38 | [emails] 39 | GROUP #[group_index] 40 | Checkbox: [your-checkbox] 41 | E-mail: [your-email] 42 | Radio: [your-radio] 43 | Select: [your-menu] 44 | [/emails] 45 | ``` 46 | 47 | ## Check out the Wiki 48 | 49 | * [Hooks available](https://github.com/felipeelia/cf7-repeatable-fields/wiki/Hooks) - How to customize the _add_ and _remove_ buttons 50 | * [Frequently Asked Questions](https://github.com/felipeelia/cf7-repeatable-fields/wiki/Frequently-Asked-Questions) 51 | 52 | ## Contribute ## 53 | You can contribute with code, issues and ideas at the [GitHub repository](https://github.com/felipeelia/cf7-repeatable-fields). 54 | 55 | If you like the plugin, [a review](https://wordpress.org/support/plugin/cf7-repeatable-fields/reviews/#new-post) is appreciated :) 56 | 57 | ## Changelog 58 | 59 | A complete listing of all notable changes to this plugin are documented in [CHANGELOG.md](https://github.com/felipeelia/cf7-repeatable-fields/blob/trunk/CHANGELOG.md). 60 | -------------------------------------------------------------------------------- /assets/js/scripts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Available events: 3 | * `wpcf7-field-groups/change`: triggered by .wpcf7-field-groups elements 4 | * `wpcf7-field-groups/added`: triggered by .wpcf7-field-group-add elements 5 | * `wpcf7-field-groups/removed`: triggered by .wpcf7-field-group-remove elements 6 | */ 7 | 8 | /** 9 | * Main function 10 | * 11 | * @param {Object} $ jQuery 12 | */ 13 | (function ($) { 14 | 'use strict'; 15 | 16 | $(function () { 17 | const $groups = $('.wpcf7-field-groups'); 18 | // Only need to work if there is any group. 19 | if (!$groups.length) { 20 | return; 21 | } 22 | 23 | // Let's grab the groups models to append them when necessary. 24 | $groups.each(function () { 25 | $(this).data( 26 | 'group-model', 27 | $(this).find('.wpcf7-field-group').eq(0).clone() 28 | ); 29 | }); 30 | 31 | $('body').on( 32 | 'wpcf7-field-groups/change', 33 | '.wpcf7-field-groups', 34 | function () { 35 | // For each group inside this we have to adjust name parameter. 36 | const $groupsInside = $(this).find('.wpcf7-field-group'); 37 | $groupsInside.each(function (index) { 38 | $(this) 39 | .find('.wpcf7-field-group-remove') 40 | .toggle(index > 0); 41 | const i = index + 1; 42 | $(this) 43 | .find('[name]') 44 | .each(function () { 45 | const $$ = $(this), 46 | $formControlWrap = $$.closest( 47 | '.wpcf7-form-control-wrap' 48 | ), 49 | name = $$.attr('name'), 50 | isArray = name.indexOf('[]') > -1, 51 | rawName = name.replace('[]', ''); 52 | let newName = 53 | rawName.replace(/__[0-9]*/, '') + '__' + i; 54 | 55 | // New attribute used by CF7 to validate 56 | $formControlWrap.attr('data-name', newName); 57 | 58 | // The form control wrap class doesn't have `[]` chars... 59 | if ( 60 | $formControlWrap.length && 61 | !$formControlWrap.hasClass(newName) 62 | ) { 63 | $formControlWrap 64 | .removeClass(rawName) 65 | .addClass(newName); 66 | } 67 | // but the field can have. 68 | newName += isArray ? '[]' : ''; 69 | $$.attr('name', newName); 70 | }); 71 | }); 72 | $(this) 73 | .find('.wpcf7-field-group-count') 74 | .val($groupsInside.length); 75 | } 76 | ); 77 | // Set thing up for the first time. 78 | $groups.trigger('wpcf7-field-groups/change'); 79 | 80 | // Handle the buttons action. 81 | $('body').on( 82 | 'click', 83 | '.wpcf7-field-group-add, .wpcf7-field-group-remove', 84 | function () { 85 | const $$ = $(this), 86 | $allGroups = $$.closest('.wpcf7-field-groups'); 87 | 88 | if ($$.hasClass('wpcf7-field-group-add')) { 89 | const $newGroup = $allGroups 90 | .data('group-model') 91 | .clone(true); 92 | $allGroups.append($newGroup); 93 | $$.trigger('wpcf7-field-groups/added', $newGroup); 94 | } else { 95 | $$.trigger('wpcf7-field-groups/removed'); 96 | $$.closest('.wpcf7-field-group').remove(); 97 | } 98 | $allGroups.trigger('wpcf7-field-groups/change'); 99 | return false; 100 | } 101 | ); 102 | 103 | // Exclusive Checkbox 104 | $groups.on( 105 | 'click', 106 | '.wpcf7-exclusive-checkbox input:checkbox', 107 | function () { 108 | const name = $(this).attr('name'); 109 | $groups 110 | .find('input:checkbox[name="' + name + '"]') 111 | .not(this) 112 | .prop('checked', false); 113 | } 114 | ); 115 | }); 116 | })(jQuery); 117 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at https://felipeelia.dev/contact/. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [10up's](https://github.com/10up) that is adapted from [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing and Maintaining 2 | 3 | First, thank you for taking the time to contribute! 4 | 5 | The following is a set of guidelines for contributors as well as information and instructions around our maintenance process. The two are closely tied together in terms of how we all work together and set expectations, so while you may not need to know everything in here to submit an issue or pull request, it's best to keep them in the same document. 6 | 7 | ## Ways to contribute 8 | 9 | Contributing isn't just writing code - it's anything that improves the project. All contributions are managed right here on GitHub. Here are some ways you can help: 10 | 11 | ### Reporting bugs 12 | 13 | If you're running into an issue, please take a look through [existing issues](https://github.com/felipeelia/cf7-repeatable-fields/issues) and [open a new one](https://github.com/felipeelia/cf7-repeatable-fields/issues/new) if needed. If you're able, include steps to reproduce, environment information, and screenshots/screencasts as relevant. 14 | 15 | ### Suggesting enhancements 16 | 17 | New features and enhancements are also managed via [issues](https://github.com/felipeelia/cf7-repeatable-fields/issues). 18 | 19 | ### Pull requests 20 | 21 | Pull requests represent a proposed solution to a specified problem. They should always reference an issue that describes the problem and contains discussion about the problem itself. Discussion on pull requests should be limited to the pull request itself, i.e. code review. 22 | 23 | ## Workflow 24 | 25 | The `trunk` branch is the main branch and contains all new code to be released in an upcoming version. A version released is just a tag of that branch. 26 | 27 | ## Release instructions 28 | 29 | 1. Branch: Starting from `trunk`, cut a release branch named `release/X.Y.Z` for your changes. 30 | 1. Version bump: Bump the version number in `cf7-repeatable-fields.php`, `package.json`, `package-lock.json`, `readme.txt`, and any other relevant files if it does not already reflect the version being released. In `cf7-repeatable-fields.php` update both the plugin "Version:" property and the plugin `CF7_REPEATABLE_FIELDS_VERSION` constant. 31 | 1. Changelog: Add/update the changelog in `CHANGELOG.md` and `readme.txt`, ensuring to link the [X.Y.Z] release reference in the footer of `CHANGELOG.md` (e.g., https://github.com/felipeelia/cf7-repeatable-fields/compare/X.Y.Z-1...X.Y.Z). Update the Unreleased link to https://github.com/felipeelia/cf7-repeatable-fields/compare/X.Y.Z...trunk 32 | 1. Props: Update `CREDITS.md` file with any new contributors, confirm maintainers are accurate. 33 | 1. Readme updates: Make any other readme changes as necessary. `README.md` is geared toward GitHub and `readme.txt` contains WordPress.org-specific content. The two are slightly different. 34 | 1. New files: Check to be sure any new files/paths that are unnecessary in the production version are included in `.distignore`. 35 | 1. POT file: Run `wp i18n make-pot . languages/cf7-repeatable-fields.pot` and commit the file. In case of errors, try to disable Xdebug (see [this comment](https://github.com/10up/ElasticPress/pull/3079#issuecomment-1291028290).) 36 | 1. Release date: Double check the release date in both changelog files. 37 | 1. Merge: Merge the release branch/PR into `trunk`. 38 | 1. Test: Run `git pull origin trunk` and test for functionality locally. 39 | 1. Release: Create a [new release](https://github.com/felipeelia/cf7-repeatable-fields/releases/new), naming the release and the new tag with the new version number (`X.Y.Z`). Paste the release changelog from `CHANGELOG.md` into the body of the release and include a link to the closed issues on the [milestone](https://github.com/felipeelia/cf7-repeatable-fields/milestone/#?closed=1). 40 | 1. SVN: Wait for the [GitHub Action](https://github.com/felipeelia/cf7-repeatable-fields/actions/workflows/deploy.yml) to finish deploying to the WordPress.org repository. If all goes well, users with SVN commit access for that plugin will receive an emailed diff of changes. 41 | 1. Check WordPress.org: Ensure that the changes are live on https://wordpress.org/plugins/cf7-repeatable-fields/. This may take a few minutes. 42 | 1. Close milestone: Edit the [milestone](https://github.com/felipeelia/cf7-repeatable-fields/milestone/#) with release date (in the `Due date (optional)` field) and link to GitHub release (in the `Description` field), then close the milestone. 43 | 1. Punt incomplete items: If any open issues or PRs which were milestoned for `X.Y.Z` do not make it into the release, update their milestone to `X.Y.Z+1`, `X.Y+1.0`, `X+1.0.0` or `Future Release`. 44 | -------------------------------------------------------------------------------- /tests/e2e/integration/cf7-repeatable-fields.cy.js: -------------------------------------------------------------------------------- 1 | describe('CF7 Repeatable Fields functionality', () => { 2 | /** 3 | * Delete synonyms recreate test posts before running tests. 4 | */ 5 | before(() => { 6 | cy.wpCliEvalCustom( 7 | ` 8 | $content = get_posts( 9 | [ 10 | 'post_type' => [ 'wpcf7_contact_form', 'page', 'email' ], 11 | 'post_status' => 'any', 12 | 'numberposts' => 999, 13 | ] 14 | ); 15 | foreach( $content as $entry ) { 16 | wp_delete_post( $entry->ID, true ); 17 | }` 18 | ); 19 | }); 20 | 21 | it('Create field and see its values in emails', () => { 22 | cy.login(); 23 | 24 | // Create form 25 | cy.visitAdminPage('admin.php?page=wpcf7-new'); 26 | cy.get('#wpcf7-form').as('formTextarea'); 27 | cy.get('@formTextarea').clear(); 28 | cy.get('@formTextarea').type( 29 | ` 30 | <label>Your name: [text* your-name autocomplete:name]</label> 31 | <label>Your email: [email* your-email autocomplete:email]</label> 32 | <label>Subject: [text* your-subject]</label> 33 | <label>Your message: [textarea your-message]</label> 34 | [field_group emails id="emails-groups" tabindex:1] 35 | <label>Your Email (required)[email* your-email]</label> 36 | [radio your-radio use_label_element default:1 "radio 1" "radio 2" "radio 3"] 37 | [select* your-menu include_blank "option1" "option 2"] 38 | [checkbox* your-checkbox "check 1" "check 2"] 39 | [/field_group] 40 | [submit "Submit"]`, 41 | { delay: 0 } 42 | ); 43 | 44 | cy.get('a[href="#mail-panel"]').click(); 45 | cy.get('#wpcf7-mail-body').as('mailTextarea'); 46 | cy.get('@mailTextarea').clear(); 47 | cy.get('@mailTextarea').type( 48 | ` 49 | From: [your-name] [your-email] 50 | Subject: [your-subject] 51 | 52 | Message Body: 53 | [your-message] 54 | 55 | The second email entered by the user was: [your-email__2] 56 | 57 | These were the groups: 58 | [emails] 59 | GROUP #[group_index] 60 | Checkbox: [your-checkbox] 61 | E-mail: [your-email] 62 | Radio: [your-radio] 63 | Select: [your-menu] 64 | [/emails]`, 65 | { delay: 0 } 66 | ); 67 | 68 | cy.get('#publishing-action input[name="wpcf7-save"]').click(); 69 | cy.get('#wpcf7-shortcode').invoke('val').as('shortcode'); 70 | 71 | // Create page with the form 72 | cy.get('@shortcode').then((shortcode) => { 73 | cy.wpCliEvalCustom( 74 | ` 75 | $post_id = wp_insert_post( 76 | [ 77 | 'post_type' => 'page', 78 | 'post_status' => 'publish', 79 | 'post_title' => 'Contact Form', 80 | 'post_content' => '${shortcode}', 81 | ] 82 | ); 83 | echo get_permalink( $post_id ); 84 | ` 85 | ).then((result) => { 86 | cy.visit(result.stdout); 87 | }); 88 | }); 89 | 90 | // Visit page and send form 91 | cy.get('input[name="your-name"]').type('Test Name'); 92 | cy.get('input[name="your-email"]').type('email@domain.com'); 93 | cy.get('input[name="your-subject"]').type('Subject'); 94 | cy.get('textarea[name="your-message"]').type('Test message'); 95 | 96 | cy.get('input[name="your-email__1"]').type('email-1@domain.com'); 97 | cy.get('input[name="your-radio__1"][value="radio 1"]').check(); 98 | cy.get('select[name="your-menu__1"]').select('option1'); 99 | cy.get('input[name="your-checkbox__1[]"][value="check 1"]').check(); 100 | 101 | cy.get('.wpcf7-field-group-add').click(); 102 | cy.get('input[name="_wpcf7_groups_count[emails]__1"]') 103 | .invoke('val') 104 | .should('eq', '2'); 105 | cy.get('input[name="your-email__2"]').should('exist'); 106 | 107 | cy.get('.wpcf7-field-group-add').last().click(); 108 | cy.get('input[name="_wpcf7_groups_count[emails]__1"]') 109 | .invoke('val') 110 | .should('eq', '3'); 111 | cy.get('input[name="your-email__3"]').should('exist'); 112 | 113 | cy.get('.wpcf7-field-group-remove').eq(1).click(); 114 | cy.get('input[name="your-email__3"]').should('not.exist'); 115 | 116 | cy.get('input[name="your-email__2"]').type('email-2@domain.com'); 117 | cy.get('input[name="your-radio__2"][value="radio 2"]').check(); 118 | cy.get('select[name="your-menu__2"]').select('option 2'); 119 | cy.get('input[name="your-checkbox__2[]"][value="check 2"]').check(); 120 | 121 | cy.get('.wpcf7-submit').click(); 122 | 123 | // Check email 124 | cy.wpCliEvalCustom( 125 | ` 126 | $email = get_posts( 127 | [ 128 | 'post_type' => 'email', 129 | 'post_status' => 'any', 130 | 'numberposts' => 1, 131 | ] 132 | ); 133 | echo $email[0]->post_content; 134 | ` 135 | ).then((result) => { 136 | const postContent = result.stdout; 137 | 138 | expect(postContent).to.contain('From: Test Name email@domain.com'); 139 | expect(postContent).to.contain('Subject: Subject'); 140 | expect(postContent).to.contain('Test message'); 141 | expect(postContent).to.contain( 142 | 'The second email entered by the user was: email-2@domain.com' 143 | ); 144 | expect(postContent).to.contain(`GROUP #1 145 | Checkbox: check 1 146 | E-mail: email-1@domain.com 147 | Radio: radio 1 148 | Select: option1`); 149 | expect(postContent).to.contain(`GROUP #2 150 | Checkbox: check 2 151 | E-mail: email-2@domain.com 152 | Radio: radio 2 153 | Select: option 2`); 154 | }); 155 | }); 156 | }); 157 | -------------------------------------------------------------------------------- /bin/install-wp-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "usage: $0 <db-name> <db-user> <db-pass> [db-host] [wp-version] [skip-database-creation]" 5 | exit 1 6 | fi 7 | 8 | DB_NAME=$1 9 | DB_USER=$2 10 | DB_PASS=$3 11 | DB_HOST=${4-localhost} 12 | WP_VERSION=${5-latest} 13 | SKIP_DB_CREATE=${6-false} 14 | 15 | TMPDIR=${TMPDIR-/tmp} 16 | TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//") 17 | WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib} 18 | WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress} 19 | 20 | download() { 21 | if [ `which curl` ]; then 22 | curl -s "$1" > "$2"; 23 | elif [ `which wget` ]; then 24 | wget -nv -O "$2" "$1" 25 | fi 26 | } 27 | 28 | if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then 29 | WP_BRANCH=${WP_VERSION%\-*} 30 | WP_TESTS_TAG="branches/$WP_BRANCH" 31 | 32 | elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then 33 | WP_TESTS_TAG="branches/$WP_VERSION" 34 | elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then 35 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then 36 | # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x 37 | WP_TESTS_TAG="tags/${WP_VERSION%??}" 38 | else 39 | WP_TESTS_TAG="tags/$WP_VERSION" 40 | fi 41 | elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 42 | WP_TESTS_TAG="trunk" 43 | else 44 | # http serves a single offer, whereas https serves multiple. we only want one 45 | download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json 46 | grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json 47 | LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') 48 | if [[ -z "$LATEST_VERSION" ]]; then 49 | echo "Latest WordPress version could not be found" 50 | exit 1 51 | fi 52 | WP_TESTS_TAG="tags/$LATEST_VERSION" 53 | fi 54 | set -ex 55 | 56 | install_wp() { 57 | 58 | if [ -d $WP_CORE_DIR ]; then 59 | return; 60 | fi 61 | 62 | mkdir -p $WP_CORE_DIR 63 | 64 | if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 65 | mkdir -p $TMPDIR/wordpress-trunk 66 | rm -rf $TMPDIR/wordpress-trunk/* 67 | svn export --quiet https://core.svn.wordpress.org/trunk $TMPDIR/wordpress-trunk/wordpress 68 | mv $TMPDIR/wordpress-trunk/wordpress/* $WP_CORE_DIR 69 | else 70 | if [ $WP_VERSION == 'latest' ]; then 71 | local ARCHIVE_NAME='latest' 72 | elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then 73 | # https serves multiple offers, whereas http serves single. 74 | download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json 75 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then 76 | # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x 77 | LATEST_VERSION=${WP_VERSION%??} 78 | else 79 | # otherwise, scan the releases and get the most up to date minor version of the major release 80 | local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'` 81 | LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1) 82 | fi 83 | if [[ -z "$LATEST_VERSION" ]]; then 84 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 85 | else 86 | local ARCHIVE_NAME="wordpress-$LATEST_VERSION" 87 | fi 88 | else 89 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 90 | fi 91 | download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz 92 | tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR 93 | fi 94 | 95 | download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php 96 | } 97 | 98 | install_test_suite() { 99 | # portable in-place argument for both GNU sed and Mac OSX sed 100 | if [[ $(uname -s) == 'Darwin' ]]; then 101 | local ioption='-i.bak' 102 | else 103 | local ioption='-i' 104 | fi 105 | 106 | # set up testing suite if it doesn't yet exist 107 | if [ ! -d $WP_TESTS_DIR ]; then 108 | # set up testing suite 109 | mkdir -p $WP_TESTS_DIR 110 | rm -rf $WP_TESTS_DIR/{includes,data} 111 | svn export --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes 112 | svn export --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data 113 | fi 114 | 115 | if [ ! -f wp-tests-config.php ]; then 116 | download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php 117 | # remove all forward slashes in the end 118 | WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") 119 | sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php 120 | sed $ioption "s:__DIR__ . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php 121 | sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php 122 | sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php 123 | sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php 124 | sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php 125 | fi 126 | 127 | } 128 | 129 | recreate_db() { 130 | shopt -s nocasematch 131 | if [[ $1 =~ ^(y|yes)$ ]] 132 | then 133 | mysqladmin drop $DB_NAME -f --user="$DB_USER" --password="$DB_PASS"$EXTRA 134 | create_db 135 | echo "Recreated the database ($DB_NAME)." 136 | else 137 | echo "Leaving the existing database ($DB_NAME) in place." 138 | fi 139 | shopt -u nocasematch 140 | } 141 | 142 | create_db() { 143 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA 144 | } 145 | 146 | install_db() { 147 | 148 | if [ ${SKIP_DB_CREATE} = "true" ]; then 149 | return 0 150 | fi 151 | 152 | # parse DB_HOST for port or socket references 153 | local PARTS=(${DB_HOST//\:/ }) 154 | local DB_HOSTNAME=${PARTS[0]}; 155 | local DB_SOCK_OR_PORT=${PARTS[1]}; 156 | local EXTRA="" 157 | 158 | if ! [ -z $DB_HOSTNAME ] ; then 159 | if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then 160 | EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" 161 | elif ! [ -z $DB_SOCK_OR_PORT ] ; then 162 | EXTRA=" --socket=$DB_SOCK_OR_PORT" 163 | elif ! [ -z $DB_HOSTNAME ] ; then 164 | EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" 165 | fi 166 | fi 167 | 168 | # create database 169 | if [ $(mysql --user="$DB_USER" --password="$DB_PASS"$EXTRA --execute='show databases;' | grep ^$DB_NAME$) ] 170 | then 171 | echo "Reinstalling will delete the existing test database ($DB_NAME)" 172 | read -p 'Are you sure you want to proceed? [y/N]: ' DELETE_EXISTING_DB 173 | recreate_db $DELETE_EXISTING_DB 174 | else 175 | create_db 176 | fi 177 | } 178 | 179 | install_wp 180 | install_test_suite 181 | install_db 182 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file, per [the Keep a Changelog standard](https://keepachangelog.com/). 4 | 5 | ## [Unreleased] 6 | 7 | <!-- 8 | ### Added 9 | ### Changed 10 | ### Deprecated 11 | ### Removed 12 | ### Fixed 13 | ### Security 14 | --> 15 | 16 | ## [2.0.2] - 2024-10-22 17 | 18 | **This is a security release.** It fixes a Stored cross-site scripting (XSS) vulnerability, that allowed users with contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. Thanks to Peter Thaleikis and the Wordfence team for reaching out about it. 19 | 20 | ### Added 21 | 22 | * New `wpcf7_field_group_content` filter. Props [@Tessachu](https://github.com/Tessachu) and [@felipeelia](https://github.com/felipeelia) via [#90](https://github.com/10up/ElasticPress/pull/90). 23 | * End-to-end basic tests. Props [@felipeelia](https://github.com/felipeelia) via [#78](https://github.com/10up/ElasticPress/pull/78). 24 | 25 | ### Changed 26 | 27 | * Node version to v20. Props [@felipeelia](https://github.com/felipeelia) via [#89](https://github.com/10up/ElasticPress/pull/89). 28 | 29 | ### Security 30 | 31 | * Sanitize wrapper div attributes. Props Peter Thaleikis and [@felipeelia](https://github.com/felipeelia) via [#90](https://github.com/10up/ElasticPress/pull/90). 32 | * Bumped `postcss` from 8.4.26 to 8.4.31. Props [@dependabot](https://github.com/dependabot) via [#70](https://github.com/10up/ElasticPress/pull/70). 33 | * Updated `ws` from 8.13.0 to 8.18.0. Props [@dependabot](https://github.com/dependabot) via [#88](https://github.com/10up/ElasticPress/pull/88). 34 | * Updated `@wordpress/scripts` from 27.7.0 to 30.3.0. Props [@dependabot](https://github.com/dependabot) via [#88](https://github.com/10up/ElasticPress/pull/88). 35 | * Bumped `braces` from 3.0.2 to 3.0.3. Props [@dependabot](https://github.com/dependabot) via [#80](https://github.com/10up/ElasticPress/pull/80). 36 | * Bumped `webpack` from 5.91.0 to 5.94.0. Props [@dependabot](https://github.com/dependabot) via [#82](https://github.com/10up/ElasticPress/pull/82). 37 | * Bumped `express` from 4.18.2 to 4.19.2. Props [@dependabot](https://github.com/dependabot) via [#74](https://github.com/10up/ElasticPress/pull/74). 38 | * Bumped `follow-redirects` from 1.15.2 to 1.15.6. Props [@dependabot](https://github.com/dependabot) via [#76](https://github.com/10up/ElasticPress/pull/76). 39 | * Bumped `webpack-dev-middleware` from 5.3.3 to 5.3.4. Props [@dependabot](https://github.com/dependabot) via [#75](https://github.com/10up/ElasticPress/pull/75). 40 | * Bumped `@babel/traverse` from 7.22.8 to 7.23.2. Props [@dependabot](https://github.com/dependabot) via [#71](https://github.com/10up/ElasticPress/pull/71). 41 | * Several node packages updated. Props [@felipeelia](https://github.com/felipeelia) via [#77](https://github.com/10up/ElasticPress/pull/77). 42 | 43 | ## [2.0.1] - 2023-09-11 44 | 45 | ### Added 46 | 47 | * End-to-end tests foundation. Props [@felipeelia](https://github.com/felipeelia) via [#64](https://github.com/10up/ElasticPress/pull/64). 48 | 49 | ### Changed 50 | 51 | * Removed unnecessary files from final package. Props [@felipeelia](https://github.com/felipeelia) via [#63](https://github.com/10up/ElasticPress/pull/63). 52 | 53 | ### Fixed 54 | 55 | * Required checkbox not showing validation messages. Props [@felipeelia](https://github.com/felipeelia) via [#62](https://github.com/10up/ElasticPress/pull/62). 56 | 57 | ## [2.0.0] - 2023-07-23 58 | 59 | **Note that this version changes minimum required versions of:** 60 | 61 | * [WordPress](https://wordpress.org): 6.0+ 62 | * [PHP](https://php.net/): 7.2+ 63 | * [Contact Form 7](https://wordpress.org/plugins/contact-form-7/): 5.7+ 64 | 65 | This release marks the (slow) resumption of this plugin development. If you want to know more about it check out [this blog post](https://felipeelia.dev/contact-form-7-repeatable-fields-2-0-0/). If you find this plugin useful, consider leaving it [a review](https://wordpress.org/support/plugin/cf7-repeatable-fields/reviews/#new-post). 66 | 67 | ### Added 68 | 69 | * Support to [wp-env](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-env/). See [6db4e08](https://github.com/felipeelia/cf7-repeatable-fields/commit/6db4e08). 70 | * `group_id` as a parameter to all filters. See [#51](https://github.com/felipeelia/cf7-repeatable-fields/pull/51). 71 | * Very basic unit testing. See [#52](https://github.com/felipeelia/cf7-repeatable-fields/pull/52). 72 | 73 | ### Changed 74 | 75 | * Linting tools and script build process. See [6db4e08](https://github.com/felipeelia/cf7-repeatable-fields/commit/6db4e08). 76 | * Docs were migrated to [GitHub wiki](https://github.com/felipeelia/cf7-repeatable-fields/wiki). See [14fdd49](https://github.com/felipeelia/cf7-repeatable-fields/commit/14fdd49). 77 | 78 | ### Fixed 79 | 80 | * Validation problem with Contact Form 5.7+. Props [@sfdeveloper](https://profiles.wordpress.org/sfdeveloper/) via [this comment](https://wordpress.org/support/topic/compatibility-issues-with-cf7-5-7/#post-16588238) - added in [6db4e08](https://github.com/felipeelia/cf7-repeatable-fields/commit/6db4e08). 81 | 82 | ## [1.1.3] - 2019-12-11 83 | 84 | * Update WP `Tested up to` field 85 | * Apply WP Coding Standards 86 | * Fix a small sanitization problem 87 | 88 | ## [1.1.2] - 2019-10-10 89 | 90 | * Fix Exclusive Checkboxes 91 | 92 | ## [1.1.1] - 2019-09-04 93 | 94 | * Add compatibility to formatted dates (`[_format_{field name} "{date format}"]`) 95 | * DEV: Copy data and events while cloning a new group (JS) 96 | * DEV: Pass `$new_group` as an extra param for the `wpcf7-field-groups/added` event. 97 | * DEV: Apply some WPCS rules and add a CF7_REPEATABLE_FIELDS_VERSION const (may affect JS cache) 98 | 99 | ## [1.1] - 2018-06-14 100 | 101 | * Replace groups in mail 2 field 102 | 103 | ## [1.0.2] - 2018/03/29 104 | 105 | * Fix repeated tags in mail body 106 | 107 | ## [1.0.1] - 2018/03/20 108 | 109 | * Fix the `wpcf7_field_group_remove_button_atts` filter name. Props to @asilvestre87 110 | 111 | ## [1.0.0] - 2018/03/19 112 | 113 | * Initial release 114 | 115 | [Unreleased]: https://github.com/felipeelia/cf7-repeatable-fields/compare/2.0.2...trunk 116 | [2.0.2]: https://github.com/felipeelia/cf7-repeatable-fields/compare/2.0.1...2.0.2 117 | [2.0.1]: https://github.com/felipeelia/cf7-repeatable-fields/compare/2.0.0...2.0.1 118 | [2.0.0]: https://github.com/felipeelia/cf7-repeatable-fields/compare/1.1.3...2.0.0 119 | [1.1.3]: https://github.com/felipeelia/cf7-repeatable-fields/compare/1.1.2...1.1.3 120 | [1.1.2]: https://github.com/felipeelia/cf7-repeatable-fields/compare/1.1.1...1.1.2 121 | [1.1.1]: https://github.com/felipeelia/cf7-repeatable-fields/compare/1.1...1.1.1 122 | [1.1]: https://github.com/felipeelia/cf7-repeatable-fields/compare/1.0.2...1.1 123 | [1.0.2]: https://github.com/felipeelia/cf7-repeatable-fields/compare/1.0.1...1.0.2 124 | [1.0.1]: https://github.com/felipeelia/cf7-repeatable-fields/compare/1.0.0...1.0.1 125 | [1.0.0]: https://github.com/felipeelia/cf7-repeatable-fields/releases/tag/1.0.0 126 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === Contact Form 7 - Repeatable Fields === 2 | Contributors: felipeelia 3 | Donate link: https://felipeelia.dev/contact-form-7-repeatable-fields/ 4 | Tags: contact form 7, cf7, repeater, repeatable 5 | Tested up to: 6.6 6 | Stable tag: 2.0.2 7 | License: GPLv2 or later 8 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 9 | 10 | Adds repeatable groups of fields to Contact Form 7. 11 | 12 | == Description == 13 | This plugin adds repeatable groups of fields to Contact Form 7. 14 | 15 | **NOTE:** Tested with Contact Form 7 5.7.7. 16 | 17 | == Usage == 18 | 19 | = Form tab = 20 | Wrap the desired fields with `[field_group your_group_id_here][/field_group]`. The shortcode accepts additional parameters, in WP shortcode format and in CF7 fields parameters format as well. 21 | 22 | Example: 23 | ~~~ 24 | [field_group emails id="emails-groups" tabindex:1] 25 | <label>Your Email (required)[email* your-email]</label> 26 | [radio your-radio use_label_element default:1 "radio 1" "radio 2" "radio 3"] 27 | [select* your-menu include_blank "option1" "option 2"] 28 | [checkbox* your-checkbox "check 1" "check 2"] 29 | [/field_group] 30 | ~~~ 31 | 32 | = Mail tab = 33 | In the mail settings, wrap the fields with your group id. You can use the `[group_index]` tag to print the group index and an additional `__<NUMBER>` to print a field at a specific index. 34 | 35 | Example: 36 | ~~~ 37 | The second email entered by the user was: [your-email__2] 38 | 39 | These were the groups: 40 | [emails] 41 | GROUP #[group_index] 42 | Checkbox: [your-checkbox] 43 | E-mail: [your-email] 44 | Radio: [your-radio] 45 | Select: [your-menu] 46 | [/emails] 47 | ~~~ 48 | 49 | == Check out the Wiki == 50 | 51 | * [Hooks available](https://github.com/felipeelia/cf7-repeatable-fields/wiki/Hooks) - How to customize the _add_ and _remove_ buttons 52 | * [Frequently Asked Questions](https://github.com/felipeelia/cf7-repeatable-fields/wiki/Frequently-Asked-Questions) 53 | 54 | == Contribute == 55 | You can contribute with code, issues and ideas at the [GitHub repository](https://github.com/felipeelia/cf7-repeatable-fields). 56 | 57 | If you like the plugin, [a review](https://wordpress.org/support/plugin/cf7-repeatable-fields/reviews/#new-post) is appreciated :) 58 | 59 | == Frequently Asked Questions == 60 | 61 | = I have a problem with the plugin. Where can I get help? = 62 | 63 | If you have identified a bug or would like to suggest an enhancement, please refer to our [GitHub repo](https://github.com/felipeelia/cf7-repeatable-fields). I do not provide support here at WordPress.org forums. 64 | 65 | = My question is not listed here. Can I search somewhere else? = 66 | 67 | Yes! Give a look at the [Frequently Asked Questions](https://github.com/felipeelia/cf7-repeatable-fields/wiki/Frequently-Asked-Questions) section of our wiki. 68 | 69 | == Changelog == 70 | 71 | = 2.0.2 - 2024-10-22 = 72 | 73 | **This is a security release.** It fixes a Stored cross-site scripting (XSS) vulnerability, that allowed users with contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. Thanks to Peter Thaleikis and the Wordfence team for reaching out about it. 74 | 75 | __Added:__ 76 | 77 | * New `wpcf7_field_group_content` filter. Props [@Tessachu](https://github.com/Tessachu) and [@felipeelia](https://github.com/felipeelia). 78 | * End-to-end basic tests. Props [@felipeelia](https://github.com/felipeelia). 79 | 80 | __Changed:__ 81 | 82 | * Node version to v20. Props [@felipeelia](https://github.com/felipeelia). 83 | 84 | __Security:__ 85 | 86 | * Sanitize wrapper div attributes. Props Peter Thaleikis and [@felipeelia](https://github.com/felipeelia). 87 | * Bumped `postcss` from 8.4.26 to 8.4.31. Props [@dependabot](https://github.com/dependabot). 88 | * Updated `ws` from 8.13.0 to 8.18.0. Props [@dependabot](https://github.com/dependabot). 89 | * Updated `@wordpress/scripts` from 27.7.0 to 30.3.0. Props [@dependabot](https://github.com/dependabot). 90 | * Bumped `braces` from 3.0.2 to 3.0.3. Props [@dependabot](https://github.com/dependabot). 91 | * Bumped `webpack` from 5.91.0 to 5.94.0. Props [@dependabot](https://github.com/dependabot). 92 | * Bumped `express` from 4.18.2 to 4.19.2. Props [@dependabot](https://github.com/dependabot). 93 | * Bumped `follow-redirects` from 1.15.2 to 1.15.6. Props [@dependabot](https://github.com/dependabot). 94 | * Bumped `webpack-dev-middleware` from 5.3.3 to 5.3.4. Props [@dependabot](https://github.com/dependabot). 95 | * Bumped `@babel/traverse` from 7.22.8 to 7.23.2. Props [@dependabot](https://github.com/dependabot). 96 | * Several node packages updated. Props [@felipeelia](https://github.com/felipeelia). 97 | 98 | = 2.0.1 - 2023-09-11 = 99 | 100 | __Added:__ 101 | 102 | * End-to-end tests foundation. 103 | 104 | __Changed:__ 105 | 106 | * Removed unnecessary files from final package. 107 | 108 | __Fixed:__ 109 | 110 | * Required checkbox not showing validation messages. 111 | 112 | = 2.0.0 - 2023-07-23 = 113 | 114 | **Note that this version changes minimum required versions of:** 115 | 116 | * [WordPress](https://wordpress.org): 6.0+ 117 | * [PHP](https://php.net/): 7.2+ 118 | * [Contact Form 7](https://wordpress.org/plugins/contact-form-7/): 5.7+ 119 | 120 | This release marks the (slow) resumption of this plugin development. If you want to know more about it check out [this blog post](https://felipeelia.dev/contact-form-7-repeatable-fields-2-0-0/). If you find this plugin useful, consider leaving it [a review](https://wordpress.org/support/plugin/cf7-repeatable-fields/reviews/#new-post). 121 | 122 | __Added:__ 123 | 124 | * Support to [wp-env](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-env/). 125 | * `group_id` as a parameter to all filters. 126 | * Very basic unit testing. 127 | 128 | __Changed:__ 129 | 130 | * Linting tools and script build process. 131 | * Docs were migrated to [GitHub wiki](https://github.com/felipeelia/cf7-repeatable-fields/wiki). 132 | 133 | __Fixed:__ 134 | 135 | * Validation problem with Contact Form 5.7+. Props [@sfdeveloper](https://profiles.wordpress.org/sfdeveloper/). 136 | 137 | = 1.1.3 - 2019-12-11 = 138 | 139 | * Update WP `Tested up to` field 140 | * Apply WP Coding Standards 141 | * Fix a small sanitization problem 142 | 143 | = 1.1.2 - 2019-10-10 = 144 | 145 | * Fix Exclusive Checkboxes 146 | 147 | = 1.1.1 - 2019-09-04 = 148 | 149 | * Add compatibility to formatted dates (`[_format_{field name} "{date format}"]`) 150 | * DEV: Copy data and events while cloning a new group (JS) 151 | * DEV: Pass `$new_group` as an extra param for the `wpcf7-field-groups/added` event. 152 | * DEV: Apply some WPCS rules and add a CF7_REPEATABLE_FIELDS_VERSION const (may affect JS cache) 153 | 154 | = 1.1 - 2018-06-14 = 155 | 156 | * Replace groups in mail 2 field 157 | 158 | = 1.0.2 - 2018/03/29 = 159 | 160 | * Fix repeated tags in mail body 161 | 162 | = 1.0.1 - 2018/03/20 = 163 | 164 | * Fix the `wpcf7_field_group_remove_button_atts` filter name. Props to @asilvestre87 165 | 166 | = 1.0.0 - 2018/03/19 = 167 | 168 | * Initial release 169 | 170 | == Upgrade Notice == 171 | 172 | = 2.0.0 = 173 | This version changes the minimum requirements of the plugin: PHP 7.2+, WordPress 6.0+, and Contact Form 7 5.7+. 174 | -------------------------------------------------------------------------------- /class-cf7-repeatable-fields.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Plugin Main Class File 4 | * 5 | * @package CF7_Repeatable_Fields 6 | */ 7 | 8 | defined( 'ABSPATH' ) || exit; 9 | 10 | /** 11 | * Main Class 12 | */ 13 | class CF7_Repeatable_Fields { 14 | /** 15 | * The only instance of the class. 16 | * 17 | * @var object 18 | */ 19 | protected static $instance = null; 20 | 21 | /** 22 | * Name of the shortcode responsible for repeating the group. 23 | * 24 | * @var string 25 | */ 26 | protected $shortcode = 'field_group'; 27 | 28 | /** 29 | * Form groups, indexed by their ids. Each group has 30 | * `tags` (an array of WPCF7_FormTag) and 31 | * `raw`, the forms as created by the user. 32 | * 33 | * @var array 34 | */ 35 | protected $groups = array(); 36 | 37 | /** 38 | * Class constructor. Run only once (Singleton). 39 | */ 40 | private function __construct() { 41 | add_shortcode( $this->shortcode, array( $this, 'shortcode_render' ) ); 42 | add_action( 'wpcf7_contact_form', array( $this, 'wpcf7_contact_form' ) ); 43 | add_action( 'wpcf7_enqueue_scripts', array( $this, 'wpcf7_enqueue_scripts' ) ); 44 | } 45 | 46 | /** 47 | * The group shortcode. Generate the `<div>` element with fields and 48 | * set `$this->tags`. 49 | * 50 | * @param array $atts Group attributes. Should have a value wo attribute to be used as the group ID. 51 | * @param string $content Everything inside the shortcode. Hopefully CF7 fields (raw tags). 52 | * @return string $content with the add and remove buttons wrapped by a `div`. 53 | */ 54 | public function shortcode_render( $atts, $content ) { 55 | // Respect classes sent by user, but add the necessary class for js. 56 | $atts = ( empty( $atts ) ) ? array() : $atts; 57 | $atts['class'] = ( isset( $atts['class'] ) ) ? $atts['class'] : ''; 58 | $atts['class'] = 'wpcf7-field-groups ' . $atts['class']; 59 | 60 | $group_id = ''; 61 | $atts = array_map( 62 | function( $att, $value ) use ( &$group_id ) { 63 | // WordPress sets numeric atts if the `attr="value"` format isn't used. 64 | if ( is_int( $att ) ) { 65 | if ( false === strpos( $value, ':' ) ) { 66 | $att = 'data-wpcf7-group-id'; 67 | $group_id = $value; 68 | } else { 69 | // User can send attributes in the same format of CF7, i.e., `attr:value`. 70 | list( $att, $value ) = explode( ':', $value ); 71 | } 72 | } 73 | return sprintf( '%s="%s"', $att, esc_attr( $value ) ); 74 | }, 75 | array_keys( $atts ), 76 | $atts 77 | ); 78 | // Abort if there is no group id. 79 | if ( empty( $group_id ) ) { 80 | return sprintf( 81 | /* translators: Format to use surrounded by code tag */ 82 | '<p>' . __( 'You need to set an ID to this group. Use %s format.', 'cf7-repeatable-fields' ) . '</p>', 83 | "<code>[{$this->shortcode} your_custom_id]</code>" 84 | ); 85 | } 86 | 87 | $form_tags_manager = WPCF7_FormTagsManager::get_instance(); 88 | $this->groups[ $group_id ] = array( 89 | 'tags' => $form_tags_manager->scan( $content ), 90 | 'raw' => $content, 91 | ); 92 | 93 | // Add and remove group buttons. TODO: make this available from form edit screen. 94 | /** 95 | * Filters the add button attributes. Additional classes and text, so far. 96 | * 97 | * @param array $add_button_atts Array of strings with `group_id`, `additional_classes` and 98 | * `text` as indexes. 99 | */ 100 | $add_button_atts = apply_filters( 101 | 'wpcf7_field_group_add_button_atts', 102 | array( 103 | 'group_id' => $group_id, 104 | 'additional_classes' => '', 105 | 'text' => '+', 106 | ) 107 | ); 108 | /** 109 | * Filters the whole add group button. This way developers can wrap it with another element. 110 | * 111 | * @param string $button_html The HTML of the add button. 112 | * @param string $group_id Current group ID. 113 | */ 114 | $add_button = apply_filters( 115 | 'wpcf7_field_group_add_button', 116 | "<button type='button' class='wpcf7-field-group-add {$add_button_atts['additional_classes']}'>" . 117 | $add_button_atts['text'] . 118 | '</button>', 119 | $group_id 120 | ); 121 | 122 | /** 123 | * Filters the remove button attributes. Additional classes and text, so far. 124 | * 125 | * @param array $remove_button_atts Array of strings with `group_id`, `additional_classes` and 126 | * `text` as indexes. 127 | */ 128 | $remove_button_atts = apply_filters( 129 | 'wpcf7_field_group_remove_button_atts', 130 | array( 131 | 'group_id' => $group_id, 132 | 'additional_classes' => '', 133 | 'text' => '-', 134 | ) 135 | ); 136 | /** 137 | * Filters the whole remove group button. This way developers can wrap it with another element. 138 | * 139 | * @param string $button_html The HTML of the remove button. 140 | * @param string $group_id Current group ID. 141 | */ 142 | $remove_button = apply_filters( 143 | 'wpcf7_field_group_remove_button', 144 | "<button type='button' class='wpcf7-field-group-remove {$remove_button_atts['additional_classes']}'>" . 145 | $remove_button_atts['text'] . 146 | '</button>', 147 | $group_id 148 | ); 149 | 150 | // Remove any attribute that is not allowed by CF7. 151 | $open_tag = wpcf7_kses( '<div ' . implode( ' ', $atts ) . '>' ); 152 | 153 | /** 154 | * Filters the group content. 155 | * 156 | * @since 2.0.2 157 | * @param string $group_content Group content HTML 158 | * @param array $atts Shortcode attributes 159 | * @param string $group_id Current group ID. 160 | */ 161 | $group_content = apply_filters( 162 | 'wpcf7_field_group_content', 163 | '<div class="wpcf7-field-group">' . 164 | do_shortcode( $content ) . 165 | $remove_button . 166 | $add_button . 167 | '<input type="hidden" class="wpcf7-field-group-count" name="_wpcf7_groups_count[' . $group_id . ']" value="1" />' . 168 | '</div>', 169 | $atts, 170 | $group_id 171 | ); 172 | 173 | return $open_tag . $group_content . '</div>'; 174 | } 175 | 176 | /** 177 | * Enqueue the necessary JS for groups manipulation. 178 | */ 179 | public function wpcf7_enqueue_scripts() { 180 | $file = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? 181 | 'assets/js/scripts.js' : 182 | 'dist/scripts.js'; 183 | 184 | wp_enqueue_script( 185 | 'wpcf7-field-group-script', 186 | plugin_dir_url( CF7_REPEATABLE_FIELDS_FILE ) . $file, 187 | array( 'jquery' ), 188 | CF7_REPEATABLE_FIELDS_VERSION, 189 | true 190 | ); 191 | } 192 | 193 | /** 194 | * Change the Contact Form attributes. This way we make CF7 process this as 195 | * the form had several groups all the time. 196 | * 197 | * Here we take the fields and change their names. Example: 198 | * A `[your-field]` field becomes `[your-field__1]` and, if it's needed, 199 | * we append the necessary `[your-field__2]`, `[your-field__3]` and so on. That 200 | * way CF7 validates the field as a normal one. 201 | * 202 | * @param WPCF7_ContactForm $contact_form The Contact Form object. 203 | */ 204 | public function wpcf7_contact_form( $contact_form ) { 205 | // Don't mess up when user is editing the form. 206 | if ( is_admin() ) { 207 | return; 208 | } 209 | 210 | // This enables shortcode in Contact Form form. Side effects? 211 | $form = do_shortcode( $contact_form->prop( 'form' ) ); 212 | $mail = $contact_form->prop( 'mail' ); 213 | $mail_2 = $contact_form->prop( 'mail_2' ); 214 | 215 | // Post info sanitization. 216 | $groups_count = $this->sanitize_groups_count(); 217 | 218 | /* 219 | * We only make our magic when user is sending the form. 220 | * There is no need to change anything when showing it for the first time. 221 | */ 222 | if ( count( $this->groups ) && ! empty( $groups_count ) ) { 223 | foreach ( $groups_count as $group_id => $group_sent_count ) { 224 | 225 | // Change the `form` property. 226 | $form_raw_tags = $this->groups[ $group_id ]['raw']; 227 | $form_tags_first_replaced = $form_raw_tags; 228 | foreach ( $this->groups[ $group_id ]['tags'] as $tag ) { 229 | $tag_type = preg_quote( $tag->type, '/' ); 230 | $tag_name = preg_quote( $tag->name, '/' ); 231 | // Change the original `name` to `name__1`. 232 | $form_tags_first_replaced = preg_replace( "/\[{$tag_type}(.*?){$tag_name}/", "[{$tag->type}\\1{$tag->name}__1", $form_tags_first_replaced ); 233 | 234 | } 235 | $form_tags_replaced = $form_tags_first_replaced; 236 | for ( $i = 2; $i <= $group_sent_count; $i++ ) { 237 | // Change the `name__1` to `name__$i`. 238 | $form_tags_replaced .= preg_replace( '/__1(\s|\])/', "__{$i}$1", $form_tags_first_replaced ); 239 | } 240 | $form = str_replace( 241 | $form_raw_tags, 242 | $form_tags_replaced, 243 | $form 244 | ); 245 | 246 | // Change the `mail` property. Users can use `[group_index]` inside a group to show it's number. 247 | $mail['body'] = $this->replace_mail_field_groups( $group_id, $group_sent_count, $mail['body'] ); 248 | $mail_2['body'] = $this->replace_mail_field_groups( $group_id, $group_sent_count, $mail_2['body'] ); 249 | } 250 | } 251 | 252 | // Set up modified properties. `form` here already was `do_shortcode`'ed. 253 | $contact_form->set_properties( 254 | array( 255 | 'form' => $form, 256 | 'mail' => $mail, 257 | 'mail_2' => $mail_2, 258 | ) 259 | ); 260 | } 261 | 262 | /** 263 | * Replace a field group in mail bodies 264 | * 265 | * @param string $group_id The group ID. 266 | * @param int $group_sent_count Groups sent count. 267 | * @param string $mail_body The text set as body by users in CF7. User for main mail and mail 2. 268 | * @return string `$mail_body` with group replaced. 269 | */ 270 | private function replace_mail_field_groups( $group_id, $group_sent_count, $mail_body ) { 271 | $group_name = preg_quote( $group_id, '/' ); 272 | $group_in_mail = preg_match_all( 273 | "/\[{$group_name}\](.*?)\[\/{$group_name}\]/s", 274 | $mail_body, 275 | $matches 276 | ); 277 | if ( $group_in_mail ) { 278 | foreach ( $matches[1] as $i => $group_raw_content ) { 279 | $group_tags_first_replaced = str_replace( 280 | '[group_index]', 281 | '[group_index__1]', 282 | $group_raw_content 283 | ); 284 | 285 | foreach ( $this->groups[ $group_id ]['tags'] as $tag ) { 286 | $tag_name_regex = preg_quote( $tag->name, '/' ); 287 | // Change the original `name` to `name__1`. 288 | // Date fields accept `_format_` as a prefix. 289 | $group_tags_first_replaced = preg_replace( 290 | "/\[(_format_)?{$tag_name_regex}(\s|\])/", 291 | "[$1{$tag->name}__1$2", 292 | $group_tags_first_replaced 293 | ); 294 | } 295 | 296 | $group_tags_replaced = $group_tags_first_replaced; 297 | 298 | for ( $j = 2; $j <= $group_sent_count; $j++ ) { 299 | // Change the `name__1` to `name__$i`. 300 | $group_tags_replaced .= preg_replace( 301 | '/__1(\s|\])/', 302 | "__{$j}$1", 303 | $group_tags_first_replaced 304 | ); 305 | } 306 | 307 | $group_tags_replaced = preg_replace( 308 | '/\[group_index__([0-9]*)\]/', 309 | '\\1', 310 | $group_tags_replaced 311 | ); 312 | 313 | $mail_body = str_replace( 314 | $matches[0][ $i ], 315 | $group_tags_replaced, 316 | $mail_body 317 | ); 318 | } 319 | } 320 | return $mail_body; 321 | } 322 | 323 | /** 324 | * Sanitization method of the `_wpcf7_groups_count` hidden input. 325 | * 326 | * @return array 327 | */ 328 | private function sanitize_groups_count() { 329 | // phpcs:disable WordPress.Security.NonceVerification.Missing -- CF7 Handles this. 330 | // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 331 | $groups_count = ( isset( $_POST['_wpcf7_groups_count'] ) ) ? wp_unslash( (array) $_POST['_wpcf7_groups_count'] ) : array(); 332 | $groups_count = array_map( 'sanitize_text_field', wp_unslash( (array) $groups_count ) ); 333 | // phpcs:enable WordPress.Security.NonceVerification.Missing 334 | // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 335 | return $groups_count; 336 | } 337 | 338 | /** 339 | * SINGLETON. Return the single class instance. 340 | * 341 | * @return object the single instance of the class. 342 | */ 343 | public static function get_instance() { 344 | if ( null === self::$instance ) { 345 | self::$instance = new self(); 346 | } 347 | return self::$instance; 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | <one line to give the program's name and a brief idea of what it does.> 294 | Copyright (C) <year> <name of author> 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | <signature of Ty Coon>, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "f177862ec473355bfc52cccbb5c91c02", 8 | "packages": [ 9 | { 10 | "name": "composer/installers", 11 | "version": "v2.2.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/composer/installers.git", 15 | "reference": "c29dc4b93137acb82734f672c37e029dfbd95b35" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/composer/installers/zipball/c29dc4b93137acb82734f672c37e029dfbd95b35", 20 | "reference": "c29dc4b93137acb82734f672c37e029dfbd95b35", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "composer-plugin-api": "^1.0 || ^2.0", 25 | "php": "^7.2 || ^8.0" 26 | }, 27 | "require-dev": { 28 | "composer/composer": "1.6.* || ^2.0", 29 | "composer/semver": "^1 || ^3", 30 | "phpstan/phpstan": "^0.12.55", 31 | "phpstan/phpstan-phpunit": "^0.12.16", 32 | "symfony/phpunit-bridge": "^5.3", 33 | "symfony/process": "^5" 34 | }, 35 | "type": "composer-plugin", 36 | "extra": { 37 | "class": "Composer\\Installers\\Plugin", 38 | "branch-alias": { 39 | "dev-main": "2.x-dev" 40 | }, 41 | "plugin-modifies-install-path": true 42 | }, 43 | "autoload": { 44 | "psr-4": { 45 | "Composer\\Installers\\": "src/Composer/Installers" 46 | } 47 | }, 48 | "notification-url": "https://packagist.org/downloads/", 49 | "license": [ 50 | "MIT" 51 | ], 52 | "authors": [ 53 | { 54 | "name": "Kyle Robinson Young", 55 | "email": "kyle@dontkry.com", 56 | "homepage": "https://github.com/shama" 57 | } 58 | ], 59 | "description": "A multi-framework Composer library installer", 60 | "homepage": "https://composer.github.io/installers/", 61 | "keywords": [ 62 | "Dolibarr", 63 | "Eliasis", 64 | "Hurad", 65 | "ImageCMS", 66 | "Kanboard", 67 | "Lan Management System", 68 | "MODX Evo", 69 | "MantisBT", 70 | "Mautic", 71 | "Maya", 72 | "OXID", 73 | "Plentymarkets", 74 | "Porto", 75 | "RadPHP", 76 | "SMF", 77 | "Starbug", 78 | "Thelia", 79 | "Whmcs", 80 | "WolfCMS", 81 | "agl", 82 | "annotatecms", 83 | "attogram", 84 | "bitrix", 85 | "cakephp", 86 | "chef", 87 | "cockpit", 88 | "codeigniter", 89 | "concrete5", 90 | "croogo", 91 | "dokuwiki", 92 | "drupal", 93 | "eZ Platform", 94 | "elgg", 95 | "expressionengine", 96 | "fuelphp", 97 | "grav", 98 | "installer", 99 | "itop", 100 | "known", 101 | "kohana", 102 | "laravel", 103 | "lavalite", 104 | "lithium", 105 | "magento", 106 | "majima", 107 | "mako", 108 | "matomo", 109 | "mediawiki", 110 | "miaoxing", 111 | "modulework", 112 | "modx", 113 | "moodle", 114 | "osclass", 115 | "pantheon", 116 | "phpbb", 117 | "piwik", 118 | "ppi", 119 | "processwire", 120 | "puppet", 121 | "pxcms", 122 | "reindex", 123 | "roundcube", 124 | "shopware", 125 | "silverstripe", 126 | "sydes", 127 | "sylius", 128 | "tastyigniter", 129 | "wordpress", 130 | "yawik", 131 | "zend", 132 | "zikula" 133 | ], 134 | "support": { 135 | "issues": "https://github.com/composer/installers/issues", 136 | "source": "https://github.com/composer/installers/tree/v2.2.0" 137 | }, 138 | "funding": [ 139 | { 140 | "url": "https://packagist.com", 141 | "type": "custom" 142 | }, 143 | { 144 | "url": "https://github.com/composer", 145 | "type": "github" 146 | }, 147 | { 148 | "url": "https://tidelift.com/funding/github/packagist/composer/composer", 149 | "type": "tidelift" 150 | } 151 | ], 152 | "time": "2022-08-20T06:45:11+00:00" 153 | } 154 | ], 155 | "packages-dev": [ 156 | { 157 | "name": "10up/phpcs-composer", 158 | "version": "dev-master", 159 | "source": { 160 | "type": "git", 161 | "url": "https://github.com/10up/phpcs-composer.git", 162 | "reference": "9c085cf0554a0b5311623548663aa9e4d8f52587" 163 | }, 164 | "dist": { 165 | "type": "zip", 166 | "url": "https://api.github.com/repos/10up/phpcs-composer/zipball/9c085cf0554a0b5311623548663aa9e4d8f52587", 167 | "reference": "9c085cf0554a0b5311623548663aa9e4d8f52587", 168 | "shasum": "" 169 | }, 170 | "require": { 171 | "automattic/vipwpcs": "^2.3", 172 | "dealerdirect/phpcodesniffer-composer-installer": "*", 173 | "phpcompatibility/phpcompatibility-wp": "^2", 174 | "squizlabs/php_codesniffer": "3.7.1", 175 | "wp-coding-standards/wpcs": "*" 176 | }, 177 | "default-branch": true, 178 | "type": "phpcodesniffer-standard", 179 | "notification-url": "https://packagist.org/downloads/", 180 | "license": [ 181 | "MIT" 182 | ], 183 | "authors": [ 184 | { 185 | "name": "Ephraim Gregor", 186 | "email": "ephraim.gregor@10up.com" 187 | } 188 | ], 189 | "support": { 190 | "issues": "https://github.com/10up/phpcs-composer/issues", 191 | "source": "https://github.com/10up/phpcs-composer/tree/master" 192 | }, 193 | "time": "2023-05-10T22:44:49+00:00" 194 | }, 195 | { 196 | "name": "automattic/vipwpcs", 197 | "version": "2.3.3", 198 | "source": { 199 | "type": "git", 200 | "url": "https://github.com/Automattic/VIP-Coding-Standards.git", 201 | "reference": "6cd0a6a82bc0ac988dbf9d6a7c2e293dc8ac640b" 202 | }, 203 | "dist": { 204 | "type": "zip", 205 | "url": "https://api.github.com/repos/Automattic/VIP-Coding-Standards/zipball/6cd0a6a82bc0ac988dbf9d6a7c2e293dc8ac640b", 206 | "reference": "6cd0a6a82bc0ac988dbf9d6a7c2e293dc8ac640b", 207 | "shasum": "" 208 | }, 209 | "require": { 210 | "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7", 211 | "php": ">=5.4", 212 | "sirbrillig/phpcs-variable-analysis": "^2.11.1", 213 | "squizlabs/php_codesniffer": "^3.5.5", 214 | "wp-coding-standards/wpcs": "^2.3" 215 | }, 216 | "require-dev": { 217 | "php-parallel-lint/php-console-highlighter": "^0.5", 218 | "php-parallel-lint/php-parallel-lint": "^1.0", 219 | "phpcompatibility/php-compatibility": "^9", 220 | "phpcsstandards/phpcsdevtools": "^1.0", 221 | "phpunit/phpunit": "^4 || ^5 || ^6 || ^7" 222 | }, 223 | "type": "phpcodesniffer-standard", 224 | "notification-url": "https://packagist.org/downloads/", 225 | "license": [ 226 | "MIT" 227 | ], 228 | "authors": [ 229 | { 230 | "name": "Contributors", 231 | "homepage": "https://github.com/Automattic/VIP-Coding-Standards/graphs/contributors" 232 | } 233 | ], 234 | "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress VIP minimum coding conventions", 235 | "keywords": [ 236 | "phpcs", 237 | "standards", 238 | "wordpress" 239 | ], 240 | "support": { 241 | "issues": "https://github.com/Automattic/VIP-Coding-Standards/issues", 242 | "source": "https://github.com/Automattic/VIP-Coding-Standards", 243 | "wiki": "https://github.com/Automattic/VIP-Coding-Standards/wiki" 244 | }, 245 | "time": "2021-09-29T16:20:23+00:00" 246 | }, 247 | { 248 | "name": "dealerdirect/phpcodesniffer-composer-installer", 249 | "version": "v0.7.2", 250 | "source": { 251 | "type": "git", 252 | "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", 253 | "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" 254 | }, 255 | "dist": { 256 | "type": "zip", 257 | "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", 258 | "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", 259 | "shasum": "" 260 | }, 261 | "require": { 262 | "composer-plugin-api": "^1.0 || ^2.0", 263 | "php": ">=5.3", 264 | "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" 265 | }, 266 | "require-dev": { 267 | "composer/composer": "*", 268 | "php-parallel-lint/php-parallel-lint": "^1.3.1", 269 | "phpcompatibility/php-compatibility": "^9.0" 270 | }, 271 | "type": "composer-plugin", 272 | "extra": { 273 | "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" 274 | }, 275 | "autoload": { 276 | "psr-4": { 277 | "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" 278 | } 279 | }, 280 | "notification-url": "https://packagist.org/downloads/", 281 | "license": [ 282 | "MIT" 283 | ], 284 | "authors": [ 285 | { 286 | "name": "Franck Nijhof", 287 | "email": "franck.nijhof@dealerdirect.com", 288 | "homepage": "http://www.frenck.nl", 289 | "role": "Developer / IT Manager" 290 | }, 291 | { 292 | "name": "Contributors", 293 | "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" 294 | } 295 | ], 296 | "description": "PHP_CodeSniffer Standards Composer Installer Plugin", 297 | "homepage": "http://www.dealerdirect.com", 298 | "keywords": [ 299 | "PHPCodeSniffer", 300 | "PHP_CodeSniffer", 301 | "code quality", 302 | "codesniffer", 303 | "composer", 304 | "installer", 305 | "phpcbf", 306 | "phpcs", 307 | "plugin", 308 | "qa", 309 | "quality", 310 | "standard", 311 | "standards", 312 | "style guide", 313 | "stylecheck", 314 | "tests" 315 | ], 316 | "support": { 317 | "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", 318 | "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" 319 | }, 320 | "time": "2022-02-04T12:51:07+00:00" 321 | }, 322 | { 323 | "name": "doctrine/instantiator", 324 | "version": "1.5.0", 325 | "source": { 326 | "type": "git", 327 | "url": "https://github.com/doctrine/instantiator.git", 328 | "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" 329 | }, 330 | "dist": { 331 | "type": "zip", 332 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", 333 | "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", 334 | "shasum": "" 335 | }, 336 | "require": { 337 | "php": "^7.1 || ^8.0" 338 | }, 339 | "require-dev": { 340 | "doctrine/coding-standard": "^9 || ^11", 341 | "ext-pdo": "*", 342 | "ext-phar": "*", 343 | "phpbench/phpbench": "^0.16 || ^1", 344 | "phpstan/phpstan": "^1.4", 345 | "phpstan/phpstan-phpunit": "^1", 346 | "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", 347 | "vimeo/psalm": "^4.30 || ^5.4" 348 | }, 349 | "type": "library", 350 | "autoload": { 351 | "psr-4": { 352 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 353 | } 354 | }, 355 | "notification-url": "https://packagist.org/downloads/", 356 | "license": [ 357 | "MIT" 358 | ], 359 | "authors": [ 360 | { 361 | "name": "Marco Pivetta", 362 | "email": "ocramius@gmail.com", 363 | "homepage": "https://ocramius.github.io/" 364 | } 365 | ], 366 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 367 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 368 | "keywords": [ 369 | "constructor", 370 | "instantiate" 371 | ], 372 | "support": { 373 | "issues": "https://github.com/doctrine/instantiator/issues", 374 | "source": "https://github.com/doctrine/instantiator/tree/1.5.0" 375 | }, 376 | "funding": [ 377 | { 378 | "url": "https://www.doctrine-project.org/sponsorship.html", 379 | "type": "custom" 380 | }, 381 | { 382 | "url": "https://www.patreon.com/phpdoctrine", 383 | "type": "patreon" 384 | }, 385 | { 386 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", 387 | "type": "tidelift" 388 | } 389 | ], 390 | "time": "2022-12-30T00:15:36+00:00" 391 | }, 392 | { 393 | "name": "myclabs/deep-copy", 394 | "version": "1.11.1", 395 | "source": { 396 | "type": "git", 397 | "url": "https://github.com/myclabs/DeepCopy.git", 398 | "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" 399 | }, 400 | "dist": { 401 | "type": "zip", 402 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", 403 | "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", 404 | "shasum": "" 405 | }, 406 | "require": { 407 | "php": "^7.1 || ^8.0" 408 | }, 409 | "conflict": { 410 | "doctrine/collections": "<1.6.8", 411 | "doctrine/common": "<2.13.3 || >=3,<3.2.2" 412 | }, 413 | "require-dev": { 414 | "doctrine/collections": "^1.6.8", 415 | "doctrine/common": "^2.13.3 || ^3.2.2", 416 | "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" 417 | }, 418 | "type": "library", 419 | "autoload": { 420 | "files": [ 421 | "src/DeepCopy/deep_copy.php" 422 | ], 423 | "psr-4": { 424 | "DeepCopy\\": "src/DeepCopy/" 425 | } 426 | }, 427 | "notification-url": "https://packagist.org/downloads/", 428 | "license": [ 429 | "MIT" 430 | ], 431 | "description": "Create deep copies (clones) of your objects", 432 | "keywords": [ 433 | "clone", 434 | "copy", 435 | "duplicate", 436 | "object", 437 | "object graph" 438 | ], 439 | "support": { 440 | "issues": "https://github.com/myclabs/DeepCopy/issues", 441 | "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" 442 | }, 443 | "funding": [ 444 | { 445 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", 446 | "type": "tidelift" 447 | } 448 | ], 449 | "time": "2023-03-08T13:26:56+00:00" 450 | }, 451 | { 452 | "name": "nikic/php-parser", 453 | "version": "v4.16.0", 454 | "source": { 455 | "type": "git", 456 | "url": "https://github.com/nikic/PHP-Parser.git", 457 | "reference": "19526a33fb561ef417e822e85f08a00db4059c17" 458 | }, 459 | "dist": { 460 | "type": "zip", 461 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17", 462 | "reference": "19526a33fb561ef417e822e85f08a00db4059c17", 463 | "shasum": "" 464 | }, 465 | "require": { 466 | "ext-tokenizer": "*", 467 | "php": ">=7.0" 468 | }, 469 | "require-dev": { 470 | "ircmaxell/php-yacc": "^0.0.7", 471 | "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" 472 | }, 473 | "bin": [ 474 | "bin/php-parse" 475 | ], 476 | "type": "library", 477 | "extra": { 478 | "branch-alias": { 479 | "dev-master": "4.9-dev" 480 | } 481 | }, 482 | "autoload": { 483 | "psr-4": { 484 | "PhpParser\\": "lib/PhpParser" 485 | } 486 | }, 487 | "notification-url": "https://packagist.org/downloads/", 488 | "license": [ 489 | "BSD-3-Clause" 490 | ], 491 | "authors": [ 492 | { 493 | "name": "Nikita Popov" 494 | } 495 | ], 496 | "description": "A PHP parser written in PHP", 497 | "keywords": [ 498 | "parser", 499 | "php" 500 | ], 501 | "support": { 502 | "issues": "https://github.com/nikic/PHP-Parser/issues", 503 | "source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0" 504 | }, 505 | "time": "2023-06-25T14:52:30+00:00" 506 | }, 507 | { 508 | "name": "phar-io/manifest", 509 | "version": "2.0.3", 510 | "source": { 511 | "type": "git", 512 | "url": "https://github.com/phar-io/manifest.git", 513 | "reference": "97803eca37d319dfa7826cc2437fc020857acb53" 514 | }, 515 | "dist": { 516 | "type": "zip", 517 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", 518 | "reference": "97803eca37d319dfa7826cc2437fc020857acb53", 519 | "shasum": "" 520 | }, 521 | "require": { 522 | "ext-dom": "*", 523 | "ext-phar": "*", 524 | "ext-xmlwriter": "*", 525 | "phar-io/version": "^3.0.1", 526 | "php": "^7.2 || ^8.0" 527 | }, 528 | "type": "library", 529 | "extra": { 530 | "branch-alias": { 531 | "dev-master": "2.0.x-dev" 532 | } 533 | }, 534 | "autoload": { 535 | "classmap": [ 536 | "src/" 537 | ] 538 | }, 539 | "notification-url": "https://packagist.org/downloads/", 540 | "license": [ 541 | "BSD-3-Clause" 542 | ], 543 | "authors": [ 544 | { 545 | "name": "Arne Blankerts", 546 | "email": "arne@blankerts.de", 547 | "role": "Developer" 548 | }, 549 | { 550 | "name": "Sebastian Heuer", 551 | "email": "sebastian@phpeople.de", 552 | "role": "Developer" 553 | }, 554 | { 555 | "name": "Sebastian Bergmann", 556 | "email": "sebastian@phpunit.de", 557 | "role": "Developer" 558 | } 559 | ], 560 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 561 | "support": { 562 | "issues": "https://github.com/phar-io/manifest/issues", 563 | "source": "https://github.com/phar-io/manifest/tree/2.0.3" 564 | }, 565 | "time": "2021-07-20T11:28:43+00:00" 566 | }, 567 | { 568 | "name": "phar-io/version", 569 | "version": "3.2.1", 570 | "source": { 571 | "type": "git", 572 | "url": "https://github.com/phar-io/version.git", 573 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" 574 | }, 575 | "dist": { 576 | "type": "zip", 577 | "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 578 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 579 | "shasum": "" 580 | }, 581 | "require": { 582 | "php": "^7.2 || ^8.0" 583 | }, 584 | "type": "library", 585 | "autoload": { 586 | "classmap": [ 587 | "src/" 588 | ] 589 | }, 590 | "notification-url": "https://packagist.org/downloads/", 591 | "license": [ 592 | "BSD-3-Clause" 593 | ], 594 | "authors": [ 595 | { 596 | "name": "Arne Blankerts", 597 | "email": "arne@blankerts.de", 598 | "role": "Developer" 599 | }, 600 | { 601 | "name": "Sebastian Heuer", 602 | "email": "sebastian@phpeople.de", 603 | "role": "Developer" 604 | }, 605 | { 606 | "name": "Sebastian Bergmann", 607 | "email": "sebastian@phpunit.de", 608 | "role": "Developer" 609 | } 610 | ], 611 | "description": "Library for handling version information and constraints", 612 | "support": { 613 | "issues": "https://github.com/phar-io/version/issues", 614 | "source": "https://github.com/phar-io/version/tree/3.2.1" 615 | }, 616 | "time": "2022-02-21T01:04:05+00:00" 617 | }, 618 | { 619 | "name": "phpcompatibility/php-compatibility", 620 | "version": "9.3.5", 621 | "source": { 622 | "type": "git", 623 | "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", 624 | "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" 625 | }, 626 | "dist": { 627 | "type": "zip", 628 | "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", 629 | "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", 630 | "shasum": "" 631 | }, 632 | "require": { 633 | "php": ">=5.3", 634 | "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" 635 | }, 636 | "conflict": { 637 | "squizlabs/php_codesniffer": "2.6.2" 638 | }, 639 | "require-dev": { 640 | "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" 641 | }, 642 | "suggest": { 643 | "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", 644 | "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." 645 | }, 646 | "type": "phpcodesniffer-standard", 647 | "notification-url": "https://packagist.org/downloads/", 648 | "license": [ 649 | "LGPL-3.0-or-later" 650 | ], 651 | "authors": [ 652 | { 653 | "name": "Wim Godden", 654 | "homepage": "https://github.com/wimg", 655 | "role": "lead" 656 | }, 657 | { 658 | "name": "Juliette Reinders Folmer", 659 | "homepage": "https://github.com/jrfnl", 660 | "role": "lead" 661 | }, 662 | { 663 | "name": "Contributors", 664 | "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" 665 | } 666 | ], 667 | "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", 668 | "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", 669 | "keywords": [ 670 | "compatibility", 671 | "phpcs", 672 | "standards" 673 | ], 674 | "support": { 675 | "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", 676 | "source": "https://github.com/PHPCompatibility/PHPCompatibility" 677 | }, 678 | "time": "2019-12-27T09:44:58+00:00" 679 | }, 680 | { 681 | "name": "phpcompatibility/phpcompatibility-paragonie", 682 | "version": "1.3.2", 683 | "source": { 684 | "type": "git", 685 | "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", 686 | "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26" 687 | }, 688 | "dist": { 689 | "type": "zip", 690 | "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", 691 | "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", 692 | "shasum": "" 693 | }, 694 | "require": { 695 | "phpcompatibility/php-compatibility": "^9.0" 696 | }, 697 | "require-dev": { 698 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7", 699 | "paragonie/random_compat": "dev-master", 700 | "paragonie/sodium_compat": "dev-master" 701 | }, 702 | "suggest": { 703 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", 704 | "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." 705 | }, 706 | "type": "phpcodesniffer-standard", 707 | "notification-url": "https://packagist.org/downloads/", 708 | "license": [ 709 | "LGPL-3.0-or-later" 710 | ], 711 | "authors": [ 712 | { 713 | "name": "Wim Godden", 714 | "role": "lead" 715 | }, 716 | { 717 | "name": "Juliette Reinders Folmer", 718 | "role": "lead" 719 | } 720 | ], 721 | "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.", 722 | "homepage": "http://phpcompatibility.com/", 723 | "keywords": [ 724 | "compatibility", 725 | "paragonie", 726 | "phpcs", 727 | "polyfill", 728 | "standards", 729 | "static analysis" 730 | ], 731 | "support": { 732 | "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", 733 | "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" 734 | }, 735 | "time": "2022-10-25T01:46:02+00:00" 736 | }, 737 | { 738 | "name": "phpcompatibility/phpcompatibility-wp", 739 | "version": "2.1.4", 740 | "source": { 741 | "type": "git", 742 | "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", 743 | "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5" 744 | }, 745 | "dist": { 746 | "type": "zip", 747 | "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5", 748 | "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5", 749 | "shasum": "" 750 | }, 751 | "require": { 752 | "phpcompatibility/php-compatibility": "^9.0", 753 | "phpcompatibility/phpcompatibility-paragonie": "^1.0" 754 | }, 755 | "require-dev": { 756 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7" 757 | }, 758 | "suggest": { 759 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", 760 | "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." 761 | }, 762 | "type": "phpcodesniffer-standard", 763 | "notification-url": "https://packagist.org/downloads/", 764 | "license": [ 765 | "LGPL-3.0-or-later" 766 | ], 767 | "authors": [ 768 | { 769 | "name": "Wim Godden", 770 | "role": "lead" 771 | }, 772 | { 773 | "name": "Juliette Reinders Folmer", 774 | "role": "lead" 775 | } 776 | ], 777 | "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.", 778 | "homepage": "http://phpcompatibility.com/", 779 | "keywords": [ 780 | "compatibility", 781 | "phpcs", 782 | "standards", 783 | "static analysis", 784 | "wordpress" 785 | ], 786 | "support": { 787 | "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", 788 | "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" 789 | }, 790 | "time": "2022-10-24T09:00:36+00:00" 791 | }, 792 | { 793 | "name": "phpunit/php-code-coverage", 794 | "version": "9.2.26", 795 | "source": { 796 | "type": "git", 797 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 798 | "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" 799 | }, 800 | "dist": { 801 | "type": "zip", 802 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", 803 | "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", 804 | "shasum": "" 805 | }, 806 | "require": { 807 | "ext-dom": "*", 808 | "ext-libxml": "*", 809 | "ext-xmlwriter": "*", 810 | "nikic/php-parser": "^4.15", 811 | "php": ">=7.3", 812 | "phpunit/php-file-iterator": "^3.0.3", 813 | "phpunit/php-text-template": "^2.0.2", 814 | "sebastian/code-unit-reverse-lookup": "^2.0.2", 815 | "sebastian/complexity": "^2.0", 816 | "sebastian/environment": "^5.1.2", 817 | "sebastian/lines-of-code": "^1.0.3", 818 | "sebastian/version": "^3.0.1", 819 | "theseer/tokenizer": "^1.2.0" 820 | }, 821 | "require-dev": { 822 | "phpunit/phpunit": "^9.3" 823 | }, 824 | "suggest": { 825 | "ext-pcov": "PHP extension that provides line coverage", 826 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" 827 | }, 828 | "type": "library", 829 | "extra": { 830 | "branch-alias": { 831 | "dev-master": "9.2-dev" 832 | } 833 | }, 834 | "autoload": { 835 | "classmap": [ 836 | "src/" 837 | ] 838 | }, 839 | "notification-url": "https://packagist.org/downloads/", 840 | "license": [ 841 | "BSD-3-Clause" 842 | ], 843 | "authors": [ 844 | { 845 | "name": "Sebastian Bergmann", 846 | "email": "sebastian@phpunit.de", 847 | "role": "lead" 848 | } 849 | ], 850 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 851 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 852 | "keywords": [ 853 | "coverage", 854 | "testing", 855 | "xunit" 856 | ], 857 | "support": { 858 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", 859 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" 860 | }, 861 | "funding": [ 862 | { 863 | "url": "https://github.com/sebastianbergmann", 864 | "type": "github" 865 | } 866 | ], 867 | "time": "2023-03-06T12:58:08+00:00" 868 | }, 869 | { 870 | "name": "phpunit/php-file-iterator", 871 | "version": "3.0.6", 872 | "source": { 873 | "type": "git", 874 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 875 | "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" 876 | }, 877 | "dist": { 878 | "type": "zip", 879 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", 880 | "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", 881 | "shasum": "" 882 | }, 883 | "require": { 884 | "php": ">=7.3" 885 | }, 886 | "require-dev": { 887 | "phpunit/phpunit": "^9.3" 888 | }, 889 | "type": "library", 890 | "extra": { 891 | "branch-alias": { 892 | "dev-master": "3.0-dev" 893 | } 894 | }, 895 | "autoload": { 896 | "classmap": [ 897 | "src/" 898 | ] 899 | }, 900 | "notification-url": "https://packagist.org/downloads/", 901 | "license": [ 902 | "BSD-3-Clause" 903 | ], 904 | "authors": [ 905 | { 906 | "name": "Sebastian Bergmann", 907 | "email": "sebastian@phpunit.de", 908 | "role": "lead" 909 | } 910 | ], 911 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 912 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 913 | "keywords": [ 914 | "filesystem", 915 | "iterator" 916 | ], 917 | "support": { 918 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", 919 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" 920 | }, 921 | "funding": [ 922 | { 923 | "url": "https://github.com/sebastianbergmann", 924 | "type": "github" 925 | } 926 | ], 927 | "time": "2021-12-02T12:48:52+00:00" 928 | }, 929 | { 930 | "name": "phpunit/php-invoker", 931 | "version": "3.1.1", 932 | "source": { 933 | "type": "git", 934 | "url": "https://github.com/sebastianbergmann/php-invoker.git", 935 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" 936 | }, 937 | "dist": { 938 | "type": "zip", 939 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", 940 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", 941 | "shasum": "" 942 | }, 943 | "require": { 944 | "php": ">=7.3" 945 | }, 946 | "require-dev": { 947 | "ext-pcntl": "*", 948 | "phpunit/phpunit": "^9.3" 949 | }, 950 | "suggest": { 951 | "ext-pcntl": "*" 952 | }, 953 | "type": "library", 954 | "extra": { 955 | "branch-alias": { 956 | "dev-master": "3.1-dev" 957 | } 958 | }, 959 | "autoload": { 960 | "classmap": [ 961 | "src/" 962 | ] 963 | }, 964 | "notification-url": "https://packagist.org/downloads/", 965 | "license": [ 966 | "BSD-3-Clause" 967 | ], 968 | "authors": [ 969 | { 970 | "name": "Sebastian Bergmann", 971 | "email": "sebastian@phpunit.de", 972 | "role": "lead" 973 | } 974 | ], 975 | "description": "Invoke callables with a timeout", 976 | "homepage": "https://github.com/sebastianbergmann/php-invoker/", 977 | "keywords": [ 978 | "process" 979 | ], 980 | "support": { 981 | "issues": "https://github.com/sebastianbergmann/php-invoker/issues", 982 | "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" 983 | }, 984 | "funding": [ 985 | { 986 | "url": "https://github.com/sebastianbergmann", 987 | "type": "github" 988 | } 989 | ], 990 | "time": "2020-09-28T05:58:55+00:00" 991 | }, 992 | { 993 | "name": "phpunit/php-text-template", 994 | "version": "2.0.4", 995 | "source": { 996 | "type": "git", 997 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 998 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" 999 | }, 1000 | "dist": { 1001 | "type": "zip", 1002 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", 1003 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", 1004 | "shasum": "" 1005 | }, 1006 | "require": { 1007 | "php": ">=7.3" 1008 | }, 1009 | "require-dev": { 1010 | "phpunit/phpunit": "^9.3" 1011 | }, 1012 | "type": "library", 1013 | "extra": { 1014 | "branch-alias": { 1015 | "dev-master": "2.0-dev" 1016 | } 1017 | }, 1018 | "autoload": { 1019 | "classmap": [ 1020 | "src/" 1021 | ] 1022 | }, 1023 | "notification-url": "https://packagist.org/downloads/", 1024 | "license": [ 1025 | "BSD-3-Clause" 1026 | ], 1027 | "authors": [ 1028 | { 1029 | "name": "Sebastian Bergmann", 1030 | "email": "sebastian@phpunit.de", 1031 | "role": "lead" 1032 | } 1033 | ], 1034 | "description": "Simple template engine.", 1035 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 1036 | "keywords": [ 1037 | "template" 1038 | ], 1039 | "support": { 1040 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues", 1041 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" 1042 | }, 1043 | "funding": [ 1044 | { 1045 | "url": "https://github.com/sebastianbergmann", 1046 | "type": "github" 1047 | } 1048 | ], 1049 | "time": "2020-10-26T05:33:50+00:00" 1050 | }, 1051 | { 1052 | "name": "phpunit/php-timer", 1053 | "version": "5.0.3", 1054 | "source": { 1055 | "type": "git", 1056 | "url": "https://github.com/sebastianbergmann/php-timer.git", 1057 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" 1058 | }, 1059 | "dist": { 1060 | "type": "zip", 1061 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", 1062 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", 1063 | "shasum": "" 1064 | }, 1065 | "require": { 1066 | "php": ">=7.3" 1067 | }, 1068 | "require-dev": { 1069 | "phpunit/phpunit": "^9.3" 1070 | }, 1071 | "type": "library", 1072 | "extra": { 1073 | "branch-alias": { 1074 | "dev-master": "5.0-dev" 1075 | } 1076 | }, 1077 | "autoload": { 1078 | "classmap": [ 1079 | "src/" 1080 | ] 1081 | }, 1082 | "notification-url": "https://packagist.org/downloads/", 1083 | "license": [ 1084 | "BSD-3-Clause" 1085 | ], 1086 | "authors": [ 1087 | { 1088 | "name": "Sebastian Bergmann", 1089 | "email": "sebastian@phpunit.de", 1090 | "role": "lead" 1091 | } 1092 | ], 1093 | "description": "Utility class for timing", 1094 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 1095 | "keywords": [ 1096 | "timer" 1097 | ], 1098 | "support": { 1099 | "issues": "https://github.com/sebastianbergmann/php-timer/issues", 1100 | "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" 1101 | }, 1102 | "funding": [ 1103 | { 1104 | "url": "https://github.com/sebastianbergmann", 1105 | "type": "github" 1106 | } 1107 | ], 1108 | "time": "2020-10-26T13:16:10+00:00" 1109 | }, 1110 | { 1111 | "name": "phpunit/phpunit", 1112 | "version": "9.6.10", 1113 | "source": { 1114 | "type": "git", 1115 | "url": "https://github.com/sebastianbergmann/phpunit.git", 1116 | "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328" 1117 | }, 1118 | "dist": { 1119 | "type": "zip", 1120 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a6d351645c3fe5a30f5e86be6577d946af65a328", 1121 | "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328", 1122 | "shasum": "" 1123 | }, 1124 | "require": { 1125 | "doctrine/instantiator": "^1.3.1 || ^2", 1126 | "ext-dom": "*", 1127 | "ext-json": "*", 1128 | "ext-libxml": "*", 1129 | "ext-mbstring": "*", 1130 | "ext-xml": "*", 1131 | "ext-xmlwriter": "*", 1132 | "myclabs/deep-copy": "^1.10.1", 1133 | "phar-io/manifest": "^2.0.3", 1134 | "phar-io/version": "^3.0.2", 1135 | "php": ">=7.3", 1136 | "phpunit/php-code-coverage": "^9.2.13", 1137 | "phpunit/php-file-iterator": "^3.0.5", 1138 | "phpunit/php-invoker": "^3.1.1", 1139 | "phpunit/php-text-template": "^2.0.3", 1140 | "phpunit/php-timer": "^5.0.2", 1141 | "sebastian/cli-parser": "^1.0.1", 1142 | "sebastian/code-unit": "^1.0.6", 1143 | "sebastian/comparator": "^4.0.8", 1144 | "sebastian/diff": "^4.0.3", 1145 | "sebastian/environment": "^5.1.3", 1146 | "sebastian/exporter": "^4.0.5", 1147 | "sebastian/global-state": "^5.0.1", 1148 | "sebastian/object-enumerator": "^4.0.3", 1149 | "sebastian/resource-operations": "^3.0.3", 1150 | "sebastian/type": "^3.2", 1151 | "sebastian/version": "^3.0.2" 1152 | }, 1153 | "suggest": { 1154 | "ext-soap": "To be able to generate mocks based on WSDL files", 1155 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" 1156 | }, 1157 | "bin": [ 1158 | "phpunit" 1159 | ], 1160 | "type": "library", 1161 | "extra": { 1162 | "branch-alias": { 1163 | "dev-master": "9.6-dev" 1164 | } 1165 | }, 1166 | "autoload": { 1167 | "files": [ 1168 | "src/Framework/Assert/Functions.php" 1169 | ], 1170 | "classmap": [ 1171 | "src/" 1172 | ] 1173 | }, 1174 | "notification-url": "https://packagist.org/downloads/", 1175 | "license": [ 1176 | "BSD-3-Clause" 1177 | ], 1178 | "authors": [ 1179 | { 1180 | "name": "Sebastian Bergmann", 1181 | "email": "sebastian@phpunit.de", 1182 | "role": "lead" 1183 | } 1184 | ], 1185 | "description": "The PHP Unit Testing framework.", 1186 | "homepage": "https://phpunit.de/", 1187 | "keywords": [ 1188 | "phpunit", 1189 | "testing", 1190 | "xunit" 1191 | ], 1192 | "support": { 1193 | "issues": "https://github.com/sebastianbergmann/phpunit/issues", 1194 | "security": "https://github.com/sebastianbergmann/phpunit/security/policy", 1195 | "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.10" 1196 | }, 1197 | "funding": [ 1198 | { 1199 | "url": "https://phpunit.de/sponsors.html", 1200 | "type": "custom" 1201 | }, 1202 | { 1203 | "url": "https://github.com/sebastianbergmann", 1204 | "type": "github" 1205 | }, 1206 | { 1207 | "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", 1208 | "type": "tidelift" 1209 | } 1210 | ], 1211 | "time": "2023-07-10T04:04:23+00:00" 1212 | }, 1213 | { 1214 | "name": "sebastian/cli-parser", 1215 | "version": "1.0.1", 1216 | "source": { 1217 | "type": "git", 1218 | "url": "https://github.com/sebastianbergmann/cli-parser.git", 1219 | "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" 1220 | }, 1221 | "dist": { 1222 | "type": "zip", 1223 | "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", 1224 | "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", 1225 | "shasum": "" 1226 | }, 1227 | "require": { 1228 | "php": ">=7.3" 1229 | }, 1230 | "require-dev": { 1231 | "phpunit/phpunit": "^9.3" 1232 | }, 1233 | "type": "library", 1234 | "extra": { 1235 | "branch-alias": { 1236 | "dev-master": "1.0-dev" 1237 | } 1238 | }, 1239 | "autoload": { 1240 | "classmap": [ 1241 | "src/" 1242 | ] 1243 | }, 1244 | "notification-url": "https://packagist.org/downloads/", 1245 | "license": [ 1246 | "BSD-3-Clause" 1247 | ], 1248 | "authors": [ 1249 | { 1250 | "name": "Sebastian Bergmann", 1251 | "email": "sebastian@phpunit.de", 1252 | "role": "lead" 1253 | } 1254 | ], 1255 | "description": "Library for parsing CLI options", 1256 | "homepage": "https://github.com/sebastianbergmann/cli-parser", 1257 | "support": { 1258 | "issues": "https://github.com/sebastianbergmann/cli-parser/issues", 1259 | "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" 1260 | }, 1261 | "funding": [ 1262 | { 1263 | "url": "https://github.com/sebastianbergmann", 1264 | "type": "github" 1265 | } 1266 | ], 1267 | "time": "2020-09-28T06:08:49+00:00" 1268 | }, 1269 | { 1270 | "name": "sebastian/code-unit", 1271 | "version": "1.0.8", 1272 | "source": { 1273 | "type": "git", 1274 | "url": "https://github.com/sebastianbergmann/code-unit.git", 1275 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" 1276 | }, 1277 | "dist": { 1278 | "type": "zip", 1279 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", 1280 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", 1281 | "shasum": "" 1282 | }, 1283 | "require": { 1284 | "php": ">=7.3" 1285 | }, 1286 | "require-dev": { 1287 | "phpunit/phpunit": "^9.3" 1288 | }, 1289 | "type": "library", 1290 | "extra": { 1291 | "branch-alias": { 1292 | "dev-master": "1.0-dev" 1293 | } 1294 | }, 1295 | "autoload": { 1296 | "classmap": [ 1297 | "src/" 1298 | ] 1299 | }, 1300 | "notification-url": "https://packagist.org/downloads/", 1301 | "license": [ 1302 | "BSD-3-Clause" 1303 | ], 1304 | "authors": [ 1305 | { 1306 | "name": "Sebastian Bergmann", 1307 | "email": "sebastian@phpunit.de", 1308 | "role": "lead" 1309 | } 1310 | ], 1311 | "description": "Collection of value objects that represent the PHP code units", 1312 | "homepage": "https://github.com/sebastianbergmann/code-unit", 1313 | "support": { 1314 | "issues": "https://github.com/sebastianbergmann/code-unit/issues", 1315 | "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" 1316 | }, 1317 | "funding": [ 1318 | { 1319 | "url": "https://github.com/sebastianbergmann", 1320 | "type": "github" 1321 | } 1322 | ], 1323 | "time": "2020-10-26T13:08:54+00:00" 1324 | }, 1325 | { 1326 | "name": "sebastian/code-unit-reverse-lookup", 1327 | "version": "2.0.3", 1328 | "source": { 1329 | "type": "git", 1330 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 1331 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" 1332 | }, 1333 | "dist": { 1334 | "type": "zip", 1335 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", 1336 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", 1337 | "shasum": "" 1338 | }, 1339 | "require": { 1340 | "php": ">=7.3" 1341 | }, 1342 | "require-dev": { 1343 | "phpunit/phpunit": "^9.3" 1344 | }, 1345 | "type": "library", 1346 | "extra": { 1347 | "branch-alias": { 1348 | "dev-master": "2.0-dev" 1349 | } 1350 | }, 1351 | "autoload": { 1352 | "classmap": [ 1353 | "src/" 1354 | ] 1355 | }, 1356 | "notification-url": "https://packagist.org/downloads/", 1357 | "license": [ 1358 | "BSD-3-Clause" 1359 | ], 1360 | "authors": [ 1361 | { 1362 | "name": "Sebastian Bergmann", 1363 | "email": "sebastian@phpunit.de" 1364 | } 1365 | ], 1366 | "description": "Looks up which function or method a line of code belongs to", 1367 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 1368 | "support": { 1369 | "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", 1370 | "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" 1371 | }, 1372 | "funding": [ 1373 | { 1374 | "url": "https://github.com/sebastianbergmann", 1375 | "type": "github" 1376 | } 1377 | ], 1378 | "time": "2020-09-28T05:30:19+00:00" 1379 | }, 1380 | { 1381 | "name": "sebastian/comparator", 1382 | "version": "4.0.8", 1383 | "source": { 1384 | "type": "git", 1385 | "url": "https://github.com/sebastianbergmann/comparator.git", 1386 | "reference": "fa0f136dd2334583309d32b62544682ee972b51a" 1387 | }, 1388 | "dist": { 1389 | "type": "zip", 1390 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", 1391 | "reference": "fa0f136dd2334583309d32b62544682ee972b51a", 1392 | "shasum": "" 1393 | }, 1394 | "require": { 1395 | "php": ">=7.3", 1396 | "sebastian/diff": "^4.0", 1397 | "sebastian/exporter": "^4.0" 1398 | }, 1399 | "require-dev": { 1400 | "phpunit/phpunit": "^9.3" 1401 | }, 1402 | "type": "library", 1403 | "extra": { 1404 | "branch-alias": { 1405 | "dev-master": "4.0-dev" 1406 | } 1407 | }, 1408 | "autoload": { 1409 | "classmap": [ 1410 | "src/" 1411 | ] 1412 | }, 1413 | "notification-url": "https://packagist.org/downloads/", 1414 | "license": [ 1415 | "BSD-3-Clause" 1416 | ], 1417 | "authors": [ 1418 | { 1419 | "name": "Sebastian Bergmann", 1420 | "email": "sebastian@phpunit.de" 1421 | }, 1422 | { 1423 | "name": "Jeff Welch", 1424 | "email": "whatthejeff@gmail.com" 1425 | }, 1426 | { 1427 | "name": "Volker Dusch", 1428 | "email": "github@wallbash.com" 1429 | }, 1430 | { 1431 | "name": "Bernhard Schussek", 1432 | "email": "bschussek@2bepublished.at" 1433 | } 1434 | ], 1435 | "description": "Provides the functionality to compare PHP values for equality", 1436 | "homepage": "https://github.com/sebastianbergmann/comparator", 1437 | "keywords": [ 1438 | "comparator", 1439 | "compare", 1440 | "equality" 1441 | ], 1442 | "support": { 1443 | "issues": "https://github.com/sebastianbergmann/comparator/issues", 1444 | "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" 1445 | }, 1446 | "funding": [ 1447 | { 1448 | "url": "https://github.com/sebastianbergmann", 1449 | "type": "github" 1450 | } 1451 | ], 1452 | "time": "2022-09-14T12:41:17+00:00" 1453 | }, 1454 | { 1455 | "name": "sebastian/complexity", 1456 | "version": "2.0.2", 1457 | "source": { 1458 | "type": "git", 1459 | "url": "https://github.com/sebastianbergmann/complexity.git", 1460 | "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" 1461 | }, 1462 | "dist": { 1463 | "type": "zip", 1464 | "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", 1465 | "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", 1466 | "shasum": "" 1467 | }, 1468 | "require": { 1469 | "nikic/php-parser": "^4.7", 1470 | "php": ">=7.3" 1471 | }, 1472 | "require-dev": { 1473 | "phpunit/phpunit": "^9.3" 1474 | }, 1475 | "type": "library", 1476 | "extra": { 1477 | "branch-alias": { 1478 | "dev-master": "2.0-dev" 1479 | } 1480 | }, 1481 | "autoload": { 1482 | "classmap": [ 1483 | "src/" 1484 | ] 1485 | }, 1486 | "notification-url": "https://packagist.org/downloads/", 1487 | "license": [ 1488 | "BSD-3-Clause" 1489 | ], 1490 | "authors": [ 1491 | { 1492 | "name": "Sebastian Bergmann", 1493 | "email": "sebastian@phpunit.de", 1494 | "role": "lead" 1495 | } 1496 | ], 1497 | "description": "Library for calculating the complexity of PHP code units", 1498 | "homepage": "https://github.com/sebastianbergmann/complexity", 1499 | "support": { 1500 | "issues": "https://github.com/sebastianbergmann/complexity/issues", 1501 | "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" 1502 | }, 1503 | "funding": [ 1504 | { 1505 | "url": "https://github.com/sebastianbergmann", 1506 | "type": "github" 1507 | } 1508 | ], 1509 | "time": "2020-10-26T15:52:27+00:00" 1510 | }, 1511 | { 1512 | "name": "sebastian/diff", 1513 | "version": "4.0.5", 1514 | "source": { 1515 | "type": "git", 1516 | "url": "https://github.com/sebastianbergmann/diff.git", 1517 | "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" 1518 | }, 1519 | "dist": { 1520 | "type": "zip", 1521 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", 1522 | "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", 1523 | "shasum": "" 1524 | }, 1525 | "require": { 1526 | "php": ">=7.3" 1527 | }, 1528 | "require-dev": { 1529 | "phpunit/phpunit": "^9.3", 1530 | "symfony/process": "^4.2 || ^5" 1531 | }, 1532 | "type": "library", 1533 | "extra": { 1534 | "branch-alias": { 1535 | "dev-master": "4.0-dev" 1536 | } 1537 | }, 1538 | "autoload": { 1539 | "classmap": [ 1540 | "src/" 1541 | ] 1542 | }, 1543 | "notification-url": "https://packagist.org/downloads/", 1544 | "license": [ 1545 | "BSD-3-Clause" 1546 | ], 1547 | "authors": [ 1548 | { 1549 | "name": "Sebastian Bergmann", 1550 | "email": "sebastian@phpunit.de" 1551 | }, 1552 | { 1553 | "name": "Kore Nordmann", 1554 | "email": "mail@kore-nordmann.de" 1555 | } 1556 | ], 1557 | "description": "Diff implementation", 1558 | "homepage": "https://github.com/sebastianbergmann/diff", 1559 | "keywords": [ 1560 | "diff", 1561 | "udiff", 1562 | "unidiff", 1563 | "unified diff" 1564 | ], 1565 | "support": { 1566 | "issues": "https://github.com/sebastianbergmann/diff/issues", 1567 | "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" 1568 | }, 1569 | "funding": [ 1570 | { 1571 | "url": "https://github.com/sebastianbergmann", 1572 | "type": "github" 1573 | } 1574 | ], 1575 | "time": "2023-05-07T05:35:17+00:00" 1576 | }, 1577 | { 1578 | "name": "sebastian/environment", 1579 | "version": "5.1.5", 1580 | "source": { 1581 | "type": "git", 1582 | "url": "https://github.com/sebastianbergmann/environment.git", 1583 | "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" 1584 | }, 1585 | "dist": { 1586 | "type": "zip", 1587 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", 1588 | "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", 1589 | "shasum": "" 1590 | }, 1591 | "require": { 1592 | "php": ">=7.3" 1593 | }, 1594 | "require-dev": { 1595 | "phpunit/phpunit": "^9.3" 1596 | }, 1597 | "suggest": { 1598 | "ext-posix": "*" 1599 | }, 1600 | "type": "library", 1601 | "extra": { 1602 | "branch-alias": { 1603 | "dev-master": "5.1-dev" 1604 | } 1605 | }, 1606 | "autoload": { 1607 | "classmap": [ 1608 | "src/" 1609 | ] 1610 | }, 1611 | "notification-url": "https://packagist.org/downloads/", 1612 | "license": [ 1613 | "BSD-3-Clause" 1614 | ], 1615 | "authors": [ 1616 | { 1617 | "name": "Sebastian Bergmann", 1618 | "email": "sebastian@phpunit.de" 1619 | } 1620 | ], 1621 | "description": "Provides functionality to handle HHVM/PHP environments", 1622 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1623 | "keywords": [ 1624 | "Xdebug", 1625 | "environment", 1626 | "hhvm" 1627 | ], 1628 | "support": { 1629 | "issues": "https://github.com/sebastianbergmann/environment/issues", 1630 | "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" 1631 | }, 1632 | "funding": [ 1633 | { 1634 | "url": "https://github.com/sebastianbergmann", 1635 | "type": "github" 1636 | } 1637 | ], 1638 | "time": "2023-02-03T06:03:51+00:00" 1639 | }, 1640 | { 1641 | "name": "sebastian/exporter", 1642 | "version": "4.0.5", 1643 | "source": { 1644 | "type": "git", 1645 | "url": "https://github.com/sebastianbergmann/exporter.git", 1646 | "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" 1647 | }, 1648 | "dist": { 1649 | "type": "zip", 1650 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", 1651 | "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", 1652 | "shasum": "" 1653 | }, 1654 | "require": { 1655 | "php": ">=7.3", 1656 | "sebastian/recursion-context": "^4.0" 1657 | }, 1658 | "require-dev": { 1659 | "ext-mbstring": "*", 1660 | "phpunit/phpunit": "^9.3" 1661 | }, 1662 | "type": "library", 1663 | "extra": { 1664 | "branch-alias": { 1665 | "dev-master": "4.0-dev" 1666 | } 1667 | }, 1668 | "autoload": { 1669 | "classmap": [ 1670 | "src/" 1671 | ] 1672 | }, 1673 | "notification-url": "https://packagist.org/downloads/", 1674 | "license": [ 1675 | "BSD-3-Clause" 1676 | ], 1677 | "authors": [ 1678 | { 1679 | "name": "Sebastian Bergmann", 1680 | "email": "sebastian@phpunit.de" 1681 | }, 1682 | { 1683 | "name": "Jeff Welch", 1684 | "email": "whatthejeff@gmail.com" 1685 | }, 1686 | { 1687 | "name": "Volker Dusch", 1688 | "email": "github@wallbash.com" 1689 | }, 1690 | { 1691 | "name": "Adam Harvey", 1692 | "email": "aharvey@php.net" 1693 | }, 1694 | { 1695 | "name": "Bernhard Schussek", 1696 | "email": "bschussek@gmail.com" 1697 | } 1698 | ], 1699 | "description": "Provides the functionality to export PHP variables for visualization", 1700 | "homepage": "https://www.github.com/sebastianbergmann/exporter", 1701 | "keywords": [ 1702 | "export", 1703 | "exporter" 1704 | ], 1705 | "support": { 1706 | "issues": "https://github.com/sebastianbergmann/exporter/issues", 1707 | "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" 1708 | }, 1709 | "funding": [ 1710 | { 1711 | "url": "https://github.com/sebastianbergmann", 1712 | "type": "github" 1713 | } 1714 | ], 1715 | "time": "2022-09-14T06:03:37+00:00" 1716 | }, 1717 | { 1718 | "name": "sebastian/global-state", 1719 | "version": "5.0.5", 1720 | "source": { 1721 | "type": "git", 1722 | "url": "https://github.com/sebastianbergmann/global-state.git", 1723 | "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" 1724 | }, 1725 | "dist": { 1726 | "type": "zip", 1727 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", 1728 | "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", 1729 | "shasum": "" 1730 | }, 1731 | "require": { 1732 | "php": ">=7.3", 1733 | "sebastian/object-reflector": "^2.0", 1734 | "sebastian/recursion-context": "^4.0" 1735 | }, 1736 | "require-dev": { 1737 | "ext-dom": "*", 1738 | "phpunit/phpunit": "^9.3" 1739 | }, 1740 | "suggest": { 1741 | "ext-uopz": "*" 1742 | }, 1743 | "type": "library", 1744 | "extra": { 1745 | "branch-alias": { 1746 | "dev-master": "5.0-dev" 1747 | } 1748 | }, 1749 | "autoload": { 1750 | "classmap": [ 1751 | "src/" 1752 | ] 1753 | }, 1754 | "notification-url": "https://packagist.org/downloads/", 1755 | "license": [ 1756 | "BSD-3-Clause" 1757 | ], 1758 | "authors": [ 1759 | { 1760 | "name": "Sebastian Bergmann", 1761 | "email": "sebastian@phpunit.de" 1762 | } 1763 | ], 1764 | "description": "Snapshotting of global state", 1765 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1766 | "keywords": [ 1767 | "global state" 1768 | ], 1769 | "support": { 1770 | "issues": "https://github.com/sebastianbergmann/global-state/issues", 1771 | "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" 1772 | }, 1773 | "funding": [ 1774 | { 1775 | "url": "https://github.com/sebastianbergmann", 1776 | "type": "github" 1777 | } 1778 | ], 1779 | "time": "2022-02-14T08:28:10+00:00" 1780 | }, 1781 | { 1782 | "name": "sebastian/lines-of-code", 1783 | "version": "1.0.3", 1784 | "source": { 1785 | "type": "git", 1786 | "url": "https://github.com/sebastianbergmann/lines-of-code.git", 1787 | "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" 1788 | }, 1789 | "dist": { 1790 | "type": "zip", 1791 | "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", 1792 | "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", 1793 | "shasum": "" 1794 | }, 1795 | "require": { 1796 | "nikic/php-parser": "^4.6", 1797 | "php": ">=7.3" 1798 | }, 1799 | "require-dev": { 1800 | "phpunit/phpunit": "^9.3" 1801 | }, 1802 | "type": "library", 1803 | "extra": { 1804 | "branch-alias": { 1805 | "dev-master": "1.0-dev" 1806 | } 1807 | }, 1808 | "autoload": { 1809 | "classmap": [ 1810 | "src/" 1811 | ] 1812 | }, 1813 | "notification-url": "https://packagist.org/downloads/", 1814 | "license": [ 1815 | "BSD-3-Clause" 1816 | ], 1817 | "authors": [ 1818 | { 1819 | "name": "Sebastian Bergmann", 1820 | "email": "sebastian@phpunit.de", 1821 | "role": "lead" 1822 | } 1823 | ], 1824 | "description": "Library for counting the lines of code in PHP source code", 1825 | "homepage": "https://github.com/sebastianbergmann/lines-of-code", 1826 | "support": { 1827 | "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", 1828 | "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" 1829 | }, 1830 | "funding": [ 1831 | { 1832 | "url": "https://github.com/sebastianbergmann", 1833 | "type": "github" 1834 | } 1835 | ], 1836 | "time": "2020-11-28T06:42:11+00:00" 1837 | }, 1838 | { 1839 | "name": "sebastian/object-enumerator", 1840 | "version": "4.0.4", 1841 | "source": { 1842 | "type": "git", 1843 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1844 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" 1845 | }, 1846 | "dist": { 1847 | "type": "zip", 1848 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", 1849 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", 1850 | "shasum": "" 1851 | }, 1852 | "require": { 1853 | "php": ">=7.3", 1854 | "sebastian/object-reflector": "^2.0", 1855 | "sebastian/recursion-context": "^4.0" 1856 | }, 1857 | "require-dev": { 1858 | "phpunit/phpunit": "^9.3" 1859 | }, 1860 | "type": "library", 1861 | "extra": { 1862 | "branch-alias": { 1863 | "dev-master": "4.0-dev" 1864 | } 1865 | }, 1866 | "autoload": { 1867 | "classmap": [ 1868 | "src/" 1869 | ] 1870 | }, 1871 | "notification-url": "https://packagist.org/downloads/", 1872 | "license": [ 1873 | "BSD-3-Clause" 1874 | ], 1875 | "authors": [ 1876 | { 1877 | "name": "Sebastian Bergmann", 1878 | "email": "sebastian@phpunit.de" 1879 | } 1880 | ], 1881 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1882 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1883 | "support": { 1884 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", 1885 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" 1886 | }, 1887 | "funding": [ 1888 | { 1889 | "url": "https://github.com/sebastianbergmann", 1890 | "type": "github" 1891 | } 1892 | ], 1893 | "time": "2020-10-26T13:12:34+00:00" 1894 | }, 1895 | { 1896 | "name": "sebastian/object-reflector", 1897 | "version": "2.0.4", 1898 | "source": { 1899 | "type": "git", 1900 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1901 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" 1902 | }, 1903 | "dist": { 1904 | "type": "zip", 1905 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", 1906 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", 1907 | "shasum": "" 1908 | }, 1909 | "require": { 1910 | "php": ">=7.3" 1911 | }, 1912 | "require-dev": { 1913 | "phpunit/phpunit": "^9.3" 1914 | }, 1915 | "type": "library", 1916 | "extra": { 1917 | "branch-alias": { 1918 | "dev-master": "2.0-dev" 1919 | } 1920 | }, 1921 | "autoload": { 1922 | "classmap": [ 1923 | "src/" 1924 | ] 1925 | }, 1926 | "notification-url": "https://packagist.org/downloads/", 1927 | "license": [ 1928 | "BSD-3-Clause" 1929 | ], 1930 | "authors": [ 1931 | { 1932 | "name": "Sebastian Bergmann", 1933 | "email": "sebastian@phpunit.de" 1934 | } 1935 | ], 1936 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1937 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1938 | "support": { 1939 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues", 1940 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" 1941 | }, 1942 | "funding": [ 1943 | { 1944 | "url": "https://github.com/sebastianbergmann", 1945 | "type": "github" 1946 | } 1947 | ], 1948 | "time": "2020-10-26T13:14:26+00:00" 1949 | }, 1950 | { 1951 | "name": "sebastian/recursion-context", 1952 | "version": "4.0.5", 1953 | "source": { 1954 | "type": "git", 1955 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1956 | "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" 1957 | }, 1958 | "dist": { 1959 | "type": "zip", 1960 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", 1961 | "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", 1962 | "shasum": "" 1963 | }, 1964 | "require": { 1965 | "php": ">=7.3" 1966 | }, 1967 | "require-dev": { 1968 | "phpunit/phpunit": "^9.3" 1969 | }, 1970 | "type": "library", 1971 | "extra": { 1972 | "branch-alias": { 1973 | "dev-master": "4.0-dev" 1974 | } 1975 | }, 1976 | "autoload": { 1977 | "classmap": [ 1978 | "src/" 1979 | ] 1980 | }, 1981 | "notification-url": "https://packagist.org/downloads/", 1982 | "license": [ 1983 | "BSD-3-Clause" 1984 | ], 1985 | "authors": [ 1986 | { 1987 | "name": "Sebastian Bergmann", 1988 | "email": "sebastian@phpunit.de" 1989 | }, 1990 | { 1991 | "name": "Jeff Welch", 1992 | "email": "whatthejeff@gmail.com" 1993 | }, 1994 | { 1995 | "name": "Adam Harvey", 1996 | "email": "aharvey@php.net" 1997 | } 1998 | ], 1999 | "description": "Provides functionality to recursively process PHP variables", 2000 | "homepage": "https://github.com/sebastianbergmann/recursion-context", 2001 | "support": { 2002 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues", 2003 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" 2004 | }, 2005 | "funding": [ 2006 | { 2007 | "url": "https://github.com/sebastianbergmann", 2008 | "type": "github" 2009 | } 2010 | ], 2011 | "time": "2023-02-03T06:07:39+00:00" 2012 | }, 2013 | { 2014 | "name": "sebastian/resource-operations", 2015 | "version": "3.0.3", 2016 | "source": { 2017 | "type": "git", 2018 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 2019 | "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" 2020 | }, 2021 | "dist": { 2022 | "type": "zip", 2023 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", 2024 | "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", 2025 | "shasum": "" 2026 | }, 2027 | "require": { 2028 | "php": ">=7.3" 2029 | }, 2030 | "require-dev": { 2031 | "phpunit/phpunit": "^9.0" 2032 | }, 2033 | "type": "library", 2034 | "extra": { 2035 | "branch-alias": { 2036 | "dev-master": "3.0-dev" 2037 | } 2038 | }, 2039 | "autoload": { 2040 | "classmap": [ 2041 | "src/" 2042 | ] 2043 | }, 2044 | "notification-url": "https://packagist.org/downloads/", 2045 | "license": [ 2046 | "BSD-3-Clause" 2047 | ], 2048 | "authors": [ 2049 | { 2050 | "name": "Sebastian Bergmann", 2051 | "email": "sebastian@phpunit.de" 2052 | } 2053 | ], 2054 | "description": "Provides a list of PHP built-in functions that operate on resources", 2055 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 2056 | "support": { 2057 | "issues": "https://github.com/sebastianbergmann/resource-operations/issues", 2058 | "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" 2059 | }, 2060 | "funding": [ 2061 | { 2062 | "url": "https://github.com/sebastianbergmann", 2063 | "type": "github" 2064 | } 2065 | ], 2066 | "time": "2020-09-28T06:45:17+00:00" 2067 | }, 2068 | { 2069 | "name": "sebastian/type", 2070 | "version": "3.2.1", 2071 | "source": { 2072 | "type": "git", 2073 | "url": "https://github.com/sebastianbergmann/type.git", 2074 | "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" 2075 | }, 2076 | "dist": { 2077 | "type": "zip", 2078 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", 2079 | "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", 2080 | "shasum": "" 2081 | }, 2082 | "require": { 2083 | "php": ">=7.3" 2084 | }, 2085 | "require-dev": { 2086 | "phpunit/phpunit": "^9.5" 2087 | }, 2088 | "type": "library", 2089 | "extra": { 2090 | "branch-alias": { 2091 | "dev-master": "3.2-dev" 2092 | } 2093 | }, 2094 | "autoload": { 2095 | "classmap": [ 2096 | "src/" 2097 | ] 2098 | }, 2099 | "notification-url": "https://packagist.org/downloads/", 2100 | "license": [ 2101 | "BSD-3-Clause" 2102 | ], 2103 | "authors": [ 2104 | { 2105 | "name": "Sebastian Bergmann", 2106 | "email": "sebastian@phpunit.de", 2107 | "role": "lead" 2108 | } 2109 | ], 2110 | "description": "Collection of value objects that represent the types of the PHP type system", 2111 | "homepage": "https://github.com/sebastianbergmann/type", 2112 | "support": { 2113 | "issues": "https://github.com/sebastianbergmann/type/issues", 2114 | "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" 2115 | }, 2116 | "funding": [ 2117 | { 2118 | "url": "https://github.com/sebastianbergmann", 2119 | "type": "github" 2120 | } 2121 | ], 2122 | "time": "2023-02-03T06:13:03+00:00" 2123 | }, 2124 | { 2125 | "name": "sebastian/version", 2126 | "version": "3.0.2", 2127 | "source": { 2128 | "type": "git", 2129 | "url": "https://github.com/sebastianbergmann/version.git", 2130 | "reference": "c6c1022351a901512170118436c764e473f6de8c" 2131 | }, 2132 | "dist": { 2133 | "type": "zip", 2134 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", 2135 | "reference": "c6c1022351a901512170118436c764e473f6de8c", 2136 | "shasum": "" 2137 | }, 2138 | "require": { 2139 | "php": ">=7.3" 2140 | }, 2141 | "type": "library", 2142 | "extra": { 2143 | "branch-alias": { 2144 | "dev-master": "3.0-dev" 2145 | } 2146 | }, 2147 | "autoload": { 2148 | "classmap": [ 2149 | "src/" 2150 | ] 2151 | }, 2152 | "notification-url": "https://packagist.org/downloads/", 2153 | "license": [ 2154 | "BSD-3-Clause" 2155 | ], 2156 | "authors": [ 2157 | { 2158 | "name": "Sebastian Bergmann", 2159 | "email": "sebastian@phpunit.de", 2160 | "role": "lead" 2161 | } 2162 | ], 2163 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 2164 | "homepage": "https://github.com/sebastianbergmann/version", 2165 | "support": { 2166 | "issues": "https://github.com/sebastianbergmann/version/issues", 2167 | "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" 2168 | }, 2169 | "funding": [ 2170 | { 2171 | "url": "https://github.com/sebastianbergmann", 2172 | "type": "github" 2173 | } 2174 | ], 2175 | "time": "2020-09-28T06:39:44+00:00" 2176 | }, 2177 | { 2178 | "name": "sirbrillig/phpcs-variable-analysis", 2179 | "version": "v2.11.16", 2180 | "source": { 2181 | "type": "git", 2182 | "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git", 2183 | "reference": "dc5582dc5a93a235557af73e523c389aac9a8e88" 2184 | }, 2185 | "dist": { 2186 | "type": "zip", 2187 | "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/dc5582dc5a93a235557af73e523c389aac9a8e88", 2188 | "reference": "dc5582dc5a93a235557af73e523c389aac9a8e88", 2189 | "shasum": "" 2190 | }, 2191 | "require": { 2192 | "php": ">=5.4.0", 2193 | "squizlabs/php_codesniffer": "^3.5.6" 2194 | }, 2195 | "require-dev": { 2196 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0", 2197 | "phpcsstandards/phpcsdevcs": "^1.1", 2198 | "phpstan/phpstan": "^1.7", 2199 | "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.5 || ^7.0 || ^8.0 || ^9.0", 2200 | "sirbrillig/phpcs-import-detection": "^1.1", 2201 | "vimeo/psalm": "^0.2 || ^0.3 || ^1.1 || ^4.24 || ^5.0@beta" 2202 | }, 2203 | "type": "phpcodesniffer-standard", 2204 | "autoload": { 2205 | "psr-4": { 2206 | "VariableAnalysis\\": "VariableAnalysis/" 2207 | } 2208 | }, 2209 | "notification-url": "https://packagist.org/downloads/", 2210 | "license": [ 2211 | "BSD-2-Clause" 2212 | ], 2213 | "authors": [ 2214 | { 2215 | "name": "Sam Graham", 2216 | "email": "php-codesniffer-variableanalysis@illusori.co.uk" 2217 | }, 2218 | { 2219 | "name": "Payton Swick", 2220 | "email": "payton@foolord.com" 2221 | } 2222 | ], 2223 | "description": "A PHPCS sniff to detect problems with variables.", 2224 | "keywords": [ 2225 | "phpcs", 2226 | "static analysis" 2227 | ], 2228 | "support": { 2229 | "issues": "https://github.com/sirbrillig/phpcs-variable-analysis/issues", 2230 | "source": "https://github.com/sirbrillig/phpcs-variable-analysis", 2231 | "wiki": "https://github.com/sirbrillig/phpcs-variable-analysis/wiki" 2232 | }, 2233 | "time": "2023-03-31T16:46:32+00:00" 2234 | }, 2235 | { 2236 | "name": "squizlabs/php_codesniffer", 2237 | "version": "3.7.1", 2238 | "source": { 2239 | "type": "git", 2240 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", 2241 | "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619" 2242 | }, 2243 | "dist": { 2244 | "type": "zip", 2245 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619", 2246 | "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619", 2247 | "shasum": "" 2248 | }, 2249 | "require": { 2250 | "ext-simplexml": "*", 2251 | "ext-tokenizer": "*", 2252 | "ext-xmlwriter": "*", 2253 | "php": ">=5.4.0" 2254 | }, 2255 | "require-dev": { 2256 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" 2257 | }, 2258 | "bin": [ 2259 | "bin/phpcs", 2260 | "bin/phpcbf" 2261 | ], 2262 | "type": "library", 2263 | "extra": { 2264 | "branch-alias": { 2265 | "dev-master": "3.x-dev" 2266 | } 2267 | }, 2268 | "notification-url": "https://packagist.org/downloads/", 2269 | "license": [ 2270 | "BSD-3-Clause" 2271 | ], 2272 | "authors": [ 2273 | { 2274 | "name": "Greg Sherwood", 2275 | "role": "lead" 2276 | } 2277 | ], 2278 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", 2279 | "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", 2280 | "keywords": [ 2281 | "phpcs", 2282 | "standards" 2283 | ], 2284 | "support": { 2285 | "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", 2286 | "source": "https://github.com/squizlabs/PHP_CodeSniffer", 2287 | "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" 2288 | }, 2289 | "time": "2022-06-18T07:21:10+00:00" 2290 | }, 2291 | { 2292 | "name": "theseer/tokenizer", 2293 | "version": "1.2.1", 2294 | "source": { 2295 | "type": "git", 2296 | "url": "https://github.com/theseer/tokenizer.git", 2297 | "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" 2298 | }, 2299 | "dist": { 2300 | "type": "zip", 2301 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", 2302 | "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", 2303 | "shasum": "" 2304 | }, 2305 | "require": { 2306 | "ext-dom": "*", 2307 | "ext-tokenizer": "*", 2308 | "ext-xmlwriter": "*", 2309 | "php": "^7.2 || ^8.0" 2310 | }, 2311 | "type": "library", 2312 | "autoload": { 2313 | "classmap": [ 2314 | "src/" 2315 | ] 2316 | }, 2317 | "notification-url": "https://packagist.org/downloads/", 2318 | "license": [ 2319 | "BSD-3-Clause" 2320 | ], 2321 | "authors": [ 2322 | { 2323 | "name": "Arne Blankerts", 2324 | "email": "arne@blankerts.de", 2325 | "role": "Developer" 2326 | } 2327 | ], 2328 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 2329 | "support": { 2330 | "issues": "https://github.com/theseer/tokenizer/issues", 2331 | "source": "https://github.com/theseer/tokenizer/tree/1.2.1" 2332 | }, 2333 | "funding": [ 2334 | { 2335 | "url": "https://github.com/theseer", 2336 | "type": "github" 2337 | } 2338 | ], 2339 | "time": "2021-07-28T10:34:58+00:00" 2340 | }, 2341 | { 2342 | "name": "wp-coding-standards/wpcs", 2343 | "version": "2.3.0", 2344 | "source": { 2345 | "type": "git", 2346 | "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", 2347 | "reference": "7da1894633f168fe244afc6de00d141f27517b62" 2348 | }, 2349 | "dist": { 2350 | "type": "zip", 2351 | "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7da1894633f168fe244afc6de00d141f27517b62", 2352 | "reference": "7da1894633f168fe244afc6de00d141f27517b62", 2353 | "shasum": "" 2354 | }, 2355 | "require": { 2356 | "php": ">=5.4", 2357 | "squizlabs/php_codesniffer": "^3.3.1" 2358 | }, 2359 | "require-dev": { 2360 | "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || ^0.6", 2361 | "phpcompatibility/php-compatibility": "^9.0", 2362 | "phpcsstandards/phpcsdevtools": "^1.0", 2363 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" 2364 | }, 2365 | "suggest": { 2366 | "dealerdirect/phpcodesniffer-composer-installer": "^0.6 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically." 2367 | }, 2368 | "type": "phpcodesniffer-standard", 2369 | "notification-url": "https://packagist.org/downloads/", 2370 | "license": [ 2371 | "MIT" 2372 | ], 2373 | "authors": [ 2374 | { 2375 | "name": "Contributors", 2376 | "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" 2377 | } 2378 | ], 2379 | "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", 2380 | "keywords": [ 2381 | "phpcs", 2382 | "standards", 2383 | "wordpress" 2384 | ], 2385 | "support": { 2386 | "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", 2387 | "source": "https://github.com/WordPress/WordPress-Coding-Standards", 2388 | "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" 2389 | }, 2390 | "time": "2020-05-13T23:57:56+00:00" 2391 | }, 2392 | { 2393 | "name": "wpackagist-plugin/contact-form-7", 2394 | "version": "5.7.7", 2395 | "source": { 2396 | "type": "svn", 2397 | "url": "https://plugins.svn.wordpress.org/contact-form-7/", 2398 | "reference": "tags/5.7.7" 2399 | }, 2400 | "dist": { 2401 | "type": "zip", 2402 | "url": "https://downloads.wordpress.org/plugin/contact-form-7.5.7.7.zip" 2403 | }, 2404 | "require": { 2405 | "composer/installers": "^1.0 || ^2.0" 2406 | }, 2407 | "type": "wordpress-plugin", 2408 | "homepage": "https://wordpress.org/plugins/contact-form-7/" 2409 | }, 2410 | { 2411 | "name": "yoast/phpunit-polyfills", 2412 | "version": "2.0.0", 2413 | "source": { 2414 | "type": "git", 2415 | "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", 2416 | "reference": "c758753e8f9dac251fed396a73c8305af3f17922" 2417 | }, 2418 | "dist": { 2419 | "type": "zip", 2420 | "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/c758753e8f9dac251fed396a73c8305af3f17922", 2421 | "reference": "c758753e8f9dac251fed396a73c8305af3f17922", 2422 | "shasum": "" 2423 | }, 2424 | "require": { 2425 | "php": ">=5.6", 2426 | "phpunit/phpunit": "^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0" 2427 | }, 2428 | "require-dev": { 2429 | "yoast/yoastcs": "^2.3.0" 2430 | }, 2431 | "type": "library", 2432 | "extra": { 2433 | "branch-alias": { 2434 | "dev-main": "2.x-dev" 2435 | } 2436 | }, 2437 | "autoload": { 2438 | "files": [ 2439 | "phpunitpolyfills-autoload.php" 2440 | ] 2441 | }, 2442 | "notification-url": "https://packagist.org/downloads/", 2443 | "license": [ 2444 | "BSD-3-Clause" 2445 | ], 2446 | "authors": [ 2447 | { 2448 | "name": "Team Yoast", 2449 | "email": "support@yoast.com", 2450 | "homepage": "https://yoast.com" 2451 | }, 2452 | { 2453 | "name": "Contributors", 2454 | "homepage": "https://github.com/Yoast/PHPUnit-Polyfills/graphs/contributors" 2455 | } 2456 | ], 2457 | "description": "Set of polyfills for changed PHPUnit functionality to allow for creating PHPUnit cross-version compatible tests", 2458 | "homepage": "https://github.com/Yoast/PHPUnit-Polyfills", 2459 | "keywords": [ 2460 | "phpunit", 2461 | "polyfill", 2462 | "testing" 2463 | ], 2464 | "support": { 2465 | "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", 2466 | "source": "https://github.com/Yoast/PHPUnit-Polyfills" 2467 | }, 2468 | "time": "2023-06-06T20:28:24+00:00" 2469 | } 2470 | ], 2471 | "aliases": [], 2472 | "minimum-stability": "stable", 2473 | "stability-flags": { 2474 | "10up/phpcs-composer": 20 2475 | }, 2476 | "prefer-stable": false, 2477 | "prefer-lowest": false, 2478 | "platform": { 2479 | "php": ">=7.0" 2480 | }, 2481 | "platform-dev": [], 2482 | "plugin-api-version": "2.3.0" 2483 | } 2484 | --------------------------------------------------------------------------------