diff --git a/.ddev/commands/playwright/playwright b/.ddev/commands/playwright/playwright new file mode 100755 index 0000000..b2e9dcc --- /dev/null +++ b/.ddev/commands/playwright/playwright @@ -0,0 +1,14 @@ +#!/bin/bash +#ddev-generated +# Remove the line above if you don't want this file to be overwritten when you run +# ddev get julienloizelet/ddev-playwright +# +# This file comes from https://github.com/julienloizelet/ddev-playwright +# +cd /var/www/html || exit 1 +cd "${PLAYWRIGHT_TEST_DIR}" || exit 1 + +export PLAYWRIGHT_BROWSERS_PATH=0 +PRE="sudo -u pwuser PLAYWRIGHT_BROWSERS_PATH=0 " + +$PRE yarn playwright "$@" diff --git a/.ddev/commands/playwright/playwright-install b/.ddev/commands/playwright/playwright-install new file mode 100755 index 0000000..b4165b9 --- /dev/null +++ b/.ddev/commands/playwright/playwright-install @@ -0,0 +1,17 @@ +#!/bin/bash +#ddev-generated +# Remove the line above if you don't want this file to be overwritten when you run +# ddev get julienloizelet/ddev-playwright +# +# This file comes from https://github.com/julienloizelet/ddev-playwright +# +cd /var/www/html || exit 1 +cd "${PLAYWRIGHT_TEST_DIR}" || exit 1 + +export PLAYWRIGHT_BROWSERS_PATH=0 +PRE="sudo -u pwuser PLAYWRIGHT_BROWSERS_PATH=0 " + +$PRE yarn install +$PRE yarn playwright install --with-deps +# Conditionally copy an .env file if an example file exists +[ -f .env.example ] && [ ! -f .env ] && $PRE cp -n .env.example .env; exit 0 diff --git a/.ddev/commands/web/orchestrate b/.ddev/commands/web/orchestrate index a7e0865..cc9a96f 100755 --- a/.ddev/commands/web/orchestrate +++ b/.ddev/commands/web/orchestrate @@ -5,7 +5,8 @@ mkdir -p "${DDEV_DOCROOT}" pushd "${DDEV_DOCROOT}" -PLUGIN_FOLDER="${DDEV_DOCROOT}/wp-content/mu-plugins/${PLUGIN_NAME:-$DDEV_PROJECT}" +MUPLUGIN_FOLDER="${DDEV_DOCROOT}/wp-content/mu-plugins/${PLUGIN_NAME:-$DDEV_PROJECT}" +TEST_PLUGIN_FOLDER="${DDEV_DOCROOT}/wp-content/plugins/wp-stash-test-plugin" VALID_ARGS=$(getopt -o fp: --long force,plugin: -- "$@") if [[ $? -ne 0 ]]; then exit 1; @@ -19,7 +20,7 @@ while [ : ]; do shift export RECREATE_ENV=1; popd - find "${DDEV_DOCROOT}" -mindepth 1 ! -regex "^${PLUGIN_FOLDER}\(/.*\)?" -delete + find "${DDEV_DOCROOT}" -mindepth 1 ! -regex "^${MUPLUGIN_FOLDER}\(/.*\)?" ! -regex "^${TEST_PLUGIN_FOLDER}\(/.*\)?" -delete pushd "${DDEV_DOCROOT}" ;; --) shift; diff --git a/.ddev/commands/web/orchestrate.d/35_activate_test_plugin.sh b/.ddev/commands/web/orchestrate.d/35_activate_test_plugin.sh new file mode 100644 index 0000000..574ae8a --- /dev/null +++ b/.ddev/commands/web/orchestrate.d/35_activate_test_plugin.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +pushd "${DDEV_DOCROOT}" || exit + +flags="" +if [ "${WP_MULTISITE}" = "true" ]; then + flags+=" --network" +fi + +wp plugin activate wp-stash-test-plugin $flags + +popd diff --git a/.ddev/config.yaml b/.ddev/config.yaml index f5d52c7..4260f1b 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -5,7 +5,7 @@ php_version: "8.0" webserver_type: nginx-fpm router_http_port: "80" router_https_port: "443" -xdebug_enabled: true +xdebug_enabled: false additional_hostnames: [] additional_fqdns: [] database: diff --git a/.ddev/docker-compose.playwright.yaml b/.ddev/docker-compose.playwright.yaml new file mode 100644 index 0000000..05906fa --- /dev/null +++ b/.ddev/docker-compose.playwright.yaml @@ -0,0 +1,38 @@ +#ddev-generated +# Remove the line above if you don't want this file to be overwritten when you run +# ddev get julienloizelet/ddev-playwright +# +# This file comes from https://github.com/julienloizelet/ddev-playwright +# +services: + playwright: + build: + context: playwright-build + container_name: ddev-${DDEV_SITENAME}-playwright + hostname: ${DDEV_SITENAME}-playwright + # These labels ensure this service is discoverable by ddev. + labels: + com.ddev.site-name: ${DDEV_SITENAME} + com.ddev.approot: $DDEV_APPROOT + environment: + # Modify the PLAYWRIGHT_TEST_DIR folder path to suit your needs + - PLAYWRIGHT_TEST_DIR=tests/Playwright + - NETWORK_IFACE=eth0 + - DISPLAY=:1 + - VIRTUAL_HOST=$DDEV_HOSTNAME + - HTTP_EXPOSE=8443:8444,9322:9323 + - HTTPS_EXPOSE=8444:8444,9323:9323 + - DDEV_UID=${DDEV_UID} + - DDEV_GID=${DDEV_GID} + expose: + - "8444" + - "9323" + depends_on: + - web + volumes: + - .:/mnt/ddev_config + - ddev-global-cache:/mnt/ddev-global-cache + - ../:/var/www/html:rw + external_links: + - ddev-router:${DDEV_HOSTNAME} + working_dir: /var/www/html diff --git a/.ddev/playwright-build/Dockerfile b/.ddev/playwright-build/Dockerfile new file mode 100644 index 0000000..33c3b5b --- /dev/null +++ b/.ddev/playwright-build/Dockerfile @@ -0,0 +1,57 @@ +#ddev-generated +# Remove the line above if you don't want this file to be overwritten when you run +# ddev get julienloizelet/ddev-playwright +# +# This file comes from https://github.com/julienloizelet/ddev-playwright +# +# If on arm64 machine, edit to use mcr.microsoft.com/playwright:focal-arm64 +FROM mcr.microsoft.com/playwright:focal + +# Debian images by default disable apt caching, so turn it on until we finish +# the build. +RUN mv /etc/apt/apt.conf.d/docker-clean /etc/apt/docker-clean-disabled + +USER root + +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update \ + && apt-get install -y sudo libnss3-tools + +# Give the pwuser user full `sudo` privileges +RUN echo "pwuser ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/pwuser \ + && chmod 0440 /etc/sudoers.d/pwuser + +# CAROOT for `mkcert` to use, has the CA config +ENV CAROOT=/mnt/ddev-global-cache/mkcert + +# Install the correct architecture binary of `mkcert` +RUN export TARGETPLATFORM=linux/$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') && mkdir -p /usr/local/bin && curl --fail -JL -s -o /usr/local/bin/mkcert "https://dl.filippo.io/mkcert/latest?for=${TARGETPLATFORM}" +RUN chmod +x /usr/local/bin/mkcert + + +# Install a window manager. +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update \ + && apt-get install -y icewm xauth + +# Install kasmvnc for remote access. +RUN /bin/bash -c 'if [ $(arch) == "aarch64" ]; then KASM_ARCH=arm64; else KASM_ARCH=amd64; fi; wget https://github.com/kasmtech/KasmVNC/releases/download/v1.1.0/kasmvncserver_bullseye_1.1.0_${KASM_ARCH}.deb' +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get install -y ./kasmvncserver*.deb + +# We're done with apt so disable caching again for the final image. +RUN mv /etc/apt/docker-clean-disabled /etc/apt/apt.conf.d/docker-clean + +# prepare KasmVNC +RUN sudo -u pwuser mkdir /home/pwuser/.vnc +COPY kasmvnc.yaml xstartup /home/pwuser/.vnc/ +RUN chown pwuser:pwuser /home/pwuser/.vnc/* +RUN sudo -u pwuser touch /home/pwuser/.vnc/.de-was-selected +RUN sudo -u pwuser /bin/bash -c 'echo -e "secret\nsecret\n" | kasmvncpasswd -wo -u pwuser' # We actually disable auth, but KASM complains without it + + +COPY entrypoint.sh /root/entrypoint.sh +ENTRYPOINT "/root/entrypoint.sh" diff --git a/.ddev/playwright-build/entrypoint.sh b/.ddev/playwright-build/entrypoint.sh new file mode 100755 index 0000000..00d9bd5 --- /dev/null +++ b/.ddev/playwright-build/entrypoint.sh @@ -0,0 +1,18 @@ +#!/bin/bash +#ddev-generated +# Remove the line above if you don't want this file to be overwritten when you run +# ddev get julienloizelet/ddev-playwright +# +# This file comes from https://github.com/julienloizelet/ddev-playwright +# + +# Change pwuser IDs to the host IDs supplied by DDEV +usermod -u ${DDEV_UID} pwuser +groupmod -g ${DDEV_GID} pwuser +usermod -a -G ssl-cert pwuser + +# Install DDEV certificate +sudo -u pwuser mkcert -install + +# Run CMD from parameters as pwuser +sudo -u pwuser vncserver -fg -disableBasicAuth diff --git a/.ddev/playwright-build/kasmvnc.yaml b/.ddev/playwright-build/kasmvnc.yaml new file mode 100644 index 0000000..c42c849 --- /dev/null +++ b/.ddev/playwright-build/kasmvnc.yaml @@ -0,0 +1,14 @@ +#ddev-generated +# Remove the line above if you don't want this file to be overwritten when you run +# ddev get julienloizelet/ddev-playwright +# +# This file comes from https://github.com/julienloizelet/ddev-playwright +# +logging: + log_writer_name: all + log_dest: syslog + level: 100 + +network: + ssl: + require_ssl: false diff --git a/.ddev/playwright-build/xstartup b/.ddev/playwright-build/xstartup new file mode 100755 index 0000000..7d5896d --- /dev/null +++ b/.ddev/playwright-build/xstartup @@ -0,0 +1,32 @@ +#!/bin/sh +#ddev-generated +# Remove the line above if you don't want this file to be overwritten when you run +# ddev get julienloizelet/ddev-playwright +# +# This file comes from https://github.com/julienloizelet/ddev-playwright +# + +export DISPLAY=:1 + +unset SESSION_MANAGER +unset DBUS_SESSION_BUS_ADDRESS +OS=`uname -s` +if [ $OS = 'Linux' ]; then + case "$WINDOWMANAGER" in + *gnome*) + if [ -e /etc/SuSE-release ]; then + PATH=$PATH:/opt/gnome/bin + export PATH + fi + ;; + esac +fi +if [ -x /etc/X11/xinit/xinitrc ]; then + exec /etc/X11/xinit/xinitrc +fi +if [ -f /etc/X11/xinit/xinitrc ]; then + exec sh /etc/X11/xinit/xinitrc +fi +[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources +xterm -geometry 80x24+10+10 -ls -title "$VNCDESKTOP Desktop" & +icewm-session diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..2f15cb3 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,13 @@ +name: Run Playwright tests via DDEV +on: + push: + workflow_dispatch: +jobs: + ddev-playwright: + uses: inpsyde/reusable-workflows/.github/workflows/ddev-playwright.yml@feature/ddev-playwright + secrets: + COMPOSER_AUTH_JSON: ${{ secrets.PACKAGIST_AUTH_JSON }} + with: + DDEV_ORCHESTRATE_CMD: ddev orchestrate + PLAYWRIGHT_INSTALL_CMD: ddev playwright-install + PLAYWRIGHT_RUN_CMD: ddev playwright test diff --git a/.idea/WP-Stash.iml b/.idea/WP-Stash.iml index bbc8f93..0dc48bc 100644 --- a/.idea/WP-Stash.iml +++ b/.idea/WP-Stash.iml @@ -6,6 +6,7 @@ + @@ -50,6 +51,11 @@ + + + + + diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index f8d235c..68ae064 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -1,13 +1,13 @@ - + mariadb true true DDEV generated data source org.mariadb.jdbc.Driver - jdbc:mariadb://127.0.0.1:32954/db?user=db&password=db + jdbc:mariadb://127.0.0.1:32805/db?user=db&password=db $ProjectFileDir$ diff --git a/.idea/php.xml b/.idea/php.xml index 9221fa7..217cf55 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -69,6 +69,11 @@ + + + + + diff --git a/.idea/phpunit.xml b/.idea/phpunit.xml index 4f8104c..3ae69d7 100644 --- a/.idea/phpunit.xml +++ b/.idea/phpunit.xml @@ -3,6 +3,8 @@ diff --git a/tests/Playwright/.env.example b/tests/Playwright/.env.example new file mode 100644 index 0000000..1c46291 --- /dev/null +++ b/tests/Playwright/.env.example @@ -0,0 +1,3 @@ +BASEURL="https://wp-stash.ddev.site" + +PAGE_URL="/" diff --git a/tests/Playwright/.gitignore b/tests/Playwright/.gitignore new file mode 100644 index 0000000..9554a24 --- /dev/null +++ b/tests/Playwright/.gitignore @@ -0,0 +1,5 @@ +.env +node_modules/ +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/tests/Playwright/package.json b/tests/Playwright/package.json new file mode 100644 index 0000000..fa037dd --- /dev/null +++ b/tests/Playwright/package.json @@ -0,0 +1,11 @@ +{ + "name": "playwright", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "devDependencies": { + "@playwright/test": "^1.34.2", + "dotenv": "^16.0.3" + }, + "scripts": {} +} diff --git a/tests/Playwright/playwright.config.js b/tests/Playwright/playwright.config.js new file mode 100644 index 0000000..2862ddc --- /dev/null +++ b/tests/Playwright/playwright.config.js @@ -0,0 +1,40 @@ +// @ts-check +const { defineConfig, devices } = require('@playwright/test'); + +require('dotenv').config({ path: '.env' }); + +/** + * @see https://playwright.dev/docs/test-configuration + */ +module.exports = defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + //workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [ + [process.env.CI ? 'github' : 'list'], + ['html', {open: 'never'}], + ], + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: process.env.BASEURL, + ignoreHTTPSErrors: true, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + } + ] +}); diff --git a/tests/Playwright/tests/helper-plugin.spec.js b/tests/Playwright/tests/helper-plugin.spec.js new file mode 100644 index 0000000..ff7c108 --- /dev/null +++ b/tests/Playwright/tests/helper-plugin.spec.js @@ -0,0 +1,26 @@ +const {test, expect} = require('@playwright/test'); + +const { + PAGE_URL, +} = process.env; +[ + 'Using object cache', + 'Using WP-Stash', + 'Get unset key', + 'Set single', + 'Delete single', + 'Get unset group', + 'Set multiple', + 'Delete multiple', +].forEach((testName) => { + test(testName, async({page}) => { + await page.goto(PAGE_URL); + + console.log('Opened ' + page.url()) + + const locator = await page.getByText(testName) + + await expect(locator).toHaveClass('pass') + }); + +}) diff --git a/tests/Playwright/yarn.lock b/tests/Playwright/yarn.lock new file mode 100644 index 0000000..a66f5e3 --- /dev/null +++ b/tests/Playwright/yarn.lock @@ -0,0 +1,33 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@playwright/test@^1.34.2": + version "1.35.0" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.35.0.tgz#532603399a0dd46731fbc31a0df5ce357dafa486" + integrity sha512-6qXdd5edCBynOwsz1YcNfgX8tNWeuS9fxy5o59D0rvHXxRtjXRebB4gE4vFVfEMXl/z8zTnAzfOs7aQDEs8G4Q== + dependencies: + "@types/node" "*" + playwright-core "1.35.0" + optionalDependencies: + fsevents "2.3.2" + +"@types/node@*": + version "20.3.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.0.tgz#719498898d5defab83c3560f45d8498f58d11938" + integrity sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ== + +dotenv@^16.0.3: + version "16.1.4" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.1.4.tgz#67ac1a10cd9c25f5ba604e4e08bc77c0ebe0ca8c" + integrity sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw== + +fsevents@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +playwright-core@1.35.0: + version "1.35.0" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.35.0.tgz#b7871b742b4a5c8714b7fa2f570c280a061cb414" + integrity sha512-muMXyPmIx/2DPrCHOD1H1ePT01o7OdKxKj2ebmCAYvqhUy+Y1bpal7B0rdoxros7YrXI294JT/DWw2LqyiqTPA== diff --git a/wp-stash-test-plugin/src/Test.php b/wp-stash-test-plugin/src/Test.php new file mode 100644 index 0000000..384849a --- /dev/null +++ b/wp-stash-test-plugin/src/Test.php @@ -0,0 +1,68 @@ +test = $test; + $this->assertion = $assertion; + $this->summary = $summary; + } + + public function execute() + { + $result = false; + try { + ob_start(); + ($this->assertion)(); + $result = true; + $message = ob_get_clean(); + if (empty($message)) { + $message = 'No details available'; + } + } catch (\Throwable $exception) { + ob_get_clean(); + $message = $exception->getMessage(); + } + ?> + +
+ + test) + ?> + + + +
+ + + + + + + + +

WP Stash

+
+

Environment

+ execute(); + (new \Inpsyde\WpStashTest\Test( + 'Using WP-Stash', + function () { + $stash = WpStash::instance(); + if (!get_class($stash->driver()) === Composite::class) { + throw new Exception("WP Stash does not use the expected driver"); + } + } + ))->execute(); + ?> +
+
+

Single cache entries

+ execute(); + + (new \Inpsyde\WpStashTest\Test( + 'Set single', + function () { + $key = 'wp-stash.single'; + $expectedValue = uniqid(); + wp_cache_set($key, $expectedValue); + $value = wp_cache_get($key); + if (!$expectedValue === $value) { + throw new Exception("Cache does not return the same value"); + } + } + ))->execute(); + + (new \Inpsyde\WpStashTest\Test( + 'Delete single', + function () { + $key = 'wp-stash.single'; + wp_cache_delete($key); + $value = wp_cache_get($key); + if (!empty($value)) { + throw new Exception("Cache not empty after deleting"); + } + } + ))->execute(); + ?> +
+
+

Cache groups

+ execute(); + + (new \Inpsyde\WpStashTest\Test( + 'Set multiple', + function () { + $values = [ + 'foo' => 1, + 'bar' => 2, + 'baz' => 3, + ]; + wp_cache_set_multiple($values); + $results = wp_cache_get_multiple(['foo', 'bar', 'baz']); + echo 'Expecting the cache to contain this exact array:'.PHP_EOL; + var_dump($results); + if (!empty(array_diff_key($values, $results))) { + throw new Exception("There should be different keys"); + } + if (!empty(array_diff($values, $results))) { + throw new Exception("There should be different values"); + } + } + ))->execute(); + + (new \Inpsyde\WpStashTest\Test( + 'Delete multiple', + function () { + $values = [ + 'foo' => 1, + 'bar' => 2, + 'baz' => 3, + ]; + wp_cache_set_multiple($values); + wp_cache_delete_multiple(['foo', 'bar', 'baz']); + $results = wp_cache_get_multiple(['foo', 'bar', 'baz']); + echo 'Expecting all these values to be false:'.PHP_EOL; + var_dump($results); + if (!empty(array_filter($results))) { + throw new Exception("There should be no truthy values here"); + } + } + ))->execute(); + ?> +
+ + + diff --git a/wp-stash-test-plugin/wp-stash-test-plugin.php b/wp-stash-test-plugin/wp-stash-test-plugin.php index d382205..3f616b3 100644 --- a/wp-stash-test-plugin/wp-stash-test-plugin.php +++ b/wp-stash-test-plugin/wp-stash-test-plugin.php @@ -12,78 +12,54 @@ declare(strict_types=1); -namespace Inpsyde\WpStash; +namespace Inpsyde\WpStashTest; +/** + * Super tiny autoloading + */ +spl_autoload_register(static function ($class) { + // project-specific namespace prefix + $prefix = __NAMESPACE__ . '\\'; -function testWpStash() -{ - echo '

Test multi cache

'; - wp_cache_flush_runtime(); - $group = 'wp-stash'; - $multiAdd = [ - 'foo' => 1, - 'bar' => 1, - 'baz' => 1, - ]; - echo '

wp_cache_add_multiple

'; - var_dump($multiAdd); - wp_cache_add_multiple( - $multiAdd, - $group - ); + // does the class use the namespace prefix? + $len = strlen($prefix); + if (strncmp($prefix, $class, strlen($prefix)) !== 0) { + // no, move to the next registered autoloader + return; + } - $result = wp_cache_get_multiple([ - 'foo', - 'bar', - 'baz', - ], - $group - ); - echo '

wp_cache_get_multiple

'; - var_dump($result); - wp_cache_delete_multiple([ - 'foo', - 'bar', - 'baz', - ], - $group - ); + // get the relative class name + $relativeClass = substr($class, $len); - $result = wp_cache_get_multiple([ - 'foo', - 'bar', - 'baz', - ], - $group - ); - echo '

wp_cache_get_multiple (after delete)

'; - var_dump($result); -} + // replace the namespace prefix with the base directory, replace namespace + // separators with directory separators in the relative class name, append + // with .php + $file = __DIR__ . '/src/' . str_replace('\\', '/', $relativeClass) . '.php'; -add_action('plugins_loaded', static function () { - if (is_admin()) { - return; - } - add_action('template_redirect', static function () { - ?> - - - - -
-

WP Stash Test

-
-            
-                
-
- - - - call( + new class ([ + 'foo' => 'bar', + ]) { + public function __construct($data) + { + $this->data = $data; + } + + public function __get($key) + { + return $this->data[$key]; + } + } + ); + exit; +});