diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index 8972995..0000000
--- a/.editorconfig
+++ /dev/null
@@ -1,12 +0,0 @@
-# More rules can be found at https://editorconfig.org
-
-root = true
-
-# Default settings for all files
-[*]
-charset = utf-8
-end_of_line = lf
-insert_final_newline = true
-trim_trailing_whitespace = true
-indent_style = space
-indent_size = 4
diff --git a/.gitignore b/.gitignore
index 244aa51..06868a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,13 +6,13 @@
/conifer
# Docs
-/_book/
-docs/.vitepress/dist
docs/.vitepress/cache
+docs/.vitepress/dist
# test artifacts
/test/wp/
/test/wp-tests-lib/
+.phpunit.result.cache
# build artifacts
/conifer-*.tar.gz
@@ -32,6 +32,4 @@ wp-cli.local.yml
# IDEs
.vscode/*
-.idea
-
-.phpunit.result.cache
+.idea/
diff --git a/.lando.yml b/.lando.yml
index 33bd89a..c48be06 100644
--- a/.lando.yml
+++ b/.lando.yml
@@ -5,9 +5,13 @@ config:
php: '8.1'
services:
+ node:
+ type: node:24
+ run:
+ - yarn && yarn docs:build
appserver:
- build_as_root:
+ run_as_root:
- apt-get update
- apt-get install zip
- apt-get install subversion -y
@@ -16,11 +20,6 @@ services:
- composer install
- ./scripts/setup-wordpress.sh
- node:
- type: node:22
- build:
- - yarn && yarn docs:build
-
database:
type: mysql:5.7
@@ -127,11 +126,6 @@ tooling:
cmd: 'yarn docs:build'
description: 'Build Conifer docs'
- rector:
- service: appserver
- cmd: 'rector'
- description: 'Run Rector commands'
-
proxy:
appserver:
- conifer.lndo.site
diff --git a/composer.json b/composer.json
index ec9de40..0aa6904 100644
--- a/composer.json
+++ b/composer.json
@@ -22,29 +22,26 @@
"prefer-stable": true,
"require": {
"php": ">=8.1",
- "ext-dom": "*",
- "ext-libxml": "*",
"timber/timber": "^2.2"
},
"require-dev": {
"10up/wp_mock": "dev-dev",
- "behat/behat": "^3.25.0",
+ "behat/behat": "^v3.29.0",
"johnpbloch/wordpress-core": "^6.5.5",
"johnpbloch/wordpress-core-installer": "^2.0.0",
"mikey179/vfsstream": "~1",
"mnsami/composer-custom-directory-installer": "^2.0",
"php-stubs/acf-pro-stubs": "^6.5",
- "phpunit/phpunit": "^9.0",
- "rector/rector": "^2.2",
- "sitecrafting/groot": "dev-master",
- "szepeviktor/phpstan-wordpress": "^2.0.3",
- "wp-coding-standards/wpcs": "^3.2.0"
+ "phpunit/phpunit": "^9",
+ "sitecrafting/groot": "^1.0",
+ "szepeviktor/phpstan-wordpress": "^v2.0.3",
+ "wp-coding-standards/wpcs": "^2.3"
},
"config": {
+ "sort-packages": true,
"platform": {
"php": "8.1"
},
- "sort-packages": true,
"allow-plugins": {
"composer/installers": true,
"dealerdirect/phpcodesniffer-composer-installer": true,
diff --git a/composer.lock b/composer.lock
index 9128247..b21a76e 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "a1143987badb25d7703a7ecf98b1c129",
+ "content-hash": "ffdb9236f5cc2e26b517c69e07fba0fd",
"packages": [
{
"name": "symfony/deprecation-contracts",
@@ -344,16 +344,16 @@
},
{
"name": "twig/twig",
- "version": "v3.22.0",
+ "version": "v3.23.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
- "reference": "4509984193026de413baf4ba80f68590a7f2c51d"
+ "reference": "a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/twigphp/Twig/zipball/4509984193026de413baf4ba80f68590a7f2c51d",
- "reference": "4509984193026de413baf4ba80f68590a7f2c51d",
+ "url": "https://api.github.com/repos/twigphp/Twig/zipball/a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9",
+ "reference": "a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9",
"shasum": ""
},
"require": {
@@ -407,7 +407,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
- "source": "https://github.com/twigphp/Twig/tree/v3.22.0"
+ "source": "https://github.com/twigphp/Twig/tree/v3.23.0"
},
"funding": [
{
@@ -419,7 +419,7 @@
"type": "tidelift"
}
],
- "time": "2025-10-29T15:56:47+00:00"
+ "time": "2026-01-23T21:00:41+00:00"
}
],
"packages-dev": [
@@ -536,16 +536,16 @@
},
{
"name": "behat/behat",
- "version": "v3.26.0",
+ "version": "v3.29.0",
"source": {
"type": "git",
"url": "https://github.com/Behat/Behat.git",
- "reference": "1b6b08efa995fe4135901b862d112adc7e95ecbb"
+ "reference": "51bdf81639a14645c5d2c06926f4aa37d204921b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/Behat/zipball/1b6b08efa995fe4135901b862d112adc7e95ecbb",
- "reference": "1b6b08efa995fe4135901b862d112adc7e95ecbb",
+ "url": "https://api.github.com/repos/Behat/Behat/zipball/51bdf81639a14645c5d2c06926f4aa37d204921b",
+ "reference": "51bdf81639a14645c5d2c06926f4aa37d204921b",
"shasum": ""
},
"require": {
@@ -554,7 +554,7 @@
"composer/xdebug-handler": "^1.4 || ^2.0 || ^3.0",
"ext-mbstring": "*",
"nikic/php-parser": "^4.19.2 || ^5.2",
- "php": ">=8.1 <8.5",
+ "php": ">=8.1 <8.6",
"psr/container": "^1.0 || ^2.0",
"symfony/config": "^5.4 || ^6.4 || ^7.0",
"symfony/console": "^5.4 || ^6.4 || ^7.0",
@@ -564,8 +564,8 @@
"symfony/yaml": "^5.4 || ^6.4 || ^7.0"
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "^3.68",
"opis/json-schema": "^2.5",
+ "php-cs-fixer/shim": "^3.89",
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^9.6",
"rector/rector": "2.1.7",
@@ -581,11 +581,6 @@
"bin/behat"
],
"type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.x-dev"
- }
- },
"autoload": {
"psr-4": {
"Behat\\Hook\\": "src/Behat/Hook/",
@@ -625,22 +620,36 @@
],
"support": {
"issues": "https://github.com/Behat/Behat/issues",
- "source": "https://github.com/Behat/Behat/tree/v3.26.0"
+ "source": "https://github.com/Behat/Behat/tree/v3.29.0"
},
- "time": "2025-10-29T09:46:14+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/acoulton",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/carlos-granados",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/stof",
+ "type": "github"
+ }
+ ],
+ "time": "2025-12-11T09:51:30+00:00"
},
{
"name": "behat/gherkin",
- "version": "v4.15.0",
+ "version": "v4.16.1",
"source": {
"type": "git",
"url": "https://github.com/Behat/Gherkin.git",
- "reference": "05a7459283e8e6af0d46ec25b8bb5960ca3cfa7b"
+ "reference": "e26037937dfd48528746764dd870bc5d0836665f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/Gherkin/zipball/05a7459283e8e6af0d46ec25b8bb5960ca3cfa7b",
- "reference": "05a7459283e8e6af0d46ec25b8bb5960ca3cfa7b",
+ "url": "https://api.github.com/repos/Behat/Gherkin/zipball/e26037937dfd48528746764dd870bc5d0836665f",
+ "reference": "e26037937dfd48528746764dd870bc5d0836665f",
"shasum": ""
},
"require": {
@@ -648,7 +657,7 @@
"php": ">=8.1 <8.6"
},
"require-dev": {
- "cucumber/gherkin-monorepo": "dev-gherkin-v36.0.0",
+ "cucumber/gherkin-monorepo": "dev-gherkin-v37.0.0",
"friendsofphp/php-cs-fixer": "^3.77",
"mikey179/vfsstream": "^1.6",
"phpstan/extension-installer": "^1",
@@ -694,9 +703,23 @@
],
"support": {
"issues": "https://github.com/Behat/Gherkin/issues",
- "source": "https://github.com/Behat/Gherkin/tree/v4.15.0"
+ "source": "https://github.com/Behat/Gherkin/tree/v4.16.1"
},
- "time": "2025-11-05T15:34:04+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/acoulton",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/carlos-granados",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/stof",
+ "type": "github"
+ }
+ ],
+ "time": "2025-12-08T16:12:58+00:00"
},
{
"name": "composer/pcre",
@@ -843,102 +866,6 @@
],
"time": "2024-05-06T16:37:16+00:00"
},
- {
- "name": "dealerdirect/phpcodesniffer-composer-installer",
- "version": "v1.2.0",
- "source": {
- "type": "git",
- "url": "https://github.com/PHPCSStandards/composer-installer.git",
- "reference": "845eb62303d2ca9b289ef216356568ccc075ffd1"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/845eb62303d2ca9b289ef216356568ccc075ffd1",
- "reference": "845eb62303d2ca9b289ef216356568ccc075ffd1",
- "shasum": ""
- },
- "require": {
- "composer-plugin-api": "^2.2",
- "php": ">=5.4",
- "squizlabs/php_codesniffer": "^3.1.0 || ^4.0"
- },
- "require-dev": {
- "composer/composer": "^2.2",
- "ext-json": "*",
- "ext-zip": "*",
- "php-parallel-lint/php-parallel-lint": "^1.4.0",
- "phpcompatibility/php-compatibility": "^9.0 || ^10.0.0@dev",
- "yoast/phpunit-polyfills": "^1.0"
- },
- "type": "composer-plugin",
- "extra": {
- "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
- },
- "autoload": {
- "psr-4": {
- "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Franck Nijhof",
- "email": "opensource@frenck.dev",
- "homepage": "https://frenck.dev",
- "role": "Open source developer"
- },
- {
- "name": "Contributors",
- "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors"
- }
- ],
- "description": "PHP_CodeSniffer Standards Composer Installer Plugin",
- "keywords": [
- "PHPCodeSniffer",
- "PHP_CodeSniffer",
- "code quality",
- "codesniffer",
- "composer",
- "installer",
- "phpcbf",
- "phpcs",
- "plugin",
- "qa",
- "quality",
- "standard",
- "standards",
- "style guide",
- "stylecheck",
- "tests"
- ],
- "support": {
- "issues": "https://github.com/PHPCSStandards/composer-installer/issues",
- "security": "https://github.com/PHPCSStandards/composer-installer/security/policy",
- "source": "https://github.com/PHPCSStandards/composer-installer"
- },
- "funding": [
- {
- "url": "https://github.com/PHPCSStandards",
- "type": "github"
- },
- {
- "url": "https://github.com/jrfnl",
- "type": "github"
- },
- {
- "url": "https://opencollective.com/php_codesniffer",
- "type": "open_collective"
- },
- {
- "url": "https://thanks.dev/u/gh/phpcsstandards",
- "type": "thanks_dev"
- }
- ],
- "time": "2025-11-11T04:32:07+00:00"
- },
{
"name": "doctrine/instantiator",
"version": "2.0.0",
@@ -1062,16 +989,16 @@
},
{
"name": "johnpbloch/wordpress-core",
- "version": "6.8.3",
+ "version": "6.9.1",
"source": {
"type": "git",
"url": "https://github.com/johnpbloch/wordpress-core.git",
- "reference": "0641ab5518c94c1ab094ad4ccdc46aa9c4657fc1"
+ "reference": "840ffab74cb3d19cc0076363358f783041b5f3cf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/johnpbloch/wordpress-core/zipball/0641ab5518c94c1ab094ad4ccdc46aa9c4657fc1",
- "reference": "0641ab5518c94c1ab094ad4ccdc46aa9c4657fc1",
+ "url": "https://api.github.com/repos/johnpbloch/wordpress-core/zipball/840ffab74cb3d19cc0076363358f783041b5f3cf",
+ "reference": "840ffab74cb3d19cc0076363358f783041b5f3cf",
"shasum": ""
},
"require": {
@@ -1079,7 +1006,7 @@
"php": ">=7.2.24"
},
"provide": {
- "wordpress/core-implementation": "6.8.3"
+ "wordpress/core-implementation": "6.9.1"
},
"type": "wordpress-core",
"notification-url": "https://packagist.org/downloads/",
@@ -1106,7 +1033,7 @@
"source": "https://core.trac.wordpress.org/browser",
"wiki": "https://codex.wordpress.org/"
},
- "time": "2025-09-30T18:14:19+00:00"
+ "time": "2026-02-03T18:03:48+00:00"
},
{
"name": "johnpbloch/wordpress-core-installer",
@@ -1415,16 +1342,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v5.6.2",
+ "version": "v5.7.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "3a454ca033b9e06b63282ce19562e892747449bb"
+ "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb",
- "reference": "3a454ca033b9e06b63282ce19562e892747449bb",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82",
+ "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82",
"shasum": ""
},
"require": {
@@ -1467,9 +1394,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0"
},
- "time": "2025-10-21T19:32:17+00:00"
+ "time": "2025-12-06T11:56:16+00:00"
},
{
"name": "phar-io/manifest",
@@ -1643,16 +1570,16 @@
},
{
"name": "php-stubs/wordpress-stubs",
- "version": "v6.8.3",
+ "version": "v6.9.1",
"source": {
"type": "git",
"url": "https://github.com/php-stubs/wordpress-stubs.git",
- "reference": "abeb5a8b58fda7ac21f15ee596f302f2959a7114"
+ "reference": "f12220f303e0d7c0844c0e5e957b0c3cee48d2f7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/abeb5a8b58fda7ac21f15ee596f302f2959a7114",
- "reference": "abeb5a8b58fda7ac21f15ee596f302f2959a7114",
+ "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/f12220f303e0d7c0844c0e5e957b0c3cee48d2f7",
+ "reference": "f12220f303e0d7c0844c0e5e957b0c3cee48d2f7",
"shasum": ""
},
"conflict": {
@@ -1663,9 +1590,10 @@
"nikic/php-parser": "^5.5",
"php": "^7.4 || ^8.0",
"php-stubs/generator": "^0.8.3",
- "phpdocumentor/reflection-docblock": "^5.4.1",
+ "phpdocumentor/reflection-docblock": "^6.0",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^9.5",
+ "symfony/polyfill-php80": "*",
"szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.1.1",
"wp-coding-standards/wpcs": "3.1.0 as 2.3.0"
},
@@ -1688,192 +1616,17 @@
],
"support": {
"issues": "https://github.com/php-stubs/wordpress-stubs/issues",
- "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.8.3"
- },
- "time": "2025-09-30T20:58:47+00:00"
- },
- {
- "name": "phpcsstandards/phpcsextra",
- "version": "1.5.0",
- "source": {
- "type": "git",
- "url": "https://github.com/PHPCSStandards/PHPCSExtra.git",
- "reference": "b598aa890815b8df16363271b659d73280129101"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/b598aa890815b8df16363271b659d73280129101",
- "reference": "b598aa890815b8df16363271b659d73280129101",
- "shasum": ""
- },
- "require": {
- "php": ">=5.4",
- "phpcsstandards/phpcsutils": "^1.2.0",
- "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1"
- },
- "require-dev": {
- "php-parallel-lint/php-console-highlighter": "^1.0",
- "php-parallel-lint/php-parallel-lint": "^1.4.0",
- "phpcsstandards/phpcsdevcs": "^1.2.0",
- "phpcsstandards/phpcsdevtools": "^1.2.1",
- "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4"
- },
- "type": "phpcodesniffer-standard",
- "extra": {
- "branch-alias": {
- "dev-stable": "1.x-dev",
- "dev-develop": "1.x-dev"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "LGPL-3.0-or-later"
- ],
- "authors": [
- {
- "name": "Juliette Reinders Folmer",
- "homepage": "https://github.com/jrfnl",
- "role": "lead"
- },
- {
- "name": "Contributors",
- "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors"
- }
- ],
- "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.",
- "keywords": [
- "PHP_CodeSniffer",
- "phpcbf",
- "phpcodesniffer-standard",
- "phpcs",
- "standards",
- "static analysis"
- ],
- "support": {
- "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues",
- "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy",
- "source": "https://github.com/PHPCSStandards/PHPCSExtra"
- },
- "funding": [
- {
- "url": "https://github.com/PHPCSStandards",
- "type": "github"
- },
- {
- "url": "https://github.com/jrfnl",
- "type": "github"
- },
- {
- "url": "https://opencollective.com/php_codesniffer",
- "type": "open_collective"
- },
- {
- "url": "https://thanks.dev/u/gh/phpcsstandards",
- "type": "thanks_dev"
- }
- ],
- "time": "2025-11-12T23:06:57+00:00"
- },
- {
- "name": "phpcsstandards/phpcsutils",
- "version": "1.2.1",
- "source": {
- "type": "git",
- "url": "https://github.com/PHPCSStandards/PHPCSUtils.git",
- "reference": "d71128c702c180ca3b27c761b6773f883394f162"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/d71128c702c180ca3b27c761b6773f883394f162",
- "reference": "d71128c702c180ca3b27c761b6773f883394f162",
- "shasum": ""
- },
- "require": {
- "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0",
- "php": ">=5.4",
- "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1"
- },
- "require-dev": {
- "ext-filter": "*",
- "php-parallel-lint/php-console-highlighter": "^1.0",
- "php-parallel-lint/php-parallel-lint": "^1.4.0",
- "phpcsstandards/phpcsdevcs": "^1.2.0",
- "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.0.0"
- },
- "type": "phpcodesniffer-standard",
- "extra": {
- "branch-alias": {
- "dev-stable": "1.x-dev",
- "dev-develop": "1.x-dev"
- }
+ "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.9.1"
},
- "autoload": {
- "classmap": [
- "PHPCSUtils/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "LGPL-3.0-or-later"
- ],
- "authors": [
- {
- "name": "Juliette Reinders Folmer",
- "homepage": "https://github.com/jrfnl",
- "role": "lead"
- },
- {
- "name": "Contributors",
- "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors"
- }
- ],
- "description": "A suite of utility functions for use with PHP_CodeSniffer",
- "homepage": "https://phpcsutils.com/",
- "keywords": [
- "PHP_CodeSniffer",
- "phpcbf",
- "phpcodesniffer-standard",
- "phpcs",
- "phpcs3",
- "phpcs4",
- "standards",
- "static analysis",
- "tokens",
- "utility"
- ],
- "support": {
- "docs": "https://phpcsutils.com/",
- "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues",
- "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy",
- "source": "https://github.com/PHPCSStandards/PHPCSUtils"
- },
- "funding": [
- {
- "url": "https://github.com/PHPCSStandards",
- "type": "github"
- },
- {
- "url": "https://github.com/jrfnl",
- "type": "github"
- },
- {
- "url": "https://opencollective.com/php_codesniffer",
- "type": "open_collective"
- },
- {
- "url": "https://thanks.dev/u/gh/phpcsstandards",
- "type": "thanks_dev"
- }
- ],
- "time": "2025-11-17T12:58:33+00:00"
+ "time": "2026-02-03T19:29:21+00:00"
},
{
"name": "phpstan/phpstan",
- "version": "2.1.32",
+ "version": "2.1.40",
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e126cad1e30a99b137b8ed75a85a676450ebb227",
- "reference": "e126cad1e30a99b137b8ed75a85a676450ebb227",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b",
+ "reference": "9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b",
"shasum": ""
},
"require": {
@@ -1918,7 +1671,7 @@
"type": "github"
}
],
- "time": "2025-11-11T15:18:17+00:00"
+ "time": "2026-02-23T15:04:35+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -2241,16 +1994,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "9.6.29",
+ "version": "9.6.34",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3"
+ "reference": "b36f02317466907a230d3aa1d34467041271ef4a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9ecfec57835a5581bc888ea7e13b51eb55ab9dd3",
- "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b36f02317466907a230d3aa1d34467041271ef4a",
+ "reference": "b36f02317466907a230d3aa1d34467041271ef4a",
"shasum": ""
},
"require": {
@@ -2272,7 +2025,7 @@
"phpunit/php-timer": "^5.0.3",
"sebastian/cli-parser": "^1.0.2",
"sebastian/code-unit": "^1.0.8",
- "sebastian/comparator": "^4.0.9",
+ "sebastian/comparator": "^4.0.10",
"sebastian/diff": "^4.0.6",
"sebastian/environment": "^5.1.5",
"sebastian/exporter": "^4.0.8",
@@ -2324,7 +2077,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.29"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.34"
},
"funding": [
{
@@ -2348,7 +2101,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-24T06:29:11+00:00"
+ "time": "2026-01-27T05:45:00+00:00"
},
{
"name": "psr/container",
@@ -2503,66 +2256,6 @@
},
"time": "2024-09-11T13:17:53+00:00"
},
- {
- "name": "rector/rector",
- "version": "2.2.8",
- "source": {
- "type": "git",
- "url": "https://github.com/rectorphp/rector.git",
- "reference": "303aa811649ccd1d32e51e62d5c85949d01b5f1b"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/rectorphp/rector/zipball/303aa811649ccd1d32e51e62d5c85949d01b5f1b",
- "reference": "303aa811649ccd1d32e51e62d5c85949d01b5f1b",
- "shasum": ""
- },
- "require": {
- "php": "^7.4|^8.0",
- "phpstan/phpstan": "^2.1.32"
- },
- "conflict": {
- "rector/rector-doctrine": "*",
- "rector/rector-downgrade-php": "*",
- "rector/rector-phpunit": "*",
- "rector/rector-symfony": "*"
- },
- "suggest": {
- "ext-dom": "To manipulate phpunit.xml via the custom-rule command"
- },
- "bin": [
- "bin/rector"
- ],
- "type": "library",
- "autoload": {
- "files": [
- "bootstrap.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "description": "Instant Upgrade and Automated Refactoring of any PHP code",
- "homepage": "https://getrector.com/",
- "keywords": [
- "automation",
- "dev",
- "migration",
- "refactoring"
- ],
- "support": {
- "issues": "https://github.com/rectorphp/rector/issues",
- "source": "https://github.com/rectorphp/rector/tree/2.2.8"
- },
- "funding": [
- {
- "url": "https://github.com/tomasvotruba",
- "type": "github"
- }
- ],
- "time": "2025-11-12T18:38:00+00:00"
- },
{
"name": "sebastian/cli-parser",
"version": "1.0.2",
@@ -2732,16 +2425,16 @@
},
{
"name": "sebastian/comparator",
- "version": "4.0.9",
+ "version": "4.0.10",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5"
+ "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/67a2df3a62639eab2cc5906065e9805d4fd5dfc5",
- "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d",
+ "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d",
"shasum": ""
},
"require": {
@@ -2794,7 +2487,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
- "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.9"
+ "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10"
},
"funding": [
{
@@ -2814,7 +2507,7 @@
"type": "tidelift"
}
],
- "time": "2025-08-10T06:51:50+00:00"
+ "time": "2026-01-24T09:22:56+00:00"
},
{
"name": "sebastian/complexity",
@@ -3576,16 +3269,16 @@
},
{
"name": "sitecrafting/groot",
- "version": "dev-master",
+ "version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/sitecrafting/groot.git",
- "reference": "92c6d81d85b074b66a0d0b0317e02c30e6b5f4e0"
+ "reference": "9b87bf9de39675140de7eb4c3c3f61f137249baa"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sitecrafting/groot/zipball/92c6d81d85b074b66a0d0b0317e02c30e6b5f4e0",
- "reference": "92c6d81d85b074b66a0d0b0317e02c30e6b5f4e0",
+ "url": "https://api.github.com/repos/sitecrafting/groot/zipball/9b87bf9de39675140de7eb4c3c3f61f137249baa",
+ "reference": "9b87bf9de39675140de7eb4c3c3f61f137249baa",
"shasum": ""
},
"require": {
@@ -3601,7 +3294,6 @@
"squizlabs/php_codesniffer": "3.*",
"wp-coding-standards/wpcs": "^0.14"
},
- "default-branch": true,
"type": "library",
"extra": {
"wordpress-install-dir": {
@@ -3619,16 +3311,20 @@
],
"authors": [
{
- "name": "SiteCrafting, Inc.",
- "email": "hello@sitecrafting.com"
+ "name": "Coby Tamayo",
+ "email": "ctamayo@sitecrafting.com"
+ },
+ {
+ "name": "Reena Hensley",
+ "email": "rhensley@sitecrafting.com"
}
],
"description": "The official SiteCrafting WordPress starter theme",
"support": {
"issues": "https://github.com/sitecrafting/groot/issues",
- "source": "https://github.com/sitecrafting/groot/tree/master"
+ "source": "https://github.com/sitecrafting/groot/tree/v1.0.0"
},
- "time": "2025-10-17T20:06:34+00:00"
+ "time": "2024-10-25T20:54:01+00:00"
},
{
"name": "squizlabs/php_codesniffer",
@@ -3711,16 +3407,16 @@
},
{
"name": "symfony/config",
- "version": "v6.4.28",
+ "version": "v6.4.34",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
- "reference": "15947c18ef3ddb0b2f4ec936b9e90e2520979f62"
+ "reference": "ce9cb0c0d281aaf188b802d4968e42bfb60701e9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/15947c18ef3ddb0b2f4ec936b9e90e2520979f62",
- "reference": "15947c18ef3ddb0b2f4ec936b9e90e2520979f62",
+ "url": "https://api.github.com/repos/symfony/config/zipball/ce9cb0c0d281aaf188b802d4968e42bfb60701e9",
+ "reference": "ce9cb0c0d281aaf188b802d4968e42bfb60701e9",
"shasum": ""
},
"require": {
@@ -3766,7 +3462,7 @@
"description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/config/tree/v6.4.28"
+ "source": "https://github.com/symfony/config/tree/v6.4.34"
},
"funding": [
{
@@ -3786,20 +3482,20 @@
"type": "tidelift"
}
],
- "time": "2025-11-01T19:52:02+00:00"
+ "time": "2026-02-24T17:34:50+00:00"
},
{
"name": "symfony/console",
- "version": "v6.4.27",
+ "version": "v6.4.34",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "13d3176cf8ad8ced24202844e9f95af11e2959fc"
+ "reference": "7b1f1c37eff5910ddda2831345467e593a5120ad"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/13d3176cf8ad8ced24202844e9f95af11e2959fc",
- "reference": "13d3176cf8ad8ced24202844e9f95af11e2959fc",
+ "url": "https://api.github.com/repos/symfony/console/zipball/7b1f1c37eff5910ddda2831345467e593a5120ad",
+ "reference": "7b1f1c37eff5910ddda2831345467e593a5120ad",
"shasum": ""
},
"require": {
@@ -3864,7 +3560,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v6.4.27"
+ "source": "https://github.com/symfony/console/tree/v6.4.34"
},
"funding": [
{
@@ -3884,20 +3580,20 @@
"type": "tidelift"
}
],
- "time": "2025-10-06T10:25:16+00:00"
+ "time": "2026-02-23T15:42:15+00:00"
},
{
"name": "symfony/dependency-injection",
- "version": "v6.4.26",
+ "version": "v6.4.34",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "5f311eaf0b321f8ec640f6bae12da43a14026898"
+ "reference": "91e49958b8a6092e48e4711894a1aeb1b151c62a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/5f311eaf0b321f8ec640f6bae12da43a14026898",
- "reference": "5f311eaf0b321f8ec640f6bae12da43a14026898",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/91e49958b8a6092e48e4711894a1aeb1b151c62a",
+ "reference": "91e49958b8a6092e48e4711894a1aeb1b151c62a",
"shasum": ""
},
"require": {
@@ -3949,7 +3645,7 @@
"description": "Allows you to standardize and centralize the way objects are constructed in your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/dependency-injection/tree/v6.4.26"
+ "source": "https://github.com/symfony/dependency-injection/tree/v6.4.34"
},
"funding": [
{
@@ -3969,20 +3665,20 @@
"type": "tidelift"
}
],
- "time": "2025-09-11T09:57:09+00:00"
+ "time": "2026-02-24T15:33:38+00:00"
},
{
"name": "symfony/event-dispatcher",
- "version": "v6.4.25",
+ "version": "v6.4.32",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "b0cf3162020603587363f0551cd3be43958611ff"
+ "reference": "99d7e101826e6610606b9433248f80c1997cd20b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b0cf3162020603587363f0551cd3be43958611ff",
- "reference": "b0cf3162020603587363f0551cd3be43958611ff",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99d7e101826e6610606b9433248f80c1997cd20b",
+ "reference": "99d7e101826e6610606b9433248f80c1997cd20b",
"shasum": ""
},
"require": {
@@ -4033,7 +3729,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.25"
+ "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.32"
},
"funding": [
{
@@ -4053,7 +3749,7 @@
"type": "tidelift"
}
],
- "time": "2025-08-13T09:41:44+00:00"
+ "time": "2026-01-05T11:13:48+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
@@ -4133,16 +3829,16 @@
},
{
"name": "symfony/filesystem",
- "version": "v6.4.24",
+ "version": "v6.4.34",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
- "reference": "75ae2edb7cdcc0c53766c30b0a2512b8df574bd8"
+ "reference": "01ffe0411b842f93c571e5c391f289c3fdd498c3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/75ae2edb7cdcc0c53766c30b0a2512b8df574bd8",
- "reference": "75ae2edb7cdcc0c53766c30b0a2512b8df574bd8",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/01ffe0411b842f93c571e5c391f289c3fdd498c3",
+ "reference": "01ffe0411b842f93c571e5c391f289c3fdd498c3",
"shasum": ""
},
"require": {
@@ -4179,7 +3875,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/filesystem/tree/v6.4.24"
+ "source": "https://github.com/symfony/filesystem/tree/v6.4.34"
},
"funding": [
{
@@ -4199,7 +3895,7 @@
"type": "tidelift"
}
],
- "time": "2025-07-10T08:14:14+00:00"
+ "time": "2026-02-24T17:51:06+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
@@ -4457,16 +4153,16 @@
},
{
"name": "symfony/string",
- "version": "v6.4.26",
+ "version": "v6.4.34",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "5621f039a71a11c87c106c1c598bdcd04a19aeea"
+ "reference": "2adaf4106f2ef4c67271971bde6d3fe0a6936432"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/5621f039a71a11c87c106c1c598bdcd04a19aeea",
- "reference": "5621f039a71a11c87c106c1c598bdcd04a19aeea",
+ "url": "https://api.github.com/repos/symfony/string/zipball/2adaf4106f2ef4c67271971bde6d3fe0a6936432",
+ "reference": "2adaf4106f2ef4c67271971bde6d3fe0a6936432",
"shasum": ""
},
"require": {
@@ -4522,7 +4218,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v6.4.26"
+ "source": "https://github.com/symfony/string/tree/v6.4.34"
},
"funding": [
{
@@ -4542,20 +4238,20 @@
"type": "tidelift"
}
],
- "time": "2025-09-11T14:32:46+00:00"
+ "time": "2026-02-08T20:44:54+00:00"
},
{
"name": "symfony/translation",
- "version": "v6.4.26",
+ "version": "v6.4.34",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "c8559fe25c7ee7aa9d28f228903a46db008156a4"
+ "reference": "d07d117db41341511671b0a1a2be48f2772189ce"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/c8559fe25c7ee7aa9d28f228903a46db008156a4",
- "reference": "c8559fe25c7ee7aa9d28f228903a46db008156a4",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/d07d117db41341511671b0a1a2be48f2772189ce",
+ "reference": "d07d117db41341511671b0a1a2be48f2772189ce",
"shasum": ""
},
"require": {
@@ -4621,7 +4317,7 @@
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/translation/tree/v6.4.26"
+ "source": "https://github.com/symfony/translation/tree/v6.4.34"
},
"funding": [
{
@@ -4641,7 +4337,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-05T18:17:25+00:00"
+ "time": "2026-02-16T20:44:03+00:00"
},
{
"name": "symfony/translation-contracts",
@@ -4808,16 +4504,16 @@
},
{
"name": "symfony/yaml",
- "version": "v6.4.26",
+ "version": "v6.4.34",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "0fc8b966fd0dcaab544ae59bfc3a433f048c17b0"
+ "reference": "7bca30dabed7900a08c5ad4f1d6483f881a64d0f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/0fc8b966fd0dcaab544ae59bfc3a433f048c17b0",
- "reference": "0fc8b966fd0dcaab544ae59bfc3a433f048c17b0",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/7bca30dabed7900a08c5ad4f1d6483f881a64d0f",
+ "reference": "7bca30dabed7900a08c5ad4f1d6483f881a64d0f",
"shasum": ""
},
"require": {
@@ -4860,7 +4556,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/yaml/tree/v6.4.26"
+ "source": "https://github.com/symfony/yaml/tree/v6.4.34"
},
"funding": [
{
@@ -4880,7 +4576,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-26T15:07:38+00:00"
+ "time": "2026-02-06T18:32:11+00:00"
},
{
"name": "szepeviktor/phpstan-wordpress",
@@ -4997,38 +4693,30 @@
},
{
"name": "wp-coding-standards/wpcs",
- "version": "3.2.0",
+ "version": "2.3.0",
"source": {
"type": "git",
"url": "https://github.com/WordPress/WordPress-Coding-Standards.git",
- "reference": "d2421de7cec3274ae622c22c744de9a62c7925af"
+ "reference": "7da1894633f168fe244afc6de00d141f27517b62"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/d2421de7cec3274ae622c22c744de9a62c7925af",
- "reference": "d2421de7cec3274ae622c22c744de9a62c7925af",
+ "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7da1894633f168fe244afc6de00d141f27517b62",
+ "reference": "7da1894633f168fe244afc6de00d141f27517b62",
"shasum": ""
},
"require": {
- "ext-filter": "*",
- "ext-libxml": "*",
- "ext-tokenizer": "*",
- "ext-xmlreader": "*",
"php": ">=5.4",
- "phpcsstandards/phpcsextra": "^1.4.0",
- "phpcsstandards/phpcsutils": "^1.1.0",
- "squizlabs/php_codesniffer": "^3.13.0"
+ "squizlabs/php_codesniffer": "^3.3.1"
},
"require-dev": {
- "php-parallel-lint/php-console-highlighter": "^1.0.0",
- "php-parallel-lint/php-parallel-lint": "^1.4.0",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || ^0.6",
"phpcompatibility/php-compatibility": "^9.0",
- "phpcsstandards/phpcsdevtools": "^1.2.0",
- "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0"
+ "phpcsstandards/phpcsdevtools": "^1.0",
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
},
"suggest": {
- "ext-iconv": "For improved results",
- "ext-mbstring": "For improved results"
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.6 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically."
},
"type": "phpcodesniffer-standard",
"notification-url": "https://packagist.org/downloads/",
@@ -5045,7 +4733,6 @@
"keywords": [
"phpcs",
"standards",
- "static analysis",
"wordpress"
],
"support": {
@@ -5053,27 +4740,18 @@
"source": "https://github.com/WordPress/WordPress-Coding-Standards",
"wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki"
},
- "funding": [
- {
- "url": "https://opencollective.com/php_codesniffer",
- "type": "custom"
- }
- ],
- "time": "2025-07-24T20:08:31+00:00"
+ "time": "2020-05-13T23:57:56+00:00"
}
],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": {
- "10up/wp_mock": 20,
- "sitecrafting/groot": 20
+ "10up/wp_mock": 20
},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
- "php": ">=8.1",
- "ext-dom": "*",
- "ext-libxml": "*"
+ "php": ">=8.1"
},
"platform-dev": {},
"platform-overrides": {
diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index e550066..b66d4c3 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -45,7 +45,7 @@ export default defineConfig({
{ text: 'Forms', link: '/forms' },
{ text: 'Notifiers', link: '/notifiers' },
{ text: 'Shortcodes', link: '/shortcodes' },
- { text: 'Twig', link: '/twig' },
+ { text: 'Twig Helpers', link: '/twig' },
],
},
{
@@ -76,4 +76,4 @@ export default defineConfig({
},
],
},
-})
+})
\ No newline at end of file
diff --git a/docs/GLOSSARY.md b/docs/GLOSSARY.md
index e318912..5ee470a 100644
--- a/docs/GLOSSARY.md
+++ b/docs/GLOSSARY.md
@@ -6,12 +6,12 @@ The anonymous function you pass to the `Conifer\Site::configure()` method. See T
Example:
-```
+```php
/* functions.php */
use Conifer\Site;
$site = new Site();
$site->configure(function() {
- /* now we're in the config callback; call add_action() and stuff here... */
+ /* now we're in the config callback; call add_action() and stuff here... */
});
```
diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md
index d2002e6..f20d793 100644
--- a/docs/SUMMARY.md
+++ b/docs/SUMMARY.md
@@ -1,33 +1,35 @@
## Getting Started
-* [What is Conifer?](/what-is-conifer.md)
-* [Requirements](/requirements.md)
-* [Installation](/installation.md)
+* [What is Conifer?](what-is-conifer.md)
+* [Requirements](requirements.md)
+* [Installation](installation.md)
* [Conifer Basics](basics.md)
## Features
* [The Site Class](site.md)
* [Posts and Post Types](posts.md)
-* [AJAX Handlers](/ajax-handlers.md)
+* [AJAX Handlers](ajax-handlers.md)
* [Alerts](alerts.md)
* [Search](search.md)
* [Forms](forms.md)
* [Admin Helpers](admin.md)
-* [Authorization](/authorization.md)
-* [Notifiers](/notifiers.md)
-* [Shortcodes](/shortcodes.md)
+* [Authorization](authorization.md)
+* [Notifiers](notifiers.md)
+* [Shortcodes](shortcodes.md)
## Contributing
-* [How to Contribute](/how-to-contribute.md)
-* [Development Setup](/dev-setup.md)
-* [Testing](/testing.md)
-* [Governance](/governance.md)
-* [Code of Conduct](/code-of-conduct.md)
+* [How to Contribute](how-to-contribute.md)
+* [Development Setup](dev-setup.md)
+* [Testing](testing.md)
+* [Governance](governance.md)
+* [Code of Conduct](code-of-conduct.md)
## Changelog
-* [2020](/changelog/2020.md)
-* [2019](/changelog/2019.md)
-* [2018](/changelog/2018.md)
+* [2026](changelog/2026.md)
+* [2024](changelog/2024.md)
+* [2020](changelog/2020.md)
+* [2019](changelog/2019.md)
+* [2018](changelog/2018.md)
\ No newline at end of file
diff --git a/docs/changelog/2026.md b/docs/changelog/2026.md
new file mode 100644
index 0000000..6333a3d
--- /dev/null
+++ b/docs/changelog/2026.md
@@ -0,0 +1,5 @@
+## v1.0.4
+
+* Update multiple dependencies.
+* Remove dependencies that were no longer needed.
+* Migrate from [Gitbook](https://www.gitbook.com) to [VitePress](https://vitepress.dev).
diff --git a/docs/code-of-conduct.md b/docs/code-of-conduct.md
index 80d183c..8c50e94 100644
--- a/docs/code-of-conduct.md
+++ b/docs/code-of-conduct.md
@@ -20,7 +20,7 @@ Examples of unacceptable behavior by participants include:
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
-* Other conduct which could reasonably be considered inappropriate in a professional setting
+* Other conduct, which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
diff --git a/docs/forms.md b/docs/forms.md
index b292e82..d958498 100644
--- a/docs/forms.md
+++ b/docs/forms.md
@@ -3,7 +3,6 @@
## Getting Started
Conifer's Form API allows you to represent your custom forms as first-class OO citizens and helps to streamline validation, entry processing, and front-end output. To get started, extend the `Conifer\Form\AbstractBase` class and add your custom fields, validators, and processing logic:
-
```php
use Conifer\Form\AbstractBase;
@@ -36,7 +35,7 @@ class MyForm extends AbstractBase {
];
}
- // Process an incoming form submission
+ // Process an incoming form submission
public function process(array $request) {
$valid = $this->validate($request);
if ($valid) {
@@ -71,6 +70,7 @@ Timber::render('my-form-page.twig', ['myForm' => $form]);
Displaying validation errors and submitted values is just as simple:
+
```twig
{% if myForm.has_errors %}
Danger, Will Robinson! Your form has the following errors: {{ myForm.get_unique_error_messages|join(', ') }}
diff --git a/docs/governance.md b/docs/governance.md
index d1d84b3..f82166b 100644
--- a/docs/governance.md
+++ b/docs/governance.md
@@ -2,8 +2,8 @@
Governance of the project is simple and straightforward:
-- **Maintainers** make consensus driven decisions about the project.
-- The **Benevolent Dictator** has the final say on all decisions but generally only exercises this power on an as-needed basis, most usually when the maintainers cannot reach consensus.
+- **Maintainers** make consensus-driven decisions about the project.
+- The **Benevolent Dictator** has the final say on all decisions but generally only exercises this power on an as-needed basis, most usually when the maintainers cannot reach consensus.
## Current Team
@@ -13,10 +13,12 @@ The team consists of:
### Benevolent Dictator
-- [Coby Tamayo](https://github.com/acobster/)
+- [Scott Dunham](https://github.com/sdunham)
### Maintainers
-- [Scott Dunham](https://github.com/sdunham)
+- [Axel Koziol](https://github.com/akoziolsc)
- [Phil Price](https://github.com/philmprice)
+- [Reena Hensley](https://github.com/rbhensley)
+- [Ryan Hendrickson](https://github.com/rhendrickson-sc)
diff --git a/docs/how-to-contribute.md b/docs/how-to-contribute.md
index 96cbd5b..a51c7c8 100644
--- a/docs/how-to-contribute.md
+++ b/docs/how-to-contribute.md
@@ -25,4 +25,4 @@ If you're wondering how best to set up Conifer to work on the source code, check
In general, please follow the Pull Request template that GitHub prompts you with when you create a PR. This comprises various criteria you should think through. Not all criteria necessarily need to be met for every PR (for example, update to documentation only don't need unit tests). Please apply good judgment, think through the effects of your change, and try to empathize with maintainers as well as future developers who may be interested in the reasoning behind a given change.
-If you edit any code, please run unit tests and coding standard checks (`lando unit`, `lando sniff`, and `lando rector`, respectively) before creating your PR. If either of these checks fails, it is your responsibility to figure out why, and fix any failures caused by your code. Of course, if you don't understand why something failed or the reasoning behind a certain test/check, feel free to reach out and we can work with you to figure out the best solution!
+If you edit any code, please run unit tests and coding standard checks (`lando unit` and `lando sniff`, respectively) before creating your PR. If either of these checks fails, it is your responsibility to figure out why, and fix any failures caused by your code. Of course, if you don't understand why something failed or the reasoning behind a certain test/check, feel free to reach out and we can work with you to figure out the best solution!
diff --git a/docs/index.md b/docs/index.md
index 4797513..b6c3cd8 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -21,5 +21,4 @@ features:
details: Easily add any Trig functions/filters your project needs.
- title: Custom admin columns and filters
details: Easily add custom admin columns and filters to your admin screens, without having to remember all the arguments to the manage_*_columns hooks.
----
-
+---
\ No newline at end of file
diff --git a/docs/installation.md b/docs/installation.md
index 161a072..2d9af32 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -4,21 +4,20 @@
This is the recommended route for most use-cases.
-```
+```bash
composer require --save sitecrafting/conifer
```
-In order to put Conifer in the wp-content/plugins directory
-automatically, we recommend [composer-custom-directory-installer](https://github.com/mnsami/composer-custom-directory-installer):
+To put Conifer in the wp-content/plugins directory automatically, we recommend [composer-custom-directory-installer](https://github.com/mnsami/composer-custom-directory-installer):
```json
{
- "require": {
- "sitecrafting/conifer": "dev-master"
- },
- "require-dev": {
- "mnsami/composer-custom-directory-installer": "^1.1"
- }
+ "require": {
+ "sitecrafting/conifer": "^1.0.0"
+ },
+ "require-dev": {
+ "mnsami/composer-custom-directory-installer": "^1.1"
+ }
}
```
@@ -30,7 +29,7 @@ automatically, we recommend [composer-custom-directory-installer](https://github
## From source
-```
+```bash
git clone https://github.com/sitecrafting/conifer /path/to/wp-content/plugins/conifer
cd /path/to/wp-content/plugins/conifer
composer install
diff --git a/docs/notifiers.md b/docs/notifiers.md
index 77665d4..98a3449 100644
--- a/docs/notifiers.md
+++ b/docs/notifiers.md
@@ -19,7 +19,7 @@ $notifier->notify_html(
// send a plaintext message with URL
$notifier->notify_plaintext(
'A Funny Thing Happened on the Internet',
- "O joy of joy! O dream of dreams!\n\nhttp://example.com/cat.gif"
+ "O joy of joy! O dream of dreams!\n\nhttps://example.com/cat.gif"
);
```
diff --git a/docs/testing.md b/docs/testing.md
index d3525d9..95a3d5a 100644
--- a/docs/testing.md
+++ b/docs/testing.md
@@ -30,12 +30,6 @@ As of 1.0, static analysis by PHPStan is also available:
lando analyze
```
-As of 1.0, Rector is also included:
-
-```shell
-lando rector
-```
-
## Writing new tests
Our guidelines for how and what to test are:
diff --git a/docs/twig-helpers.md b/docs/twig-helpers.md
new file mode 100644
index 0000000..e124609
--- /dev/null
+++ b/docs/twig-helpers.md
@@ -0,0 +1,107 @@
+# Twig Helpers
+
+Conifer defines the `Conifer\Twig\HelperInterface` for quickly defining custom Twig functions and filters.
+
+The interface is simple:
+
+```php
+add_twig_helper(new ThemeTwigHelper());
+```
+
+Each public method must return an array of callables, keyed by the function/filter name to be used in Twig templates. For example, say you want to define a `zany_caps()` Twig filter, for getting all zany with capitalization:
+
+```twig
+{{ 'hello world' | zany_caps }}
+{# renders: #}
+HeLlO WoRlD
+```
+
+First, write a `ThemeTwigHelper` and define your filter as an instance method. Inside the array returned by `get_filters()`, include the method as a `callable`:
+
+```php
+ function(string $input): string {
+ return strtoupper($input);
+ },
+ 'no_caps' => function(string $input): string {
+ return strtolower($input);
+ },
+ ];
+ }
+
+ public function get_filters() : array {
+ return [
+ 'zany_caps' => [$this, 'zany_caps'],
+ ];
+ }
+
+ public function zany_caps(string $input) {
+ $chars = explode('', $input);
+ $zanyChars = array_map(function(string $char, int $i) {
+ // capitalize even letters; force odd letters to lowercase
+ return $i % 2 === 0 ? strtoupper($char) : strtolower($char);
+ }, $chars, array_keys($chars));
+ // glue everything back together
+ return implode('', $zanyChars);
+ }
+}
+```
+
+Now, instantiate and register your helper:
+
+```php
+$site->add_twig_helper(new ThemeTwigHelper());
+```
+
+The text being filtered is always the first argument to the callback, just like when you register a callback directly with `Twig_SimpleFilter`.
+
+Note that in this example, we didn't define any Twig functions, but to keep PHP happy, we still have to implement both public methods, where one simply returns an empty array.
+
+## Filters and Functions can be any callable
+
+Note that each callable returned in `get_filters()` or `get_functions()` can be *any* callable. It doesn't have to be an instance method! For example, say you wanted to use PHP's [`ltrim()`](http://us3.php.net/manual/en/function.ltrim.php) as a Twig filter:
+
+```twig
+{{ 'asdfg' | ltrim('sad') }}
+{# renders: #}
+fg
+```
+
+Because the string `"ltrim"` is a callable, you can return it inside the result of `get_filters()`:
+
+```php
+public function get_filters() : array {
+ return [
+ 'ltrim' => 'ltrim',
+ ]
+}
+```
+
+## Built-in Helpers
+
+Conifer comes with several built-in helpers defining various utility functions and filters.
+These built-in helpers can change in-between releases, so please check the [GitHub repository](https://github.com/sitecrafting/conifer/tree/main/lib/Conifer/Twig) for the most up-to-date list.
diff --git a/docs/twig.md b/docs/twig.md
deleted file mode 100644
index f62a7af..0000000
--- a/docs/twig.md
+++ /dev/null
@@ -1,96 +0,0 @@
-# Twig Helpers
-
-Conifer defines the `Conifer\Twig\HelperInterface` for quickly defining custom Twig functions and filters.
-
-The interface is simple:
-
-```php
-namespace Conifer\Twig;
-
-interface HelperInterface {
- public function get_functions() : array;
- public function get_filters() : array;
-}
-```
-
-To register custom Twig helpers, just pass an instance of `HelperInterface` to the `Conifer\Site::add_twig_helper()` method:
-
-```php
-$site->add_twig_helper(new MyTwigHelper());
-```
-
-Each public method must return an array of callables, keyed by the function/filter name to be used in Twig templates. For example, say you want to define a `zany_caps()` Twig filter, for getting all zany with capitalization:
-
-```twig
-{{ 'hello world' | zany_caps }}
-{# renders: #}
-HeLlO WoRlD
-```
-
-First, write a `CapsHelper` and define your filter as an instance method. Inside the array returned by `get_filters()`, include the method as a `callable`:
-
-```php
-use Conifer\Twig\HelperInterface;
-
-class CapsHelper implements HelperInterface {
- public function get_filters() : array {
- return [
- 'zany_caps' => [$this, 'zany_caps'],
- ];
- }
-
- public function zany_caps(string $text) {
- $chars = explode('', $text);
- $zanyChars = array_map(function(string $char, int $i) {
- // capitalize even letters; force odd letters to lowercase
- return $i % 2 === 0 ? strtoupper($char) : strtolower($char);
- }, $chars, array_keys($chars));
- // glue everything back together
- return implode('', $zanyChars);
- }
-
- public function get_functions() : array { return []; }
-}
-```
-
-Now, simply instantiate and register your helper:
-
-```php
-$site->add_twig_helper(new CapsHelper());
-```
-
-The text being filtered is always the first argument to the callback, just like when you register a callback directly with `Twig_SimpleFilter`.
-
-Note that in this example, we didn't define any Twig functions, but to keep PHP happy, we still have to implement both public methods, where one simply returns an empty array.
-
-## Filters and Functions can be any callable
-
-Note too that each callable returned in `get_filters()` or `get_functions()` can be *any* callable, as long as it operates on strings. It doesn't have to be an instance method! For example, say you wanted to use PHP's [`ltrim()`](http://us3.php.net/manual/en/function.ltrim.php) as a Twig filter:
-
-```twig
-{{ 'asdfg' | ltrim('sad') }}
-{# renders: #}
-fg
-```
-
-Because the string `"ltrim"` is a callable, you can simply return it inside the result of `get_filters()`:
-
-```php
- public function get_filters() : array {
- return [
- 'ltrim' => 'ltrim',
- ]
- }
-```
-
-## Built-in Helpers
-
-Conifer comes with several built-in helpers defining various utility functions and filters. See the API reference for details about what they do:
-
-* `FormHelper`
-* `ImageHelper`
-* `NumberHelper`
-* `TermHelper`
-* `TextHelper`
-* `WordPressHelper`
-
diff --git a/lib/Conifer/Admin/Notice.php b/lib/Conifer/Admin/Notice.php
index 1e8b5c8..da26750 100644
--- a/lib/Conifer/Admin/Notice.php
+++ b/lib/Conifer/Admin/Notice.php
@@ -1,5 +1,4 @@
*/
-declare(strict_types=1);
-
namespace Conifer\Admin;
/**
* Provides a high-level API for dislaying all sorts of WP Admin notices
*/
class Notice {
- /**
- * The session array key where flash notice data is stored
- *
- * @var string
- */
- const FLASH_SESSION_KEY = 'conifer_admin_notices';
-
- /**
- * Whether flash notices are enabled. Default: false
- */
- private static bool $flash_enabled = false;
-
- /**
- * Classes to put on the notice
- *
- * @var string
- */
- protected $classes;
-
- /**
- * Constructor
- *
- * @param string $message the message to display
- * @param string $extraClasses any extra HTML class to display.
- * Multiple classes can be specified with a space-separated string, e.g.
- * `"one two three"`
- */
- public function __construct(protected string $message, string $extraClasses = '' ) {
- // clean up classes and convert to an array
- $classes = array_map(trim(...), array_filter(explode(' ', $extraClasses)));
-
- $this->classes = array_unique(array_merge([ 'notice' ], $classes));
- }
-
- /**
- * Clear all flash notices in session
- */
- public static function clear_flash_notices(): void {
- $_SESSION[static::FLASH_SESSION_KEY] = [];
- }
-
- /**
- * Enable flash notices to be stored in the `$_SESSION` superglobal
- */
- public static function enable_flash_notices(): void {
- self::$flash_enabled = true;
-
- add_action('admin_init', [ static::class, 'display_flash_notices' ]);
- }
-
- /**
- * Disable flash notices
- */
- public static function disable_flash_notices(): void {
- self::$flash_enabled = false;
- }
-
- /**
- * Whether flash notices are enabled
- *
- * @return bool
- */
- public static function flash_notices_enabled(): bool {
- return self::$flash_enabled;
- }
-
- /**
- * Display any flash notices stored in session during the admin_notices hook
- */
- public static function display_flash_notices(): void {
- if (!static::flash_notices_enabled()) {
- return;
- }
-
- foreach (static::get_flash_notices() as $notice) {
- $notice->display();
- }
-
- static::clear_flash_notices();
- }
-
- /**
- * Get the flash notices to be displayed based on session data
- *
- * @return Notice[] an array of Notice instances
- */
- public static function get_flash_notices(): array {
- if (!static::flash_notices_enabled()) {
- return [];
- }
-
- $sessionNotices = $_SESSION[static::FLASH_SESSION_KEY] ?? [];
- if (empty($sessionNotices) || !is_array($sessionNotices)) {
- return [];
- }
-
- // filter out invalid notice data
- $sessionNotices = array_filter($sessionNotices, fn($notice ): bool => static::valid_session_notice($notice), ARRAY_FILTER_USE_BOTH);
-
- return array_map(fn(array $notice ): self => new static($notice['message'], $notice['class'] ?? ''), $sessionNotices);
- }
-
- /**
- * Display the admin notice
- *
- * @see https://codex.wordpress.org/Plugin_API/Action_Reference/admin_notices
- */
- public function display(): void {
- add_action('admin_notices', function (): void {
- // Because this class is designed to echo HTML, the user is responsible
- // for ensuring the message doesn't contain any malicious markup.
- // Class is already escaped.
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
- echo $this->html();
- });
+ /**
+ * The session array key where flash notice data is stored
+ *
+ * @var string
+ */
+ const FLASH_SESSION_KEY = 'conifer_admin_notices';
+
+ /**
+ * Whether flash notices are enabled. Default: false
+ *
+ * @var bool
+ */
+ private static $flash_enabled = false;
+
+ /**
+ * Classes to put on the notice
+ *
+ * @var string
+ */
+ protected $classes;
+
+ /**
+ * The message to display
+ *
+ * @var string
+ */
+ protected $message;
+
+ /**
+ * Constructor
+ *
+ * @param string $message the message to display
+ * @param string $extraClasses any extra HTML class to display.
+ * Multiple classes can be specified with a space-separated string, e.g.
+ * `"one two three"`
+ */
+ public function __construct(string $message, string $extraClasses = '') {
+ $this->message = $message;
+
+ // clean up classes and convert to an array
+ $classes = array_map('trim', array_filter(explode(' ', $extraClasses)));
+
+ $this->classes = array_unique(array_merge(['notice'], $classes));
+ }
+
+ /**
+ * Clear all flash notices in session
+ */
+ public static function clear_flash_notices() {
+ $_SESSION[static::FLASH_SESSION_KEY] = [];
+ }
+
+ /**
+ * Enable flash notices to be stored in the `$_SESSION` superglobal
+ */
+ public static function enable_flash_notices() {
+ self::$flash_enabled = true;
+
+ add_action('admin_init', [static::class, 'display_flash_notices']);
+ }
+
+ /**
+ * Disable flash notices
+ */
+ public static function disable_flash_notices() {
+ self::$flash_enabled = false;
+ }
+
+ /**
+ * Whether flash notices are enabled
+ *
+ * @return bool
+ */
+ public static function flash_notices_enabled() : bool {
+ return self::$flash_enabled;
+ }
+
+ /**
+ * Display any flash notices stored in session during the admin_notices hook
+ */
+ public static function display_flash_notices() {
+ if (!static::flash_notices_enabled()) {
+ return;
}
- /**
- * Display this notice as an error
- */
- public function error(): void {
- $this->add_class('notice-error');
- $this->display();
+ foreach (static::get_flash_notices() as $notice) {
+ $notice->display();
}
- /**
- * Display this notice as a warning
- */
- public function warning(): void {
- $this->add_class('notice-warning');
- $this->display();
+ static::clear_flash_notices();
+ }
+
+ /**
+ * Get the flash notices to be displayed based on session data
+ *
+ * @return Notice[] an array of Notice instances
+ */
+ public static function get_flash_notices() : array {
+ if (!static::flash_notices_enabled()) {
+ return [];
}
- /**
- * Display this notice as an info message
- */
- public function info(): void {
- $this->add_class('notice-info');
- $this->display();
+ $sessionNotices = $_SESSION[static::FLASH_SESSION_KEY] ?? [];
+ if (empty($sessionNotices) || !is_array($sessionNotices)) {
+ return [];
}
- /**
- * Display this notice as a success message
- */
- public function success(): void {
- $this->add_class('notice-success');
- $this->display();
+ // filter out invalid notice data
+ $sessionNotices = array_filter($sessionNotices, function($notice, $idx) {
+ return static::valid_session_notice($notice);
+ }, ARRAY_FILTER_USE_BOTH);
+
+ return array_map(function(array $notice) : self {
+ return new static($notice['message'], $notice['class'] ?? '');
+ }, $sessionNotices);
+ }
+
+ /**
+ * Display the admin notice
+ *
+ * @see https://codex.wordpress.org/Plugin_API/Action_Reference/admin_notices
+ */
+ public function display() {
+ add_action('admin_notices', function() {
+ // Because this class is designed to echo HTML, the user is responsible
+ // for ensuring the message doesn't contain any malicious markup.
+ // Class is already escaped.
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ echo $this->html();
+ });
+ }
+
+ /**
+ * Display this notice as an error
+ */
+ public function error() {
+ $this->add_class('notice-error');
+ $this->display();
+ }
+
+ /**
+ * Display this notice as a warning
+ */
+ public function warning() {
+ $this->add_class('notice-warning');
+ $this->display();
+ }
+
+ /**
+ * Display this notice as an info message
+ */
+ public function info() {
+ $this->add_class('notice-info');
+ $this->display();
+ }
+
+ /**
+ * Display this notice as a success message
+ */
+ public function success() {
+ $this->add_class('notice-success');
+ $this->display();
+ }
+
+ /**
+ * Display this notice as an error message on the next page load
+ */
+ public function flash_error() {
+ $this->add_class('notice-error');
+ $this->flash();
+ }
+
+ /**
+ * Display this notice as a warning on the next page load
+ */
+ public function flash_warning() {
+ $this->add_class('notice-warning');
+ $this->flash();
+ }
+
+ /**
+ * Display this notice as an info message on the next page load
+ */
+ public function flash_info() {
+ $this->add_class('notice-info');
+ $this->flash();
+ }
+
+ /**
+ * Display this notice as a success message on the next page load
+ */
+ public function flash_success() {
+ $this->add_class('notice-success');
+ $this->flash();
+ }
+
+ /**
+ * Display this notice on the next page load
+ */
+ public function flash() {
+ // set up a handler for the admin_notices action, to ensure that any
+ // flash notices are added AFTER displaying notices for this request
+ add_action('admin_notices', function() {
+ $_SESSION[static::FLASH_SESSION_KEY]
+ = $_SESSION[static::FLASH_SESSION_KEY] ?? [];
+
+ $_SESSION[static::FLASH_SESSION_KEY][] = [
+ 'class' => $this->get_class(),
+ 'message' => $this->message,
+ ];
+ });
+ }
+
+ /**
+ * Get the message `
` markup
+ *
+ * @return string the HTML to be rendered
+ */
+ public function html() : string {
+ // default to error style
+ if (!$this->has_style_class()) {
+ $this->add_class('notice-error');
}
- /**
- * Display this notice as an error message on the next page load
- */
- public function flash_error(): void {
- $this->add_class('notice-error');
- $this->flash();
+ return sprintf(
+ '
',
+ esc_attr($this->get_class()),
+ $this->message
+ );
+ }
+
+ /**
+ * Add an HTML class to be rendered on this notice
+ *
+ * @param string $class the class to be added
+ * @return Notice
+ */
+ public function add_class(string $class) : self {
+ if ($this->has_class($class)) {
+ // noop
+ return $this;
}
- /**
- * Display this notice as a warning on the next page load
- */
- public function flash_warning(): void {
- $this->add_class('notice-warning');
- $this->flash();
+ $this->classes[] = trim($class);
+ return $this;
+ }
+
+ /**
+ * Get the HTML class or classes to be rendered in the notice markup
+ *
+ * @return string e.g. `"notice notice-error"`
+ */
+ public function get_class() : string {
+ return trim(implode(' ', $this->classes));
+ }
+
+ /**
+ * Whether this notice has a special style class that WordPress targets
+ * in its built-in admin styles.
+ *
+ * @return bool
+ */
+ public function has_style_class() : bool {
+ $styleClasses = [
+ 'notice-error',
+ 'notice-warning',
+ 'notice-info',
+ 'notice-success',
+ ];
+
+ foreach ($styleClasses as $class) {
+ if ($this->has_class($class)) {
+ return true;
+ }
}
- /**
- * Display this notice as an info message on the next page load
- */
- public function flash_info(): void {
- $this->add_class('notice-info');
- $this->flash();
- }
-
- /**
- * Display this notice as a success message on the next page load
- */
- public function flash_success(): void {
- $this->add_class('notice-success');
- $this->flash();
- }
-
- /**
- * Display this notice on the next page load
- */
- public function flash(): void {
- // set up a handler for the admin_notices action, to ensure that any
- // flash notices are added AFTER displaying notices for this request
- add_action('admin_notices', function (): void {
- $_SESSION[static::FLASH_SESSION_KEY] ??= [];
-
- $_SESSION[static::FLASH_SESSION_KEY][] = [
- 'class' => $this->get_class(),
- 'message' => $this->message,
- ];
- });
- }
-
- /**
- * Get the message `
` markup
- *
- * @return string the HTML to be rendered
- */
- public function html(): string {
- // default to error style
- if (!$this->has_style_class()) {
- $this->add_class('notice-error');
- }
-
- return sprintf(
- '
',
- esc_attr($this->get_class()),
- $this->message
- );
- }
-
- /**
- * Add an HTML class to be rendered on this notice
- *
- * @param string $class_name the class to be added
- * @return Notice
- */
- public function add_class(string $class_name ): self {
- if ($this->has_class($class_name)) {
- // noop
- return $this;
- }
-
- $this->classes[] = trim($class_name);
- return $this;
- }
-
- /**
- * Get the HTML class or classes to be rendered in the notice markup
- *
- * @return string e.g. `"notice notice-error"`
- */
- public function get_class(): string {
- return trim(implode(' ', $this->classes));
- }
-
- /**
- * Whether this notice has a special style class that WordPress targets
- * in its built-in admin styles.
- *
- * @return bool
- */
- public function has_style_class(): bool {
- $styleClasses = [
- 'notice-error',
- 'notice-warning',
- 'notice-info',
- 'notice-success',
- ];
-
- foreach ($styleClasses as $class) {
- if ($this->has_class($class)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Whether this Notice has the given $class
- *
- * @param string $class_name
- * @return bool
- */
- public function has_class(string $class_name ): bool {
- return in_array($class_name, $this->classes, true);
- }
-
-
- /**
- * Validate a session notice array
- *
- * @param $notice
- * @return bool
- */
- protected static function valid_session_notice($notice ): bool {
- return is_array($notice)
- && !empty($notice['message'])
- && is_string($notice['message'])
- && (empty($notice['class']) || is_string($notice['class']));
- }
+ return false;
+ }
+
+ /**
+ * Whether this Notice has the given $class
+ *
+ * @param bool
+ */
+ public function has_class(string $class) : bool {
+ return in_array($class, $this->classes, true);
+ }
+
+
+ /**
+ * Validate a session notice array
+ *
+ * @return bool
+ */
+ protected static function valid_session_notice($notice) : bool {
+ return is_array($notice)
+ && !empty($notice['message'])
+ && is_string($notice['message'])
+ && (empty($notice['class']) || is_string($notice['class']));
+ }
}
diff --git a/lib/Conifer/Admin/Page.php b/lib/Conifer/Admin/Page.php
index 850a9e6..352ecc6 100644
--- a/lib/Conifer/Admin/Page.php
+++ b/lib/Conifer/Admin/Page.php
@@ -7,8 +7,6 @@
* @author Coby Tamayo
*/
-declare(strict_types=1);
-
namespace Conifer\Admin;
/**
@@ -29,221 +27,245 @@
* ```
*/
abstract class Page {
- /**
- * The menu_title
- */
- protected string $menu_title;
-
- /**
- * The menu_slug
- *
- * @var string
- */
- protected string $slug;
-
- /**
- * Render the content of this admin Page.
- *
- * @param array $data optional view data for rendering in a specific context
- * @return string
- */
- abstract public function render(array $data = [] ): string;
-
- /**
- * Constructor
- *
- * @see https://developer.wordpress.org/reference/functions/add_menu_page/
- * @param string $title the page_title for this page
- * @param string $menuTitle the menu_title for this Page.
- * Defaults to `$title`
- * @param string $capability the capability required to view this Page.
- * Defaults to `"manage_options"`.
- * @param string $slug the menu_slug for this Page.
- * Defaults to the sanitized `$menuTitle`.
- * @param string $icon_url the icon_url for this Page
- */
- public function __construct(
- protected string $title,
- string $menuTitle = '',
- protected string $capability = 'manage_options',
- string $slug = '',
- protected string $icon_url = ''
- ) {
- $this->menu_title = !empty($menuTitle) ? $menuTitle : $this->title;
- $this->slug = !empty($slug) ? $slug : sanitize_key($this->menu_title);
- }
-
- /**
- * Add this Admin Page to the admin main menu
- *
- * @return Page returns this Page
- */
- public function add(): Page {
- add_action('admin_menu', $this->do_add(...));
-
- return $this;
- }
-
- /**
- * The callback to the `admin_menu` action.
- */
- public function do_add(): Page {
- $renderCallback = function (): void {
- // NOTE: Since render() is specifically for outputting HTML in the admin
- // area, users are responsible for escaping their own output accordingly.
+ /**
+ * The page_title
+ *
+ * @var string
+ */
+ protected $title;
+
+ /**
+ * The menu_title
+ *
+ * @var string
+ */
+ protected $menu_title;
+
+ /**
+ * The capability
+ *
+ * @var string
+ */
+ protected $capability;
+
+ /**
+ * The menu_slug
+ *
+ * @var string
+ */
+ protected $slug;
+
+ /**
+ * The icon_url
+ *
+ * @var string
+ */
+ protected $icon_url;
+
+ /**
+ * Render the content of this admin Page.
+ *
+ * @param array $data optional view data for rendering in a specific context
+ * @return string
+ */
+ abstract public function render(array $data = []) : string;
+
+ /**
+ * Constructor
+ *
+ * @see https://developer.wordpress.org/reference/functions/add_menu_page/
+ * @param string $title the page_title for this page
+ * @param string $menuTitle the menu_title for this Page.
+ * Defaults to `$title`
+ * @param string $capability the capability required to view this Page.
+ * Defaults to `"manage_options"`.
+ * @param string $slug the menu_slug for this Page.
+ * Defaults to the sanitized `$menuTitle`.
+ * @param string $iconUrl the icon_url for this Page
+ */
+ public function __construct(
+ string $title,
+ string $menuTitle = '',
+ string $capability = 'manage_options',
+ string $slug = '',
+ string $iconUrl = ''
+ ) {
+ $this->title = $title;
+ $this->menu_title = $menuTitle ?: $title;
+ $this->capability = $capability;
+ $this->slug = $slug ?: sanitize_key($this->menu_title);
+ $this->icon_url = $iconUrl;
+ }
+
+ /**
+ * Add this Admin Page to the admin main menu
+ *
+ * @return Page returns this Page
+ */
+ public function add() : Page {
+ add_action('admin_menu', [$this, 'do_add']);
+
+ return $this;
+ }
+
+ /**
+ * The callback to the `admin_menu` action.
+ */
+ public function do_add(): void {
+ $renderCallback = function() {
+ // NOTE: Since render() is specifically for outputting HTML in the admin
+ // area, users are responsible for escaping their own output accordingly.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
- echo $this->render([ 'slug' => $this->slug ]);
- };
-
- add_menu_page(
- $this->title,
- $this->menu_title,
- $this->capability,
- $this->slug,
- $renderCallback,
- $this->icon_url
- );
-
- return $this;
- }
-
- /**
- * Add a sub-menu admin page to this Page.
- *
- * @see https://developer.wordpress.org/reference/functions/add_submenu_page/
- * @param string $class_name the `SubPage` child class to instantiate to
- * represent the new sub-page.
- * @param string $title the page_title for the sub-page
- * @param string $menuTitle the menu_title for the sub-page.
- * Defaults to `$title`.
- * @param string $capability the capability required for viewing the sub-page.
- * Defaults to the required capability for this Page.
- * @param string $slug the menu_slug for the sub-page.
- * Defaults to the sanitized `$menuTitle`.
- * @return Page returns this Page.
- */
- public function add_sub_page(
- string $class_name,
- string $title,
- string $menuTitle = '',
- string $capability = '',
- string $slug = ''
- ): self {
- $page = new $class_name($this, $title, $menuTitle, $capability, $slug);
- $page->add();
-
- return $this;
- }
-
- /**
- * Get the `page_title` to be passed to WP when this Page is added.
- *
- * @see https://developer.wordpress.org/reference/functions/add_menu_page/
- * @see https://developer.wordpress.org/reference/functions/add_submenu_page/
- * @return string
- */
- public function get_title(): string {
- return $this->title;
- }
-
- /**
- * Get the `menu_title` to be passed to WP when this Page is added.
- *
- * @see https://developer.wordpress.org/reference/functions/add_menu_page/
- * @see https://developer.wordpress.org/reference/functions/add_submenu_page/
- * @return string
- */
- public function get_menu_title(): string {
- return $this->menu_title;
- }
-
- /**
- * Get the `capability` to be passed to WP when this Page is added.
- *
- * @see https://developer.wordpress.org/reference/functions/add_menu_page/
- * @see https://developer.wordpress.org/reference/functions/add_submenu_page/
- * @return string
- */
- public function get_capability(): string {
- return $this->capability;
- }
-
- /**
- * Get the `menu_slug` to be passed to WP when this Page is added.
- * When adding sub-pages, this is what is passed as `parent_slug`
- *
- * @see https://developer.wordpress.org/reference/functions/add_menu_page/
- * @see https://developer.wordpress.org/reference/functions/add_submenu_page/
- * @return string
- */
- public function get_slug(): string {
- return $this->slug;
- }
-
- /**
- * Get the `icon_url` to be passed to WP when this Page is added.
- *
- * @see https://developer.wordpress.org/reference/functions/add_menu_page/
- * @return string
- */
- public function get_icon_url(): string {
- return $this->icon_url;
- }
-
- /**
- * Set the slug for this Admin Page.
- *
- * @param string $slug the menu_slug for this Page
- * @return Page returns this Page object
- */
- public function set_slug(string $slug ): Page {
- $this->slug = $slug;
- return $this;
- }
-
- /**
- * Set the menu_title for this Admin Page.
- *
- * @param string $menuTitle the title to display in the Admin menu
- * @return Page returns this Page object
- */
- public function set_menu_title(string $menuTitle ): Page {
- $this->menu_title = $menuTitle;
- return $this;
- }
-
- /**
- * Set the capability required to view this Admin Page.
- *
- * @param string $capability the WP capability string, e.g. "edit_posts"
- * @return Page returns this Page object
- */
- public function set_capability(string $capability ): Page {
- $this->capability = $capability;
- return $this;
- }
-
- /**
- * Set the title for this Admin Page.
- *
- * @param string $title the element text to display on this Admin Page
- * @return Page returns this Page object
- */
- public function set_title(string $title ): Page {
- $this->title = $title;
- return $this;
- }
-
- /**
- * Set the icon_url for this Admin Page.
- *
- * @param string $url the icon_url to be displayed in the Menu for this
- * Admin Page.
- *
- * @return Page returns this Page object
- */
- public function set_icon_url(string $url ): Page {
- $this->icon_url = $url;
- return $this;
- }
+ echo $this->render([ 'slug' => $this->slug ]);
+ };
+
+ add_menu_page(
+ $this->title,
+ $this->menu_title,
+ $this->capability,
+ $this->slug,
+ $renderCallback,
+ $this->icon_url
+ );
+ }
+
+ /**
+ * Add a sub-menu admin page to this Page.
+ *
+ * @see https://developer.wordpress.org/reference/functions/add_submenu_page/
+ * @param string $class the `SubPage` child class to instantiate to
+ * represent the new sub-page.
+ * @param string $title the page_title for the sub-page
+ * @param string $menuTitle the menu_title for the sub-page.
+ * Defaults to `$title`.
+ * @param string $capability the capability required for viewing the sub-page.
+ * Defaults to the required capability for this Page.
+ * @param string $slug the menu_slug for the sub-page.
+ * Defaults to the sanitized `$menuTitle`.
+ * @return Page returns this Page.
+ */
+ public function add_sub_page(
+ string $class,
+ string $title,
+ string $menuTitle = '',
+ string $capability = '',
+ string $slug = ''
+ ) : self {
+ $page = new $class($this, $title, $menuTitle, $capability, $slug);
+ $page->add();
+
+ return $this;
+ }
+
+ /**
+ * Get the `page_title` to be passed to WP when this Page is added.
+ *
+ * @see https://developer.wordpress.org/reference/functions/add_menu_page/
+ * @see https://developer.wordpress.org/reference/functions/add_submenu_page/
+ * @return string
+ */
+ public function get_title() : string {
+ return $this->title;
+ }
+
+ /**
+ * Get the `menu_title` to be passed to WP when this Page is added.
+ *
+ * @see https://developer.wordpress.org/reference/functions/add_menu_page/
+ * @see https://developer.wordpress.org/reference/functions/add_submenu_page/
+ * @return string
+ */
+ public function get_menu_title() : string {
+ return $this->menu_title;
+ }
+
+ /**
+ * Get the `capability` to be passed to WP when this Page is added.
+ *
+ * @see https://developer.wordpress.org/reference/functions/add_menu_page/
+ * @see https://developer.wordpress.org/reference/functions/add_submenu_page/
+ * @return string
+ */
+ public function get_capability() : string {
+ return $this->capability;
+ }
+
+ /**
+ * Get the `menu_slug` to be passed to WP when this Page is added.
+ * When adding sub-pages, this is what is passed as `parent_slug`
+ *
+ * @see https://developer.wordpress.org/reference/functions/add_menu_page/
+ * @see https://developer.wordpress.org/reference/functions/add_submenu_page/
+ * @return string
+ */
+ public function get_slug() : string {
+ return $this->slug;
+ }
+
+ /**
+ * Get the `icon_url` to be passed to WP when this Page is added.
+ *
+ * @see https://developer.wordpress.org/reference/functions/add_menu_page/
+ * @return string
+ */
+ public function get_icon_url() : string {
+ return $this->icon_url;
+ }
+
+ /**
+ * Set the slug for this Admin Page.
+ *
+ * @param string $slug the menu_slug for this Page
+ * @return Page returns this Page object
+ */
+ public function set_slug(string $slug) : Page {
+ $this->slug = $slug;
+ return $this;
+ }
+
+ /**
+ * Set the menu_title for this Admin Page.
+ *
+ * @param string $menuTitle the title to display in the Admin menu
+ * @return Page returns this Page object
+ */
+ public function set_menu_title(string $menuTitle) : Page {
+ $this->menu_title = $menuTitle;
+ return $this;
+ }
+
+ /**
+ * Set the capability required to view this Admin Page.
+ *
+ * @param string $capability the WP capability string, e.g. "edit_posts"
+ * @return Page returns this Page object
+ */
+ public function set_capability(string $capability) : Page {
+ $this->capability = $capability;
+ return $this;
+ }
+
+ /**
+ * Set the title for this Admin Page.
+ *
+ * @param string $title the element text to display on this Admin Page
+ * @return Page returns this Page object
+ */
+ public function set_title(string $title) : Page {
+ $this->title = $title;
+ return $this;
+ }
+
+ /**
+ * Set the icon_url for this Admin Page.
+ *
+ * @param string $url the icon_url to be displayed in the Menu for this
+ * Admin Page.
+ *
+ * @return Page returns this Page object
+ */
+ public function set_icon_url(string $url) : Page {
+ $this->icon_url = $url;
+ return $this;
+ }
}
diff --git a/lib/Conifer/Admin/SubPage.php b/lib/Conifer/Admin/SubPage.php
index 11ad8d8..618fb87 100644
--- a/lib/Conifer/Admin/SubPage.php
+++ b/lib/Conifer/Admin/SubPage.php
@@ -7,8 +7,6 @@
* @author Coby Tamayo
*/
-declare(strict_types=1);
-
namespace Conifer\Admin;
/**
@@ -45,65 +43,72 @@
* ```
*/
abstract class SubPage extends Page {
- /**
- * Constructor
- *
- * @see https://developer.wordpress.org/reference/functions/add_submenu_page/
- * @param Page $parent_page the parent Admin Page, which provides the `parent_slug`
- * for this submenu page.
- * @param string $title the `page_title` to use as the element text
- * on this SubPage.
- * @param string $menuTitle the title to display for this SubPage in the
- * admin menu.
- * @param string $capability the WP capability required to view this SubPage.
- * Defaults to the capabaility set on the parent AdminPage.
- * @param string $slug the `menu_slug` for this SubPage.
- */
- public function __construct(
- protected Page $parent_page,
- string $title,
- string $menuTitle = '',
- string $capability = '',
- string $slug = ''
- ) {
- parent::__construct(
- $title,
- $menuTitle,
- !empty($capability) ? $capability : $this->parent_page->get_capability(),
- $slug
- );
- }
+ /**
+ * The parent Page
+ *
+ * @var Page
+ */
+ protected $parent;
- /**
- * Add this SubPage to the WP Admin menu
- *
- * @return Page returns this SubPage
- */
- public function add(): Page {
- add_action('admin_menu', $this->do_add(...));
- return $this;
- }
+ /**
+ * Constructor
+ *
+ * @see https://developer.wordpress.org/reference/functions/add_submenu_page/
+ * @param Page $parent the parent Admin Page, which provides the `parent_slug`
+ * for this submenu page.
+ * @param string $title the `page_title` to use as the element text
+ * on this SubPage.
+ * @param string $menuTitle the title to display for this SubPage in the
+ * admin menu.
+ * @param string $capability the WP capability required to view this SubPage.
+ * Defaults to the capabaility set on the parent AdminPage.
+ * @param string $slug the `menu_slug` for this SubPage.
+ */
+ public function __construct(
+ Page $parent,
+ string $title,
+ string $menuTitle = '',
+ string $capability = '',
+ string $slug = ''
+ ) {
+ $this->parent = $parent;
- /**
- * The callback to the `admin_menu` action.
- */
- public function do_add(): Page {
- $renderCallback = function (): void {
- // NOTE: Since render() is specifically for outputting HTML in the admin
- // area, users are responsible for escaping their own output accordingly.
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
- echo $this->render([ 'slug' => $this->slug ]);
- };
+ parent::__construct(
+ $title,
+ $menuTitle,
+ $capability ?: $parent->get_capability(),
+ $slug
+ );
+ }
- add_submenu_page(
- $this->parent_page->get_slug(),
- $this->title,
- $this->menu_title,
- $this->capability,
- $this->slug,
- $renderCallback
- );
+ /**
+ * Add this SubPage to the WP Admin menu
+ *
+ * @return Page returns this SubPage
+ */
+ public function add() : Page {
+ add_action('admin_menu', [$this, 'do_add']);
+ return $this;
+ }
+
+ /**
+ * The callback to the `admin_menu` action.
+ */
+ public function do_add(): void {
+ $renderCallback = function() {
+ // NOTE: Since render() is specifically for outputting HTML in the admin
+ // area, users are responsible for escaping their own output accordingly.
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ echo $this->render([ 'slug' => $this->slug ]);
+ };
- return $this;
- }
+ add_submenu_page(
+ $this->parent->get_slug(),
+ $this->title,
+ $this->menu_title,
+ $this->capability,
+ $this->slug,
+ $renderCallback
+ );
+ }
}
diff --git a/lib/Conifer/AjaxHandler/AbstractBase.php b/lib/Conifer/AjaxHandler/AbstractBase.php
index 9085fd5..1ce9a04 100644
--- a/lib/Conifer/AjaxHandler/AbstractBase.php
+++ b/lib/Conifer/AjaxHandler/AbstractBase.php
@@ -1,5 +1,4 @@
set_cookie($_COOKIE);
- $handler->send_json_response($handler->execute());
- }
-
- /**
- * Handle an HTTP POST request.
- */
- public static function handle_post(): void {
- static::handle($_POST); // phpcs:ignore WordPress.Security.NonceVerification.Missing
- }
-
- /**
- * Handle an HTTP GET request.
- */
- public static function handle_get(): void {
- static::handle($_GET); // phpcs:ignore WordPress.CSRF.NonceVerification.NoNonceVerification
- }
-
-
- /*
- * Instance Methods
- */
- /**
- * Constructor.
- * TODO decide whether to require Monolog??
- *
- * @param array $request the raw request params, i.e. GET/POST
- * @throws LogicException If the request array doesn't contain an action
- */
- public function __construct(array $request ) {
- if (empty($request['action'])) {
- throw new LogicException(
- 'Trying to handle an AJAX call without an action! The "action" request parameter is required.'
- );
- }
-
- $this->action = $request['action'];
- $this->request = $request;
- $this->action_methods = [];
- }
-
- /**
- * Send $response as a JSON HTTP response and close the connection.
- *
- * @param array $response the response to be converted to JSON
- */
- protected function send_json_response(array $response ): void {
- wp_send_json($response);
+ $handler = new static($requestData ?? $_REQUEST);
+ $handler->set_cookie($_COOKIE);
+ $handler->send_json_response($handler->execute());
+ }
+
+ /**
+ * Handle an HTTP POST request.
+ */
+ public static function handle_post() {
+ static::handle($_POST); // phpcs:ignore WordPress.Security.NonceVerification.Missing
+ }
+
+ /**
+ * Handle an HTTP GET request.
+ */
+ public static function handle_get() {
+ static::handle($_GET); // phpcs:ignore WordPress.CSRF.NonceVerification.NoNonceVerification
+ }
+
+
+ /*
+ * Instance Methods
+ */
+
+ /**
+ * Constructor.
+ * TODO decide whether to require Monolog??
+ *
+ * @param array $request the raw request params, i.e. GET/POST
+ * @throws LogicException If the request array doesn't contain an action
+ */
+ public function __construct(array $request) {
+ if (empty($request['action'])) {
+ throw new LogicException(
+ 'Trying to handle an AJAX call without an action! The "action" request parameter is required.'
+ );
}
- /**
- * Get a param value from the request
- *
- * @param mixed $name the key of the value to get from the request
- * @return mixed the value for the request param. Defaults to the empty string if not set.
- */
- protected function param(mixed $name ): mixed {
- return $this->request[$name] ?? '';
+ $this->action = $request['action'];
+ $this->request = $request;
+ $this->action_methods = [];
+ }
+
+ /**
+ * Send $response as a JSON HTTP response and close the connection.
+ *
+ * @param array $response the response to be converted to JSON
+ */
+ protected function send_json_response(array $response) {
+ wp_send_json($response);
+ }
+
+ /**
+ * Get a param value from the request
+ *
+ * @param mixed $name the key of the value to get from the request
+ * @return mixed the value for the request param. Defaults to the empty string if not set.
+ */
+ protected function param($name) {
+ return isset($this->request[$name]) ? $this->request[$name] : '';
+ }
+
+ /**
+ * Get a param value from the cookie
+ *
+ * @param mixed $name the key of the value to get from the cookie
+ * @return mixed the value for the cookie param. Defaults to the empty string if not set.
+ */
+ protected function cookie($name) {
+ return isset($this->cookie[$name]) ? $this->cookie[$name] : '';
+ }
+
+ /**
+ * Dispatch a handler method dynamically based on the requested action
+ *
+ * @return mixed $response the result of calling the corrsponding *_action method.
+ * This **should** be an array.
+ * @throws LogicException If the specified action doesn't exist in the action_methods array
+ * @throws BadMethodCallException If the action method doesn't exist or isn't an instance method
+ */
+ protected function dispatch_action() {
+ // check that a handler is configure for the current action
+ if (empty($this->action_methods[$this->action])) {
+ throw new LogicException("No handler method specified for action: {$this->action}!");
}
- /**
- * Get a param value from the cookie
- *
- * @param mixed $name the key of the value to get from the cookie
- * @return mixed the value for the cookie param. Defaults to the empty string if not set.
- */
- protected function cookie(mixed $name ): mixed {
- return $this->cookie[$name] ?? '';
+ // check that the handler method has been implemented
+ $method = $this->action_methods[$this->action];
+ $reflection = new ReflectionClass($this);
+ if (!$reflection->hasMethod($method)) {
+ throw new BadMethodCallException("Method `{$method}` for action {$this->action} has not been implemented!");
}
- /**
- * Dispatch a handler method dynamically based on the requested action
- *
- * @return mixed $response the result of calling the corresponding *_action method.
- * This **should** be an array.
- * @throws LogicException If the specified action doesn't exist in the action_methods array
- * @throws BadMethodCallException If the action method doesn't exist or isn't an instance method
- */
- protected function dispatch_action(): mixed {
- // check that a handler is configure for the current action
- if (empty($this->action_methods[$this->action])) {
- throw new LogicException(sprintf('No handler method specified for action: %s!', $this->action));
- }
-
- // check that the handler method has been implemented
- $method = $this->action_methods[$this->action];
- $reflection = new ReflectionClass($this);
- if (!$reflection->hasMethod($method)) {
- throw new BadMethodCallException(sprintf('Method `%s` for action %s has not been implemented!', $method, $this->action));
- }
-
- // check that we're calling an instance method
- if ($reflection->getMethod($method)->isStatic()) {
- throw new BadMethodCallException(sprintf('Method %s for action %s must not be static!', $method, $this->action));
- }
-
- return $this->{$method}();
+ // check that we're calling an instance method
+ if ($reflection->getMethod($method)->isStatic()) {
+ throw new BadMethodCallException("Method {$method} for action {$this->action} must not be static!");
}
- /**
- * Adds the specified action name to the action_methods array as a key,
- * with the specified method name as the value. Used to determine the method
- * user to handle the specified action.
- *
- * @param string $action The name of the action to be mapper to a method name
- * @param string $methodName The name of a method name to be used when handling thins action
- * @return AbstractBase The current AbstractBase class instance
- */
- protected function map_action(string $action, string $methodName ): static {
- $this->action_methods[$action] = $methodName;
- return $this;
- }
-
- /**
- * Saves the request cookie array to this AbstractBase handler instance
- *
- * @param array $cookie The request cookie array
- */
- private function set_cookie(array $cookie ): void {
- $this->cookie = $cookie;
- }
+ return $this->{$method}();
+ }
+
+ /**
+ * Adds the specified action name to the action_methods array as a key,
+ * with the specified method name as the value. Used to determine the method
+ * user to handle the specified action.
+ *
+ * @param string $action The name of the action to be mapper to a method name
+ * @param string $methodName The name of a method name to be used when handling thins action
+ * @return Conifer\AjaxHandler\AbstractBase The current AbstractBase class instance
+ */
+ protected function map_action($action, $methodName) {
+ $this->action_methods[$action] = $methodName;
+ return $this;
+ }
+
+ /**
+ * Saves the request cookie array to this AbstractBase handler instance
+ *
+ * @param array $cookie The request cookie array
+ */
+ private function set_cookie(array $cookie) {
+ $this->cookie = $cookie;
+ }
}
diff --git a/lib/Conifer/Alert/DismissableAlert.php b/lib/Conifer/Alert/DismissableAlert.php
index af8ef78..9b33333 100644
--- a/lib/Conifer/Alert/DismissableAlert.php
+++ b/lib/Conifer/Alert/DismissableAlert.php
@@ -7,8 +7,6 @@
* @author Coby Tamayo
*/
-declare(strict_types=1);
-
namespace Conifer\Alert;
/**
@@ -16,118 +14,131 @@
* persistence for tracking dismissals across requests
*/
class DismissableAlert {
- /**
- * Constructor. Takes the Alert message as a string.
- * Takes an optional array of options.
- *
- * @param string $message Alert message text
- * @param array $options an array with any of the following keys:
- *
- * - 'cookies': (array) The $_COOKIE superglobal (to allow for filtering)
- * - 'cookie_prefix': (string) The prefix for the cookie that indicates
- * whether this Alert has been dismissed. This is appended to a hash of
- * the message text to guaranteed uniqueness. Defaults to:
- * "wp-user_dismissed_alert_"
- * Note that on certain hosting
- * platforms, notably Pantheon, cookies may be filtered out by the edge
- * cache based on this prefix. Pantheon *does not* filter out cookies
- * starting with `wp-` or `wordpress_`, so it's a good idea to start your
- * prefix with one of these. See below for more details.
- * - 'cookie_expires': UTC datetime when cookie should expire. Defaults to
- * UTC-formatted string of one year from now.
- * - 'cookie_path': the path to set for the cookie. Defaults to "/"
- *
- * @see https://pantheon.io/docs/cookies
- */
- public function __construct(
- protected string $message,
- protected array $options = []
- ) {
- }
+ /**
+ * The Alert message
+ *
+ * @var string
+ */
+ protected $message;
- /**
- * The full text of the cookie. This is handy for setting a cookie in
- * JavaScript via `document.cookie = "..."`
- *
- * @return string
- */
- public function cookie_text(): string {
- return sprintf(
- '%s=1; expires=%s; path=%s',
- $this->cookie_name(),
- $this->cookie_expires(),
- $this->cookie_path()
- );
- }
+ /**
+ * Configurable options to the constructor
+ *
+ * @var array
+ */
+ protected $options;
- /**
- * Returns an identifier unique to the Alert message
- *
- * @return string
- */
- public function cookie_name(): string {
- return $this->cookie_prefix() . md5($this->message);
- }
+ /**
+ * Constructor. Takes the Alert message as a string.
+ * Takes an optional array of options.
+ *
+ * @param string $message Alert message text
+ * @param array $options an array with any of the following keys:
+ *
+ * - 'cookies': (array) The $_COOKIE superglobal (to allow for filtering)
+ * - 'cookie_prefix': (string) The prefix for the cookie that indicates
+ * whether this Alert has been dismissed. This is appended to a hash of
+ * the message text to guaranteed uniqueness. Defaults to:
+ * "wp-user_dismissed_alert_"
+ * Note that on certain hosting
+ * platforms, notably Pantheon, cookies may be filtered out by the edge
+ * cache based on this prefix. Pantheon *does not* filter out cookies
+ * starting with `wp-` or `wordpress_`, so it's a good idea to start your
+ * prefix with one of these. See below for more details.
+ * - 'cookie_expires': UTC datetime when cookie should expire. Defaults to
+ * UTC-formatted string of one year from now.
+ * - 'cookie_path': the path to set for the cookie. Defaults to "/"
+ *
+ * @see https://pantheon.io/docs/cookies
+ */
+ public function __construct(string $message, array $options = []) {
+ $this->message = $message;
+ $this->options = $options;
+ }
- /**
- * Get the cookie expiration date/time. This is only used in setting the
- * cookies on dismissal.
- *
- * @return string
- */
- public function cookie_expires(): string {
- if (!empty($this->options['cookie_expires'])) {
- return $this->options['cookie_expires'];
- }
+ /**
+ * The full text of the cookie. This is handy for setting a cookie in
+ * JavaScript via `document.cookie = "..."`
+ *
+ * @return string
+ */
+ public function cookie_text() : string {
+ return sprintf(
+ '%s=1; expires=%s; path=%s',
+ $this->cookie_name(),
+ $this->cookie_expires(),
+ $this->cookie_path()
+ );
+ }
- $now = new \DateTime('now', new \DateTimeZone('UTC'));
- $now->modify('+1 year');
- return $now->format('r');
- }
+ /**
+ * Returns an identifier unique to the Alert message
+ *
+ * @return string
+ */
+ public function cookie_name() : string {
+ return $this->cookie_prefix() . md5($this->message);
+ }
- /**
- * Get the path to set for the cookie. Defaults to "/"
- *
- * @return string
- */
- public function cookie_path(): string {
- return $this->options['cookie_path'] ?? '/';
+ /**
+ * Get the cookie expiration date/time. This is only used in setting the
+ * cookies on dismissal.
+ *
+ * @return string
+ */
+ public function cookie_expires() : string {
+ if (!empty($this->options['cookie_expires'])) {
+ return $this->options['cookie_expires'];
}
- /**
- * Get the Alert message
- *
- * @return string
- */
- public function message(): string {
- return $this->message;
- }
+ $now = new \DateTime('now', new \DateTimeZone('UTC'));
+ $now->modify('+1 year');
+ return $now->format('r');
+ }
- /**
- * Whether this Alert has been dismissed (based on the user's cookies)
- *
- * @return bool
- */
- public function dismissed(): bool {
- return !empty($this->cookies()[$this->cookie_name()]);
- }
+ /**
+ * Get the path to set for the cookie. Defaults to "/"
+ *
+ * @return string
+ */
+ public function cookie_path() : string {
+ return $this->options['cookie_path'] ?? '/';
+ }
+ /**
+ * Get the Alert message
+ *
+ * @return string
+ */
+ public function message() : string {
+ return $this->message;
+ }
- /**
- * Get the cookies from options or the $_COOKIE superglobal
- *
- * @return array
- */
- protected function cookies(): array {
- return (array) ($this->options['cookies'] ?? $_COOKIE);
- }
+ /**
+ * Whether this Alert has been dismissed (based on the user's cookies)
+ *
+ * @return bool
+ */
+ public function dismissed() : bool {
+ return !empty($this->cookies()[$this->cookie_name()]);
+ }
- /**
- * String that the cookie should start with
- *
- * @return string
- */
- protected function cookie_prefix(): string {
- return $this->options['cookie_prefix'] ?? 'wp-user_dismissed_alert_';
- }
+
+ /**
+ * Get the cookies from options or the $_COOKIE superglobal
+ *
+ * @return array
+ */
+ protected function cookies() : array {
+ return (array) ($this->options['cookies'] ?? $_COOKIE);
+ }
+
+ /**
+ * String that the cookie should start with
+ *
+ * @return string
+ */
+ protected function cookie_prefix() : string {
+ return $this->options['cookie_prefix'] ?? 'wp-user_dismissed_alert_';
+ }
}
diff --git a/lib/Conifer/Authorization/AbstractPolicy.php b/lib/Conifer/Authorization/AbstractPolicy.php
index 396b0f6..ff1ca73 100644
--- a/lib/Conifer/Authorization/AbstractPolicy.php
+++ b/lib/Conifer/Authorization/AbstractPolicy.php
@@ -1,5 +1,4 @@
*/
-declare(strict_types=1);
-
namespace Conifer\Authorization;
/**
@@ -16,10 +13,10 @@
* authorization logic
*/
abstract class AbstractPolicy implements PolicyInterface {
- /**
- * Create and adopt a new instance
- */
- public static function register(): PolicyInterface {
- return (new static())->adopt();
- }
+ /**
+ * Create and adopt a new instance
+ */
+ public static function register() : PolicyInterface {
+ return (new static())->adopt();
+ }
}
diff --git a/lib/Conifer/Authorization/PolicyInterface.php b/lib/Conifer/Authorization/PolicyInterface.php
index 1695376..8005c0b 100644
--- a/lib/Conifer/Authorization/PolicyInterface.php
+++ b/lib/Conifer/Authorization/PolicyInterface.php
@@ -1,5 +1,4 @@
*/
-declare(strict_types=1);
-
namespace Conifer\Authorization;
/**
* Interface for a high-level authorization API
*/
interface PolicyInterface {
- /**
- * Put this policy in place, typically via an action or filter
- */
- public function adopt(): self;
+ /**
+ * Put this policy in place, typically via an action or filter
+ */
+ public function adopt() : self;
- /**
- * Create and adopt a new instance of this interface
- */
- public static function register(): self;
+ /**
+ * Create and adopt a new instance of this interface
+ */
+ public static function register() : self;
}
diff --git a/lib/Conifer/Authorization/ShortcodePolicy.php b/lib/Conifer/Authorization/ShortcodePolicy.php
index 368bd55..a62022c 100644
--- a/lib/Conifer/Authorization/ShortcodePolicy.php
+++ b/lib/Conifer/Authorization/ShortcodePolicy.php
@@ -1,5 +1,4 @@
*/
-declare(strict_types=1);
-
namespace Conifer\Authorization;
use Timber\Timber;
@@ -20,99 +17,112 @@
*/
abstract class ShortcodePolicy extends AbstractPolicy {
- /**
- * Sets the shortcode tag for the new shortcode policy
- *
- * @param string $tag
- */
- public function __construct(protected string $tag = 'protected' ) {
- }
-
- /**
- * Filter the shortcode content based on the implementation of the `decide`
- * method.
- *
- * @return PolicyInterface fluent interface
- */
- public function adopt(): PolicyInterface {
- add_shortcode($this->tag(), fn(array $atts, string $content = '' ): string => $this->enforce($atts, $content, $this->get_user()));
-
- return $this;
- }
-
-
- /**
- * Determine whether the user has access to content based on shortcode
- * attributes, user data, and possibly the content itself.
- *
- * @param array $atts the shortcode attributes
- * @param string $content the shortcode content
- * @param \Timber\User $user the user to check against
- * @return bool whether `$user` meets the criteria described in `$atts`
- */
- abstract public function decide(
- array $atts,
- string $content,
- User $user
- ): bool;
-
- /**
- * Get the shortcode tag to be declared
- *
- * @see https://codex.wordpress.org/Function_Reference/add_shortcode
- * @return string the shortcode tag to declare
- */
- protected function tag(): string {
- return $this->tag;
- }
-
- /**
- * Filter the shortcode content based on the current user's data
- *
- * @param string $template the template file being loaded
- * @param \Timber\User the User whose privileges we want to check
- */
- public function enforce(
- array $atts,
- string $content,
- User $user
- ): string {
- $authorized = $this->decide($atts, $content, $user);
-
- return $authorized
- ? $this->filter_authorized($content)
- : $this->filter_unauthorized($content);
- }
-
-
- /**
- * Get the user to check against shortcode attributes.
- * Override this method to perform authorization against someone other
- * than the current user.
- *
- * @return \Timber\User
- */
- protected function get_user(): User {
- return Timber::get_user();
- }
-
- /**
- * Get the filtered shortcode content to display to unauthorized users.
- * Override this method to display something other than the empty string.
- *
- * @return string the content to display
- */
- protected function filter_unauthorized(string $content ): string {
- return '';
- }
-
- /**
- * Get the filtered shortcode content to display to _authorized_ users.
- * Override this method to display something other thatn the original content.
- *
- * @return string the content to display
- */
- protected function filter_authorized(string $content ): string {
- return $content;
- }
+ /**
+ * The shortcode tag
+ *
+ * @var string
+ */
+ protected $tag;
+
+ /**
+ * Sets the shortcode tag for the new shortcode policy
+ *
+ * @param string $tag
+ */
+ public function __construct(string $tag = 'protected') {
+ $this->tag = $tag;
+ }
+
+ /**
+ * Filter the shortcode content based on the implementation of the `decide`
+ * method.
+ *
+ * @return PolicyInterface fluent interface
+ */
+ public function adopt() : PolicyInterface {
+ add_shortcode($this->tag(), function(
+ array $atts,
+ string $content = ''
+ ) : string {
+ return $this->enforce($atts, $content, $this->get_user());
+ });
+
+ return $this;
+ }
+
+
+ /**
+ * Determine whether the user has access to content based on shortcode
+ * attributes, user data, and possibly the content itself.
+ *
+ * @param array $atts the shortcode attributes
+ * @param string $content the shortcode content
+ * @param \Timber\User $user the user to check against
+ * @return bool whether `$user` meets the criteria described in `$atts`
+ */
+ abstract public function decide(
+ array $atts,
+ string $content,
+ User $user
+ ) : bool;
+
+ /**
+ * Get the shortcode tag to be declared
+ *
+ * @see https://codex.wordpress.org/Function_Reference/add_shortcode
+ * @return string the shortcode tag to declare
+ */
+ protected function tag() : string {
+ return $this->tag;
+ }
+
+ /**
+ * Filter the shortcode content based on the current user's data
+ *
+ * @param string $template the template file being loaded
+ * @param \Timber\User the User whose privileges we want to check
+ */
+ public function enforce(
+ array $atts,
+ string $content,
+ User $user
+ ) : string {
+ $authorized = $this->decide($atts, $content, $user);
+
+ return $authorized
+ ? $this->filter_authorized($content)
+ : $this->filter_unauthorized($content);
+ }
+
+
+ /**
+ * Get the user to check against shortcode attributes.
+ * Override this method to perform authorization against someone other
+ * than the current user.
+ *
+ * @return \Timber\User
+ */
+ protected function get_user() : User {
+ return Timber::get_user();
+ }
+
+ /**
+ * Get the filtered shortcode content to display to unauthorized users.
+ * Override this method to display something other than the empty string.
+ *
+ * @return string the content to display
+ */
+ protected function filter_unauthorized(string $content) : string {
+ return '';
+ }
+
+ /**
+ * Get the filtered shortcode content to display to _authorized_ users.
+ * Override this method to display something other thatn the original content.
+ *
+ * @return string the content to display
+ */
+ protected function filter_authorized(string $content) : string {
+ return $content;
+ }
}
diff --git a/lib/Conifer/Authorization/TemplatePolicy.php b/lib/Conifer/Authorization/TemplatePolicy.php
index 779eaac..a76fa1a 100644
--- a/lib/Conifer/Authorization/TemplatePolicy.php
+++ b/lib/Conifer/Authorization/TemplatePolicy.php
@@ -1,5 +1,4 @@
*/
-declare(strict_types=1);
-
namespace Conifer\Authorization;
use Timber\Timber;
@@ -19,27 +16,25 @@
* authorization logic
*/
abstract class TemplatePolicy extends AbstractPolicy {
- /**
- * Adopt this policy
- *
- * If `$this->enforce()` redirects for any reason, then the $template passed in won't be returned.
- *
- * @return PolicyInterface fluent interface
- */
- public function adopt(): PolicyInterface {
- add_filter('template_include', function (string $template ): string {
- $this->enforce($template, Timber::get_user());
- return $template;
- });
+ /**
+ * Adopt this policy
+ *
+ * @return PolicyInterface fluent interface
+ */
+ public function adopt() : PolicyInterface {
+ add_filter('template_include', function(string $template) {
+ $this->enforce($template, Timber::get_user());
+ return $template;
+ });
- return $this;
- }
+ return $this;
+ }
- /**
- * Enforce this template-level policy
- *
- * @param string $template the template file being loaded
- * @param User $user the User whose privileges we want to check
- */
- abstract public function enforce(string $template, User $user );
+ /**
+ * Enforce this template-level policy
+ *
+ * @param string $template the template file being loaded
+ * @param \Timber\User the User whose privileges we want to check
+ */
+ abstract public function enforce(string $template, User $user);
}
diff --git a/lib/Conifer/Authorization/UserRoleShortcodePolicy.php b/lib/Conifer/Authorization/UserRoleShortcodePolicy.php
index e052040..af18e4f 100644
--- a/lib/Conifer/Authorization/UserRoleShortcodePolicy.php
+++ b/lib/Conifer/Authorization/UserRoleShortcodePolicy.php
@@ -1,5 +1,4 @@
*/
-declare(strict_types=1);
-
namespace Conifer\Authorization;
use Timber\User;
@@ -17,29 +14,34 @@
* A ShortcodePolicy that filters content based on the current user's role
*/
class UserRoleShortcodePolicy extends ShortcodePolicy {
- /**
- * Check whether the user's role matches up with the required role
- * declared in the shortcode
- *
- * @inheritdoc
- */
- public function decide(
- array $atts,
- string $content,
- User $user
- ): bool {
- // Parse the role[s] attribute to determine which roles are authorized
- $roleAttr = $atts['role'] ?? $atts['roles'] ?? 'administrator';
- $authorizedRoles = array_map(trim(...), explode(',', $roleAttr));
-
- // Get the user's roles for comparison
- // WP returns user roles in an idiosyncratic way: role names are keys and
- // `true` values means the user has that role. We just want to flatten
- // this to a simple array of role/capability strings
- // If the user is not logged in and has no roles the users wp_capabilities returns false and we want an empty array
- $userRoles = $user->meta('wp_capabilities') === false ? [] : array_keys(array_filter($user->meta('wp_capabilities')));
+ /**
+ * Check whether the user's role matches up with the required role
+ * declared in the shortcode
+ *
+ * @inheritdoc
+ */
+ public function decide(
+ array $atts,
+ string $content,
+ User $user
+ ) : bool {
+ // Parse the role[s] attribute to determine which roles are authorized
+ $roleAttr = $atts['role'] ?? $atts['roles'] ?? 'administrator';
+ $authorizedRoles = array_map('trim', explode(',', $roleAttr));
- // Make sure the user has at least one authorized role
- return array_intersect($authorizedRoles, $userRoles) !== [];
+ // Get the user's roles for comparison
+ // WP returns user roles in an idiosyncratic way: role names are keys and
+ // `true` values means the user has that role. We just want to flatten
+ // this to a simple array of role/capability strings
+ // If the user is not logged in and has no roles the users wp_capabilities returns false and we want an empty array
+ if ($user->meta('wp_capabilities') === false) {
+ $userRoles = [];
+ } else {
+ $userRoles = array_keys(array_filter($user->meta('wp_capabilities')));
}
+
+ // Make sure the user has at least one authorized role
+ return !empty(array_intersect($authorizedRoles, $userRoles));
+ }
+
}
diff --git a/lib/Conifer/Form/AbstractBase.php b/lib/Conifer/Form/AbstractBase.php
index 5d3afd8..23417e2 100644
--- a/lib/Conifer/Form/AbstractBase.php
+++ b/lib/Conifer/Form/AbstractBase.php
@@ -1,5 +1,4 @@
*/
-declare(strict_types=1);
-
namespace Conifer\Form;
use Closure;
-use InvalidArgumentException;
/**
* Abstract form base class, encapsulating the Conifer Form API
@@ -105,628 +101,648 @@
* are validated, and the messages that are displayed for specific reasons.
*/
abstract class AbstractBase {
- const MESSAGE_FIELD_REQUIRED = '%s is required';
-
- const MESSAGE_INVALID_MIME_TYPE = 'Wrong file type for %s';
-
- const MESSAGE_FILE_SIZE = 'The uploaded file for %s exceeds the maximum allowed size';
-
- const MESSAGE_UPLOAD_ERROR = 'An error occurred with the upload for %s';
-
- /**
- * The fields configured for this form as an array of arrays.
- *
- * Each key in the top-level array is the name of a field; each field is
- * represented simply as an array. Validations are declarative, in that each
- * field tells this class exactly how to validate it.
- *
- * @var mixed[]
- */
- protected array $fields = [];
-
- /**
- * The errors collected while processing this form, as arrays. Each error array
- * should have a "message" and a "field" index.
- *
- * @var mixed[]
- */
- protected array $errors = [];
-
- /**
- * Whether this form submission was processed successfully.
- *
- * @var boolean
- */
- protected bool $success = false;
-
- /**
- * Process the form submission.
- *
- * @param array $request the submitted form data, e.g. $_POST
- */
- abstract public function process(array $request );
-
- /**
- * Constructor
- *
- * @var ?array $files Uploaded files for this form, e.g. $_FILES
- */
- public function __construct(
- protected ?array $files = null
- ) {
- }
-
- /**
- * Create a new instance from the request array (e.g. $_POST)
- * and return the hydrated form object. Takes a variable number of arguments,
- * but the first argument MUST be the submitted values, as an associative
- * array. The remaining arguments, if any, are passed to the constructor.
- *
- * @return AbstractBase the form object
- * @throws InvalidArgumentException if the first argument is not an array
- */
- public static function create_from_submission(...$args ): AbstractBase {
- [$submission] = array_splice($args, 0, 1);
-
- if (!is_array($submission)) {
- throw new InvalidArgumentException(
- 'First argument to create_from_submission()'
- . ' must be an array of submitted values'
- );
- }
-
- // hydrate the fields and return the new instance
- return (new static(...$args))->hydrate($submission);
- }
-
- /**
- * Get the fields configured for this form
- *
- * @return mixed[] an array of form fields.
- */
- public function get_fields(): array {
- return $this->fields;
- }
-
- /**
- * Get a field definition by its name
- *
- * @return ?array the field, or null if it doesn't exist
- */
- public function get_field(string $name ): ?array {
- return $this->fields[$name] ?? null;
- }
-
- /**
- * Get the current value for the given form field.
- *
- * @param string $field the name of the form field whose value you want.
- * @return mixed the submitted value, or the existing persisted value if no
- * value has been submitted, or otherwise null.
- */
- public function get(string $field ): mixed {
- return $this->{$field} ?? null;
- }
-
- /**
- * Get the files configured uploaded to this form
- *
- * @return array an array of uploaded files.
- */
- public function get_files(): array {
- $this->throw_files_exception_if_not_set();
- return $this->files;
- }
-
- /**
- * Set the uploaded files for this form, generally to
- * the contents of the $_FILES superglobal.
- *
- * @return AbstractBase This form instance
- */
- public function set_files(array $files = null ): AbstractBase {
- $this->files = $files;
- return $this;
- }
-
- /**
- * Get an uploaded file by field name
- *
- * @param string $field The name of the form field used to upload the file you want
- * @return array An array of data for the given file upload. Will be empty if the
- * field doesn't exist or an error occurred during upload.
- */
- public function get_file(string $field ): array {
- $this->throw_files_exception_if_not_set();
- $file = $this->files[$field] ?? [];
-
- // Return an empty array if an upload error occurred
- if ($file && $file['error'] !== UPLOAD_ERR_OK) {
- $file = [];
- }
-
- return $file;
- }
-
- /**
- * Whether or not `$field` was checked in the submission, optionally
- * matching on `$value` (e.g. for radio buttons).
- *
- * @param string $field the `name` of the field
- * @param string|null $value (optional) the value to check against. This is
- * necessary e.g. for radio inputs, where there's more than one possible
- * value.
- * @return bool true if the field was checked
- */
- public function checked(string $field, string $value = null ): bool {
- $fieldValue = $this->get($field);
-
- // at the very least, check that the field is present in the submission...
- if (null === $fieldValue) {
- return false;
- }
-
- if (is_array($fieldValue)) {
- // array field, e.g. multiple checkboxes or multiselect.
- return in_array($value, $fieldValue, true);
- }
-
- // Single value.
- // EITHER the caller specified no value to match against,
- // OR the submitted value matches the caller's value exactly.
- return (!isset($value) || $fieldValue === $value);
- }
-
- /**
- * Whether `$field` was selected with the value `$optionValue`.
- *
- * ```php
- * if ($form->selected('company_to_contact', 'acme')) {
- * $acmeClient = new AcmeClient();
- * $acmeClient->sendMessage('Someone chose you!');
- * }
- * ```
- *
- * @param string $field the name of the field to check
- * @param mixed $optionValue the value of the option to check against the
- * actual submitted value
- * @return bool
- */
- public function selected(string $field, mixed $optionValue ): bool {
- $fieldValue = $this->get($field);
-
- // at the very least, check that the field is present in the submission...
- if (null === $fieldValue) {
- return false;
- }
-
- if (is_array($fieldValue)) {
- return in_array($optionValue, $fieldValue, true);
- }
-
- return $fieldValue === $optionValue;
- }
-
- /**
- * Get the errors collected while processing this form, if any
- *
- * @return mixed[]
- */
- public function get_errors(): array {
- return $this->errors;
- }
-
- /**
- * Whether this form has any validation errors
- *
- * @return bool
- */
- public function has_errors(): bool {
- return $this->errors !== [];
- }
-
- /**
- * Whether the field `$fieldName` has any validation errors
- *
- * @param string $fieldName the name of the field to check
- * @return bool
- */
- public function has_errors_for(string $fieldName ): bool {
- return $this->get_errors_for($fieldName) !== [];
- }
-
- /**
- * Whether this form has been processed without errors
- *
- * @return boolean
- */
- public function succeeded(): bool {
- return $this->success;
- }
-
- /**
- * Get all unique error messages as a flat array,
- * e.g. for displaying in a list at the top of the