diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml
new file mode 100644
index 00000000..82f6a99b
--- /dev/null
+++ b/.github/workflows/unit-test.yml
@@ -0,0 +1,46 @@
+name: Unit Tests
+
+on:
+ pull_request:
+ push:
+ branches:
+ - 'main*'
+
+# Cancels all previous workflow runs for pull requests that have not completed.
+concurrency:
+ # The concurrency group contains the workflow name and the branch name for pull requests
+ # or the commit hash for any other events.
+ group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
+ cancel-in-progress: true
+
+jobs:
+ unit-php:
+ name: PHP
+ runs-on: ubuntu-latest
+ if: ${{ github.repository == 'WordPress/block-hydration-experiments' || github.event_name == 'pull_request' }}
+
+ steps:
+ - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
+
+ - name: Install Composer dependencies
+ run: |
+ composer install
+
+ - name: Use desired version of NodeJS
+ uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # v3.5.1
+ with:
+ node-version: 14
+ cache: npm
+
+ - name: Npm install and build
+ run: |
+ npm ci
+ npm run build
+
+ - name: Run WordPress
+ run: |
+ npm run wp-env start
+
+ - name: Running single site unit tests
+ run: npm run test:unit:php
+ if: ${{ success() || failure() }}
diff --git a/.gitignore b/.gitignore
index b7dab5e9..fb1ad958 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
node_modules
-build
\ No newline at end of file
+build
+.phpunit.result.cache
+composer.lock
\ No newline at end of file
diff --git a/.wp-env.json b/.wp-env.json
index 11755f86..3a9dcb4c 100644
--- a/.wp-env.json
+++ b/.wp-env.json
@@ -5,5 +5,12 @@
],
"config": {
"SCRIPT_DEBUG": true
+ },
+ "env": {
+ "tests": {
+ "mappings": {
+ "wp-content/plugins/block-hydration-experiments": "."
+ }
+ }
}
}
diff --git a/composer.json b/composer.json
new file mode 100644
index 00000000..212e3bbd
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,6 @@
+{
+ "require-dev": {
+ "phpunit/phpunit": "^9.5",
+ "yoast/phpunit-polyfills": "^1.0"
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index d70f9c5a..e040fa8d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2154,9 +2154,9 @@
"dev": true
},
"@sindresorhus/is": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz",
- "integrity": "sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==",
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
+ "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
"dev": true
},
"@sinonjs/commons": {
@@ -2393,15 +2393,15 @@
}
},
"@types/cacheable-request": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz",
- "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==",
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
+ "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
"dev": true,
"requires": {
"@types/http-cache-semantics": "*",
- "@types/keyv": "*",
+ "@types/keyv": "^3.1.4",
"@types/node": "*",
- "@types/responselike": "*"
+ "@types/responselike": "^1.0.0"
}
},
"@types/connect": {
@@ -3162,16 +3162,16 @@
}
},
"@wordpress/env": {
- "version": "4.9.0",
- "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-4.9.0.tgz",
- "integrity": "sha512-C2g5aOYxl1Bd9lypvEMjXZ1s1Gx/JHpFWuPlCAI8gAzwzB9jCIZkqpU85GsGScpZLAANS/N7wF3LMY68UkN9fQ==",
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-5.8.0.tgz",
+ "integrity": "sha512-AwT7jdiztYe0DkckjCfey5y5Fwgna3O218FtT9M3Ubg2o49mjA+C52KDcevtabynmpSCHAnhVcy7JDst5xn7zQ==",
"dev": true,
"requires": {
"chalk": "^4.0.0",
"copy-dir": "^1.3.0",
"docker-compose": "^0.22.2",
"extract-zip": "^1.6.7",
- "got": "^10.7.0",
+ "got": "^11.8.5",
"inquirer": "^7.1.0",
"js-yaml": "^3.13.1",
"ora": "^4.0.2",
@@ -5166,14 +5166,10 @@
"dev": true
},
"cacheable-lookup": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-2.0.1.tgz",
- "integrity": "sha512-EMMbsiOTcdngM/K6gV/OxF2x0t07+vMOWxZNSCRQMjO2MY2nhZQ6OYhOOpyQrbhqsgtvKGI7hcq6xjnA92USjg==",
- "dev": true,
- "requires": {
- "@types/keyv": "^3.1.1",
- "keyv": "^4.0.0"
- }
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
+ "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
+ "dev": true
},
"cacheable-request": {
"version": "7.0.2",
@@ -5485,7 +5481,7 @@
"clone-deep": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz",
- "integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==",
+ "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=",
"dev": true,
"requires": {
"for-own": "^0.1.3",
@@ -5513,20 +5509,12 @@
"dev": true,
"requires": {
"mimic-response": "^1.0.0"
- },
- "dependencies": {
- "mimic-response": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
- "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
- "dev": true
- }
}
},
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
- "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
"dev": true
},
"collect-v8-coverage": {
@@ -5547,7 +5535,7 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
"colord": {
@@ -5592,7 +5580,7 @@
"commondir": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
- "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
+ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
"dev": true
},
"compressible": {
@@ -5631,7 +5619,7 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
}
}
@@ -5639,7 +5627,7 @@
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"concat-stream": {
@@ -5717,7 +5705,7 @@
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
- "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
"dev": true
},
"copy-dir": {
@@ -6045,7 +6033,7 @@
"cwd": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz",
- "integrity": "sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA==",
+ "integrity": "sha1-FyQAaUBXwioTsM8WFix+S3p/5Wc=",
"dev": true,
"requires": {
"find-pkg": "^0.1.2",
@@ -6081,13 +6069,13 @@
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
- "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true
},
"decamelize-keys": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz",
- "integrity": "sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==",
+ "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=",
"dev": true,
"requires": {
"decamelize": "^1.1.0",
@@ -6097,7 +6085,7 @@
"map-obj": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
- "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==",
+ "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
"dev": true
}
}
@@ -6109,18 +6097,26 @@
"dev": true
},
"decompress-response": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-5.0.0.tgz",
- "integrity": "sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"dev": true,
"requires": {
- "mimic-response": "^2.0.0"
+ "mimic-response": "^3.1.0"
+ },
+ "dependencies": {
+ "mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "dev": true
+ }
}
},
"dedent": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
- "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==",
+ "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=",
"dev": true
},
"deep-extend": {
@@ -6208,7 +6204,7 @@
"globby": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
- "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==",
+ "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
"dev": true,
"requires": {
"array-union": "^1.0.1",
@@ -6221,7 +6217,7 @@
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
}
@@ -6240,7 +6236,7 @@
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"dev": true
},
"depd": {
@@ -6291,7 +6287,7 @@
"dns-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
- "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==",
+ "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=",
"dev": true
},
"dns-packet": {
@@ -6404,16 +6400,10 @@
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
"dev": true
},
- "duplexer3": {
- "version": "0.1.5",
- "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz",
- "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==",
- "dev": true
- },
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
"dev": true
},
"electron-to-chromium": {
@@ -6443,7 +6433,7 @@
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
- "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
"dev": true
},
"end-of-stream": {
@@ -6562,13 +6552,13 @@
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
"dev": true
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"escodegen": {
@@ -6593,7 +6583,7 @@
"levn": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
- "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
+ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
"dev": true,
"requires": {
"prelude-ls": "~1.1.2",
@@ -6617,13 +6607,13 @@
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
- "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
+ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
"dev": true
},
"type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
- "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
+ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
"dev": true,
"requires": {
"prelude-ls": "~1.1.2"
@@ -6921,7 +6911,7 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
}
}
@@ -7152,7 +7142,7 @@
"etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
- "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
"dev": true
},
"eventemitter3": {
@@ -7195,13 +7185,13 @@
"exit": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
- "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==",
+ "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
"dev": true
},
"expand-tilde": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz",
- "integrity": "sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==",
+ "integrity": "sha1-C4HrqJflo9MdHD0QL48BRB5VlEk=",
"dev": true,
"requires": {
"os-homedir": "^1.0.1"
@@ -7481,7 +7471,7 @@
"fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
},
"fastest-levenshtein": {
@@ -7520,7 +7510,7 @@
"fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
- "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+ "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
"dev": true,
"requires": {
"pend": "~1.2.0"
@@ -7547,7 +7537,7 @@
"filename-reserved-regex": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
- "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==",
+ "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=",
"dev": true
},
"filenamify": {
@@ -7597,7 +7587,7 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
}
}
@@ -7616,7 +7606,7 @@
"find-file-up": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-0.1.3.tgz",
- "integrity": "sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==",
+ "integrity": "sha1-z2gJG8+fMApA2kEbN9pczlovvqA=",
"dev": true,
"requires": {
"fs-exists-sync": "^0.1.0",
@@ -7632,7 +7622,7 @@
"find-pkg": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-0.1.2.tgz",
- "integrity": "sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==",
+ "integrity": "sha1-G9wiwG42NlUy4qJIBGhUuXiNpVc=",
"dev": true,
"requires": {
"find-file-up": "^0.1.2"
@@ -7741,13 +7731,13 @@
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
- "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==",
+ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
"dev": true
},
"for-own": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
- "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==",
+ "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=",
"dev": true,
"requires": {
"for-in": "^1.0.1"
@@ -7779,7 +7769,7 @@
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
- "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
"dev": true
},
"fs-constants": {
@@ -7791,7 +7781,7 @@
"fs-exists-sync": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz",
- "integrity": "sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==",
+ "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=",
"dev": true
},
"fs-monkey": {
@@ -7803,7 +7793,7 @@
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"fsevents": {
@@ -7923,7 +7913,7 @@
"global-modules": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz",
- "integrity": "sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==",
+ "integrity": "sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=",
"dev": true,
"requires": {
"global-prefix": "^0.1.4",
@@ -7933,7 +7923,7 @@
"global-prefix": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz",
- "integrity": "sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==",
+ "integrity": "sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=",
"dev": true,
"requires": {
"homedir-polyfill": "^1.0.0",
@@ -7976,30 +7966,26 @@
"globjoin": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz",
- "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==",
+ "integrity": "sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM=",
"dev": true
},
"got": {
- "version": "10.7.0",
- "resolved": "https://registry.npmjs.org/got/-/got-10.7.0.tgz",
- "integrity": "sha512-aWTDeNw9g+XqEZNcTjMMZSy7B7yE9toWOFYip7ofFTLleJhvZwUxxTxkTpKvF+p1SAA4VHmuEy7PiHTHyq8tJg==",
+ "version": "11.8.6",
+ "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
+ "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
"dev": true,
"requires": {
- "@sindresorhus/is": "^2.0.0",
- "@szmarczak/http-timer": "^4.0.0",
+ "@sindresorhus/is": "^4.0.0",
+ "@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
- "cacheable-lookup": "^2.0.0",
- "cacheable-request": "^7.0.1",
- "decompress-response": "^5.0.0",
- "duplexer3": "^0.1.4",
- "get-stream": "^5.0.0",
+ "@types/responselike": "^1.0.0",
+ "cacheable-lookup": "^5.0.3",
+ "cacheable-request": "^7.0.2",
+ "decompress-response": "^6.0.0",
+ "http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
- "mimic-response": "^2.1.0",
"p-cancelable": "^2.0.0",
- "p-event": "^4.0.0",
- "responselike": "^2.0.0",
- "to-readable-stream": "^2.0.0",
- "type-fest": "^0.10.0"
+ "responselike": "^2.0.0"
}
},
"graceful-fs": {
@@ -8053,7 +8039,7 @@
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"has-property-descriptors": {
@@ -8116,7 +8102,7 @@
"hpack.js": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
- "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==",
+ "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=",
"dev": true,
"requires": {
"inherits": "^2.0.1",
@@ -8166,7 +8152,7 @@
"http-deceiver": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
- "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==",
+ "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=",
"dev": true
},
"http-errors": {
@@ -8223,6 +8209,24 @@
"micromatch": "^4.0.2"
}
},
+ "http2-wrapper": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
+ "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
+ "dev": true,
+ "requires": {
+ "quick-lru": "^5.1.1",
+ "resolve-alpn": "^1.0.0"
+ },
+ "dependencies": {
+ "quick-lru": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
+ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
+ "dev": true
+ }
+ }
+ },
"https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
@@ -8318,7 +8322,7 @@
"imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
"dev": true
},
"indent-string": {
@@ -8330,7 +8334,7 @@
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
@@ -8453,7 +8457,7 @@
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
- "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
"dev": true
},
"is-bigint": {
@@ -8523,13 +8527,13 @@
"is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
- "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
+ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
"dev": true
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
},
"is-fullwidth-code-point": {
@@ -8668,7 +8672,7 @@
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
- "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
"dev": true
},
"is-unicode-supported": {
@@ -8689,7 +8693,7 @@
"is-windows": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz",
- "integrity": "sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==",
+ "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=",
"dev": true
},
"is-wsl": {
@@ -8704,19 +8708,19 @@
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true
},
"isobject": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
- "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
"dev": true
},
"istanbul-lib-coverage": {
@@ -11027,7 +11031,7 @@
"json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
- "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
"dev": true
},
"json2php": {
@@ -11059,9 +11063,9 @@
}
},
"keyv": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.0.tgz",
- "integrity": "sha512-2YvuMsA+jnFGtBareKqgANOEKe1mk3HKiXu2fRmAfyxG0MJAywNhi5ttWA3PMjl4NmpyjZNbFifR2vNjW1znfA==",
+ "version": "4.5.2",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz",
+ "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==",
"dev": true,
"requires": {
"json-buffer": "3.0.1"
@@ -11070,7 +11074,7 @@
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"requires": {
"is-buffer": "^1.1.5"
@@ -11103,7 +11107,7 @@
"language-tags": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz",
- "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==",
+ "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=",
"dev": true,
"requires": {
"language-subtag-registry": "~0.3.2"
@@ -11112,7 +11116,7 @@
"lazy-cache": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
- "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==",
+ "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=",
"dev": true
},
"leven": {
@@ -11193,13 +11197,13 @@
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
- "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
+ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
"dev": true
},
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
- "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
+ "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
"dev": true
},
"lodash.merge": {
@@ -11211,13 +11215,13 @@
"lodash.truncate": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
- "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==",
+ "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=",
"dev": true
},
"lodash.uniq": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
- "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
+ "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=",
"dev": true
},
"log-symbols": {
@@ -11306,7 +11310,7 @@
"map-values": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/map-values/-/map-values-1.0.1.tgz",
- "integrity": "sha512-BbShUnr5OartXJe1GeccAWtfro11hhgNJg6G9/UtWKjVGvV5U4C09cg5nk8JUevhXODaXY+hQ3xxMUKSs62ONQ==",
+ "integrity": "sha1-douOecAJvytk/ugG4ip7HEGQyZA=",
"dev": true
},
"markdown-it": {
@@ -11416,13 +11420,13 @@
"mdurl": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
- "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==",
+ "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=",
"dev": true
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
- "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
"dev": true
},
"mem": {
@@ -11495,7 +11499,7 @@
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
- "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==",
+ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=",
"dev": true
},
"merge-stream": {
@@ -11513,7 +11517,7 @@
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
- "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
"dev": true
},
"micromatch": {
@@ -11554,9 +11558,9 @@
"dev": true
},
"mimic-response": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
- "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
+ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
"dev": true
},
"min-indent": {
@@ -11650,7 +11654,7 @@
"is-plain-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
- "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
+ "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
"dev": true
},
"kind-of": {
@@ -11664,7 +11668,7 @@
"mixin-object": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz",
- "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==",
+ "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=",
"dev": true,
"requires": {
"for-in": "^0.1.3",
@@ -11674,7 +11678,7 @@
"for-in": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz",
- "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==",
+ "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=",
"dev": true
}
}
@@ -11731,7 +11735,7 @@
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
"dev": true
},
"negotiator": {
@@ -11776,19 +11780,19 @@
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
"dev": true
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=",
"dev": true
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
- "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"dev": true,
"requires": {
"tr46": "~0.0.3",
@@ -11806,7 +11810,7 @@
"node-int64": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
- "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=",
"dev": true
},
"node-releases": {
@@ -11844,7 +11848,7 @@
"normalize-range": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
- "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=",
"dev": true
},
"normalize-url": {
@@ -12000,13 +12004,13 @@
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true
},
"object-filter": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/object-filter/-/object-filter-1.0.2.tgz",
- "integrity": "sha512-NahvP2vZcy1ZiiYah30CEPw0FpDcSkSePJBMpzl5EQgCmISijiGuJm3SPYp7U+Lf2TljyaIw3E5EgkEx/TNEVA==",
+ "integrity": "sha1-rwt5f/6+r4pSxmN87b6IFs/sG8g=",
"dev": true
},
"object-inspect": {
@@ -12100,7 +12104,7 @@
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
@@ -12224,7 +12228,7 @@
"os-homedir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
- "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==",
+ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
"dev": true
},
"os-tmpdir": {
@@ -12245,21 +12249,6 @@
"integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==",
"dev": true
},
- "p-event": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz",
- "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==",
- "dev": true,
- "requires": {
- "p-timeout": "^3.1.0"
- }
- },
- "p-finally": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
- "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
- "dev": true
- },
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -12294,15 +12283,6 @@
"retry": "^0.13.1"
}
},
- "p-timeout": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
- "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
- "dev": true,
- "requires": {
- "p-finally": "^1.0.0"
- }
- },
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
@@ -12351,7 +12331,7 @@
"parse-passwd": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
- "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==",
+ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
"dev": true
},
"parse5": {
@@ -12411,13 +12391,13 @@
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"path-is-inside": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
- "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==",
+ "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
"dev": true
},
"path-key": {
@@ -12435,7 +12415,7 @@
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
- "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==",
+ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
"dev": true
},
"path-type": {
@@ -12447,7 +12427,7 @@
"pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
- "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
"dev": true
},
"php-parser": {
@@ -12477,13 +12457,13 @@
"pinkie": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
- "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==",
+ "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
"dev": true
},
"pinkie-promise": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
- "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==",
+ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
"dev": true,
"requires": {
"pinkie": "^2.0.0"
@@ -12605,7 +12585,7 @@
"postcss-media-query-parser": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz",
- "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==",
+ "integrity": "sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ=",
"dev": true
},
"postcss-merge-longhand": {
@@ -12817,7 +12797,7 @@
"postcss-resolve-nested-selector": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz",
- "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==",
+ "integrity": "sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4=",
"dev": true
},
"postcss-safe-parser": {
@@ -12980,7 +12960,7 @@
"pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
- "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==",
+ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
"dev": true
},
"psl": {
@@ -13315,7 +13295,7 @@
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"dev": true
},
"require-from-string": {
@@ -13333,7 +13313,7 @@
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
- "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
},
"resolve": {
@@ -13347,6 +13327,12 @@
"supports-preserve-symlinks-flag": "^1.0.0"
}
},
+ "resolve-alpn": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
+ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
+ "dev": true
+ },
"resolve-bin": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/resolve-bin/-/resolve-bin-0.4.3.tgz",
@@ -13368,7 +13354,7 @@
"resolve-dir": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz",
- "integrity": "sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==",
+ "integrity": "sha1-shklmlYC+sXFxJatiUpujMQwJh4=",
"dev": true,
"requires": {
"expand-tilde": "^1.2.2",
@@ -13548,7 +13534,7 @@
"select-hose": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
- "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==",
+ "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=",
"dev": true
},
"selfsigned": {
@@ -13599,7 +13585,7 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
}
}
@@ -13643,7 +13629,7 @@
"serve-index": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
- "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==",
+ "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=",
"dev": true,
"requires": {
"accepts": "~1.3.4",
@@ -13667,13 +13653,13 @@
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
- "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
+ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
"dev": true
},
"http-errors": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
- "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==",
+ "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
"dev": true,
"requires": {
"depd": "~1.1.2",
@@ -13685,13 +13671,13 @@
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
},
"setprototypeof": {
@@ -13703,7 +13689,7 @@
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
- "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
+ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
"dev": true
}
}
@@ -13729,7 +13715,7 @@
"shallow-clone": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz",
- "integrity": "sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==",
+ "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=",
"dev": true,
"requires": {
"is-extendable": "^0.1.1",
@@ -13741,7 +13727,7 @@
"kind-of": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz",
- "integrity": "sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==",
+ "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=",
"dev": true,
"requires": {
"is-buffer": "^1.0.2"
@@ -13750,7 +13736,7 @@
"lazy-cache": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz",
- "integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==",
+ "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=",
"dev": true
}
}
@@ -13788,9 +13774,9 @@
"dev": true
},
"simple-git": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.14.1.tgz",
- "integrity": "sha512-1ThF4PamK9wBORVGMK9HK5si4zoGS2GpRO7tkAFObA4FZv6dKaCVHLQT+8zlgiBm6K2h+wEU9yOaFCu/SR3OyA==",
+ "version": "3.15.1",
+ "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.15.1.tgz",
+ "integrity": "sha512-73MVa5984t/JP4JcQt0oZlKGr42ROYWC3BcUZfuHtT3IHKPspIvL0cZBnvPXF7LL3S/qVeVHVdYYmJ3LOTw4Rg==",
"dev": true,
"requires": {
"@kwsites/file-exists": "^1.1.1",
@@ -14171,7 +14157,7 @@
"style-search": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz",
- "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==",
+ "integrity": "sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI=",
"dev": true
},
"stylehacks": {
@@ -14416,7 +14402,7 @@
"svg-tags": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz",
- "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==",
+ "integrity": "sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=",
"dev": true
},
"svgo": {
@@ -14612,7 +14598,7 @@
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
- "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"throat": {
@@ -14624,7 +14610,7 @@
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
- "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
"thunky": {
@@ -14651,13 +14637,7 @@
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
- "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
- "dev": true
- },
- "to-readable-stream": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-2.1.0.tgz",
- "integrity": "sha512-o3Qa6DGg1CEXshSdvWNX2sN4QHqg03SPq7U6jPXRahlQdl5dK8oXjkU/2/sGrnOZKeGV1zLSO8qPwyKklPPE7w==",
+ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
"dev": true
},
"to-regex-range": {
@@ -14717,7 +14697,7 @@
"trim-repeated": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
- "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==",
+ "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=",
"dev": true,
"requires": {
"escape-string-regexp": "^1.0.2"
@@ -14782,12 +14762,6 @@
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
"dev": true
},
- "type-fest": {
- "version": "0.10.0",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.10.0.tgz",
- "integrity": "sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw==",
- "dev": true
- },
"type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@@ -14878,7 +14852,7 @@
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
- "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
"dev": true
},
"update-browserslist-db": {
@@ -14958,13 +14932,13 @@
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true
},
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
- "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
"dev": true
},
"uuid": {
@@ -15003,7 +14977,7 @@
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
- "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
"dev": true
},
"w3c-hr-time": {
@@ -15531,7 +15505,7 @@
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"write-file-atomic": {
@@ -15604,7 +15578,7 @@
"yauzl": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
- "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+ "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=",
"dev": true,
"requires": {
"buffer-crc32": "~0.2.3",
diff --git a/package.json b/package.json
index dff3a556..2ab26bed 100644
--- a/package.json
+++ b/package.json
@@ -8,10 +8,11 @@
"start": "webpack --mode=development --watch",
"dev": "npm start",
"test": "jest",
+ "pretest:unit:php": "wp-env start",
+ "test:unit:php": "wp-env run tests-wordpress /var/www/html/wp-content/plugins/block-hydration-experiments/vendor/bin/phpunit -c /var/www/html/wp-content/plugins/block-hydration-experiments/phpunit.xml.dist --verbose",
"test:watch": "jest --watch",
"plugin-zip": "wp-scripts plugin-zip",
- "wp-env": "wp-env",
- "ssr": "node ssr.mjs"
+ "wp-env": "wp-env"
},
"prettier": {
"useTabs": true,
@@ -30,7 +31,7 @@
"@babel/preset-env": "^7.17.10",
"@prettier/plugin-php": "^0.18.9",
"@types/jest": "^27.5.1",
- "@wordpress/env": "^4.4.0",
+ "@wordpress/env": "^5.8.0",
"@wordpress/scripts": "^24.3.0",
"babel-jest": "^28.1.0",
"jest": "^28.1.0",
@@ -39,7 +40,6 @@
"dependencies": {
"@preact/signals": "^1.1.2",
"hpq": "^1.3.0",
- "preact": "^10.10.6",
- "ultrahtml": "^0.4.0"
+ "preact": "^10.10.6"
}
}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 00000000..f4000463
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,23 @@
+
',
+ $tags->get_updated_html()
+ );
+ // $this->assertSame( './wordpress.png', $tags->get_attribute( 'src' ) ); // FIXME: Broken; will be fixed by https://github.com/WordPress/gutenberg/pull/46598.
+ $this->assertSame( $context_before->get_context(), $context->get_context(), 'wp-bind directive changed context' );
+ }
+}
diff --git a/phpunit/directives/wp-context.php b/phpunit/directives/wp-context.php
new file mode 100644
index 00000000..eee20c72
--- /dev/null
+++ b/phpunit/directives/wp-context.php
@@ -0,0 +1,64 @@
+ array( 'open' => false ) ) );
+
+ $markup = <<The tabs!
-
` is + * not, that `
` itself is a special element. + */ + +class WP_HTML_Processor extends WP_HTML_Tag_Processor { + public function next_within_balanced_tags( &$state, $query = null, $max_depth = 1000 ) { + if ( empty( $state ) ) { + $state['budget'] = 1000; + $state['tag_name'] = $this->get_tag(); + $state['balanced_depth'] = 1; + $state['depth'] = 1; + + if ( self::is_html_void_element( $this->get_tag() ) ) { + return false; + } + } + + while ( $this->next_tag( array( 'tag_closers' => 'visit' ) ) && $state['budget']-- > 0 ) { + if ( + $this->get_tag() === $state['tag_name'] && + $this->is_tag_closer() && + $state['balanced_depth'] === 1 + ) { + return false; + } + + if ( $state['depth'] <= $max_depth ) { + $this->parse_query( $query ); + if ( $this->matches() ) { + return true; + } + } + + if ( ! self::is_html_void_element( $this->get_tag() ) ) { + $state['depth'] += $this->is_tag_closer() ? -1 : 1; + } + + if ( $this->get_tag() === $state['tag_name'] ) { + $state['balanced_depth'] += $this->is_tag_closer() ? -1 : 1; + } + } + + return false; + } + + public function get_content_inside_balanced_tags() { + static $start_name = null; + static $end_name = null; + + if ( null === $start_name || array_key_exists( $start_name, $this->bookmarks ) ) { + $rand_id = rand( 1, PHP_INT_MAX ); + $start_name = "start_{$rand_id}"; + } + + if ( null === $end_name || array_key_exists( $end_name, $this->bookmarks ) ) { + $rand_id = rand( 1, PHP_INT_MAX ); + $end_name = "start_{$rand_id}"; + } + + $this->set_bookmark( $start_name ); + $tag_name = $this->get_tag(); + $depth = 1; + + if ( self::is_html_void_element( $tag_name ) ) { + return ''; + } + + while ( $this->next_tag( [ 'tag_closers' => 'visit' ] ) ) { + if ( $this->get_tag() !== $tag_name ) { + continue; + } + + if ( $this->is_tag_closer() && $depth === 1 ) { + $this->set_bookmark( $end_name ); + break; + } + + $depth += $this->is_tag_closer() ? -1 : 1; + } + + $content = $this->content_inside_bookmarks( $start_name, $end_name ); + $this->seek( $start_name ); + + $this->release_bookmark( $start_name ); + $this->release_bookmark( $end_name ); + + return $content; + } + + private function content_inside_bookmarks( $start_bookmark, $end_bookmark ) { + if ( ! isset( $this->bookmarks[ $start_bookmark ], $this->bookmarks[ $end_bookmark ] ) ) { + return null; + } + + $start = $this->bookmarks[ $start_bookmark ]; + $end = $this->bookmarks[ $end_bookmark ]; + + return substr( $this->get_updated_html(), $start->end + 1, $end->start - $start->end - 2 ); + } + + /* + * HTML-related Utility Functions + */ + + /** + * @see https://html.spec.whatwg.org/#elements-2 + */ + public static function is_html_void_element( $tag_name ) { + switch ( $tag_name ) { + case 'AREA': + case 'BASE': + case 'BR': + case 'COL': + case 'EMBED': + case 'HR': + case 'IMG': + case 'INPUT': + case 'LINK': + case 'META': + case 'SOURCE': + case 'TRACK': + case 'WBR': + return true; + + default: + return false; + } + } +} diff --git a/src/html/class-wp-html-span.php b/src/html/class-wp-html-span.php new file mode 100644 index 00000000..39e60366 --- /dev/null +++ b/src/html/class-wp-html-span.php @@ -0,0 +1,52 @@ +start = $start; + $this->end = $end; + } +} diff --git a/src/html/class-wp-html-tag-processor.php b/src/html/class-wp-html-tag-processor.php new file mode 100644 index 00000000..514637ec --- /dev/null +++ b/src/html/class-wp-html-tag-processor.php @@ -0,0 +1,1862 @@ + "c" not " c" + * @TODO: Skip over `/` in attributes area, split attribute names by `/` + * @TODO: Decode HTML references/entities in class names when matching. + * E.g. match having class `1<"2` needs to recognize `class="1<"2"`. + * @TODO: Decode character references in `get_attribute()` + * @TODO: Properly escape attribute value in `set_attribute()` + * @TODO: Add slow mode to escape character entities in CSS class names? + * (This requires a custom decoder since `html_entity_decode()` + * doesn't handle attribute character reference decoding rules. + * + * @package WordPress + * @subpackage HTML + * @since 6.2.0 + */ + +/** + * Processes an input HTML document by applying a specified set + * of patches to that input. Tokenizes HTML but does not fully + * parse the input document. + * + * ## Usage + * + * Use of this class requires three steps: + * + * 1. Create a new class instance with your input HTML document. + * 2. Find the tag(s) you are looking for. + * 3. Request changes to the attributes in those tag(s). + * + * Example: + * ```php + * $tags = new WP_HTML_Tag_Processor( $html ); + * if ( $tags->next_tag( [ 'tag_name' => 'option' ] ) ) { + * $tags->set_attribute( 'selected', true ); + * } + * ``` + * + * ### Finding tags + * + * The `next_tag()` function moves the internal cursor through + * your input HTML document until it finds a tag meeting any of + * the supplied restrictions in the optional query argument. If + * no argument is provided then it will find the next HTML tag, + * regardless of what kind it is. + * + * If you want to _find whatever the next tag is_: + * ```php + * $tags->next_tag(); + * ``` + * + * | Goal | Query | + * |-----------------------------------------------------------|----------------------------------------------------------------------------| + * | Find any tag. | `$tags->next_tag();` | + * | Find next image tag. | `$tags->next_tag( [ 'tag_name' => 'img' ] );` | + * | Find next tag containing the `fullwidth` CSS class. | `$tags->next_tag( [ 'class_name' => 'fullwidth' ] );` | + * | Find next image tag containing the `fullwidth` CSS class. | `$tags->next_tag( [ 'tag_name' => 'img', 'class_name' => 'fullwidth' ] );` | + * + * If a tag was found meeting your criteria then `next_tag()` + * will return `true` and you can proceed to modify it. If it + * returns `false`, however, it failed to find the tag and + * moved the cursor to the end of the file. + * + * Once the cursor reaches the end of the file the processor + * is done and if you want to reach an earlier tag you will + * need to recreate the processor and start over. The internal + * cursor can only proceed forward, never backing up. + * + * #### Custom queries + * + * Sometimes it's necessary to further inspect an HTML tag than + * the query syntax here permits. In these cases one may further + * inspect the search results using the read-only functions + * provided by the processor or external state or variables. + * + * Example: + * ```php + * // Paint up to the first five DIV or SPAN tags marked with the "jazzy" style. + * $remaining_count = 5; + * while ( $remaining_count > 0 && $tags->next_tag() ) { + * if ( + * ( 'DIV' === $tags->get_tag() || 'SPAN' === $tags->get_tag() ) && + * 'jazzy' === $tags->get_attribute( 'data-style' ) + * ) { + * $tags->add_class( 'theme-style-everest-jazz' ); + * $remaining_count--; + * } + * } + * ``` + * + * `get_attribute()` will return `null` if the attribute wasn't present + * on the tag when it was called. It may return `""` (the empty string) + * in cases where the attribute was present but its value was empty. + * For boolean attributes, those whose name is present but no value is + * given, it will return `true` (the only way to set `false` for an + * attribute is to remove it). + * + * ### Modifying HTML attributes for a found tag + * + * Once you've found the start of an opening tag you can modify + * any number of the attributes on that tag. You can set a new + * value for an attribute, remove the entire attribute, or do + * nothing and move on to the next opening tag. + * + * Example: + * ```php + * if ( $tags->next_tag( [ 'class' => 'wp-group-block' ] ) ) { + * $tags->set_attribute( 'title', 'This groups the contained content.' ); + * $tags->remove_attribute( 'data-test-id' ); + * } + * ``` + * + * If `set_attribute()` is called for an existing attribute it will + * overwrite the existing value. Similarly, calling `remove_attribute()` + * for a non-existing attribute has no effect on the document. Both + * of these methods are safe to call without knowing if a given attribute + * exists beforehand. + * + * ### Modifying CSS classes for a found tag + * + * The tag processor treats the `class` attribute as a special case. + * Because it's a common operation to add or remove CSS classes you + * can do so using this interface. + * + * As with attribute values, adding or removing CSS classes is a safe + * operation that doesn't require checking if the attribute or class + * exists before making changes. If removing the only class then the + * entire `class` attribute will be removed. + * + * Example: + * ```php + * // from `Yippee!` + * // to `Yippee!` + * $tags->add_class( 'is-active' ); + * + * // from `Yippee!` + * // to `Yippee!` + * $tags->add_class( 'is-active' ); + * + * // from `Yippee!` + * // to `Yippee!` + * $tags->add_class( 'is-active' ); + * + * // from `` + * // to ` + * $tags->remove_class( 'rugby' ); + * + * // from `` + * // to ` + * $tags->remove_class( 'rugby' ); + * + * // from `` + * // to ` + * $tags->remove_class( 'rugby' ); + * ``` + * + * ## Design limitations + * + * @TODO: Expand this section + * + * - no nesting: cannot match open and close tag + * - only move forward, never backward + * - class names not decoded if they contain character references + * - only secures against HTML escaping issues; requires + * manually sanitizing or escaping values based on the needs of + * each individual attribute, since different attributes have + * different needs. + * + * @since 6.2.0 + */ +class WP_HTML_Tag_Processor { + /** + * The maximum number of bookmarks allowed to exist at + * any given time. + * + * @see set_bookmark(); + * @since 6.2.0 + * @var int + */ + const MAX_BOOKMARKS = 10; + + /** + * Maximum number of times seek() can be called. + * Prevents accidental infinite loops. + * + * @see seek() + * @since 6.2.0 + * @var int + */ + const MAX_SEEK_OPS = 1000; + + /** + * The HTML document to parse. + * + * @since 6.2.0 + * @var string + */ + private $html; + + /** + * The last query passed to next_tag(). + * + * @since 6.2.0 + * @var array|null + */ + private $last_query; + + /** + * The tag name this processor currently scans for. + * + * @since 6.2.0 + * @var string|null + */ + private $sought_tag_name; + + /** + * The CSS class name this processor currently scans for. + * + * @since 6.2.0 + * @var string|null + */ + private $sought_class_name; + + /** + * The match offset this processor currently scans for. + * + * @since 6.2.0 + * @var int|null + */ + private $sought_match_offset; + + /** + * Whether to visit tag closers, e.g. , when walking an input document. + * + * @since 6.2.0 + * @var boolean + */ + private $stop_on_tag_closers; + + /** + * The updated HTML document. + * + * @since 6.2.0 + * @var string + */ + private $updated_html = ''; + + /** + * How many bytes from the original HTML document were already read. + * + * @since 6.2.0 + * @var int + */ + private $parsed_bytes = 0; + + /** + * How many bytes from the original HTML document were already treated + * with the requested replacements. + * + * @since 6.2.0 + * @var int + */ + private $updated_bytes = 0; + + /** + * Byte offset in input document where current tag name starts. + * + * Example: + * ``` + *
+ * // supposing the parser is working through this content
+ * // and stops after recognizing the `id` attribute
+ * //
+ * // ^ parsing will continue from this point
+ * $this->attributes = [
+ * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 )
+ * ];
+ *
+ * // when picking up parsing again, or when asking to find the
+ * // `class` attribute we will continue and add to this array
+ * $this->attributes = [
+ * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ),
+ * 'class' => new WP_HTML_Attribute_Match( 'class', 'outline', 18, 32 )
+ * ];
+ *
+ * // Note that only the `class` attribute value is stored in the index.
+ * // That's because it is the only value used by this class at the moment.
+ *
+ *
+ * @since 6.2.0
+ * @var WP_HTML_Attribute_Token[]
+ */
+ private $attributes = array();
+
+ /**
+ * Which class names to add or remove from a tag.
+ *
+ * These are tracked separately from attribute updates because they are
+ * semantically distinct, whereas this interface exists for the common
+ * case of adding and removing class names while other attributes are
+ * generally modified as with DOM `setAttribute` calls.
+ *
+ * When modifying an HTML document these will eventually be collapsed
+ * into a single lexical update to replace the `class` attribute.
+ *
+ * Example:
+ *
+ * // Add the `wp-block-group` class, remove the `wp-group` class.
+ * $classname_updates = [
+ * // Indexed by a comparable class name
+ * 'wp-block-group' => WP_HTML_Tag_Processor::ADD_CLASS,
+ * 'wp-group' => WP_HTML_Tag_Processor::REMOVE_CLASS
+ * ];
+ *
+ *
+ * @since 6.2.0
+ * @var bool[]
+ */
+ private $classname_updates = array();
+
+ /**
+ * Tracks a semantic location in the original HTML which
+ * shifts with updates as they are applied to the document.
+ *
+ * @since 6.2.0
+ * @var WP_HTML_Span[]
+ */
+ protected $bookmarks = array();
+
+ const ADD_CLASS = true;
+ const REMOVE_CLASS = false;
+ const SKIP_CLASS = null;
+
+ /**
+ * Lexical replacements to apply to input HTML document.
+ *
+ * HTML modifications collapse into lexical replacements in order to
+ * provide an efficient mechanism to update documents lazily and in
+ * order to support a variety of semantic modifications without
+ * building a complicated parsing machinery. That is, it's up to
+ * the calling class to generate the lexical modification from the
+ * semantic change requested.
+ *
+ * Example:
+ *
+ * // Replace an attribute stored with a new value, indices
+ * // sourced from the lazily-parsed HTML recognizer.
+ * $start = $attributes['src']->start;
+ * $end = $attributes['src']->end;
+ * $modifications[] = new WP_HTML_Text_Replacement( $start, $end, get_the_post_thumbnail_url() );
+ *
+ * // Correspondingly, something like this
+ * // will appear in the replacements array.
+ * $replacements = [
+ * WP_HTML_Text_Replacement( 14, 28, 'https://my-site.my-domain/wp-content/uploads/2014/08/kittens.jpg' )
+ * ];
+ *
+ *
+ * @since 6.2.0
+ * @var WP_HTML_Text_Replacement[]
+ */
+ private $attribute_updates = array();
+
+ /**
+ * Tracks how many times we've performed a `seek()`
+ * so that we can prevent accidental infinite loops.
+ *
+ * @see seek
+ * @since 6.2.0
+ * @var int
+ */
+ private $seek_count = 0;
+
+ /**
+ * Constructor.
+ *
+ * @since 6.2.0
+ *
+ * @param string $html HTML to process.
+ */
+ public function __construct( $html ) {
+ $this->html = $html;
+ }
+
+ /**
+ * Finds the next tag matching the $query.
+ *
+ * @since 6.2.0
+ *
+ * @param array|string $query {
+ * Which tag name to find, having which class, etc.
+ *
+ * @type string|null $tag_name Which tag to find, or `null` for "any tag."
+ * @type int|null $match_offset Find the Nth tag matching all search criteria.
+ * 0 for "first" tag, 2 for "third," etc.
+ * Defaults to first tag.
+ * @type string|null $class_name Tag must contain this whole class name to match.
+ * }
+ * @return boolean Whether a tag was matched.
+ */
+ public function next_tag( $query = null ) {
+ $this->parse_query( $query );
+ $already_found = 0;
+
+ do {
+ if ( $this->parsed_bytes >= strlen( $this->html ) ) {
+ return false;
+ }
+
+ /*
+ * Unfortunately we can't try to search for only the tag name we want because that might
+ * lead us to skip over other tags and lose track of our place. So we need to search for
+ * _every_ tag and then check after we find one if it's the one we are looking for.
+ */
+ if ( false === $this->parse_next_tag() ) {
+ $this->parsed_bytes = strlen( $this->html );
+
+ return false;
+ }
+
+ while ( $this->parse_next_attribute() ) {
+ continue;
+ }
+
+ $tag_ends_at = strpos( $this->html, '>', $this->parsed_bytes );
+ if ( false === $tag_ends_at ) {
+ return false;
+ }
+ $this->tag_ends_at = $tag_ends_at;
+ $this->parsed_bytes = $tag_ends_at;
+
+ if ( $this->matches() ) {
+ ++$already_found;
+ }
+
+ // Avoid copying the tag name string when possible.
+ $t = $this->html[ $this->tag_name_starts_at ];
+ if ( 's' === $t || 'S' === $t || 't' === $t || 'T' === $t ) {
+ $tag_name = $this->get_tag();
+
+ if ( 'SCRIPT' === $tag_name && ! $this->skip_script_data() ) {
+ $this->parsed_bytes = strlen( $this->html );
+ return false;
+ } elseif (
+ ( 'TEXTAREA' === $tag_name || 'TITLE' === $tag_name ) &&
+ ! $this->skip_rcdata( $tag_name )
+ ) {
+ $this->parsed_bytes = strlen( $this->html );
+ return false;
+ }
+ }
+ } while ( $already_found < $this->sought_match_offset );
+
+ return true;
+ }
+
+
+ /**
+ * Sets a bookmark in the HTML document.
+ *
+ * Bookmarks represent specific places or tokens in the HTML
+ * document, such as a tag opener or closer. When applying
+ * edits to a document, such as setting an attribute, the
+ * text offsets of that token may shift; the bookmark is
+ * kept updated with those shifts and remains stable unless
+ * the entire span of text in which the token sits is removed.
+ *
+ * Release bookmarks when they are no longer needed.
+ *
+ * Example:
+ * ```
+ * Surprising fact you may not know!
+ * ^ ^
+ * \-|-- this `H2` opener bookmark tracks the token
+ *
+ * Surprising fact you may no…
+ * ^ ^
+ * \-|-- it shifts with edits
+ * ```
+ *
+ * Bookmarks provide the ability to seek to a previously-scanned
+ * place in the HTML document. This avoids the need to re-scan
+ * the entire thing.
+ *
+ * Example:
+ * ```
+ * - One
- Two
- Three
+ * ^^^^
+ * want to note this last item
+ *
+ * $p = new WP_HTML_Tag_Processor( $html );
+ * $in_list = false;
+ * while ( $p->next_tag( [ 'tag_closers' => $in_list ? 'visit' : 'skip' ] ) ) {
+ * if ( 'UL' === $p->get_tag() ) {
+ * if ( $p->is_tag_closer() ) {
+ * $in_list = false;
+ * $p->set_bookmark( 'resume' );
+ * if ( $p->seek( 'last-li' ) ) {
+ * $p->add_class( 'last-li' );
+ * }
+ * $p->seek( 'resume' );
+ * $p->release_bookmark( 'last-li' );
+ * $p->release_bookmark( 'resume' );
+ * } else {
+ * $in_list = true;
+ * }
+ * }
+ *
+ * if ( 'LI' === $p->get_tag() ) {
+ * $p->set_bookmark( 'last-li' );
+ * }
+ * }
+ * ```
+ *
+ * Because bookmarks maintain their position they don't
+ * expose any internal offsets for the HTML document
+ * and can't be used with normal string functions.
+ *
+ * Because bookmarks allocate memory and require processing
+ * for every applied update they are limited and require
+ * a name. They should not be created inside a loop.
+ *
+ * Bookmarks are a powerful tool to enable complicated behavior;
+ * consider double-checking that you need this tool if you are
+ * reaching for it, as inappropriate use could lead to broken
+ * HTML structure or unwanted processing overhead.
+ *
+ * @param string $name Identifies this particular bookmark.
+ * @return false|void
+ * @throws Exception Throws on invalid bookmark name if WP_DEBUG set.
+ */
+ public function set_bookmark( $name ) {
+ if ( null === $this->tag_name_starts_at ) {
+ return false;
+ }
+
+ if ( ! array_key_exists( $name, $this->bookmarks ) && count( $this->bookmarks ) >= self::MAX_BOOKMARKS ) {
+ if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
+ throw new Exception( "Tried to jump to a non-existent HTML bookmark {$name}." );
+ }
+ return false;
+ }
+
+ $this->bookmarks[ $name ] = new WP_HTML_Span(
+ $this->tag_name_starts_at - 1,
+ $this->tag_ends_at
+ );
+
+ return true;
+ }
+
+
+ /**
+ * Removes a bookmark if you no longer need to use it.
+ *
+ * Releasing a bookmark frees up the small performance
+ * overhead they require, mainly in the form of compute
+ * costs when modifying the document.
+ *
+ * @param string $name Name of the bookmark to remove.
+ * @return bool
+ */
+ public function release_bookmark( $name ) {
+ if ( ! array_key_exists( $name, $this->bookmarks ) ) {
+ return false;
+ }
+
+ unset( $this->bookmarks[ $name ] );
+
+ return true;
+ }
+
+
+ /**
+ * Skips the contents of the title and textarea tags until an appropriate
+ * tag closer is found.
+ *
+ * @see https://html.spec.whatwg.org/multipage/parsing.html#rcdata-state
+ * @param string $tag_name – the lowercase tag name which will close the RCDATA region.
+ * @since 6.2.0
+ */
+ private function skip_rcdata( $tag_name ) {
+ $html = $this->html;
+ $doc_length = strlen( $html );
+ $tag_length = strlen( $tag_name );
+
+ $at = $this->parsed_bytes;
+
+ while ( false !== $at && $at < $doc_length ) {
+ $at = strpos( $this->html, '', $at );
+
+ // If we have no possible tag closer then fail.
+ if ( false === $at || ( $at + $tag_length ) >= $doc_length ) {
+ $this->parsed_bytes = $doc_length;
+ return false;
+ }
+
+ $at += 2;
+
+ /*
+ * We have to find a case-insensitive match to the tag name.
+ * Note also that since tag names are limited to US-ASCII
+ * characters we can ignore any kind of Unicode normalizing
+ * forms when comparing. If we get a non-ASCII character it
+ * will never be a match.
+ */
+ for ( $i = 0; $i < $tag_length; $i++ ) {
+ $tag_char = $tag_name[ $i ];
+ $html_char = $html[ $at + $i ];
+
+ if ( $html_char !== $tag_char && strtoupper( $html_char ) !== $tag_char ) {
+ $at += $i;
+ continue 2;
+ }
+ }
+
+ $at += $tag_length;
+ $this->parsed_bytes = $at;
+
+ /*
+ * Ensure we terminate the tag name, otherwise we might,
+ * for example, accidentally match the sequence
+ * "" for "".
+ */
+ $c = $html[ $at ];
+ if ( ' ' !== $c && "\t" !== $c && "\r" !== $c && "\n" !== $c && '/' !== $c && '>' !== $c ) {
+ continue;
+ }
+
+ while ( $this->parse_next_attribute() ) {
+ continue;
+ }
+ $at = $this->parsed_bytes;
+ if ( $at >= strlen( $this->html ) ) {
+ return false;
+ }
+
+ if ( '>' === $html[ $at ] || '/' === $html[ $at ] ) {
+ ++$this->parsed_bytes;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Skips the contents of tags, so if we're not seeing the
+ * start of one of these tokens we can proceed to the next
+ * potential match in the text.
+ */
+ if ( ! (
+ $at + 6 < $doc_length &&
+ ( 's' === $html[ $at ] || 'S' === $html[ $at ] ) &&
+ ( 'c' === $html[ $at + 1 ] || 'C' === $html[ $at + 1 ] ) &&
+ ( 'r' === $html[ $at + 2 ] || 'R' === $html[ $at + 2 ] ) &&
+ ( 'i' === $html[ $at + 3 ] || 'I' === $html[ $at + 3 ] ) &&
+ ( 'p' === $html[ $at + 4 ] || 'P' === $html[ $at + 4 ] ) &&
+ ( 't' === $html[ $at + 5 ] || 'T' === $html[ $at + 5 ] )
+ ) ) {
+ ++$at;
+ continue;
+ }
+
+ /*
+ * We also have to make sure we terminate the script tag opener/closer
+ * to avoid making partial matches on strings like `= $doc_length ) {
+ continue;
+ }
+ $at += 6;
+ $c = $html[ $at ];
+ if ( ' ' !== $c && "\t" !== $c && "\r" !== $c && "\n" !== $c && '/' !== $c && '>' !== $c ) {
+ ++$at;
+ continue;
+ }
+
+ if ( 'escaped' === $state && ! $is_closing ) {
+ $state = 'double-escaped';
+ continue;
+ }
+
+ if ( 'double-escaped' === $state && $is_closing ) {
+ $state = 'escaped';
+ continue;
+ }
+
+ if ( $is_closing ) {
+ $this->parsed_bytes = $at;
+ if ( $this->parsed_bytes >= $doc_length ) {
+ return false;
+ }
+
+ while ( $this->parse_next_attribute() ) {
+ continue;
+ }
+
+ if ( '>' === $html[ $this->parsed_bytes ] ) {
+ ++$this->parsed_bytes;
+ return true;
+ }
+ }
+
+ ++$at;
+ }
+
+ return false;
+ }
+
+ /**
+ * Parses the next tag.
+ *
+ * @since 6.2.0
+ */
+ private function parse_next_tag() {
+ $this->after_tag();
+
+ $html = $this->html;
+ $doc_length = strlen( $html );
+ $at = $this->parsed_bytes;
+
+ while ( false !== $at && $at < $doc_length ) {
+ $at = strpos( $html, '<', $at );
+ if ( false === $at ) {
+ return false;
+ }
+
+ if ( '/' === $this->html[ $at + 1 ] ) {
+ $this->is_closing_tag = true;
+ $at++;
+ } else {
+ $this->is_closing_tag = false;
+ }
+
+ /*
+ * HTML tag names must start with [a-zA-Z] otherwise they are not tags.
+ * For example, "<3" is rendered as text, not a tag opener. This means
+ * if we have at least one letter following the "<" then we _do_ have
+ * a tag opener and can process it as such. This is more common than
+ * HTML comments, DOCTYPE tags, and other structure starting with "<"
+ * so it's good to check first for the presence of the tag.
+ *
+ * Reference:
+ * * https://html.spec.whatwg.org/multipage/parsing.html#data-state
+ * * https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
+ */
+ $tag_name_prefix_length = strspn( $html, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', $at + 1 );
+ if ( $tag_name_prefix_length > 0 ) {
+ ++$at;
+ $this->tag_name_length = $tag_name_prefix_length + strcspn( $html, " \t\f\r\n/>", $at + $tag_name_prefix_length );
+ $this->tag_name_starts_at = $at;
+ $this->parsed_bytes = $at + $this->tag_name_length;
+ return true;
+ }
+
+ // If we didn't find a tag opener, and we can't be
+ // transitioning into different markup states, then
+ // we can abort because there aren't any more tags.
+ if ( $at + 1 >= strlen( $html ) ) {
+ return false;
+ }
+
+ //
+ // https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
+ if (
+ strlen( $html ) > $at + 3 &&
+ '-' === $html[ $at + 2 ] &&
+ '-' === $html[ $at + 3 ]
+ ) {
+ $closer_at = strpos( $html, '-->', $at + 4 );
+ if ( false === $closer_at ) {
+ return false;
+ }
+
+ $at = $closer_at + 3;
+ continue;
+ }
+
+ //
+ // The CDATA is case-sensitive.
+ // https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
+ if (
+ strlen( $html ) > $at + 8 &&
+ '[' === $html[ $at + 2 ] &&
+ 'C' === $html[ $at + 3 ] &&
+ 'D' === $html[ $at + 4 ] &&
+ 'A' === $html[ $at + 5 ] &&
+ 'T' === $html[ $at + 6 ] &&
+ 'A' === $html[ $at + 7 ] &&
+ '[' === $html[ $at + 8 ]
+ ) {
+ $closer_at = strpos( $html, ']]>', $at + 9 );
+ if ( false === $closer_at ) {
+ return false;
+ }
+
+ $at = $closer_at + 3;
+ continue;
+ }
+
+ /*
+ *
+ * These are ASCII-case-insensitive.
+ * https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
+ */
+ if (
+ strlen( $html ) > $at + 8 &&
+ 'D' === strtoupper( $html[ $at + 2 ] ) &&
+ 'O' === strtoupper( $html[ $at + 3 ] ) &&
+ 'C' === strtoupper( $html[ $at + 4 ] ) &&
+ 'T' === strtoupper( $html[ $at + 5 ] ) &&
+ 'Y' === strtoupper( $html[ $at + 6 ] ) &&
+ 'P' === strtoupper( $html[ $at + 7 ] ) &&
+ 'E' === strtoupper( $html[ $at + 8 ] )
+ ) {
+ $closer_at = strpos( $html, '>', $at + 9 );
+ if ( false === $closer_at ) {
+ return false;
+ }
+
+ $at = $closer_at + 1;
+ continue;
+ }
+
+ /*
+ * Anything else here is an incorrectly-opened comment and transitions
+ * to the bogus comment state - we can skip to the nearest >.
+ */
+ $at = strpos( $html, '>', $at + 1 );
+ continue;
+ }
+
+ /*
+ * transitions to a bogus comment state – we can skip to the nearest >
+ * https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
+ */
+ if ( '?' === $html[ $at + 1 ] ) {
+ $closer_at = strpos( $html, '>', $at + 2 );
+ if ( false === $closer_at ) {
+ return false;
+ }
+
+ $at = $closer_at + 1;
+ continue;
+ }
+
+ ++$at;
+ }
+
+ return false;
+ }
+
+ /**
+ * Parses the next attribute.
+ *
+ * @since 6.2.0
+ */
+ private function parse_next_attribute() {
+ // Skip whitespace and slashes.
+ $this->parsed_bytes += strspn( $this->html, " \t\f\r\n/", $this->parsed_bytes );
+ if ( $this->parsed_bytes >= strlen( $this->html ) ) {
+ return false;
+ }
+
+ /*
+ * Treat the equal sign ("=") as a part of the attribute name if it is the
+ * first encountered byte:
+ * https://html.spec.whatwg.org/multipage/parsing.html#before-attribute-name-state
+ */
+ $name_length = '=' === $this->html[ $this->parsed_bytes ]
+ ? 1 + strcspn( $this->html, "=/> \t\f\r\n", $this->parsed_bytes + 1 )
+ : strcspn( $this->html, "=/> \t\f\r\n", $this->parsed_bytes );
+
+ // No attribute, just tag closer.
+ if ( 0 === $name_length || $this->parsed_bytes + $name_length >= strlen( $this->html ) ) {
+ return false;
+ }
+
+ $attribute_start = $this->parsed_bytes;
+ $attribute_name = substr( $this->html, $attribute_start, $name_length );
+ $this->parsed_bytes += $name_length;
+ if ( $this->parsed_bytes >= strlen( $this->html ) ) {
+ return false;
+ }
+
+ $this->skip_whitespace();
+ if ( $this->parsed_bytes >= strlen( $this->html ) ) {
+ return false;
+ }
+
+ $has_value = '=' === $this->html[ $this->parsed_bytes ];
+ if ( $has_value ) {
+ ++$this->parsed_bytes;
+ $this->skip_whitespace();
+ if ( $this->parsed_bytes >= strlen( $this->html ) ) {
+ return false;
+ }
+
+ switch ( $this->html[ $this->parsed_bytes ] ) {
+ case "'":
+ case '"':
+ $quote = $this->html[ $this->parsed_bytes ];
+ $value_start = $this->parsed_bytes + 1;
+ $value_length = strcspn( $this->html, $quote, $value_start );
+ $attribute_end = $value_start + $value_length + 1;
+ $this->parsed_bytes = $attribute_end;
+ break;
+
+ default:
+ $value_start = $this->parsed_bytes;
+ $value_length = strcspn( $this->html, "> \t\f\r\n", $value_start );
+ $attribute_end = $value_start + $value_length;
+ $this->parsed_bytes = $attribute_end;
+ }
+ } else {
+ $value_start = $this->parsed_bytes;
+ $value_length = 0;
+ $attribute_end = $attribute_start + $name_length;
+ }
+
+ if ( $attribute_end >= strlen( $this->html ) ) {
+ return false;
+ }
+
+ if ( $this->is_closing_tag ) {
+ return true;
+ }
+
+ // If an attribute is listed many times, only use the first declaration and ignore the rest.
+ if ( ! array_key_exists( $attribute_name, $this->attributes ) ) {
+ $this->attributes[ $attribute_name ] = new WP_HTML_Attribute_Token(
+ $attribute_name,
+ $value_start,
+ $value_length,
+ $attribute_start,
+ $attribute_end,
+ ! $has_value
+ );
+ }
+
+ return $this->attributes[ $attribute_name ];
+ }
+
+ /**
+ * Move the pointer past any immediate successive whitespace.
+ *
+ * @since 6.2.0
+ *
+ * @return void
+ */
+ private function skip_whitespace() {
+ $this->parsed_bytes += strspn( $this->html, " \t\f\r\n", $this->parsed_bytes );
+ }
+
+ /**
+ * Applies attribute updates and cleans up once a tag is fully parsed.
+ *
+ * @since 6.2.0
+ *
+ * @return void
+ */
+ private function after_tag() {
+ $this->class_name_updates_to_attributes_updates();
+ $this->apply_attributes_updates();
+ $this->tag_name_starts_at = null;
+ $this->tag_name_length = null;
+ $this->tag_ends_at = null;
+ $this->is_closing_tag = null;
+ $this->attributes = array();
+ }
+
+ /**
+ * Converts class name updates into tag attributes updates
+ * (they are accumulated in different data formats for performance).
+ *
+ * This method is only meant to run right before the attribute updates are applied.
+ * The behavior in all other cases is undefined.
+ *
+ * @return void
+ * @since 6.2.0
+ *
+ * @see $classname_updates
+ * @see $attribute_updates
+ */
+ private function class_name_updates_to_attributes_updates() {
+ if ( count( $this->classname_updates ) === 0 || isset( $this->attribute_updates['class'] ) ) {
+ $this->classname_updates = array();
+ return;
+ }
+
+ $existing_class = isset( $this->attributes['class'] )
+ ? substr( $this->html, $this->attributes['class']->value_starts_at, $this->attributes['class']->value_length )
+ : '';
+
+ /**
+ * Updated "class" attribute value.
+ *
+ * This is incrementally built as we scan through the existing class
+ * attribute, omitting removed classes as we do so, and then appending
+ * added classes at the end. Only when we're done processing will the
+ * value contain the final new value.
+
+ * @var string
+ */
+ $class = '';
+
+ /**
+ * Tracks the cursor position in the existing class
+ * attribute value where we're currently parsing.
+ *
+ * @var integer
+ */
+ $at = 0;
+
+ /**
+ * Indicates if we have made any actual modifications to the existing
+ * class attribute value, used to short-circuit string copying.
+ *
+ * It's possible that we are intending to remove certain classes and
+ * add others in such a way that we don't modify the existing value
+ * because calls to `add_class()` and `remove_class()` occur
+ * independent of the input values sent to the WP_HTML_Tag_Processor. That is, we
+ * might call `remove_class()` for a class that isn't already present
+ * and we might call `add_class()` for one that is, in which case we
+ * wouldn't need to break apart the string and rebuild it.
+ *
+ * This flag is set upon the first change that requires a string update.
+ *
+ * @var boolean
+ */
+ $modified = false;
+
+ // Remove unwanted classes by only copying the new ones.
+ $existing_class_length = strlen( $existing_class );
+ while ( $at < $existing_class_length ) {
+ // Skip to the first non-whitespace character.
+ $ws_at = $at;
+ $ws_length = strspn( $existing_class, " \t\f\r\n", $ws_at );
+ $at += $ws_length;
+
+ // Capture the class name – it's everything until the next whitespace.
+ $name_length = strcspn( $existing_class, " \t\f\r\n", $at );
+ if ( 0 === $name_length ) {
+ // We're done, no more class names.
+ break;
+ }
+
+ $name = substr( $existing_class, $at, $name_length );
+ $at += $name_length;
+
+ // If this class is marked for removal, start processing the next one.
+ $remove_class = (
+ isset( $this->classname_updates[ $name ] ) &&
+ self::REMOVE_CLASS === $this->classname_updates[ $name ]
+ );
+
+ // Once we've seen a class, we should never add it again.
+ if ( ! $remove_class ) {
+ $this->classname_updates[ $name ] = self::SKIP_CLASS;
+ }
+
+ if ( $remove_class ) {
+ $modified = true;
+ continue;
+ }
+
+ /*
+ * Otherwise, append it to the new "class" attribute value.
+ *
+ * By preserving the existing whitespace instead of only adding a single
+ * space (which is a valid transformation we can make) we'll introduce
+ * fewer changes to the HTML content and hopefully make comparing
+ * before/after easier for people trying to debug the modified output.
+ */
+ $class .= substr( $existing_class, $ws_at, $ws_length );
+ $class .= $name;
+ }
+
+ // Add new classes by appending the ones we haven't already seen.
+ foreach ( $this->classname_updates as $name => $operation ) {
+ if ( self::ADD_CLASS === $operation ) {
+ $modified = true;
+
+ $class .= strlen( $class ) > 0 ? ' ' : '';
+ $class .= $name;
+ }
+ }
+
+ $this->classname_updates = array();
+ if ( ! $modified ) {
+ return;
+ }
+
+ if ( strlen( $class ) > 0 ) {
+ $this->set_attribute( 'class', $class );
+ } else {
+ $this->remove_attribute( 'class' );
+ }
+ }
+
+ /**
+ * Applies updates to attributes.
+ *
+ * @since 6.2.0
+ */
+ private function apply_attributes_updates() {
+ if ( ! count( $this->attribute_updates ) ) {
+ return;
+ }
+
+ /**
+ * Attribute updates can be enqueued in any order but as we
+ * progress through the document to replace them we have to
+ * make our replacements in the order in which they are found
+ * in that document.
+ *
+ * Sorting the updates ensures we don't make our replacements
+ * out of order, which could otherwise lead to mangled output,
+ * partially-duplicate attributes, and overwritten attributes.
+ */
+ usort( $this->attribute_updates, array( self::class, 'sort_start_ascending' ) );
+
+ foreach ( $this->attribute_updates as $diff ) {
+ $this->updated_html .= substr( $this->html, $this->updated_bytes, $diff->start - $this->updated_bytes );
+ $this->updated_html .= $diff->text;
+ $this->updated_bytes = $diff->end;
+ }
+
+ foreach ( $this->bookmarks as $bookmark ) {
+ /**
+ * As we loop through $this->attribute_updates, we keep comparing
+ * $bookmark->start and $bookmark->end to $diff->start. We can't
+ * change it and still expect the correct result, so let's accumulate
+ * the deltas separately and apply them all at once after the loop.
+ */
+ $head_delta = 0;
+ $tail_delta = 0;
+
+ foreach ( $this->attribute_updates as $diff ) {
+ $update_head = $bookmark->start >= $diff->start;
+ $update_tail = $bookmark->end >= $diff->start;
+
+ if ( ! $update_head && ! $update_tail ) {
+ break;
+ }
+
+ $delta = strlen( $diff->text ) - ( $diff->end - $diff->start );
+
+ if ( $update_head ) {
+ $head_delta += $delta;
+ }
+
+ if ( $update_tail ) {
+ $tail_delta += $delta;
+ }
+ }
+
+ $bookmark->start += $head_delta;
+ $bookmark->end += $tail_delta;
+ }
+
+ $this->attribute_updates = array();
+ }
+
+ /**
+ * Move the current pointer in the Tag Processor to a given bookmark's location.
+ *
+ * In order to prevent accidental infinite loops, there's a
+ * maximum limit on the number of times seek() can be called.
+ *
+ * @param string $bookmark_name Jump to the place in the document identified by this bookmark name.
+ * @return bool
+ * @throws Exception Throws on invalid bookmark name if WP_DEBUG set.
+ */
+ public function seek( $bookmark_name ) {
+ if ( ! array_key_exists( $bookmark_name, $this->bookmarks ) ) {
+ if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
+ throw new Exception( 'Invalid bookmark name' );
+ }
+ return false;
+ }
+
+ if ( ++$this->seek_count > self::MAX_SEEK_OPS ) {
+ if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
+ throw new Exception( 'Too many calls to seek() - this can lead to performance issues.' );
+ }
+ return false;
+ }
+
+ // Flush out any pending updates to the document.
+ $this->get_updated_html();
+
+ // Point this tag processor before the sought tag opener and consume it.
+ $this->parsed_bytes = $this->bookmarks[ $bookmark_name ]->start;
+ $this->updated_bytes = $this->parsed_bytes;
+ $this->updated_html = substr( $this->html, 0, $this->updated_bytes );
+ return $this->next_tag();
+ }
+
+ /**
+ * Sort function to arrange objects with a start property in ascending order.
+ *
+ * @since 6.2.0
+ *
+ * @param object $a First attribute update.
+ * @param object $b Second attribute update.
+ * @return integer
+ */
+ private static function sort_start_ascending( $a, $b ) {
+ return $a->start - $b->start;
+ }
+
+ /**
+ * Returns the value of the parsed attribute in the currently-opened tag.
+ *
+ * Example:
+ *
+ * $p = new WP_HTML_Tag_Processor( 'Test' );
+ * $p->next_tag( [ 'class_name' => 'test' ] ) === true;
+ * $p->get_attribute( 'data-test-id' ) === '14';
+ * $p->get_attribute( 'enabled' ) === true;
+ * $p->get_attribute( 'aria-label' ) === null;
+ *
+ * $p->next_tag( [] ) === false;
+ * $p->get_attribute( 'class' ) === null;
+ *
+ *
+ * @since 6.2.0
+ *
+ * @param string $name Name of attribute whose value is requested.
+ * @return string|true|null Value of attribute or `null` if not available.
+ * Boolean attributes return `true`.
+ */
+ public function get_attribute( $name ) {
+ if ( null === $this->tag_name_starts_at ) {
+ return null;
+ }
+
+ $comparable = strtolower( $name );
+ if ( ! isset( $this->attributes[ $comparable ] ) ) {
+ return null;
+ }
+
+ $attribute = $this->attributes[ $comparable ];
+
+ if ( true === $attribute->is_true ) {
+ return true;
+ }
+
+ $raw_value = substr( $this->html, $attribute->value_starts_at, $attribute->value_length );
+
+ return html_entity_decode( $raw_value );
+ }
+
+ /**
+ * Returns parsed attributes matching a given prefix in the currently-opened tag.
+ *
+ * Example:
+ *
+ * $p = new WP_HTML_Tag_Processor( 'Test' );
+ * $p->next_tag( [ 'class_name' => 'test' ] ) === true;
+ * $p->get_attributes_by_prefix( 'data-' ) === array( 'data-enabled' => true, 'data-test-id' => '14' );
+ *
+ * $p->next_tag( [] ) === false;
+ * $p->get_attributes_by_prefix( 'data' ) === null;
+ *
+ *
+ * @since 6.2.0
+ *
+ * @param string $prefix Prefix of attributes whose value is requested.
+ * @return array|null Associative array of matching attribute names and values, or `null` if not at a tag.
+ * Boolean attributes map to `true`.
+ */
+ function get_attributes_by_prefix( $prefix ) {
+ if ( null === $this->tag_name_starts_at ) {
+ return null;
+ }
+
+ $comparable = strtolower( $prefix );
+ $matches = array_filter(
+ array_keys( $this->attributes ),
+ function( $attr ) use ( $comparable ) {
+ return str_starts_with( $attr, $comparable );
+ }
+ );
+
+ $results = array();
+ foreach ( $matches as $attr ) {
+ $results[ $attr ] = $this->get_attribute( $attr );
+ }
+ return $results;
+ }
+
+ /**
+ * Returns the lowercase name of the currently-opened tag.
+ *
+ * Example:
+ *
+ * $p = new WP_HTML_Tag_Processor( 'Test' );
+ * $p->next_tag( [] ) === true;
+ * $p->get_tag() === 'DIV';
+ *
+ * $p->next_tag( [] ) === false;
+ * $p->get_tag() === null;
+ *
+ *
+ * @since 6.2.0
+ *
+ * @return string|null Name of current tag in input HTML, or `null` if none currently open.
+ */
+ public function get_tag() {
+ if ( null === $this->tag_name_starts_at ) {
+ return null;
+ }
+
+ $tag_name = substr( $this->html, $this->tag_name_starts_at, $this->tag_name_length );
+
+ return strtoupper( $tag_name );
+ }
+
+ /**
+ * Indicates if the current tag token is a tag closer.
+ *
+ * Example:
+ *
+ * $p = new WP_HTML_Tag_Processor( '' );
+ * $p->next_tag( [ 'tag_name' => 'div', 'tag_closers' => 'visit' ] );
+ * $p->is_tag_closer() === false;
+ *
+ * $p->next_tag( [ 'tag_name' => 'div', 'tag_closers' => 'visit' ] );
+ * $p->is_tag_closer() === true;
+ *
+ *
+ * @return bool
+ */
+ public function is_tag_closer() {
+ return $this->is_closing_tag;
+ }
+
+ /**
+ * Updates or creates a new attribute on the currently matched tag with the value passed.
+ *
+ * For boolean attributes special handling is provided:
+ * - When `true` is passed as the value, then only the attribute name is added to the tag.
+ * - When `false` is passed, the attribute gets removed if it existed before.
+ *
+ * For string attributes, the value is escaped using the `esc_attr` function.
+ *
+ * @since 6.2.0
+ *
+ * @param string $name The attribute name to target.
+ * @param string|boolean $value The new attribute value.
+ * @throws Exception When WP_DEBUG is true and the attribute name is invalid.
+ */
+ public function set_attribute( $name, $value ) {
+ if ( $this->is_closing_tag || null === $this->tag_name_starts_at ) {
+ return false;
+ }
+
+ /*
+ * Verify that the attribute name is allowable. In WP_DEBUG
+ * environments we want to crash quickly to alert developers
+ * of typos and issues; but in production we don't want to
+ * interrupt a normal page view, so we'll silently avoid
+ * updating the attribute in those cases.
+ *
+ * Of note, we're disallowing more characters than are strictly
+ * forbidden in HTML5. This is to prevent additional security
+ * risks deeper in the WordPress and plugin stack. Specifically
+ * we reject the less-than (<) greater-than (>) and ampersand (&).
+ *
+ * The use of a PCRE match allows us to look for specific Unicode
+ * code points without writing a UTF-8 decoder. Whereas scanning
+ * for one-byte characters is trivial (with `strcspn`), scanning
+ * for the longer byte sequences would be more complicated, and
+ * this shouldn't be in the hot path for execution so we can
+ * compromise on the efficiency at this point.
+ *
+ * @see https://html.spec.whatwg.org/#attributes-2
+ */
+ if ( preg_match(
+ '~[' .
+ // Syntax-like characters.
+ '"\'>& =' .
+ // Control characters.
+ '\x{00}-\x{1F}' .
+ // HTML noncharacters.
+ '\x{FDD0}-\x{FDEF}' .
+ '\x{FFFE}\x{FFFF}\x{1FFFE}\x{1FFFF}\x{2FFFE}\x{2FFFF}\x{3FFFE}\x{3FFFF}' .
+ '\x{4FFFE}\x{4FFFF}\x{5FFFE}\x{5FFFF}\x{6FFFE}\x{6FFFF}\x{7FFFE}\x{7FFFF}' .
+ '\x{8FFFE}\x{8FFFF}\x{9FFFE}\x{9FFFF}\x{AFFFE}\x{AFFFF}\x{BFFFE}\x{BFFFF}' .
+ '\x{CFFFE}\x{CFFFF}\x{DFFFE}\x{DFFFF}\x{EFFFE}\x{EFFFF}\x{FFFFE}\x{FFFFF}' .
+ '\x{10FFFE}\x{10FFFF}' .
+ ']~Ssu',
+ $name
+ ) ) {
+ if ( WP_DEBUG ) {
+ throw new Exception( 'Invalid attribute name' );
+ }
+
+ return;
+ }
+
+ /*
+ * > The values "true" and "false" are not allowed on boolean attributes.
+ * > To represent a false value, the attribute has to be omitted altogether.
+ * - HTML5 spec, https://html.spec.whatwg.org/#boolean-attributes
+ */
+ if ( false === $value ) {
+ $this->remove_attribute( $name );
+ return;
+ }
+
+ if ( true === $value ) {
+ $updated_attribute = $name;
+ } else {
+ $escaped_new_value = esc_attr( $value );
+ $updated_attribute = "{$name}=\"{$escaped_new_value}\"";
+ }
+
+ if ( isset( $this->attributes[ $name ] ) ) {
+ /*
+ * Update an existing attribute.
+ *
+ * Example – set attribute id to "new" in :
+ *
+ * ^-------------^
+ * start end
+ * replacement: `id="new"`
+ *
+ * Result:
+ */
+ $existing_attribute = $this->attributes[ $name ];
+ $this->attribute_updates[ $name ] = new WP_HTML_Text_Replacement(
+ $existing_attribute->start,
+ $existing_attribute->end,
+ $updated_attribute
+ );
+ } else {
+ /*
+ * Create a new attribute at the tag's name end.
+ *
+ * Example – add attribute id="new" to :
+ *
+ * ^
+ * start and end
+ * replacement: ` id="new"`
+ *
+ * Result:
+ */
+ $this->attribute_updates[ $name ] = new WP_HTML_Text_Replacement(
+ $this->tag_name_starts_at + $this->tag_name_length,
+ $this->tag_name_starts_at + $this->tag_name_length,
+ ' ' . $updated_attribute
+ );
+ }
+ }
+
+ /**
+ * Removes an attribute of the currently matched tag.
+ *
+ * @since 6.2.0
+ *
+ * @param string $name The attribute name to remove.
+ */
+ public function remove_attribute( $name ) {
+ if ( $this->is_closing_tag || ! isset( $this->attributes[ $name ] ) ) {
+ return false;
+ }
+
+ /*
+ * Removes an existing tag attribute.
+ *
+ * Example – remove the attribute id from :
+ *
+ * ^-------------^
+ * start end
+ * replacement: ``
+ *
+ * Result:
+ */
+ $this->attribute_updates[ $name ] = new WP_HTML_Text_Replacement(
+ $this->attributes[ $name ]->start,
+ $this->attributes[ $name ]->end,
+ ''
+ );
+ }
+
+ /**
+ * Adds a new class name to the currently matched tag.
+ *
+ * @since 6.2.0
+ *
+ * @param string $class_name The class name to add.
+ */
+ public function add_class( $class_name ) {
+ if ( $this->is_closing_tag ) {
+ return false;
+ }
+
+ if ( null !== $this->tag_name_starts_at ) {
+ $this->classname_updates[ $class_name ] = self::ADD_CLASS;
+ }
+ }
+
+ /**
+ * Removes a class name from the currently matched tag.
+ *
+ * @since 6.2.0
+ *
+ * @param string $class_name The class name to remove.
+ */
+ public function remove_class( $class_name ) {
+ if ( $this->is_closing_tag ) {
+ return false;
+ }
+
+ if ( null !== $this->tag_name_starts_at ) {
+ $this->classname_updates[ $class_name ] = self::REMOVE_CLASS;
+ }
+ }
+
+ /**
+ * Returns the string representation of the HTML Tag Processor.
+ *
+ * @since 6.2.0
+ * @see get_updated_html
+ *
+ * @return string The processed HTML.
+ */
+ public function __toString() {
+ return $this->get_updated_html();
+ }
+
+ /**
+ * Returns the string representation of the HTML Tag Processor.
+ *
+ * @since 6.2.0
+ *
+ * @return string The processed HTML.
+ */
+ public function get_updated_html() {
+ // Short-circuit if there are no new updates to apply.
+ if ( ! count( $this->classname_updates ) && ! count( $this->attribute_updates ) ) {
+ return $this->updated_html . substr( $this->html, $this->updated_bytes );
+ }
+
+ // Otherwise: apply the updates, rewind before the current tag, and parse it again.
+ $delta_between_updated_html_end_and_current_tag_end = substr(
+ $this->html,
+ $this->updated_bytes,
+ $this->tag_name_starts_at + $this->tag_name_length - $this->updated_bytes
+ );
+ $updated_html_up_to_current_tag_name_end = $this->updated_html . $delta_between_updated_html_end_and_current_tag_end;
+
+ // 1. Apply the attributes updates to the original HTML
+ $this->class_name_updates_to_attributes_updates();
+ $this->apply_attributes_updates();
+
+ // 2. Replace the original HTML with the updated HTML
+ $this->html = $this->updated_html . substr( $this->html, $this->updated_bytes );
+ $this->updated_html = $updated_html_up_to_current_tag_name_end;
+ $this->updated_bytes = strlen( $this->updated_html );
+
+ // 3. Point this tag processor at the original tag opener and consume it
+ $this->parsed_bytes = strlen( $updated_html_up_to_current_tag_name_end ) - $this->tag_name_length - 2;
+ $this->next_tag();
+
+ return $this->html;
+ }
+
+ /**
+ * Prepares tag search criteria from input interface.
+ *
+ * @since 6.2.0
+ *
+ * @param array|string $query {
+ * Which tag name to find, having which class.
+ *
+ * @type string|null $tag_name Which tag to find, or `null` for "any tag."
+ * @type string|null $class_name Tag must contain this class name to match.
+ * @type string $tag_closers "visit" or "skip": whether to stop on tag closers, e.g.
.
+ * }
+ */
+ protected function parse_query( $query ) {
+ if ( null !== $query && $query === $this->last_query ) {
+ return;
+ }
+
+ $this->last_query = $query;
+ $this->sought_tag_name = null;
+ $this->sought_class_name = null;
+ $this->sought_match_offset = 1;
+ $this->stop_on_tag_closers = false;
+
+ // A single string value means "find the tag of this name".
+ if ( is_string( $query ) ) {
+ $this->sought_tag_name = $query;
+ return;
+ }
+
+ // If not using the string interface we have to pass an associative array.
+ if ( ! is_array( $query ) ) {
+ return;
+ }
+
+ if ( isset( $query['tag_name'] ) && is_string( $query['tag_name'] ) ) {
+ $this->sought_tag_name = $query['tag_name'];
+ }
+
+ if ( isset( $query['class_name'] ) && is_string( $query['class_name'] ) ) {
+ $this->sought_class_name = $query['class_name'];
+ }
+
+ if ( isset( $query['match_offset'] ) && is_int( $query['match_offset'] ) && 0 < $query['match_offset'] ) {
+ $this->sought_match_offset = $query['match_offset'];
+ }
+
+ if ( isset( $query['tag_closers'] ) ) {
+ $this->stop_on_tag_closers = 'visit' === $query['tag_closers'];
+ }
+ }
+
+
+ /**
+ * Checks whether a given tag and its attributes match the search criteria.
+ *
+ * @since 6.2.0
+ *
+ * @return boolean
+ */
+ protected function matches() {
+ if ( $this->is_closing_tag && ! $this->stop_on_tag_closers ) {
+ return false;
+ }
+
+ // Do we match a case-insensitive HTML tag name?
+ if ( null !== $this->sought_tag_name ) {
+ /*
+ * String (byte) length lookup is fast. If they aren't the
+ * same length then they can't be the same string values.
+ */
+ if ( strlen( $this->sought_tag_name ) !== $this->tag_name_length ) {
+ return false;
+ }
+
+ /*
+ * Otherwise we have to check for each character if they
+ * are the same, and only `strtoupper()` if we have to.
+ * Presuming that most people will supply lowercase tag
+ * names and most HTML will contain lowercase tag names,
+ * most of the time this runs we shouldn't expect to
+ * actually run the case-folding comparison.
+ */
+ for ( $i = 0; $i < $this->tag_name_length; $i++ ) {
+ $html_char = $this->html[ $this->tag_name_starts_at + $i ];
+ $tag_char = $this->sought_tag_name[ $i ];
+
+ if ( $html_char !== $tag_char && strtoupper( $html_char ) !== $tag_char ) {
+ return false;
+ }
+ }
+ }
+
+ $needs_class_name = null !== $this->sought_class_name;
+
+ if ( $needs_class_name && ! isset( $this->attributes['class'] ) ) {
+ return false;
+ }
+
+ // Do we match a byte-for-byte (case-sensitive and encoding-form-sensitive) class name?
+ if ( $needs_class_name ) {
+ $class_start = $this->attributes['class']->value_starts_at;
+ $class_end = $class_start + $this->attributes['class']->value_length;
+ $class_at = $class_start;
+
+ /*
+ * We're going to have to jump through potential matches here because
+ * it's possible that we have classes containing the class name we're
+ * looking for. For instance, if we are looking for "even" we don't
+ * want to be confused when we come to the class "not-even." This is
+ * secured by ensuring that we find our sought-after class and that
+ * it's surrounded on both sides by proper boundaries.
+ *
+ * See https://html.spec.whatwg.org/#attributes-3
+ * See https://html.spec.whatwg.org/#space-separated-tokens
+ */
+ while (
+ // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
+ false !== ( $class_at = strpos( $this->html, $this->sought_class_name, $class_at ) ) &&
+ $class_at < $class_end
+ ) {
+ /*
+ * Verify this class starts at a boundary. If it were at 0 we'd be at
+ * the start of the string and that would be fine, otherwise we have
+ * to start at a place where the preceding character is whitespace.
+ */
+ if ( $class_at > $class_start ) {
+ $character = $this->html[ $class_at - 1 ];
+
+ if ( ' ' !== $character && "\t" !== $character && "\f" !== $character && "\r" !== $character && "\n" !== $character ) {
+ $class_at += strlen( $this->sought_class_name );
+ continue;
+ }
+ }
+
+ /*
+ * Similarly, verify this class ends at a boundary as well. Here we
+ * can end at the very end of the string value, otherwise we have
+ * to end at a place where the next character is whitespace.
+ */
+ if ( $class_at + strlen( $this->sought_class_name ) < $class_end ) {
+ $character = $this->html[ $class_at + strlen( $this->sought_class_name ) ];
+
+ if ( ' ' !== $character && "\t" !== $character && "\f" !== $character && "\r" !== $character && "\n" !== $character ) {
+ $class_at += strlen( $this->sought_class_name );
+ continue;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/src/html/class-wp-html-text-replacement.php b/src/html/class-wp-html-text-replacement.php
new file mode 100644
index 00000000..e3ada169
--- /dev/null
+++ b/src/html/class-wp-html-text-replacement.php
@@ -0,0 +1,59 @@
+start = $start;
+ $this->end = $end;
+ $this->text = $text;
+ }
+}
diff --git a/src/html/index.php b/src/html/index.php
new file mode 100644
index 00000000..d952f640
--- /dev/null
+++ b/src/html/index.php
@@ -0,0 +1,13 @@
+ {
- let result = '[';
- Object.entries(props).forEach(([key, value]) => {
- result += `["${key}", "${value}"]`;
- });
- result += ']';
- return result;
-};
-
-const start = async () => {
- const php = await readFile('./src' + file, {
- encoding: 'utf8',
- });
- const output = await transform(php, {
- components: {
- 'wp-show': (props, children) =>
- html`
- ${children}
- `,
- },
- });
- await writeFile('./build' + file, output);
- console.log('done!');
-};
-
-start();
diff --git a/vendor/.gitignore b/vendor/.gitignore
new file mode 100644
index 00000000..c96a04f0
--- /dev/null
+++ b/vendor/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
\ No newline at end of file
diff --git a/wp-directives.php b/wp-directives.php
index 60fb3e11..7193258d 100644
--- a/wp-directives.php
+++ b/wp-directives.php
@@ -10,6 +10,11 @@
* Text Domain: wp-directives
*/
+require_once __DIR__ . '/src/html/index.php';
+
+require_once __DIR__ . '/src/directives/wp-context.php';
+require_once __DIR__ . '/src/directives/wp-show.php';
+
function wp_directives_loader()
{
// Load the Admin page.
@@ -142,65 +147,50 @@ function wp_directives_client_site_transitions_option()
* SSR in PHP
*/
-// wp-show
-function wpx_show_open_tag($prop_entries)
-{
- global $wpx;
- $props = wpx_prop_entries_to_array($prop_entries);
- $attributes = wpx_prop_entries_to_attributes($prop_entries);
- $value = wpx_get_state($props['when']);
- if ($value) {
- echo "";
- } else {
- echo "";
- }
-}
-
-function wpx_show_close_tag($prop_entries)
-{
- global $wpx;
- $props = wpx_prop_entries_to_array($prop_entries);
- $value = wpx_get_state($props['when']);
- if ($value) {
- echo ' ';
- } else {
- echo ' ';
- }
-}
-
// Utils
$GLOBALS['wpx'] = [];
function wpx($data)
{
global $wpx;
- $wpx = array_merge_recursive($wpx, $data);
+ $wpx = array_replace_recursive($wpx, $data);
}
-function wpx_get_state($path)
-{
- global $wpx;
- $current = $wpx;
- $array = explode('.', $path);
- foreach ($array as $p) {
- $current = $current[$p];
- }
- return $current;
-}
+add_filter(
+ 'render_block',
+ 'wp_process_directives',
+ 10,
+ 3
+);
-function wpx_prop_entries_to_array($prop_entries)
-{
- $array = [];
- foreach ($prop_entries as list($key, $value)) {
- $array[$key] = $value;
+function wp_process_directives( $block_content, $block, $instance ) {
+ // TODO: Add some directive/components registration mechanism.
+ $directives = array(
+ 'wp-context' => 'process_wp_context',
+ 'wp-show' => 'process_wp_show',
+ );
+
+ $tags = new WP_HTML_Processor( $block_content );
+
+ $context = new WP_Directive_Context;
+ while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
+ $tag_name = strtolower( $tags->get_tag() );
+ if ( array_key_exists( $tag_name, $directives ) ) {
+ call_user_func_array( $directives[$tag_name], array( &$tags, &$context ) );
+ } else {
+ // Components can't have directives (unless we change our mind about this).
+ foreach ( $directives as $directive => $directive_processor ) {
+ $attributes = $tags->get_attributes_by_prefix( $directive );
+ if ( empty( $attributes ) ) {
+ continue;
+ }
+
+ call_user_func_array( $directive_processor, array( &$tags, &$context ) );
+ }
+ }
}
- return $array;
-}
-function wpx_prop_entries_to_attributes($prop_entries)
-{
- $attributes = '';
- foreach ($prop_entries as list($key, $value)) {
- $attributes .= "$key='$value'";
+ if ( ! empty( $context->get_context() ) ) {
+ return '' . $block_content;
}
- return $attributes;
+ return $block_content;
}