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/dropin/object-cache.php b/dropin/object-cache.php
index f6d1ff0..1359ab9 100644
--- a/dropin/object-cache.php
+++ b/dropin/object-cache.php
@@ -46,6 +46,28 @@ function wp_cache_add($key, $data, $group = '', $expire = 0)
return $wp_object_cache->add($key, $data, $group, (int) $expire);
}
+/**
+ * Adds multiple values to the cache in one call.
+ *
+ * @since 6.0.0
+ *
+ * @see WP_Object_Cache::add_multiple()
+ * @global WP_Object_Cache $wp_object_cache Object cache global instance.
+ *
+ * @param array $data Array of keys and values to be set.
+ * @param string $group Optional. Where the cache contents are grouped. Default empty.
+ * @param int $expire Optional. When to expire the cache contents, in seconds.
+ * Default 0 (no expiration).
+ * @return bool[] Array of return values, grouped by key. Each value is either
+ * true on success, or false if cache key and group already exist.
+ */
+function wp_cache_add_multiple( array $data, $group = '', $expire = 0 ) {
+ global $wp_object_cache;
+ assert($wp_object_cache instanceof ObjectCacheProxy);
+
+ return $wp_object_cache->add_multiple( $data, $group, $expire );
+}
+
/**
* Closes the cache.
*
@@ -170,7 +192,7 @@ function wp_cache_incr($key, $offset = 1, $group = '')
*/
function wp_cache_init()
{
- $autoloadFile = __DIR__.'/../vendor/autoload.php';
+ $autoloadFile = __DIR__ . '/../vendor/autoload.php';
if (file_exists($autoloadFile)) {
require_once $autoloadFile;
}
@@ -287,11 +309,6 @@ function wp_cache_add_non_persistent_groups($groups)
function wp_cache_reset()
{
_deprecated_function(__FUNCTION__, '3.5');
-
- global $wp_object_cache;
- assert($wp_object_cache instanceof ObjectCacheProxy);
-
- return $wp_object_cache->reset();
}
/**
@@ -314,3 +331,107 @@ function wp_cache_get_multiple($keys, $group = '', $force = false)
return $wp_object_cache->get_multiple($keys, $group, $force);
}
+
+/**
+ * Sets multiple values to the cache in one call.
+ *
+ * @since 6.0.0
+ *
+ * @see WP_Object_Cache::set_multiple()
+ * @global WP_Object_Cache $wp_object_cache Object cache global instance.
+ *
+ * @param array $data Array of keys and values to be set.
+ * @param string $group Optional. Where the cache contents are grouped. Default empty.
+ * @param int $expire Optional. When to expire the cache contents, in seconds.
+ * Default 0 (no expiration).
+ * @return bool[] Array of return values, grouped by key. Each value is either
+ * true on success, or false on failure.
+ */
+function wp_cache_set_multiple( array $data, $group = '', $expire = 0 ) {
+ global $wp_object_cache;
+ assert($wp_object_cache instanceof ObjectCacheProxy);
+
+ return $wp_object_cache->set_multiple($data, $group, $expire);
+}
+
+/**
+ * Deletes multiple values from the cache in one call.
+ *
+ * @since 6.0.0
+ *
+ * @see WP_Object_Cache::delete_multiple()
+ * @global WP_Object_Cache $wp_object_cache Object cache global instance.
+ *
+ * @param array $keys Array of keys under which the cache to deleted.
+ * @param string $group Optional. Where the cache contents are grouped. Default empty.
+ * @return bool[] Array of return values, grouped by key. Each value is either
+ * true on success, or false if the contents were not deleted.
+ */
+function wp_cache_delete_multiple( array $keys, $group = '' ) {
+ global $wp_object_cache;
+ assert($wp_object_cache instanceof ObjectCacheProxy);
+
+ return $wp_object_cache->delete_multiple( $keys, $group );
+}
+
+/**
+ * Removes all cache items from the in-memory runtime cache.
+ *
+ * @since 6.0.0
+ *
+ * @see WP_Object_Cache::flush()
+ *
+ * @return bool True on success, false on failure.
+ */
+function wp_cache_flush_runtime() {
+ global $wp_object_cache;
+ assert($wp_object_cache instanceof ObjectCacheProxy);
+ return $wp_object_cache->flush_runtime();
+}
+
+/**
+ * Removes all cache items in a group, if the object cache implementation supports it.
+ *
+ * Before calling this function, always check for group flushing support using the
+ * `wp_cache_supports( 'flush_group' )` function.
+ *
+ * @since 6.1.0
+ *
+ * @see WP_Object_Cache::flush_group()
+ * @global WP_Object_Cache $wp_object_cache Object cache global instance.
+ *
+ * @param string $group Name of group to remove from cache.
+ * @return bool True if group was flushed, false otherwise.
+ */
+function wp_cache_flush_group( $group ) {
+ global $wp_object_cache;
+ assert($wp_object_cache instanceof ObjectCacheProxy);
+ return $wp_object_cache->flush_group($group);
+}
+
+/**
+ * Determines whether the object cache implementation supports a particular feature.
+ *
+ * @since 6.1.0
+ *
+ * @param string $feature Name of the feature to check for. Possible values include:
+ * 'add_multiple', 'set_multiple', 'get_multiple', 'delete_multiple',
+ * 'flush_runtime', 'flush_group'.
+ * @return bool True if the feature is supported, false otherwise.
+ */
+function wp_cache_supports($feature): bool
+{
+
+ switch ($feature) {
+ case 'add_multiple':
+ case 'set_multiple':
+ case 'get_multiple':
+ case 'delete_multiple':
+ case 'flush_runtime':
+ case 'flush_group':
+ return true;
+
+ default:
+ return false;
+ }
+}
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 10495d1..9a0e5f7 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -1,5 +1,6 @@
+
./src
-
\ No newline at end of file
+
diff --git a/src/Generator/CacheKeyGenerator.php b/src/Generator/CacheKeyGenerator.php
index 906ffe0..6b164e7 100644
--- a/src/Generator/CacheKeyGenerator.php
+++ b/src/Generator/CacheKeyGenerator.php
@@ -1,6 +1,5 @@
globalGroups[$group])) {
$parts[] = $this->blogId;
diff --git a/src/ObjectCacheProxy.php b/src/ObjectCacheProxy.php
index 6c8514c..1f4d65a 100644
--- a/src/ObjectCacheProxy.php
+++ b/src/ObjectCacheProxy.php
@@ -7,6 +7,7 @@
namespace Inpsyde\WpStash;
use Inpsyde\WpStash\Generator\KeyGen;
+use Inpsyde\WpStash\Stash\PersistenceAwareComposite;
// because WordPress...
// phpcs:disable
@@ -91,18 +92,18 @@ class ObjectCacheProxy
/**
* Sets up object properties
*
- * @since WP 2.0.8
- *
* @param StashAdapter $non_persistent
* @param StashAdapter $persistent
* @param KeyGen $key_gen
+ *
+ * @since WP 2.0.8
+ *
*/
public function __construct(
StashAdapter $non_persistent,
StashAdapter $persistent,
KeyGen $key_gen
) {
-
$this->non_persistent = $non_persistent;
$this->persistent = $persistent;
$this->key_gen = $key_gen;
@@ -111,12 +112,12 @@ public function __construct(
/**
* Make private properties readable for backwards compatibility.
*
- * @since WP 4.0.0
- * @access public
- *
* @param string $name Property to get.
*
* @return mixed Property.
+ * @since WP 4.0.0
+ * @access public
+ *
*/
public function __get($name)
{
@@ -126,13 +127,13 @@ public function __get($name)
/**
* Make private properties settable for backwards compatibility.
*
- * @since WP 4.0.0
- * @access public
- *
* @param string $name Property to set.
* @param mixed $value Property value.
*
* @return mixed Newly-set property.
+ * @since WP 4.0.0
+ * @access public
+ *
*/
public function __set($name, $value)
{
@@ -142,12 +143,12 @@ public function __set($name, $value)
/**
* Make private properties checkable for backwards compatibility.
*
- * @since WP 4.0.0
- * @access public
- *
* @param string $name Property to check if set.
*
* @return bool Whether the property is set.
+ * @since WP 4.0.0
+ * @access public
+ *
*/
public function __isset($name)
{
@@ -157,10 +158,11 @@ public function __isset($name)
/**
* Make private properties un-settable for backwards compatibility.
*
+ * @param string $name Property to unset.
+ *
* @since WP 4.0.0
* @access public
*
- * @param string $name Property to unset.
*/
public function __unset($name)
{
@@ -170,18 +172,18 @@ public function __unset($name)
/**
* Adds data to the cache if it doesn't already exist.
*
- * @uses WP_Object_Cache::_exists Checks to see if the cache already has data.
- * @uses WP_Object_Cache::set Sets the data after the checking the cache
- * contents existence.
- *
- * @since WP 2.0.0
- *
* @param int|string $key What to call the contents in the cache
* @param mixed $data The contents to store in the cache
* @param string $group Where to group the cache contents
* @param int $expire When to expire the cache contents
*
* @return bool False if cache key and group already exist, true on success
+ * @since WP 2.0.0
+ *
+ * @uses WP_Object_Cache::_exists Checks to see if the cache already has data.
+ * @uses WP_Object_Cache::set Sets the data after the checking the cache
+ * contents existence.
+ *
*/
public function add($key, $data, $group = 'default', $expire = 0)
{
@@ -189,12 +191,39 @@ public function add($key, $data, $group = 'default', $expire = 0)
return false;
}
- $cache_key = $this->key_gen->create((string) $key, (string) $group);
+ $cache_key = $this->key_gen->create((string)$key, (string)$group);
return $this->choose_pool($group)
->add($cache_key, $data, $expire);
}
+ /**
+ * Adds multiple values to the cache in one call.
+ *
+ * @param array $data Array of keys and values to be added.
+ * @param string $group Optional. Where the cache contents are grouped. Default empty.
+ * @param int $expire Optional. When to expire the cache contents, in seconds.
+ * Default 0 (no expiration).
+ *
+ * @return bool[] Array of return values, grouped by key. Each value is either
+ * true on success, or false if cache key and group already exist.
+ * @since 6.0.0
+ *
+ */
+ public function add_multiple(array $data, string $group, int $expire)
+ {
+ if (wp_suspend_cache_addition()) {
+ return array_fill_keys(array_keys($data), false);
+ }
+ $originalKeys = array_keys($data);
+ $data = $this->transform_keys_for_group($data, $group);
+
+ return array_combine(
+ $originalKeys,
+ $this->choose_pool($group)->addMultiple($data, $expire)
+ );
+ }
+
/**
* @param $group
*
@@ -218,7 +247,7 @@ private function choose_pool($group): StashAdapter
*/
public function add_global_groups($groups): bool
{
- if (! $this->key_gen instanceof Generator\MultisiteKeyGen) {
+ if (!$this->key_gen instanceof Generator\MultisiteKeyGen) {
return false;
}
$this->key_gen->addGlobalGroups($groups);
@@ -229,15 +258,15 @@ public function add_global_groups($groups): bool
/**
* Sets the list of non persistent groups.
*
- * @since WP 2.6.0
- *
* @param array $groups List of non persistent groups.
*
* @return array
+ * @since WP 2.6.0
+ *
*/
public function add_non_persistent_groups($groups): array
{
- $groups = (array) $groups;
+ $groups = (array)$groups;
$groups = array_fill_keys($groups, true);
$this->non_persistent_groups = array_merge($this->non_persistent_groups, $groups);
@@ -248,13 +277,13 @@ public function add_non_persistent_groups($groups): array
/**
* Decrement numeric cache item's value
*
- * @since WP 3.3.0
- *
* @param int|string $key The cache key to increment
* @param int $offset The amount by which to decrement the item's value. Default is 1.
* @param string $group The group the key is in.
*
* @return false|int False on failure, the item's new value on success.
+ * @since WP 3.3.0
+ *
*/
public function decr($key, $offset = 1, $group = 'default')
{
@@ -267,16 +296,16 @@ public function decr($key, $offset = 1, $group = 'default')
*
* If the cache key does not exist in the group, then nothing will happen.
*
- * @since WP 2.0.0
- *
* @param int|string $key What the contents in the cache are called
* @param string $group Where the cache contents are grouped
*
* @return bool False if the contents weren't deleted and true on success
+ * @since WP 2.0.0
+ *
*/
public function delete($key, $group = 'default'): bool
{
- $cache_key = $this->key_gen->create((string) $key, (string) $group);
+ $cache_key = $this->key_gen->create((string)$key, (string)$group);
return $this->choose_pool($group)
->delete($cache_key);
@@ -285,9 +314,9 @@ public function delete($key, $group = 'default'): bool
/**
* Clears the object cache of all data
*
+ * @return bool Always returns true
* @since WP 2.0.0
*
- * @return bool Always returns true
*/
public function flush(): bool
{
@@ -304,25 +333,23 @@ public function flush(): bool
*/
public function purge(): bool
{
-
return $this->persistent->purge() && $this->non_persistent->purge();
}
/**
* Increment numeric cache item's value
*
- * @since WP 3.3.0
- *
* @param int|string $key The cache key to increment
* @param int $offset The amount by which to increment the item's value. Default is 1.
* @param string $group The group the key is in.
*
* @return false|int False on failure, the item's new value on success.
+ * @since WP 3.3.0
*/
public function incr($key, $offset = 1, $group = 'default')
{
$data = $this->get($key, $group);
- if (! $data || ! is_numeric($data)) {
+ if (!$data || !is_numeric($data)) {
return false;
}
@@ -338,18 +365,18 @@ public function incr($key, $offset = 1, $group = 'default')
*
* On failure, the number of cache misses will be incremented.
*
- * @since WP 2.0.0
- *
* @param int|string $key What the contents in the cache are called
* @param string $group Where the cache contents are grouped
* @param bool $force Whether to force a refetch rather than relying on the local cache (default is false)
*
* @return bool|mixed False on failure to retrieve contents or the cache
* contents on success
+ * @since WP 2.0.0
+ *
*/
public function get($key, $group = 'default', $force = false, &$found = null)
{
- $cache_key = $this->key_gen->create((string) $key, (string) $group);
+ $cache_key = $this->key_gen->create((string)$key, (string)$group);
$result = $this->choose_pool($group)
->get($cache_key);
@@ -372,18 +399,18 @@ public function get($key, $group = 'default', $force = false, &$found = null)
* expire for each time a page is accessed and PHP finishes. The method is
* more for cache plugins which use files.
*
- * @since WP 2.0.0
- *
* @param int|string $key What to call the contents in the cache
* @param mixed $data The contents to store in the cache
* @param string $group Where to group the cache contents
* @param int $expire Not Used
*
* @return bool Always returns true
+ * @since WP 2.0.0
+ *
*/
public function set($key, $data, $group = 'default', $expire = 0)
{
- $cache_key = $this->key_gen->create((string) $key, (string) $group);
+ $cache_key = $this->key_gen->create((string)$key, (string)$group);
return $this->choose_pool($group)
->set($cache_key, $data, $expire);
@@ -392,19 +419,19 @@ public function set($key, $data, $group = 'default', $expire = 0)
/**
* Replace the contents in the cache, if contents already exist
*
- * @since WP 2.0.0
- * @see WP_Object_Cache::set()
- *
* @param int|string $key What to call the contents in the cache
* @param mixed $data The contents to store in the cache
* @param string $group Where to group the cache contents
* @param int $expire When to expire the cache contents
*
* @return bool False if not exists, true if contents were replaced
+ * @see WP_Object_Cache::set()
+ *
+ * @since WP 2.0.0
*/
public function replace($key, $data, $group = 'default', $expire = 0)
{
- $cache_key = $this->key_gen->create((string) $key, (string) $group);
+ $cache_key = $this->key_gen->create((string)$key, (string)$group);
return $this->choose_pool($group)
->replace($cache_key, $data, $expire);
@@ -438,38 +465,143 @@ public function stats()
}
/**
- * Switch the interal blog id.
+ * Switch the internal blog id.
*
* This changes the blog id used to create keys in blog specific groups.
*
+ * @param int $blog_id Blog ID
+ *
* @since WP 3.5.0
*
- * @param int $blog_id Blog ID
*/
public function switch_to_blog($blog_id)
{
- if (! ($this->key_gen instanceof Generator\MultisiteKeyGen)) {
+ if (!($this->key_gen instanceof Generator\MultisiteKeyGen)) {
return;
}
- $this->key_gen->switchToBlog((int) $blog_id);
+ $this->key_gen->switchToBlog((int)$blog_id);
}
/**
- * @param $keys
+ * @param array $keys
* @param string $group
- * @param false $force
+ * @param bool $force
+ *
* @return array
*/
- public function get_multiple($keys, $group = '', $force = false)
+ public function get_multiple(array $keys, string $group = '', bool $force = false): array
{
$keys = array_unique($keys);
$cache_keys = [];
foreach ($keys as $key) {
- $cache_keys[] = $this->key_gen->create((string) $key, (string) $group);
+ $cache_keys[] = $this->key_gen->create((string)$key, (string)$group);
}
$items = $this->choose_pool($group)->getMultiple($cache_keys);
return array_combine($keys, $items);
}
+
+ /**
+ * @return bool
+ */
+ public function flush_runtime(): bool
+ {
+ $this->non_persistent->clear();
+ $this->persistent->clearNonPersistent();
+
+ return true;
+ }
+
+ /**
+ * @param string $group
+ *
+ * @return bool
+ */
+ public function flush_group(string $group): bool
+ {
+ $this->choose_pool($group)->clear();
+
+ return true;
+ }
+
+ /**
+ * Sets multiple values to the cache in one call.
+ *
+ * @since 6.0.0
+ *
+ * @param array $data Array of key and value to be set.
+ * @param string $group Optional. Where the cache contents are grouped. Default empty.
+ * @param int $expire Optional. When to expire the cache contents, in seconds.
+ * Default 0 (no expiration).
+ * @return bool[] Array of return values, grouped by key. Each value is always true.
+ */
+ public function set_multiple(array $data, string $group, int $expire): array
+ {
+ $originalKeys = array_keys($data);
+ $data = $this->transform_keys_for_group($data, $group);
+ $pool = $this->choose_pool($group);
+
+ return array_combine($originalKeys, $pool->setMultiple($data, $expire));
+ }
+
+ /**
+ * Runs our cache key generator across all entries
+ *
+ * @param array $data
+ * @param string $group
+ *
+ * @return array
+ */
+ private function transform_keys_for_group(array $data, string $group): array
+ {
+ return $this->array_map_key(
+ function ($key) use ($group) {
+ return $this->key_gen->create((string)$key, $group);
+ },
+ $data
+ );
+ }
+
+ /**
+ * Changes array keys based on a callback
+ * @param $callback
+ * @param $array
+ *
+ * @return mixed
+ */
+ private function array_map_key($callback, $array)
+ {
+ $result = [];
+ array_walk(
+ $array,
+ function ($val, $key) use ($callback,&$result) {
+ $result[$callback($key, $val)] = $val;
+ }
+ );
+
+ return $result;
+ }
+
+ /**
+ * Deletes multiple values from the cache in one call.
+ *
+ * @since 6.0.0
+ *
+ * @param array $keys Array of keys to be deleted.
+ * @param string $group Optional. Where the cache contents are grouped. Default empty.
+ * @return bool[] Array of return values, grouped by key. Each value is either
+ * true on success, or false if the contents were not deleted.
+ */
+ public function delete_multiple(array $keys, string $group)
+ {
+ $keys = array_unique($keys);
+ $cache_keys = [];
+ foreach ($keys as $key) {
+ $cache_keys[] = $this->key_gen->create((string)$key, (string)$group);
+ }
+
+ $items = $this->choose_pool($group)->deleteMultiple($cache_keys);
+ return array_combine($keys, $items);
+ }
}
diff --git a/src/Stash/PersistenceAwareComposite.php b/src/Stash/PersistenceAwareComposite.php
new file mode 100644
index 0000000..5ddb827
--- /dev/null
+++ b/src/Stash/PersistenceAwareComposite.php
@@ -0,0 +1,25 @@
+drivers as $driver) {
+ if (!$driver->isPersistent()) {
+ $driver->clear();
+ }
+ }
+ }
+}
diff --git a/src/StashAdapter.php b/src/StashAdapter.php
index 6d093ed..bac09f4 100644
--- a/src/StashAdapter.php
+++ b/src/StashAdapter.php
@@ -5,12 +5,15 @@
namespace Inpsyde\WpStash;
+use Inpsyde\WpStash\Generator\KeyGen;
+use Inpsyde\WpStash\Stash\PersistenceAwareComposite;
use Stash\Interfaces\ItemInterface;
use Stash\Invalidation;
use Stash\Pool;
// phpcs:disable Inpsyde.CodeQuality.VariablesName.SnakeCaseVar
// phpcs:disable Inpsyde.CodeQuality.ForbiddenPublicProperty.Found
+// phpcs:disable Inpsyde.CodeQuality.NoAccessors.NoSetter
/**
* Class StashAdapter
@@ -68,6 +71,42 @@ public function add(string $key, $data, int $expire = 0): bool
return $this->set($key, $data, $expire);
}
+ /**
+ * Sets multiple items in one call if they do not yet exist
+ *
+ * @param array $data
+ * @param int $expire
+ *
+ * @return array
+ */
+ public function addMultiple(array $data, int $expire = 0): array
+ {
+ $result = [];
+ $keys = array_keys($data);
+ foreach ($this->pool->getItems($keys) as $item) {
+ $key = $item->getKey();
+ $wpCacheKey = '/' . $key; // Item swallows our first slash with implode
+ if ($this->pool->hasItem($key)) {
+ $result[$wpCacheKey] = false;
+ continue;
+ }
+ /**
+ * @var ItemInterface $item
+ */
+ $item->set($data[$wpCacheKey]);
+ if ($expire) {
+ $item->expiresAfter($expire);
+ }
+
+ $item->setInvalidationMethod(Invalidation::OLD);
+ $this->pool->saveDeferred($item);
+
+ $result[$key] = true;
+ }
+
+ return $result;
+ }
+
/**
* Set/update a cache item.
*
@@ -145,10 +184,41 @@ public function getMultiple(array $keys): array
{
$result = [];
foreach ($this->pool->getItems($keys) as $item) {
+ $key = $item->getKey();
+ $wpCacheKey = '/' . $key; // Item swallows our first slash with implode
/**
* @var ItemInterface $item
*/
- $result[$item->getKey()] = $this->getValueFromItem($item);
+ $result[$wpCacheKey] = $this->getValueFromItem($item);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param array $data
+ * @param int $expire
+ *
+ * @return array
+ */
+ public function setMultiple(array $data, int $expire = 0): array
+ {
+ $result = [];
+ $keys = array_keys($data);
+ foreach ($this->pool->getItems($keys) as $item) {
+ $key = $item->getKey();
+ $wpCacheKey = '/' . $key; // Item swallows our first slash with implode
+ /**
+ * @var ItemInterface $item
+ */
+ $item->set($data[$wpCacheKey]);
+ if ($expire) {
+ $item->expiresAfter($expire);
+ }
+
+ $item->setInvalidationMethod(Invalidation::OLD);
+ $this->pool->saveDeferred($item);
+ $result[$wpCacheKey] = true;
}
return $result;
@@ -246,4 +316,34 @@ public function __destruct()
{
$this->pool->commit();
}
+
+ public function deleteMultiple(array $cache_keys): array
+ {
+ $result = [];
+ /**
+ * Pool::deleteItems() unfortunately does not provide the required metadata
+ */
+ foreach ($this->pool->getItems($cache_keys) as $item) {
+ /**
+ * @var ItemInterface $item
+ */
+ $result[$item->getKey()] = $item->clear();
+ }
+
+ return $result;
+ }
+
+ /**
+ * It would be good to be able to do this closer to the Stash API in the future.
+ * For now, there is no other way to access only the non-persistent drivers of a composite.
+ * @return void
+ */
+ public function clearNonPersistent(): void
+ {
+ $driver = $this->pool->getDriver();
+ if (!$driver instanceof PersistenceAwareComposite) {
+ return;
+ }
+ $driver->clearNonPersistent();
+ }
}
diff --git a/src/WpStash.php b/src/WpStash.php
index 7000b7b..2fdf62f 100644
--- a/src/WpStash.php
+++ b/src/WpStash.php
@@ -7,6 +7,7 @@
namespace Inpsyde\WpStash;
use Inpsyde\WpStash\Generator\KeyGen;
+use Inpsyde\WpStash\Stash\PersistenceAwareComposite;
use Stash\Driver\Composite;
use Stash\Driver\Ephemeral;
use Stash\Interfaces\DriverInterface;
@@ -130,7 +131,7 @@ public function driver(): DriverInterface
&& !$driver instanceof Composite
&& !$driver instanceof Ephemeral
) {
- $driver = new Composite(
+ $driver = new PersistenceAwareComposite(
[
'drivers' => [
new Ephemeral(),
diff --git a/tests/PHPUnit/Unit/Generator/CacheKeyGeneratorTest.php b/tests/PHPUnit/Unit/Generator/CacheKeyGeneratorTest.php
index 244afb3..699ea71 100644
--- a/tests/PHPUnit/Unit/Generator/CacheKeyGeneratorTest.php
+++ b/tests/PHPUnit/Unit/Generator/CacheKeyGeneratorTest.php
@@ -1,4 +1,6 @@
- {
+ 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 6da91f8..3f616b3 100644
--- a/wp-stash-test-plugin/wp-stash-test-plugin.php
+++ b/wp-stash-test-plugin/wp-stash-test-plugin.php
@@ -12,3 +12,54 @@
declare(strict_types=1);
+namespace Inpsyde\WpStashTest;
+/**
+ * Super tiny autoloading
+ */
+spl_autoload_register(static function ($class) {
+ // project-specific namespace prefix
+ $prefix = __NAMESPACE__ . '\\';
+
+ // 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;
+ }
+
+ // get the relative class name
+ $relativeClass = substr($class, $len);
+
+ // 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';
+
+ // if the file exists, require it
+ file_exists($file) and require $file;
+});
+add_action('template_redirect', function () {
+ $foo=1;
+
+ /**
+ * Super tiny templating just in case..
+ */
+ \Closure::fromCallable(function () {
+ require __DIR__ . '/template.php';
+ })->call(
+ new class ([
+ 'foo' => 'bar',
+ ]) {
+ public function __construct($data)
+ {
+ $this->data = $data;
+ }
+
+ public function __get($key)
+ {
+ return $this->data[$key];
+ }
+ }
+ );
+ exit;
+});