<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[49532] branches/4.7: Build/Test Tools: Backport the local Docker environment to the 4.7 branch.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/49532">49532</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/49532","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>desrosj</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2020-11-06 17:37:22 +0000 (Fri, 06 Nov 2020)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Build/Test Tools: Backport the local Docker environment to the 4.7 branch.

This commit introduces the Docker-based local WordPress development environment to the 4.7 branch and converts the Travis test jobs to utilize this environment for easier and more consistent testing.

Until existing blockers with the PHP 5.2 Docker container can be solved, the PHP 5.2 test job will remain using the Travis `precise` image.

Merges [45745,45762,45783-45784,45800,45819,45885,46320,46999,47225,47912,48121,49335,49358,49360,49362] to the 4.7 branch.
See <a href="https://core.trac.wordpress.org/ticket/48301">#48301</a>, <a href="https://core.trac.wordpress.org/ticket/47767">#47767</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#branches47travisyml">branches/4.7/.travis.yml</a></li>
<li><a href="#branches47npmshrinkwrapjson">branches/4.7/npm-shrinkwrap.json</a></li>
<li><a href="#branches47packagejson">branches/4.7/package.json</a></li>
<li><a href="#branches47testsphpunittestsajaxCustomizeMenusphp">branches/4.7/tests/phpunit/tests/ajax/CustomizeMenus.php</a></li>
<li><a href="#branches47testsphpunittestsimageeditor_imagickphp">branches/4.7/tests/phpunit/tests/image/editor_imagick.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#branches47env">branches/4.7/.env</a></li>
<li><a href="#branches47dockercomposeyml">branches/4.7/docker-compose.yml</a></li>
<li><a href="#branches47testsphpunitincludesobjectcachephp">branches/4.7/tests/phpunit/includes/object-cache.php</a></li>
<li>branches/4.7/tools/local-env/</li>
<li><a href="#branches47toolslocalenvdefaulttemplate">branches/4.7/tools/local-env/default.template</a></li>
<li><a href="#branches47toolslocalenvmysqlinitsql">branches/4.7/tools/local-env/mysql-init.sql</a></li>
<li><a href="#branches47toolslocalenvphpconfigini">branches/4.7/tools/local-env/php-config.ini</a></li>
<li><a href="#branches47toolslocalenvphpunitconfigini">branches/4.7/tools/local-env/phpunit-config.ini</a></li>
<li>branches/4.7/tools/local-env/scripts/</li>
<li><a href="#branches47toolslocalenvscriptsdockerjs">branches/4.7/tools/local-env/scripts/docker.js</a></li>
<li><a href="#branches47toolslocalenvscriptsinstalljs">branches/4.7/tools/local-env/scripts/install.js</a></li>
<li><a href="#branches47toolslocalenvscriptsstartjs">branches/4.7/tools/local-env/scripts/start.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="branches47env"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: branches/4.7/.env</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.7/.env                         (rev 0)
+++ branches/4.7/.env   2020-11-06 17:37:22 UTC (rev 49532)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,59 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+##
+# Default configuration options for the local dev environment.
+#
+# All of these options can be overridden by setting them as environment variables before starting
+# the environment. You will need to restart your environment when changing any of these.
+#
+# Below, the following substitutions can be made:
+# - '{version}': any major.minor PHP version from 5.2 onwards.
+# - '{phpunit_version}': any major PHPUnit version starting with 4.
+##
+
+# The site will be available at http://localhost:LOCAL_PORT
+LOCAL_PORT=8889
+
+# Where to run WordPress from. Valid options are 'src' and 'build'.
+LOCAL_DIR=src
+
+# The PHP version to use. Valid options are 'latest', and '{version}-fpm'.
+LOCAL_PHP=7.1-fpm
+
+##
+# The PHPUnit version to use when running tests.
+#
+# Support for new PHPUnit versions is not backported to past versions, so some old WordPress branches require an older
+# version to run tests.
+#
+# Valid versions are:
+# - 'latest' for the highest version of PHPUnit supported on the highest version of PHP supported.
+# - '{version}-fpm' for the highest version of PHPUnit supported on the specified version of PHP.
+# - '{phpunit_version}-php-{version}-fpm' for a specific version of PHPUnit on the specified version of PHP. This format
+# is only available for PHP versions 5.6 and higher.
+#
+# For the full list of available options, see https://hub.docker.com/r/wordpressdevelop/phpunit/tags.
+#
+# For full documentation on PHPUnit compatibility and WordPress versions, see
+# https://make.wordpress.org/core/handbook/references/phpunit-compatibility-and-wordpress-versions/.
+#
+# This defaults to the value assigned to the value of LOCAL_PHP.
+##
+LOCAL_PHPUNIT=6-php-${LOCAL_PHP}
+
+# Whether or not to enable XDebug.
+LOCAL_PHP_XDEBUG=false
+
+# Whether or not to enable Memcached.
+LOCAL_PHP_MEMCACHED=false
+
+# The MySQL version to use. See https://hub.docker.com/_/mysql/ for valid versions.
+LOCAL_MYSQL=5.7
+
+# The debug settings to add to `wp-config.php`.
+LOCAL_WP_DEBUG=true
+LOCAL_WP_DEBUG_LOG=true
+LOCAL_WP_DEBUG_DISPLAY=true
+LOCAL_SCRIPT_DEBUG=true
+LOCAL_WP_ENVIRONMENT_TYPE=local
+
+# The URL to use when running e2e tests.
+WP_BASE_URL=http://localhost:${LOCAL_PORT}
</ins></span></pre></div>
<a id="branches47travisyml"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: branches/4.7/.travis.yml</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.7/.travis.yml  2020-11-06 17:34:44 UTC (rev 49531)
+++ branches/4.7/.travis.yml    2020-11-06 17:37:22 UTC (rev 49532)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1,94 +1,129 @@
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-sudo: false
-dist: trusty
</del><span class="cx" style="display: block; padding: 0 10px"> language: php
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+services:
+  - docker
+
</ins><span class="cx" style="display: block; padding: 0 10px"> cache:
</span><span class="cx" style="display: block; padding: 0 10px">   apt: true
</span><span class="cx" style="display: block; padding: 0 10px">   directories:
</span><span class="cx" style="display: block; padding: 0 10px">     - $HOME/.npm
</span><span class="cx" style="display: block; padding: 0 10px">     - vendor
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-    - $HOME/.composer/cache
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> env:
</span><span class="cx" style="display: block; padding: 0 10px">   global:
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-    - WP_TRAVISCI=travis:phpunit
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    - LOCAL_DIR: build
+    - NPM_INSTALL: true
+    - WP_INSTALL: true
+    - PHP_FPM_UID: "`id -u`"
+    - PHP_FPM_GID: "`id -g`"
+
</ins><span class="cx" style="display: block; padding: 0 10px"> matrix:
</span><span class="cx" style="display: block; padding: 0 10px">   include:
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-  - php: 7.1
-    env: WP_TRAVISCI=travis:js
-  - php: 7.1
-  - php: 5.6
-    env: WP_TRAVIS_OBJECT_CACHE=true
-    services: memcached
-  - php: 5.2
-    dist: precise
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    - env: WP_TRAVISCI=travis:js LOCAL_PHP=7.1-fpm WP_INSTALL=false
+      name: "JS Tests"
+    - env: LOCAL_PHPUNIT=6-php-7.1-fpm WP_TRAVISCI=test:php
+      name: "PHPUnit Tests: PHP 7.1"
+    - env: LOCAL_PHP_MEMCACHED=true LOCAL_PHP=5.6-fpm LOCAL_PHPUNIT=4-php-5.6-fpm WP_TRAVISCI=test:php
+      name: "PHPUnit Tests: PHP 5.6 with Memcached"
+    - env: WP_TRAVISCI=travis:phpunit WP_INSTALL=false
+      php: 5.2
+      dist: precise
+      name: "PHPUnit Tests: PHP 5.2"
+  fast_finish: true
+
</ins><span class="cx" style="display: block; padding: 0 10px"> before_install:
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-- |
-  if [[ "$WP_TRAVISCI" == "travis:phpunit" ]]; then
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  - |
+    if [[ "$WP_TRAVISCI" == "test:php" ]]; then
+      travis_retry svn checkout https://plugins.svn.wordpress.org/wordpress-importer/tags/0.6.3/ tests/phpunit/data/plugins/wordpress-importer
+    fi
+  - |
+    if [[ "$WP_TRAVISCI" != "travis:phpunit" ]]; then
+      curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
+      sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
+      sudo apt-get update
+      sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-ce
+    fi
+  - |
+    sudo rm /usr/local/bin/docker-compose
+    curl -L https://github.com/docker/compose/releases/download/1.24.0/docker-compose-`uname -s`-`uname -m` > docker-compose
+    chmod +x docker-compose
+    sudo mv docker-compose /usr/local/bin
+  - |
+    if [[ "$WP_TRAVISCI" == "travis:phpunit" ]]; then
</ins><span class="cx" style="display: block; padding: 0 10px">       mysql -u root -e "CREATE DATABASE wordpress_tests;"
</span><span class="cx" style="display: block; padding: 0 10px">       cp wp-tests-config-sample.php wp-tests-config.php
</span><span class="cx" style="display: block; padding: 0 10px">       sed -i "s/youremptytestdbnamehere/wordpress_tests/" wp-tests-config.php
</span><span class="cx" style="display: block; padding: 0 10px">       sed -i "s/yourusernamehere/root/" wp-tests-config.php
</span><span class="cx" style="display: block; padding: 0 10px">       sed -i "s/yourpasswordhere//" wp-tests-config.php
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-      svn checkout https://plugins.svn.wordpress.org/wordpress-importer/trunk tests/phpunit/data/plugins/wordpress-importer
-  fi
-- |
-  if [[ "$WP_TRAVIS_OBJECT_CACHE" == "true" ]]; then
-    curl https://raw.githubusercontent.com/tollmanz/wordpress-pecl-memcached-object-cache/584392b56dc4adbe52bd2c7b86f875e23a3e5f75/object-cache.php > src/wp-content/object-cache.php
-    echo "extension = memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
-  fi
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+      travis_retry svn checkout https://plugins.svn.wordpress.org/wordpress-importer/tags/0.6.3/ tests/phpunit/data/plugins/wordpress-importer
+    fi
+
</ins><span class="cx" style="display: block; padding: 0 10px"> before_script:
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-- |
-  # Remove Xdebug for a huge performance increase, but not from nightly:
-  stable='^[0-9\.]+$'
-  if [[ "$TRAVIS_PHP_VERSION" =~ $stable ]]; then
-    phpenv config-rm xdebug.ini
-  fi
-- |
-  # Export Composer's global bin dir to PATH, but not on PHP 5.2:
-  if [[ ${TRAVIS_PHP_VERSION:0:3} != "5.2" ]]; then
-    composer config --list --global
-    export PATH=`composer config --list --global | grep '\[home\]' | { read a; echo "${a#* }/vendor/bin:$PATH"; }`
-  fi
-- |
-  # Install the specified version of PHPUnit depending on the PHP version:
-  if [[ "$WP_TRAVISCI" == "travis:phpunit" ]]; then
-    case "$TRAVIS_PHP_VERSION" in
-      7.1|7.0|nightly)
-        echo "Using PHPUnit 6.x"
-        composer global require "phpunit/phpunit:^6"
-        ;;
-      5.6|5.5|5.4|5.3)
-        echo "Using PHPUnit 4.x"
-        composer global require "phpunit/phpunit:^4"
-        ;;
-      5.2)
-        # Do nothing, use default PHPUnit 3.6.x
-        echo "Using default PHPUnit, hopefully 3.6"
-        ;;
-      *)
-        echo "No PHPUnit version handling for PHP version $TRAVIS_PHP_VERSION"
-        exit 1
-        ;;
-    esac
-  fi
-- npm --version
-- node --version
-- nvm install 6.9.1
-- npm install -g grunt-cli
-- npm install
-- npm prune
-- mysql --version
-- phpenv versions
-- php --version
-- php -m
-- npm --version
-- node --version
-- which phpunit
-- phpunit --version
-- curl --version
-- grunt --version
-- git --version
-- svn --version
-script: grunt $WP_TRAVISCI
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  - npm --version
+  - node --version
+  - nvm install
+  - |
+    if [[ "$WP_TRAVISCI" == "travis:phpunit" ]]; then
+      phpenv config-rm xdebug.ini
+      mysql --version
+      phpenv versions
+      php --version
+      php -m
+    fi
+  - |
+    if [[ "$NPM_INSTALL" == "true" ]]; then
+      npm install
+    fi
+  - |
+    if [[ "$WP_TRAVISCI" == "test:php" ]]; then
+      npm run env:start
+      npm run build
+      docker-compose run --rm mysql mysql --version
+      docker-compose run --rm php php --version
+      docker-compose run --rm php php -m
+      docker-compose run --rm phpunit phpunit --version
+    fi
+  - |
+    if [[ "$LOCAL_PHP_MEMCACHED" == "true" ]]; then
+      cp tests/phpunit/includes/object-cache.php build/wp-content/object-cache.php
+      docker run --name memcached --net $(basename "$PWD")_wpdevnet -d memcached
+    fi
+  - |
+    if [[ "$WP_INSTALL" == "true" ]]; then
+      # Run the install process after memcached has started.
+      npm run env:install
+    fi
+  - npm --version
+  - node --version
+  - curl --version
+  - git --version
+  - svn --version
+  - php --version
+  - php -i
+  - locale -a
+
+script:
+  - |
+    if [[ "$WP_TRAVISCI" == "test:php" ]]; then
+      npm run test:php -- --verbose -c phpunit.xml.dist &&
+      npm run test:php -- --verbose -c phpunit.xml.dist --group ajax &&
+      npm run test:php -- --verbose -c tests/phpunit/multisite.xml &&
+      npm run test:php -- --verbose -c tests/phpunit/multisite.xml --group ms-files &&
+      npm run test:php -- --verbose -c phpunit.xml.dist --group external-http &&
+      npm run test:php -- --verbose -c phpunit.xml.dist --group restapi-jsclient &&
+      # __fakegroup__ is excluded to force PHPUnit to ignore the <exclude> settings in phpunit.xml.dist.
+      LOCAL_PHP_XDEBUG=true npm run test:php -- -v --group xdebug --exclude-group __fakegroup__
+    else
+      npm run grunt $WP_TRAVISCI
+    fi
+
+after_script:
+  - |
+    if [[ "$WP_TEST_REPORTER" == "true" ]]; then
+      git clone https://github.com/WordPress/phpunit-test-runner.git test-runner
+      docker-compose run --rm -e WPT_PREPARE_DIR=/var/www -e WPT_TEST_DIR=/var/www php php test-runner/report.php
+    fi
+
</ins><span class="cx" style="display: block; padding: 0 10px"> notifications:
</span><span class="cx" style="display: block; padding: 0 10px">   slack:
</span><span class="cx" style="display: block; padding: 0 10px">     rooms:
</span></span></pre></div>
<a id="branches47dockercomposeyml"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: branches/4.7/docker-compose.yml</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.7/docker-compose.yml                           (rev 0)
+++ branches/4.7/docker-compose.yml     2020-11-06 17:37:22 UTC (rev 49532)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,136 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+version: '3.7'
+
+services:
+
+  ##
+  # The web server container.
+  ##
+  wordpress-develop:
+    image: nginx:alpine
+
+    networks:
+      - wpdevnet
+
+    ports:
+      - ${LOCAL_PORT-8889}:80
+
+    environment:
+      LOCAL_DIR: ${LOCAL_DIR-src}
+
+    volumes:
+      - ./tools/local-env/default.template:/etc/nginx/conf.d/default.template
+      - ./:/var/www
+
+    # Load our config file, substituting environment variables into the config.
+    command: /bin/sh -c "envsubst '$$LOCAL_DIR' < /etc/nginx/conf.d/default.template > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'"
+
+    depends_on:
+      - php
+
+  ##
+  # The PHP container.
+  ##
+  php:
+    image: wordpressdevelop/php:${LOCAL_PHP-7.1-fpm}
+
+    networks:
+      - wpdevnet
+
+    environment:
+      LOCAL_PHP_XDEBUG: ${LOCAL_PHP_XDEBUG-false}
+      LOCAL_PHP_MEMCACHED: ${LOCAL_PHP_MEMCACHED-false}
+      PHP_FPM_UID: ${PHP_FPM_UID-1000}
+      PHP_FPM_GID: ${PHP_FPM_GID-1000}
+
+    volumes:
+      - ./tools/local-env/php-config.ini:/usr/local/etc/php/conf.d/php-config.ini
+      - ./:/var/www
+
+    depends_on:
+      - mysql
+
+  ##
+  # The MySQL container.
+  ##
+  mysql:
+    image: mysql:${LOCAL_MYSQL-5.7}
+
+    networks:
+      - wpdevnet
+
+    ports:
+      - "3306"
+
+    environment:
+      MYSQL_ROOT_PASSWORD: password
+
+    volumes:
+      - ./tools/local-env/mysql-init.sql:/docker-entrypoint-initdb.d/mysql-init.sql
+      - mysql:/var/lib/mysql
+
+    # For compatibility with PHP versions that don't support the caching_sha2_password auth plugin used in MySQL 8.0.
+    command: --default-authentication-plugin=mysql_native_password
+
+  ##
+  # The WP CLI container.
+  ##
+  cli:
+    image: wordpressdevelop/cli:${LOCAL_PHP-7.1-fpm}
+
+    networks:
+      - wpdevnet
+
+    environment:
+      LOCAL_PHP_XDEBUG: ${LOCAL_PHP_XDEBUG-false}
+      LOCAL_PHP_MEMCACHED: ${LOCAL_PHP_MEMCACHED-false}
+      PHP_FPM_UID: ${PHP_FPM_UID-1000}
+      PHP_FPM_GID: ${PHP_FPM_GID-1000}
+
+    volumes:
+      - ./:/var/www
+
+    # The init directive ensures the command runs with a PID > 1, so Ctrl+C works correctly.
+    init: true
+
+  ##
+  # The PHPUnit container.
+  ##
+  phpunit:
+    image: wordpressdevelop/phpunit:${LOCAL_PHPUNIT-6-php-7.1-fpm}
+
+    networks:
+      - wpdevnet
+
+    environment:
+      LOCAL_PHP_XDEBUG: ${LOCAL_PHP_XDEBUG-false}
+      LOCAL_PHP_MEMCACHED: ${LOCAL_PHP_MEMCACHED-false}
+      LOCAL_DIR: ${LOCAL_DIR-src}
+      WP_MULTISITE: ${WP_MULTISITE-false}
+      PHP_FPM_UID: ${PHP_FPM_UID-1000}
+      PHP_FPM_GID: ${PHP_FPM_GID-1000}
+      TRAVIS_BRANCH: ${TRAVIS_BRANCH-false}
+      TRAVIS_PULL_REQUEST: ${TRAVIS_PULL_REQUEST-false}
+      GITHUB_REF: ${GITHUB_REF-false}
+      GITHUB_EVENT_NAME: ${GITHUB_EVENT_NAME-false}
+
+    volumes:
+      - ./tools/local-env/phpunit-config.ini:/usr/local/etc/php/conf.d/phpunit-config.ini
+      - ./:/var/www
+      - phpunit-uploads:/var/www/${LOCAL_DIR-src}/wp-content/uploads
+
+    # The init directive ensures the command runs with a PID > 1, so Ctrl+C works correctly.
+    init: true
+
+    depends_on:
+      - mysql
+
+volumes:
+  # So that sites aren't wiped every time containers are restarted, MySQL uses a persistent volume.
+  mysql: {}
+  # Using a volume for the uploads directory improves PHPUnit performance.
+  phpunit-uploads: {}
+
+networks:
+  # Creating our own network allows us to connect between containers using their service name.
+  wpdevnet:
+    driver: bridge
</ins></span></pre></div>
<a id="branches47npmshrinkwrapjson"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: branches/4.7/npm-shrinkwrap.json</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.7/npm-shrinkwrap.json  2020-11-06 17:34:44 UTC (rev 49531)
+++ branches/4.7/npm-shrinkwrap.json    2020-11-06 17:37:22 UTC (rev 49532)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1,6 +1,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> {
</span><span class="cx" style="display: block; padding: 0 10px">   "name": "WordPress",
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-  "version": "4.7.0",
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  "version": "4.7.19",
</ins><span class="cx" style="display: block; padding: 0 10px">   "dependencies": {
</span><span class="cx" style="display: block; padding: 0 10px">     "abbrev": {
</span><span class="cx" style="display: block; padding: 0 10px">       "version": "1.0.9",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -14,6 +14,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">       "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz",
</span><span class="cx" style="display: block; padding: 0 10px">       "dev": true
</span><span class="cx" style="display: block; padding: 0 10px">     },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "ajv": {
+      "version": "6.12.6",
+      "from": "ajv@>=6.12.3 <7.0.0",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+      "dev": true
+    },
</ins><span class="cx" style="display: block; padding: 0 10px">     "align-text": {
</span><span class="cx" style="display: block; padding: 0 10px">       "version": "0.1.4",
</span><span class="cx" style="display: block; padding: 0 10px">       "from": "align-text@>=0.1.3 <0.2.0",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -281,6 +287,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">       "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
</span><span class="cx" style="display: block; padding: 0 10px">       "dev": true
</span><span class="cx" style="display: block; padding: 0 10px">     },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "asynckit": {
+      "version": "0.4.0",
+      "from": "asynckit@>=0.4.0 <0.5.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "dev": true
+    },
</ins><span class="cx" style="display: block; padding: 0 10px">     "autoprefixer": {
</span><span class="cx" style="display: block; padding: 0 10px">       "version": "6.5.3",
</span><span class="cx" style="display: block; padding: 0 10px">       "from": "autoprefixer@>=6.5.1 <7.0.0",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -837,6 +849,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">       "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz",
</span><span class="cx" style="display: block; padding: 0 10px">       "dev": true
</span><span class="cx" style="display: block; padding: 0 10px">     },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "core-js": {
+      "version": "2.6.11",
+      "from": "core-js@>=2.5.7 <3.0.0",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz",
+      "dev": true
+    },
</ins><span class="cx" style="display: block; padding: 0 10px">     "core-util-is": {
</span><span class="cx" style="display: block; padding: 0 10px">       "version": "1.0.2",
</span><span class="cx" style="display: block; padding: 0 10px">       "from": "core-util-is@>=1.0.0 <1.1.0",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1218,6 +1236,18 @@
</span><span class="cx" style="display: block; padding: 0 10px">       "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
</span><span class="cx" style="display: block; padding: 0 10px">       "dev": true
</span><span class="cx" style="display: block; padding: 0 10px">     },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "dotenv": {
+      "version": "8.2.0",
+      "from": "dotenv@>=8.2.0 <9.0.0",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
+      "dev": true
+    },
+    "dotenv-expand": {
+      "version": "5.1.0",
+      "from": "dotenv-expand@>=5.1.0 <6.0.0",
+      "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
+      "dev": true
+    },
</ins><span class="cx" style="display: block; padding: 0 10px">     "download": {
</span><span class="cx" style="display: block; padding: 0 10px">       "version": "4.4.3",
</span><span class="cx" style="display: block; padding: 0 10px">       "from": "download@>=4.1.2 <5.0.0",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1468,6 +1498,18 @@
</span><span class="cx" style="display: block; padding: 0 10px">       "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.2.0.tgz",
</span><span class="cx" style="display: block; padding: 0 10px">       "dev": true
</span><span class="cx" style="display: block; padding: 0 10px">     },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "fast-deep-equal": {
+      "version": "3.1.3",
+      "from": "fast-deep-equal@>=3.1.1 <4.0.0",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "dev": true
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "from": "fast-json-stable-stringify@>=2.0.0 <3.0.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "dev": true
+    },
</ins><span class="cx" style="display: block; padding: 0 10px">     "faye-websocket": {
</span><span class="cx" style="display: block; padding: 0 10px">       "version": "0.10.0",
</span><span class="cx" style="display: block; padding: 0 10px">       "from": "faye-websocket@>=0.10.0 <0.11.0",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1898,6 +1940,20 @@
</span><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px">       }
</span><span class="cx" style="display: block; padding: 0 10px">     },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "grunt-cli": {
+      "version": "0.1.13",
+      "from": "grunt-cli@0.1.13",
+      "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-0.1.13.tgz",
+      "dev": true,
+      "dependencies": {
+        "resolve": {
+          "version": "0.3.1",
+          "from": "resolve@>=0.3.1 <0.4.0",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.3.1.tgz",
+          "dev": true
+        }
+      }
+    },
</ins><span class="cx" style="display: block; padding: 0 10px">     "grunt-contrib-clean": {
</span><span class="cx" style="display: block; padding: 0 10px">       "version": "1.0.0",
</span><span class="cx" style="display: block; padding: 0 10px">       "from": "grunt-contrib-clean@>=1.0.0 <1.1.0",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2238,6 +2294,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">       "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-1.0.0.tgz",
</span><span class="cx" style="display: block; padding: 0 10px">       "dev": true
</span><span class="cx" style="display: block; padding: 0 10px">     },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "har-schema": {
+      "version": "2.0.0",
+      "from": "har-schema@>=2.0.0 <3.0.0",
+      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+      "dev": true
+    },
</ins><span class="cx" style="display: block; padding: 0 10px">     "har-validator": {
</span><span class="cx" style="display: block; padding: 0 10px">       "version": "2.0.6",
</span><span class="cx" style="display: block; padding: 0 10px">       "from": "har-validator@>=2.0.6 <2.1.0",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2755,6 +2817,20 @@
</span><span class="cx" style="display: block; padding: 0 10px">       "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
</span><span class="cx" style="display: block; padding: 0 10px">       "dev": true
</span><span class="cx" style="display: block; padding: 0 10px">     },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "isemail": {
+      "version": "3.2.0",
+      "from": "isemail@>=3.0.0 <4.0.0",
+      "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz",
+      "dev": true,
+      "dependencies": {
+        "punycode": {
+          "version": "2.1.1",
+          "from": "punycode@>=2.0.0 <3.0.0",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+          "dev": true
+        }
+      }
+    },
</ins><span class="cx" style="display: block; padding: 0 10px">     "isexe": {
</span><span class="cx" style="display: block; padding: 0 10px">       "version": "1.1.2",
</span><span class="cx" style="display: block; padding: 0 10px">       "from": "isexe@>=1.1.1 <2.0.0",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2780,6 +2856,20 @@
</span><span class="cx" style="display: block; padding: 0 10px">       "dev": true,
</span><span class="cx" style="display: block; padding: 0 10px">       "optional": true
</span><span class="cx" style="display: block; padding: 0 10px">     },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "joi": {
+      "version": "13.7.0",
+      "from": "joi@>=13.0.0 <14.0.0",
+      "resolved": "https://registry.npmjs.org/joi/-/joi-13.7.0.tgz",
+      "dev": true,
+      "dependencies": {
+        "hoek": {
+          "version": "5.0.4",
+          "from": "hoek@>=5.0.0 <6.0.0",
+          "resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.4.tgz",
+          "dev": true
+        }
+      }
+    },
</ins><span class="cx" style="display: block; padding: 0 10px">     "jpegtran-bin": {
</span><span class="cx" style="display: block; padding: 0 10px">       "version": "3.1.0",
</span><span class="cx" style="display: block; padding: 0 10px">       "from": "jpegtran-bin@>=3.0.0 <4.0.0",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2832,6 +2922,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">       "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
</span><span class="cx" style="display: block; padding: 0 10px">       "dev": true
</span><span class="cx" style="display: block; padding: 0 10px">     },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "from": "json-schema-traverse@>=0.4.1 <0.5.0",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "dev": true
+    },
</ins><span class="cx" style="display: block; padding: 0 10px">     "json-stable-stringify": {
</span><span class="cx" style="display: block; padding: 0 10px">       "version": "0.0.1",
</span><span class="cx" style="display: block; padding: 0 10px">       "from": "json-stable-stringify@>=0.0.0 <0.1.0",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3644,6 +3740,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">       "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
</span><span class="cx" style="display: block; padding: 0 10px">       "dev": true
</span><span class="cx" style="display: block; padding: 0 10px">     },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "performance-now": {
+      "version": "2.1.0",
+      "from": "performance-now@>=2.1.0 <3.0.0",
+      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+      "dev": true
+    },
</ins><span class="cx" style="display: block; padding: 0 10px">     "phantomjs-prebuilt": {
</span><span class="cx" style="display: block; padding: 0 10px">       "version": "2.1.13",
</span><span class="cx" style="display: block; padding: 0 10px">       "from": "phantomjs-prebuilt@>=2.1.3 <3.0.0",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3730,6 +3832,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">       "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
</span><span class="cx" style="display: block; padding: 0 10px">       "dev": true
</span><span class="cx" style="display: block; padding: 0 10px">     },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "psl": {
+      "version": "1.8.0",
+      "from": "psl@>=1.1.28 <2.0.0",
+      "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
+      "dev": true
+    },
</ins><span class="cx" style="display: block; padding: 0 10px">     "public-encrypt": {
</span><span class="cx" style="display: block; padding: 0 10px">       "version": "4.0.0",
</span><span class="cx" style="display: block; padding: 0 10px">       "from": "public-encrypt@>=4.0.0 <5.0.0",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3971,6 +4079,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">       "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz",
</span><span class="cx" style="display: block; padding: 0 10px">       "dev": true
</span><span class="cx" style="display: block; padding: 0 10px">     },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "rx": {
+      "version": "4.1.0",
+      "from": "rx@>=4.1.0 <5.0.0",
+      "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz",
+      "dev": true
+    },
</ins><span class="cx" style="display: block; padding: 0 10px">     "rx-lite": {
</span><span class="cx" style="display: block; padding: 0 10px">       "version": "3.1.2",
</span><span class="cx" style="display: block; padding: 0 10px">       "from": "rx-lite@>=3.1.2 <4.0.0",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3977,6 +4091,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">       "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz",
</span><span class="cx" style="display: block; padding: 0 10px">       "dev": true
</span><span class="cx" style="display: block; padding: 0 10px">     },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "safe-buffer": {
+      "version": "5.2.1",
+      "from": "safe-buffer@>=5.1.2 <6.0.0",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "dev": true
+    },
</ins><span class="cx" style="display: block; padding: 0 10px">     "sass-graph": {
</span><span class="cx" style="display: block; padding: 0 10px">       "version": "2.1.2",
</span><span class="cx" style="display: block; padding: 0 10px">       "from": "sass-graph@>=2.1.1 <3.0.0",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4453,6 +4573,20 @@
</span><span class="cx" style="display: block; padding: 0 10px">       "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
</span><span class="cx" style="display: block; padding: 0 10px">       "dev": true
</span><span class="cx" style="display: block; padding: 0 10px">     },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "topo": {
+      "version": "3.0.3",
+      "from": "topo@>=3.0.0 <4.0.0",
+      "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz",
+      "dev": true,
+      "dependencies": {
+        "hoek": {
+          "version": "6.1.3",
+          "from": "hoek@>=6.0.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz",
+          "dev": true
+        }
+      }
+    },
</ins><span class="cx" style="display: block; padding: 0 10px">     "tough-cookie": {
</span><span class="cx" style="display: block; padding: 0 10px">       "version": "2.3.2",
</span><span class="cx" style="display: block; padding: 0 10px">       "from": "tough-cookie@>=2.3.0 <2.4.0",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4566,6 +4700,20 @@
</span><span class="cx" style="display: block; padding: 0 10px">       "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz",
</span><span class="cx" style="display: block; padding: 0 10px">       "dev": true
</span><span class="cx" style="display: block; padding: 0 10px">     },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "uri-js": {
+      "version": "4.4.0",
+      "from": "uri-js@>=4.2.2 <5.0.0",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz",
+      "dev": true,
+      "dependencies": {
+        "punycode": {
+          "version": "2.1.1",
+          "from": "punycode@^2.1.0",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+          "dev": true
+        }
+      }
+    },
</ins><span class="cx" style="display: block; padding: 0 10px">     "uri-path": {
</span><span class="cx" style="display: block; padding: 0 10px">       "version": "1.0.0",
</span><span class="cx" style="display: block; padding: 0 10px">       "from": "uri-path@>=1.0.0 <2.0.0",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4675,6 +4823,122 @@
</span><span class="cx" style="display: block; padding: 0 10px">       "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",
</span><span class="cx" style="display: block; padding: 0 10px">       "dev": true
</span><span class="cx" style="display: block; padding: 0 10px">     },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "wait-on": {
+      "version": "3.2.0",
+      "from": "wait-on@>=3.2.0 <3.3.0",
+      "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-3.2.0.tgz",
+      "dev": true,
+      "dependencies": {
+        "assert-plus": {
+          "version": "1.0.0",
+          "from": "assert-plus@>=1.0.0 <2.0.0",
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+          "dev": true
+        },
+        "aws-sign2": {
+          "version": "0.7.0",
+          "from": "aws-sign2@>=0.7.0 <0.8.0",
+          "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+          "dev": true
+        },
+        "aws4": {
+          "version": "1.11.0",
+          "from": "aws4@>=1.8.0 <2.0.0",
+          "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
+          "dev": true
+        },
+        "caseless": {
+          "version": "0.12.0",
+          "from": "caseless@>=0.12.0 <0.13.0",
+          "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+          "dev": true
+        },
+        "combined-stream": {
+          "version": "1.0.8",
+          "from": "combined-stream@>=1.0.6 <1.1.0",
+          "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+          "dev": true
+        },
+        "extend": {
+          "version": "3.0.2",
+          "from": "extend@>=3.0.2 <3.1.0",
+          "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+          "dev": true
+        },
+        "form-data": {
+          "version": "2.3.3",
+          "from": "form-data@>=2.3.2 <2.4.0",
+          "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+          "dev": true
+        },
+        "har-validator": {
+          "version": "5.1.5",
+          "from": "har-validator@>=5.1.3 <5.2.0",
+          "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
+          "dev": true
+        },
+        "http-signature": {
+          "version": "1.2.0",
+          "from": "http-signature@>=1.2.0 <1.3.0",
+          "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+          "dev": true
+        },
+        "mime-db": {
+          "version": "1.44.0",
+          "from": "mime-db@1.44.0",
+          "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
+          "dev": true
+        },
+        "mime-types": {
+          "version": "2.1.27",
+          "from": "mime-types@>=2.1.19 <2.2.0",
+          "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
+          "dev": true
+        },
+        "oauth-sign": {
+          "version": "0.9.0",
+          "from": "oauth-sign@>=0.9.0 <0.10.0",
+          "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+          "dev": true
+        },
+        "punycode": {
+          "version": "2.1.1",
+          "from": "punycode@^2.1.1",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+          "dev": true
+        },
+        "qs": {
+          "version": "6.5.2",
+          "from": "qs@>=6.5.2 <6.6.0",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+          "dev": true
+        },
+        "request": {
+          "version": "2.88.2",
+          "from": "request@>=2.88.0 <3.0.0",
+          "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
+          "dev": true
+        },
+        "tough-cookie": {
+          "version": "2.5.0",
+          "from": "tough-cookie@>=2.5.0 <2.6.0",
+          "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+          "dev": true
+        },
+        "tunnel-agent": {
+          "version": "0.6.0",
+          "from": "tunnel-agent@>=0.6.0 <0.7.0",
+          "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+          "dev": true
+        },
+        "uuid": {
+          "version": "3.4.0",
+          "from": "uuid@>=3.3.2 <4.0.0",
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+          "dev": true
+        }
+      }
+    },
</ins><span class="cx" style="display: block; padding: 0 10px">     "ware": {
</span><span class="cx" style="display: block; padding: 0 10px">       "version": "1.3.0",
</span><span class="cx" style="display: block; padding: 0 10px">       "from": "ware@>=1.2.0 <2.0.0",
</span></span></pre></div>
<a id="branches47packagejson"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: branches/4.7/package.json</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.7/package.json 2020-11-06 17:34:44 UTC (rev 49531)
+++ branches/4.7/package.json   2020-11-06 17:37:22 UTC (rev 49532)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -13,8 +13,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">   "license": "GPL-2.0+",
</span><span class="cx" style="display: block; padding: 0 10px">   "devDependencies": {
</span><span class="cx" style="display: block; padding: 0 10px">     "autoprefixer": "^6.5.1",
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "dotenv": "^8.2.0",
+    "dotenv-expand": "^5.1.0",
</ins><span class="cx" style="display: block; padding: 0 10px">     "grunt": "~0.4.5",
</span><span class="cx" style="display: block; padding: 0 10px">     "grunt-browserify": "~5.0.0",
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "grunt-cli": "0.1.13",
</ins><span class="cx" style="display: block; padding: 0 10px">     "grunt-contrib-clean": "~1.0.0",
</span><span class="cx" style="display: block; padding: 0 10px">     "grunt-contrib-compress": "~1.3.0",
</span><span class="cx" style="display: block; padding: 0 10px">     "grunt-contrib-concat": "~1.0.0",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -32,6 +35,25 @@
</span><span class="cx" style="display: block; padding: 0 10px">     "grunt-postcss": "~0.7.1",
</span><span class="cx" style="display: block; padding: 0 10px">     "grunt-rtlcss": "~2.0.1",
</span><span class="cx" style="display: block; padding: 0 10px">     "grunt-sass": "~1.2.1",
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-    "matchdep": "~1.0.0"
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+    "matchdep": "~1.0.0",
+    "wait-on": "~3.2.0"
+  },
+  "scripts": {
+    "build": "grunt build",
+    "build:dev": "grunt build --dev",
+    "dev": "grunt watch --dev",
+    "test": "grunt test",
+    "watch": "grunt watch",
+    "grunt": "grunt",
+    "env:start": "node ./tools/local-env/scripts/start.js",
+    "env:stop": "node ./tools/local-env/scripts/docker.js down",
+    "env:restart": "npm run env:stop && npm run env:start",
+    "env:clean": "node ./tools/local-env/scripts/docker.js down -v --remove-orphans",
+    "env:reset": "node ./tools/local-env/scripts/docker.js down --rmi all -v --remove-orphans",
+    "env:install": "node ./tools/local-env/scripts/install.js",
+    "env:cli": "node ./tools/local-env/scripts/docker.js run cli",
+    "env:logs": "node ./tools/local-env/scripts/docker.js logs",
+    "env:pull": "node ./tools/local-env/scripts/docker.js pull",
+    "test:php": "node ./tools/local-env/scripts/docker.js run --rm phpunit phpunit"
</ins><span class="cx" style="display: block; padding: 0 10px">   }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="branches47testsphpunitincludesobjectcachephp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: branches/4.7/tests/phpunit/includes/object-cache.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.7/tests/phpunit/includes/object-cache.php                              (rev 0)
+++ branches/4.7/tests/phpunit/includes/object-cache.php        2020-11-06 17:37:22 UTC (rev 49532)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,2106 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Adds a value to cache.
+ *
+ * If the specified key already exists, the value is not stored and the function
+ * returns false.
+ *
+ * @link http://www.php.net/manual/en/memcached.add.php
+ *
+ * @param string    $key        The key under which to store the value.
+ * @param mixed     $value      The value to store.
+ * @param string    $group      The group value appended to the $key.
+ * @param int       $expiration The expiration time, defaults to 0.
+ * @return bool                 Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_add( $key, $value, $group = '', $expiration = 0 ) {
+       global $wp_object_cache;
+       return $wp_object_cache->add( $key, $value, $group, $expiration );
+}
+
+/**
+ * Adds a value to cache on a specific server.
+ *
+ * Using a server_key value, the object can be stored on a specified server as opposed
+ * to a random server in the stack. Note that this method will add the key/value to the
+ * _cache object as part of the runtime cache. It will add it to an array for the
+ * specified server_key.
+ *
+ * @link http://www.php.net/manual/en/memcached.addbykey.php
+ *
+ * @param string    $server_key     The key identifying the server to store the value on.
+ * @param string    $key            The key under which to store the value.
+ * @param mixed     $value          The value to store.
+ * @param string    $group          The group value appended to the $key.
+ * @param int       $expiration     The expiration time, defaults to 0.
+ * @return bool                     Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_add_by_key( $server_key, $key, $value, $group = '', $expiration = 0 ) {
+       global $wp_object_cache;
+       return $wp_object_cache->addByKey( $server_key, $key, $value, $group, $expiration );
+}
+
+/**
+ * Add a single server to the list of Memcached servers.
+ *
+ * @link http://www.php.net/manual/en/memcached.addserver.php
+ *
+ * @param string        $host   The hostname of the memcache server.
+ * @param int           $port   The port on which memcache is running.
+ * @param int           $weight The weight of the server relative to the total weight of all the servers in the pool.
+ * @return bool                 Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_add_server( $host, $port, $weight = 0 ) {
+       global $wp_object_cache;
+       return $wp_object_cache->addServer( $host, $port, $weight );
+}
+
+/**
+ * Adds an array of servers to the pool.
+ *
+ * Each individual server in the array must include a domain and port, with an optional
+ * weight value: $servers = array( array( '127.0.0.1', 11211, 0 ) );
+ *
+ * @link http://www.php.net/manual/en/memcached.addservers.php
+ *
+ * @param array     $servers    Array of server to register.
+ * @return bool                 True on success; false on failure.
+ */
+function wp_cache_add_servers( $servers ) {
+       global $wp_object_cache;
+       return $wp_object_cache->addServers( $servers );
+}
+
+/**
+ * Append data to an existing item.
+ *
+ * This method should throw an error if it is used with compressed data. This
+ * is an expected behavior. Memcached casts the value to be appended to the initial value to the
+ * type of the initial value. Be careful as this leads to unexpected behavior at times. Due to
+ * how memcached treats types, the behavior has been mimicked in the internal cache to produce
+ * similar results and improve consistency. It is recommend that appends only occur with data of
+ * the same type.
+ *
+ * @link http://www.php.net/manual/en/memcached.append.php
+ *
+ * @param string    $key    The key under which to store the value.
+ * @param mixed     $value  Must be string as appending mixed values is not well-defined
+ * @param string    $group  The group value appended to the $key.
+ * @return bool             Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_append( $key, $value, $group = '' ) {
+       global $wp_object_cache;
+       return $wp_object_cache->append( $key, $value, $group );
+}
+
+/**
+ * Append data to an existing item by server key.
+ *
+ * This method should throw an error if it is used with compressed data. This
+ * is an expected behavior. Memcached casts the value to be appended to the initial value to the
+ * type of the initial value. Be careful as this leads to unexpected behavior at times. Due to
+ * how memcached treats types, the behavior has been mimicked in the internal cache to produce
+ * similar results and improve consistency. It is recommend that appends only occur with data of
+ * the same type.
+ *
+ * @link http://www.php.net/manual/en/memcached.appendbykey.php
+ *
+ * @param string    $server_key     The key identifying the server to store the value on.
+ * @param string    $key            The key under which to store the value.
+ * @param mixed     $value          Must be string as appending mixed values is not well-defined
+ * @param string    $group          The group value appended to the $key.
+ * @return bool                     Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_append_by_key( $server_key, $key, $value, $group = '' ) {
+       global $wp_object_cache;
+       return $wp_object_cache->appendByKey( $server_key, $key, $value, $group );
+}
+
+/**
+ * Performs a "check and set" to store data.
+ *
+ * The set will be successful only if the no other request has updated the value since it was fetched by
+ * this request.
+ *
+ * @link http://www.php.net/manual/en/memcached.cas.php
+ *
+ * @param float     $cas_token  Unique value associated with the existing item. Generated by memcached.
+ * @param string    $key        The key under which to store the value.
+ * @param mixed     $value      The value to store.
+ * @param string    $group      The group value appended to the $key.
+ * @param int       $expiration The expiration time, defaults to 0.
+ * @return bool                 Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_cas( $cas_token, $key, $value, $group = '', $expiration = 0 ) {
+       global $wp_object_cache;
+       return $wp_object_cache->cas( $cas_token, $key, $value, $group, $expiration );
+}
+
+/**
+ * Performs a "check and set" to store data with a server key.
+ *
+ * The set will be successful only if the no other request has updated the value since it was fetched by
+ * this request.
+ *
+ * @link http://www.php.net/manual/en/memcached.casbykey.php
+ *
+ * @param string    $server_key The key identifying the server to store the value on.
+ * @param float     $cas_token  Unique value associated with the existing item. Generated by memcached.
+ * @param string    $key        The key under which to store the value.
+ * @param mixed     $value      The value to store.
+ * @param string    $group      The group value appended to the $key.
+ * @param int       $expiration The expiration time, defaults to 0.
+ * @return bool                 Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_cas_by_key( $cas_token, $server_key, $key, $value, $group = '', $expiration = 0 ) {
+       global $wp_object_cache;
+       return $wp_object_cache->casByKey( $cas_token, $server_key, $key, $value, $group, $expiration );
+}
+
+/**
+ * Closes the cache.
+ *
+ * This function has ceased to do anything since WordPress 2.5. The
+ * functionality was removed along with the rest of the persistent cache. This
+ * does not mean that plugins can't implement this function when they need to
+ * make sure that the cache is cleaned up after WordPress no longer needs it.
+ *
+ * @since 2.0.0
+ *
+ * @return  bool    Always returns True
+ */
+function wp_cache_close() {
+       return true;
+}
+
+/**
+ * Decrement a numeric item's value.
+ *
+ * @link http://www.php.net/manual/en/memcached.decrement.php
+ *
+ * @param string    $key    The key under which to store the value.
+ * @param int       $offset The amount by which to decrement the item's value.
+ * @param string    $group  The group value appended to the $key.
+ * @return int|bool         Returns item's new value on success or FALSE on failure.
+ */
+function wp_cache_decrement( $key, $offset = 1, $group = '' ) {
+       global $wp_object_cache;
+       return $wp_object_cache->decrement( $key, $offset, $group );
+}
+
+/**
+ * Decrement a numeric item's value.
+ *
+ * Same as wp_cache_decrement. Original WordPress caching backends use wp_cache_decr. I
+ * want both spellings to work.
+ *
+ * @link http://www.php.net/manual/en/memcached.decrement.php
+ *
+ * @param string    $key    The key under which to store the value.
+ * @param int       $offset The amount by which to decrement the item's value.
+ * @param string    $group  The group value appended to the $key.
+ * @return int|bool         Returns item's new value on success or FALSE on failure.
+ */
+function wp_cache_decr( $key, $offset = 1, $group = '' ) {
+       return wp_cache_decrement( $key, $offset, $group );
+}
+
+/**
+ * Remove the item from the cache.
+ *
+ * Remove an item from memcached with identified by $key after $time seconds. The
+ * $time parameter allows an object to be queued for deletion without immediately
+ * deleting. Between the time that it is queued and the time it's deleted, add,
+ * replace, and get will fail, but set will succeed.
+ *
+ * @link http://www.php.net/manual/en/memcached.delete.php
+ *
+ * @param string    $key    The key under which to store the value.
+ * @param string    $group  The group value appended to the $key.
+ * @param int       $time   The amount of time the server will wait to delete the item in seconds.
+ * @return bool             Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_delete( $key, $group = '', $time = 0 ) {
+       global $wp_object_cache;
+       return $wp_object_cache->delete( $key, $group, $time );
+}
+
+/**
+ * Remove the item from the cache by server key.
+ *
+ * Remove an item from memcached with identified by $key after $time seconds. The
+ * $time parameter allows an object to be queued for deletion without immediately
+ * deleting. Between the time that it is queued and the time it's deleted, add,
+ * replace, and get will fail, but set will succeed.
+ *
+ * @link http://www.php.net/manual/en/memcached.deletebykey.php
+ *
+ * @param string        $server_key The key identifying the server to store the value on.
+ * @param string        $key        The key under which to store the value.
+ * @param string        $group      The group value appended to the $key.
+ * @param int           $time       The amount of time the server will wait to delete the item in seconds.
+ * @return bool                     Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_delete_by_key( $server_key, $key, $group = '', $time = 0 ) {
+       global $wp_object_cache;
+       return $wp_object_cache->deleteByKey( $server_key, $key, $group, $time );
+}
+
+/**
+ * Fetch the next result.
+ *
+ * @link http://www.php.net/manual/en/memcached.fetch.php
+ *
+ * @return  array|bool   Returns the next result or FALSE otherwise.
+ */
+function wp_cache_fetch() {
+       global $wp_object_cache;
+       return $wp_object_cache->fetch();
+}
+
+/**
+ * Fetch all remaining results from the last request.
+ *
+ * @link http://www.php.net/manual/en/memcached.fetchall.php
+ *
+ * @return  array|bool  Returns the results or FALSE on failure.
+ */
+function wp_cache_fetch_all() {
+       global $wp_object_cache;
+       return $wp_object_cache->fetchAll();
+}
+
+/**
+ * Invalidate all items in the cache.
+ *
+ * @link http://www.php.net/manual/en/memcached.flush.php
+ *
+ * @param int       $delay  Number of seconds to wait before invalidating the items.
+ * @return bool             Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_flush( $delay = 0 ) {
+       global $wp_object_cache;
+       return $wp_object_cache->flush( $delay );
+}
+
+/**
+ * Retrieve object from cache.
+ *
+ * Gets an object from cache based on $key and $group. In order to fully support the $cache_cb and $cas_token
+ * parameters, the runtime cache is ignored by this function if either of those values are set. If either of
+ * those values are set, the request is made directly to the memcached server for proper handling of the
+ * callback and/or token.
+ *
+ * Note that the $deprecated and $found args are only here for compatibility with the native wp_cache_get function.
+ *
+ * @link http://www.php.net/manual/en/memcached.get.php
+ *
+ * @param string        $key        The key under which to store the value.
+ * @param string        $group      The group value appended to the $key.
+ * @param bool          $force      Whether or not to force a cache invalidation.
+ * @param null|bool     $found      Variable passed by reference to determine if the value was found or not.
+ * @param null|string   $cache_cb   Read-through caching callback.
+ * @param null|float    $cas_token  The variable to store the CAS token in.
+ * @return bool|mixed               Cached object value.
+ */
+function wp_cache_get( $key, $group = '', $force = false, &$found = null, $cache_cb = null, &$cas_token = null ) {
+       global $wp_object_cache;
+
+       if ( func_num_args() > 4 )
+               return $wp_object_cache->get( $key, $group, $force, $found, '', false, $cache_cb, $cas_token );
+       else
+               return $wp_object_cache->get( $key, $group, $force, $found );
+}
+
+/**
+ * Retrieve object from cache from specified server.
+ *
+ * Gets an object from cache based on $key, $group and $server_key. In order to fully support the $cache_cb and $cas_token
+ * parameters, the runtime cache is ignored by this function if either of those values are set. If either of
+ * those values are set, the request is made directly to the memcached server for proper handling of the
+ * callback and/or token.
+ *
+ * @link http://www.php.net/manual/en/memcached.getbykey.php
+ *
+ * @param string        $server_key The key identifying the server to store the value on.
+ * @param string        $key        The key under which to store the value.
+ * @param string        $group      The group value appended to the $key.
+ * @param bool          $force      Whether or not to force a cache invalidation.
+ * @param null|bool     $found      Variable passed by reference to determine if the value was found or not.
+ * @param null|string   $cache_cb   Read-through caching callback.
+ * @param null|float    $cas_token  The variable to store the CAS token in.
+ * @return bool|mixed               Cached object value.
+ */
+function wp_cache_get_by_key( $server_key, $key, $group = '', $force = false, &$found = null, $cache_cb = NULL, &$cas_token = NULL ) {
+       global $wp_object_cache;
+
+       if ( func_num_args() > 5 )
+               return $wp_object_cache->getByKey( $server_key, $key, $group, $force, $found, $cache_cb, $cas_token );
+       else
+               return $wp_object_cache->getByKey( $server_key, $key, $group, $force, $found );
+}
+
+/**
+ * Request multiple keys without blocking.
+ *
+ * @link http://www.php.net/manual/en/memcached.getdelayed.php
+ *
+ * @param string|array  $keys       Array or string of key(s) to request.
+ * @param string|array  $groups     Array or string of group(s) for the key(s). See buildKeys for more on how these are handled.
+ * @param bool          $with_cas   Whether to request CAS token values also.
+ * @param null          $value_cb   The result callback or NULL.
+ * @return bool                     Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_get_delayed( $keys, $groups = '', $with_cas = false, $value_cb = NULL ) {
+       global $wp_object_cache;
+       return $wp_object_cache->getDelayed( $keys, $groups, $with_cas, $value_cb );
+}
+
+/**
+ * Request multiple keys without blocking from a specified server.
+ *
+ * @link http://www.php.net/manual/en/memcached.getdelayed.php
+ *
+ * @param string        $server_key The key identifying the server to store the value on.
+ * @param string|array  $keys       Array or string of key(s) to request.
+ * @param string|array  $groups     Array or string of group(s) for the key(s). See buildKeys for more on how these are handled.
+ * @param bool          $with_cas   Whether to request CAS token values also.
+ * @param null          $value_cb   The result callback or NULL.
+ * @return bool                     Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_get_delayed_by_key( $server_key, $keys, $groups = '', $with_cas = false, $value_cb = NULL ) {
+       global $wp_object_cache;
+       return $wp_object_cache->getDelayedByKey( $server_key, $keys, $groups, $with_cas, $value_cb );
+}
+
+/**
+ * Gets multiple values from memcached in one request.
+ *
+ * See the buildKeys method definition to understand the $keys/$groups parameters.
+ *
+ * @link http://www.php.net/manual/en/memcached.getmulti.php
+ *
+ * @param array         $keys       Array of keys to retrieve.
+ * @param string|array  $groups     If string, used for all keys. If arrays, corresponds with the $keys array.
+ * @param null|array    $cas_tokens The variable to store the CAS tokens for the found items.
+ * @param int           $flags      The flags for the get operation.
+ * @return bool|array               Returns the array of found items or FALSE on failure.
+ */
+function wp_cache_get_multi( $keys, $groups = '', &$cas_tokens = NULL, $flags = NULL ) {
+       global $wp_object_cache;
+
+       if ( func_num_args() > 2 )
+               return $wp_object_cache->getMulti( $keys, $groups, '', $cas_tokens, $flags );
+       else
+               return $wp_object_cache->getMulti( $keys, $groups );
+}
+
+/**
+ * Gets multiple values from memcached in one request by specified server key.
+ *
+ * See the buildKeys method definition to understand the $keys/$groups parameters.
+ *
+ * @link http://www.php.net/manual/en/memcached.getmultibykey.php
+ *
+ * @param string        $server_key The key identifying the server to store the value on.
+ * @param array         $keys       Array of keys to retrieve.
+ * @param string|array  $groups     If string, used for all keys. If arrays, corresponds with the $keys array.
+ * @param null|array    $cas_tokens The variable to store the CAS tokens for the found items.
+ * @param int           $flags      The flags for the get operation.
+ * @return bool|array               Returns the array of found items or FALSE on failure.
+ */
+function wp_cache_get_multi_by_key( $server_key, $keys, $groups = '', &$cas_tokens = NULL, $flags = NULL ) {
+       global $wp_object_cache;
+
+       if ( func_num_args() > 3 )
+               return $wp_object_cache->getMultiByKey( $server_key, $keys, $groups, $cas_tokens, $flags );
+       else
+               return $wp_object_cache->getMultiByKey( $server_key, $keys, $groups );
+}
+
+/**
+ * Retrieve a Memcached option value.
+ *
+ * @link http://www.php.net/manual/en/memcached.getoption.php
+ *
+ * @param int   $option One of the Memcached::OPT_* constants.
+ * @return mixed        Returns the value of the requested option, or FALSE on error.
+ */
+function wp_cache_get_option( $option ) {
+       global $wp_object_cache;
+       return $wp_object_cache->getOption( $option );
+}
+
+/**
+ * Return the result code of the last option.
+ *
+ * @link http://www.php.net/manual/en/memcached.getresultcode.php
+ *
+ * @return int  Result code of the last Memcached operation.
+ */
+function wp_cache_get_result_code() {
+       global $wp_object_cache;
+       return $wp_object_cache->getResultCode();
+}
+
+/**
+ * Return the message describing the result of the last operation.
+ *
+ * @link http://www.php.net/manual/en/memcached.getresultmessage.php
+ *
+ * @return string   Message describing the result of the last Memcached operation.
+ */
+function wp_cache_get_result_message() {
+       global $wp_object_cache;
+       return $wp_object_cache->getResultMessage();
+}
+
+/**
+ * Get server information by key.
+ *
+ * @link http://www.php.net/manual/en/memcached.getserverbykey.php
+ *
+ * @param string    $server_key The key identifying the server to store the value on.
+ * @return array                Array with host, post, and weight on success, FALSE on failure.
+ */
+function wp_cache_get_server_by_key( $server_key ) {
+       global $wp_object_cache;
+       return $wp_object_cache->getServerByKey( $server_key );
+}
+
+/**
+ * Get the list of servers in the pool.
+ *
+ * @link http://www.php.net/manual/en/memcached.getserverlist.php
+ *
+ * @return array    The list of all servers in the server pool.
+ */
+function wp_cache_get_server_list() {
+       global $wp_object_cache;
+       return $wp_object_cache->getServerList();
+}
+
+/**
+ * Get server pool statistics.
+ *
+ * @link http://www.php.net/manual/en/memcached.getstats.php
+ *
+ * @return array    Array of server statistics, one entry per server.
+ */
+function wp_cache_get_stats() {
+       global $wp_object_cache;
+       return $wp_object_cache->getStats();
+}
+
+/**
+ * Get server pool memcached version information.
+ *
+ * @link http://www.php.net/manual/en/memcached.getversion.php
+ *
+ * @return array    Array of server versions, one entry per server.
+ */
+function wp_cache_get_version() {
+       global $wp_object_cache;
+       return $wp_object_cache->getVersion();
+}
+
+/**
+ * Increment a numeric item's value.
+ *
+ * @link http://www.php.net/manual/en/memcached.increment.php
+ *
+ * @param string    $key    The key under which to store the value.
+ * @param int       $offset The amount by which to increment the item's value.
+ * @param string    $group  The group value appended to the $key.
+ * @return int|bool         Returns item's new value on success or FALSE on failure.
+ */
+function wp_cache_increment( $key, $offset = 1, $group = '' ) {
+       global $wp_object_cache;
+       return $wp_object_cache->increment( $key, $offset, $group );
+}
+
+/**
+ * Increment a numeric item's value.
+ *
+ * This is the same as wp_cache_increment, but kept for back compatibility. The original
+ * WordPress caching backends use wp_cache_incr. I want both to work.
+ *
+ * @link http://www.php.net/manual/en/memcached.increment.php
+ *
+ * @param string    $key    The key under which to store the value.
+ * @param int       $offset The amount by which to increment the item's value.
+ * @param string    $group  The group value appended to the $key.
+ * @return int|bool         Returns item's new value on success or FALSE on failure.
+ */
+function wp_cache_incr( $key, $offset = 1, $group = '' ) {
+       return wp_cache_increment( $key, $offset, $group );
+}
+
+/**
+ * Prepend data to an existing item.
+ *
+ * This method should throw an error if it is used with compressed data. This is an expected behavior.
+ * Memcached casts the value to be prepended to the initial value to the type of the initial value. Be
+ * careful as this leads to unexpected behavior at times. For instance, prepending (float) 45.23 to
+ * (int) 23 will result in 45, because the value is first combined (45.2323) then cast to "integer"
+ * (the original value), which will be (int) 45. Due to how memcached treats types, the behavior has been
+ * mimicked in the internal cache to produce similar results and improve consistency. It is recommend
+ * that prepends only occur with data of the same type.
+ *
+ * @link http://www.php.net/manual/en/memcached.prepend.php
+ *
+ * @param string    $key    The key under which to store the value.
+ * @param string    $value  Must be string as prepending mixed values is not well-defined.
+ * @param string    $group  The group value prepended to the $key.
+ * @return bool             Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_prepend( $key, $value, $group = '' ) {
+       global $wp_object_cache;
+       return $wp_object_cache->prepend( $key, $value, $group );
+}
+
+/**
+ * Append data to an existing item by server key.
+ *
+ * This method should throw an error if it is used with compressed data. This is an expected behavior.
+ * Memcached casts the value to be prepended to the initial value to the type of the initial value. Be
+ * careful as this leads to unexpected behavior at times. For instance, prepending (float) 45.23 to
+ * (int) 23 will result in 45, because the value is first combined (45.2323) then cast to "integer"
+ * (the original value), which will be (int) 45. Due to how memcached treats types, the behavior has been
+ * mimicked in the internal cache to produce similar results and improve consistency. It is recommend
+ * that prepends only occur with data of the same type.
+ *
+ * @link http://www.php.net/manual/en/memcached.prependbykey.php
+ *
+ * @param string    $server_key     The key identifying the server to store the value on.
+ * @param string    $key            The key under which to store the value.
+ * @param string    $value          Must be string as prepending mixed values is not well-defined.
+ * @param string    $group          The group value prepended to the $key.
+ * @return bool                     Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_prepend_by_key( $server_key, $key, $value, $group = '' ) {
+       global $wp_object_cache;
+       return $wp_object_cache->prependByKey( $server_key, $key, $value, $group );
+}
+
+/**
+ * Replaces a value in cache.
+ *
+ * This method is similar to "add"; however, is does not successfully set a value if
+ * the object's key is not already set in cache.
+ *
+ * @link http://www.php.net/manual/en/memcached.replace.php
+ *
+ * @param string    $key        The key under which to store the value.
+ * @param mixed     $value      The value to store.
+ * @param string    $group      The group value appended to the $key.
+ * @param int       $expiration The expiration time, defaults to 0.
+ * @return bool                 Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_replace( $key, $value, $group = '', $expiration = 0 ) {
+       global $wp_object_cache;
+       return $wp_object_cache->replace( $key, $value, $group, $expiration );
+}
+
+/**
+ * Replaces a value in cache on a specific server.
+ *
+ * This method is similar to "addByKey"; however, is does not successfully set a value if
+ * the object's key is not already set in cache.
+ *
+ * @link http://www.php.net/manual/en/memcached.addbykey.php
+ *
+ * @param string    $server_key     The key identifying the server to store the value on.
+ * @param string    $key            The key under which to store the value.
+ * @param mixed     $value          The value to store.
+ * @param string    $group          The group value appended to the $key.
+ * @param int       $expiration     The expiration time, defaults to 0.
+ * @return bool                     Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_replace_by_key( $server_key, $key, $value, $group = '', $expiration = 0 ) {
+       global $wp_object_cache;
+       return $wp_object_cache->replaceByKey( $server_key, $key, $value, $group, $expiration );
+}
+
+/**
+ * Sets a value in cache.
+ *
+ * The value is set whether or not this key already exists in memcached.
+ *
+ * @link http://www.php.net/manual/en/memcached.set.php
+ *
+ * @param string    $key        The key under which to store the value.
+ * @param mixed     $value      The value to store.
+ * @param string    $group      The group value appended to the $key.
+ * @param int       $expiration The expiration time, defaults to 0.
+ * @return bool                 Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_set( $key, $value, $group = '', $expiration = 0 ) {
+       global $wp_object_cache;
+       return $wp_object_cache->set( $key, $value, $group, $expiration );
+}
+
+/**
+ * Sets a value in cache.
+ *
+ * The value is set whether or not this key already exists in memcached.
+ *
+ * @link http://www.php.net/manual/en/memcached.set.php
+ *
+ * @param string    $server_key     The key identifying the server to store the value on.
+ * @param string    $key            The key under which to store the value.
+ * @param mixed     $value          The value to store.
+ * @param string    $group          The group value appended to the $key.
+ * @param int       $expiration     The expiration time, defaults to 0.
+ * @return bool                     Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_set_by_key( $server_key, $key, $value, $group = '', $expiration = 0 ) {
+       global $wp_object_cache;
+       return $wp_object_cache->setByKey( $server_key, $key, $value, $group, $expiration );
+}
+
+/**
+ * Set multiple values to cache at once.
+ *
+ * By sending an array of $items to this function, all values are saved at once to
+ * memcached, reducing the need for multiple requests to memcached. The $items array
+ * keys and values are what are stored to memcached. The keys in the $items array
+ * are merged with the $groups array/string value via buildKeys to determine the
+ * final key for the object.
+ *
+ * @param array         $items      An array of key/value pairs to store on the server.
+ * @param string|array  $groups     Group(s) to merge with key(s) in $items.
+ * @param int           $expiration The expiration time, defaults to 0.
+ * @return bool                     Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_set_multi( $items, $groups = '', $expiration = 0 ) {
+       global $wp_object_cache;
+       return $wp_object_cache->setMulti( $items, $groups, $expiration );
+}
+
+/**
+ * Set multiple values to cache at once on specified server.
+ *
+ * By sending an array of $items to this function, all values are saved at once to
+ * memcached, reducing the need for multiple requests to memcached. The $items array
+ * keys and values are what are stored to memcached. The keys in the $items array
+ * are merged with the $groups array/string value via buildKeys to determine the
+ * final key for the object.
+ *
+ * @param string        $server_key The key identifying the server to store the value on.
+ * @param array         $items      An array of key/value pairs to store on the server.
+ * @param string|array  $groups     Group(s) to merge with key(s) in $items.
+ * @param int           $expiration The expiration time, defaults to 0.
+ * @return bool                     Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_set_multi_by_key( $server_key, $items, $groups = 'default', $expiration = 0 ) {
+       global $wp_object_cache;
+       return $wp_object_cache->setMultiByKey( $server_key, $items, $groups, $expiration );
+}
+
+/**
+ * Set a Memcached option.
+ *
+ * @link http://www.php.net/manual/en/memcached.setoption.php
+ *
+ * @param int       $option Option name.
+ * @param mixed     $value  Option value.
+ * @return bool             Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_set_option( $option, $value ) {
+       global $wp_object_cache;
+       return $wp_object_cache->setOption( $option, $value );
+}
+
+/**
+ * Switch blog prefix, which changes the cache that is accessed.
+ *
+ * @param  int     $blog_id    Blog to switch to.
+ * @return void
+ */
+function wp_cache_switch_to_blog( $blog_id ) {
+       global $wp_object_cache;
+       return $wp_object_cache->switch_to_blog( $blog_id );
+}
+
+
+/**
+ * Sets up Object Cache Global and assigns it.
+ *
+ * @global  WP_Object_Cache     $wp_object_cache    WordPress Object Cache
+ * @return  void
+ */
+function wp_cache_init() {
+       global $wp_object_cache;
+       $wp_object_cache = new WP_Object_Cache();
+}
+
+/**
+ * Adds a group or set of groups to the list of non-persistent groups.
+ *
+ * @param   string|array    $groups     A group or an array of groups to add.
+ * @return  void
+ */
+function wp_cache_add_global_groups( $groups ) {
+       global $wp_object_cache;
+       $wp_object_cache->add_global_groups( $groups );
+}
+
+/**
+ * Adds a group or set of groups to the list of non-Memcached groups.
+ *
+ * @param   string|array    $groups     A group or an array of groups to add.
+ * @return  void
+ */
+function wp_cache_add_non_persistent_groups( $groups ) {
+       global $wp_object_cache;
+       $wp_object_cache->add_non_persistent_groups( $groups );
+}
+
+class WP_Object_Cache {
+
+       /**
+        * Holds the Memcached object.
+        *
+        * @var Memcached
+        */
+       public $m;
+
+       /**
+        * Hold the Memcached server details.
+        *
+        * @var array
+        */
+       public $servers;
+
+       /**
+        * Holds the non-Memcached objects.
+        *
+        * @var array
+        */
+       public $cache = array();
+
+       /**
+        * List of global groups.
+        *
+        * @var array
+        */
+       public $global_groups = array( 'users', 'userlogins', 'usermeta', 'site-options', 'site-lookup', 'blog-lookup', 'blog-details', 'rss' );
+
+       /**
+        * List of groups not saved to Memcached.
+        *
+        * @var array
+        */
+       public $no_mc_groups = array( 'comment', 'counts' );
+
+       /**
+        * Prefix used for global groups.
+        *
+        * @var string
+        */
+       public $global_prefix = '';
+
+       /**
+        * Prefix used for non-global groups.
+        *
+        * @var string
+        */
+       public $blog_prefix = '';
+
+       /**
+        * Instantiate the Memcached class.
+        *
+        * Instantiates the Memcached class and returns adds the servers specified
+        * in the $memcached_servers global array.
+        *
+        * @link    http://www.php.net/manual/en/memcached.construct.php
+        *
+        * @param   null    $persistent_id      To create an instance that persists between requests, use persistent_id to specify a unique ID for the instance.
+        */
+       public function __construct( $persistent_id = NULL ) {
+               global $memcached_servers, $blog_id, $table_prefix;
+
+               if ( is_null( $persistent_id ) || ! is_string( $persistent_id ) )
+                       $this->m = new Memcached();
+               else
+                       $this->m = new Memcached( $persistent_id );
+
+               if ( isset( $memcached_servers ) )
+                       $this->servers = $memcached_servers;
+               else
+                       $this->servers = array( array( 'memcached', 11211 ) );
+
+               $this->addServers( $this->servers );
+
+               /**
+                * This approach is borrowed from Sivel and Boren. Use the salt for easy cache invalidation and for
+                * multi single WP installs on the same server.
+                */
+               if ( ! defined( 'WP_CACHE_KEY_SALT' ) )
+                       define( 'WP_CACHE_KEY_SALT', '' );
+
+               // Assign global and blog prefixes for use with keys
+               if ( function_exists( 'is_multisite' ) ) {
+                       $this->global_prefix = ( is_multisite() || defined( 'CUSTOM_USER_TABLE' ) && defined( 'CUSTOM_USER_META_TABLE' ) ) ? '' : $table_prefix;
+                       $this->blog_prefix = ( is_multisite() ? $blog_id : $table_prefix ) . ':';
+               }
+
+               // Setup cacheable values for handling expiration times
+               $this->thirty_days = 60 * 60 * 24 * 30;
+               $this->now         = time();
+       }
+
+       /**
+        * Adds a value to cache.
+        *
+        * If the specified key already exists, the value is not stored and the function
+        * returns false.
+        *
+        * @link    http://www.php.net/manual/en/memcached.add.php
+        *
+        * @param   string      $key            The key under which to store the value.
+        * @param   mixed       $value          The value to store.
+        * @param   string      $group          The group value appended to the $key.
+        * @param   int         $expiration     The expiration time, defaults to 0.
+        * @param   string      $server_key     The key identifying the server to store the value on.
+        * @param   bool        $byKey          True to store in internal cache by key; false to not store by key
+        * @return  bool                        Returns TRUE on success or FALSE on failure.
+        */
+       public function add( $key, $value, $group = 'default', $expiration = 0, $server_key = '', $byKey = false ) {
+               /*
+                * Ensuring that wp_suspend_cache_addition is defined before calling, because sometimes an advanced-cache.php
+                * file will load object-cache.php before wp-includes/functions.php is loaded. In those cases, if wp_cache_add
+                * is called in advanced-cache.php before any more of WordPress is loaded, we get a fatal error because
+                * wp_suspend_cache_addition will not be defined until wp-includes/functions.php is loaded.
+                */
+               if ( function_exists( 'wp_suspend_cache_addition' ) && wp_suspend_cache_addition() ) {
+                       return false;
+               }
+
+               $derived_key = $this->buildKey( $key, $group );
+               $expiration  = $this->sanitize_expiration( $expiration );
+
+               // If group is a non-Memcached group, save to runtime cache, not Memcached
+               if ( in_array( $group, $this->no_mc_groups ) ) {
+
+                       // Add does not set the value if the key exists; mimic that here
+                       if ( isset( $this->cache[$derived_key] ) )
+                               return false;
+
+                       $this->add_to_internal_cache( $derived_key, $value );
+
+                       return true;
+               }
+
+               // Save to Memcached
+               if ( $byKey )
+                       $result = $this->m->addByKey( $server_key, $derived_key, $value, $expiration );
+               else
+                       $result = $this->m->add( $derived_key, $value, $expiration );
+
+               // Store in runtime cache if add was successful
+               if ( Memcached::RES_SUCCESS === $this->getResultCode() )
+                       $this->add_to_internal_cache( $derived_key, $value );
+
+               return $result;
+       }
+
+       /**
+        * Adds a value to cache on a specific server.
+        *
+        * Using a server_key value, the object can be stored on a specified server as opposed
+        * to a random server in the stack. Note that this method will add the key/value to the
+        * _cache object as part of the runtime cache. It will add it to an array for the
+        * specified server_key.
+        *
+        * @link    http://www.php.net/manual/en/memcached.addbykey.php
+        *
+        * @param   string      $server_key     The key identifying the server to store the value on.
+        * @param   string      $key            The key under which to store the value.
+        * @param   mixed       $value          The value to store.
+        * @param   string      $group          The group value appended to the $key.
+        * @param   int         $expiration     The expiration time, defaults to 0.
+        * @return  bool                        Returns TRUE on success or FALSE on failure.
+        */
+       public function addByKey( $server_key, $key, $value, $group = 'default', $expiration = 0 ) {
+               return $this->add( $key, $value, $group, $expiration, $server_key, true );
+       }
+
+       /**
+        * Add a single server to the list of Memcached servers.
+        *
+        * @link http://www.php.net/manual/en/memcached.addserver.php
+        *
+        * @param   string      $host           The hostname of the memcache server.
+        * @param   int         $port           The port on which memcache is running.
+        * @param   int         $weight         The weight of the server relative to the total weight of all the servers in the pool.
+        * @return  bool                        Returns TRUE on success or FALSE on failure.
+        */
+       public function addServer( $host, $port, $weight = 0 ) {
+               $host = is_string( $host ) ? $host : '127.0.0.1';
+               $port = is_numeric( $port ) && $port > 0 ? $port : 11211;
+               $weight = is_numeric( $weight ) && $weight > 0 ? $weight : 1;
+
+               return $this->m->addServer( $host, $port, $weight );
+       }
+
+       /**
+        * Adds an array of servers to the pool.
+        *
+        * Each individual server in the array must include a domain and port, with an optional
+        * weight value: $servers = array( array( '127.0.0.1', 11211, 0 ) );
+        *
+        * @link    http://www.php.net/manual/en/memcached.addservers.php
+        *
+        * @param   array       $servers        Array of server to register.
+        * @return  bool                        True on success; false on failure.
+        */
+       public function addServers( $servers ) {
+               if ( ! is_object( $this->m ) )
+                       return false;
+
+               return $this->m->addServers( $servers );
+       }
+
+       /**
+        * Append data to an existing item.
+        *
+        * This method should throw an error if it is used with compressed data. This
+        * is an expected behavior. Memcached casts the value to be appended to the initial value to the
+        * type of the initial value. Be careful as this leads to unexpected behavior at times. Due to
+        * how memcached treats types, the behavior has been mimicked in the internal cache to produce
+        * similar results and improve consistency. It is recommend that appends only occur with data of
+        * the same type.
+        *
+        * @link    http://www.php.net/manual/en/memcached.append.php
+        *
+        * @param   string      $key            The key under which to store the value.
+        * @param   mixed       $value          Must be string as appending mixed values is not well-defined.
+        * @param   string      $group          The group value appended to the $key.
+        * @param   string      $server_key     The key identifying the server to store the value on.
+        * @param   bool        $byKey          True to store in internal cache by key; false to not store by key
+        * @return  bool                        Returns TRUE on success or FALSE on failure.
+        */
+       public function append( $key, $value, $group = 'default', $server_key = '', $byKey = false ) {
+               if ( ! is_string( $value ) && ! is_int( $value ) && ! is_float( $value ) )
+                       return false;
+
+               $derived_key = $this->buildKey( $key, $group );
+
+               // If group is a non-Memcached group, append to runtime cache value, not Memcached
+               if ( in_array( $group, $this->no_mc_groups ) ) {
+                       if ( ! isset( $this->cache[$derived_key] ) )
+                               return false;
+
+                       $combined = $this->combine_values( $this->cache[$derived_key], $value, 'app' );
+                       $this->add_to_internal_cache( $derived_key, $combined );
+                       return true;
+               }
+
+               // Append to Memcached value
+               if ( $byKey )
+                       $result = $this->m->appendByKey( $server_key, $derived_key, $value );
+               else
+                       $result = $this->m->append( $derived_key, $value );
+
+               // Store in runtime cache if add was successful
+               if ( Memcached::RES_SUCCESS === $this->getResultCode() ) {
+                       $combined = $this->combine_values( $this->cache[$derived_key], $value, 'app' );
+                       $this->add_to_internal_cache( $derived_key, $combined );
+               }
+
+               return $result;
+       }
+
+       /**
+        * Append data to an existing item by server key.
+        *
+        * This method should throw an error if it is used with compressed data. This
+        * is an expected behavior. Memcached casts the value to be appended to the initial value to the
+        * type of the initial value. Be careful as this leads to unexpected behavior at times. Due to
+        * how memcached treats types, the behavior has been mimicked in the internal cache to produce
+        * similar results and improve consistency. It is recommend that appends only occur with data of
+        * the same type.
+        *
+        * @link    http://www.php.net/manual/en/memcached.appendbykey.php
+        *
+        * @param   string      $server_key     The key identifying the server to store the value on.
+        * @param   string      $key            The key under which to store the value.
+        * @param   mixed       $value          Must be string as appending mixed values is not well-defined
+        * @param   string      $group          The group value appended to the $key.
+        * @return  bool                        Returns TRUE on success or FALSE on failure.
+        */
+       public function appendByKey( $server_key, $key, $value, $group = 'default' ) {
+               return $this->append( $key, $value, $group, $server_key, true );
+       }
+
+       /**
+        * Performs a "check and set" to store data.
+        *
+        * The set will be successful only if the no other request has updated the value since it was fetched since
+        * this request.
+        *
+        * @link    http://www.php.net/manual/en/memcached.cas.php
+        *
+        * @param   float       $cas_token      Unique value associated with the existing item. Generated by memcached.
+        * @param   string      $key            The key under which to store the value.
+        * @param   mixed       $value          The value to store.
+        * @param   string      $group          The group value appended to the $key.
+        * @param   int         $expiration     The expiration time, defaults to 0.
+        * @param   string      $server_key     The key identifying the server to store the value on.
+        * @param   bool        $byKey          True to store in internal cache by key; false to not store by key
+        * @return  bool                        Returns TRUE on success or FALSE on failure.
+        */
+       public function cas( $cas_token, $key, $value, $group = 'default', $expiration = 0, $server_key = '', $byKey = false ) {
+               $derived_key = $this->buildKey( $key, $group );
+               $expiration  = $this->sanitize_expiration( $expiration );
+
+               /**
+                * If group is a non-Memcached group, save to runtime cache, not Memcached. Note
+                * that since check and set cannot be emulated in the run time cache, this value
+                * operation is treated as a normal "add" for no_mc_groups.
+                */
+               if ( in_array( $group, $this->no_mc_groups ) ) {
+                       $this->add_to_internal_cache( $derived_key, $value );
+                       return true;
+               }
+
+               // Save to Memcached
+               if ( $byKey )
+                       $result = $this->m->casByKey( $cas_token, $server_key, $derived_key, $value, $expiration );
+               else
+                       $result = $this->m->cas( $cas_token, $derived_key, $value, $expiration );
+
+               // Store in runtime cache if cas was successful
+               if ( Memcached::RES_SUCCESS === $this->getResultCode() )
+                       $this->add_to_internal_cache( $derived_key, $value );
+
+               return $result;
+       }
+
+       /**
+        * Performs a "check and set" to store data with a server key.
+        *
+        * The set will be successful only if the no other request has updated the value since it was fetched by
+        * this request.
+        *
+        * @link    http://www.php.net/manual/en/memcached.casbykey.php
+        *
+        * @param   string      $server_key     The key identifying the server to store the value on.
+        * @param   float       $cas_token      Unique value associated with the existing item. Generated by memcached.
+        * @param   string      $key            The key under which to store the value.
+        * @param   mixed       $value          The value to store.
+        * @param   string      $group          The group value appended to the $key.
+        * @param   int         $expiration     The expiration time, defaults to 0.
+        * @return  bool                        Returns TRUE on success or FALSE on failure.
+        */
+       public function casByKey( $cas_token, $server_key, $key, $value, $group = 'default', $expiration = 0 ) {
+               return $this->cas( $cas_token, $key, $value, $group, $expiration, $server_key, true );
+       }
+
+       /**
+        * Decrement a numeric item's value.
+        *
+        * @link http://www.php.net/manual/en/memcached.decrement.php
+        *
+        * @param string    $key    The key under which to store the value.
+        * @param int       $offset The amount by which to decrement the item's value.
+        * @param string    $group  The group value appended to the $key.
+        * @return int|bool         Returns item's new value on success or FALSE on failure.
+        */
+       public function decrement( $key, $offset = 1, $group = 'default' ) {
+               $derived_key = $this->buildKey( $key, $group );
+
+               // Decrement values in no_mc_groups
+               if ( in_array( $group, $this->no_mc_groups ) ) {
+
+                       // Only decrement if the key already exists and value is 0 or greater (mimics memcached behavior)
+                       if ( isset( $this->cache[$derived_key] ) && $this->cache[$derived_key] >= 0 ) {
+
+                               // If numeric, subtract; otherwise, consider it 0 and do nothing
+                               if ( is_numeric( $this->cache[$derived_key] ) )
+                                       $this->cache[$derived_key] -= (int) $offset;
+                               else
+                                       $this->cache[$derived_key] = 0;
+
+                               // Returned value cannot be less than 0
+                               if ( $this->cache[$derived_key] < 0 )
+                                       $this->cache[$derived_key] = 0;
+
+                               return $this->cache[$derived_key];
+                       } else {
+                               return false;
+                       }
+               }
+
+               $result = $this->m->decrement( $derived_key, $offset );
+
+               if ( Memcached::RES_SUCCESS === $this->getResultCode() )
+                       $this->add_to_internal_cache( $derived_key, $result );
+
+               return $result;
+       }
+
+       /**
+        * Decrement a numeric item's value.
+        *
+        * Alias for $this->decrement. Other caching backends use this abbreviated form of the function. It *may* cause
+        * breakage somewhere, so it is nice to have. This function will also allow the core unit tests to pass.
+        *
+        * @param string    $key    The key under which to store the value.
+        * @param int       $offset The amount by which to decrement the item's value.
+        * @param string    $group  The group value appended to the $key.
+        * @return int|bool         Returns item's new value on success or FALSE on failure.
+        */
+       public function decr( $key, $offset = 1, $group = 'default' ) {
+               return $this->decrement( $key, $offset, $group );
+       }
+
+       /**
+        * Remove the item from the cache.
+        *
+        * Remove an item from memcached with identified by $key after $time seconds. The
+        * $time parameter allows an object to be queued for deletion without immediately
+        * deleting. Between the time that it is queued and the time it's deleted, add,
+        * replace, and get will fail, but set will succeed.
+        *
+        * @link http://www.php.net/manual/en/memcached.delete.php
+        *
+        * @param   string      $key        The key under which to store the value.
+        * @param   string      $group      The group value appended to the $key.
+        * @param   int         $time       The amount of time the server will wait to delete the item in seconds.
+        * @param   string      $server_key The key identifying the server to store the value on.
+        * @param   bool        $byKey      True to store in internal cache by key; false to not store by key
+        * @return  bool                    Returns TRUE on success or FALSE on failure.
+        */
+       public function delete( $key, $group = 'default', $time = 0, $server_key = '', $byKey = false ) {
+               $derived_key = $this->buildKey( $key, $group );
+
+               // Remove from no_mc_groups array
+               if ( in_array( $group, $this->no_mc_groups ) ) {
+                       if ( isset( $this->cache[$derived_key] ) )
+                               unset( $this->cache[$derived_key] );
+
+                       return true;
+               }
+
+               if ( $byKey )
+                       $result = $this->m->deleteByKey( $server_key, $derived_key, $time );
+               else
+                       $result = $this->m->delete( $derived_key, $time );
+
+               if ( Memcached::RES_SUCCESS === $this->getResultCode() )
+                       unset( $this->cache[$derived_key] );
+
+               return $result;
+       }
+
+       /**
+        * Remove the item from the cache by server key.
+        *
+        * Remove an item from memcached with identified by $key after $time seconds. The
+        * $time parameter allows an object to be queued for deletion without immediately
+        * deleting. Between the time that it is queued and the time it's deleted, add,
+        * replace, and get will fail, but set will succeed.
+        *
+        * @link http://www.php.net/manual/en/memcached.deletebykey.php
+        *
+        * @param   string      $server_key The key identifying the server to store the value on.
+        * @param   string      $key        The key under which to store the value.
+        * @param   string      $group      The group value appended to the $key.
+        * @param   int         $time       The amount of time the server will wait to delete the item in seconds.
+        * @return  bool                    Returns TRUE on success or FALSE on failure.
+        */
+       public function deleteByKey( $server_key, $key, $group = 'default', $time = 0 ) {
+               return $this->delete( $key, $group, $time, $server_key, true );
+       }
+
+       /**
+        * Fetch the next result.
+        *
+        * @link http://www.php.net/manual/en/memcached.fetch.php
+        *
+        * @return array|bool   Returns the next result or FALSE on failure.
+        */
+       public function fetch() {
+               return $this->m->fetch();
+       }
+
+       /**
+        * Fetch all remaining results from the last request.
+        *
+        * @link http://www.php.net/manual/en/memcached.fetchall.php
+        *
+        * @return  array|bool          Returns the results or FALSE on failure.
+        */
+       public function fetchAll() {
+               return $this->m->fetchAll();
+       }
+
+       /**
+        * Invalidate all items in the cache.
+        *
+        * @link http://www.php.net/manual/en/memcached.flush.php
+        *
+        * @param   int     $delay      Number of seconds to wait before invalidating the items.
+        * @return  bool                Returns TRUE on success or FALSE on failure.
+        */
+       public function flush( $delay = 0 ) {
+               $result = $this->m->flush( $delay );
+
+               // Only reset the runtime cache if memcached was properly flushed
+               if ( Memcached::RES_SUCCESS === $this->getResultCode() )
+                       $this->cache = array();
+
+               return $result;
+       }
+
+       /**
+        * Retrieve object from cache.
+        *
+        * Gets an object from cache based on $key and $group. In order to fully support the $cache_cb and $cas_token
+        * parameters, the runtime cache is ignored by this function if either of those values are set. If either of
+        * those values are set, the request is made directly to the memcached server for proper handling of the
+        * callback and/or token. Note that the $cas_token variable cannot be directly passed to the function. The
+        * variable need to be first defined with a non null value.
+        *
+        * If using the $cache_cb argument, the new value will always have an expiration of time of 0 (forever). This
+        * is a limitation of the Memcached PECL extension.
+        *
+        * @link http://www.php.net/manual/en/memcached.get.php
+        *
+        * @param   string          $key        The key under which to store the value.
+        * @param   string          $group      The group value appended to the $key.
+        * @param   bool            $force      Whether or not to force a cache invalidation.
+        * @param   null|bool       $found      Variable passed by reference to determine if the value was found or not.
+        * @param   string          $server_key The key identifying the server to store the value on.
+        * @param   bool            $byKey      True to store in internal cache by key; false to not store by key
+        * @param   null|callable   $cache_cb   Read-through caching callback.
+        * @param   null|float      $cas_token  The variable to store the CAS token in.
+        * @return  bool|mixed                  Cached object value.
+        */
+       public function get( $key, $group = 'default', $force = false, &$found = null, $server_key = '', $byKey = false, $cache_cb = NULL, &$cas_token = NULL ) {
+               $derived_key = $this->buildKey( $key, $group );
+
+               // Assume object is not found
+               $found = false;
+
+               // If either $cache_db, or $cas_token is set, must hit Memcached and bypass runtime cache
+               if ( func_num_args() > 6 && ! in_array( $group, $this->no_mc_groups ) ) {
+                       if ( $byKey )
+                               $value = $this->m->getByKey( $server_key, $derived_key, $cache_cb, $cas_token );
+                       else
+                               $value = $this->m->get( $derived_key, $cache_cb, $cas_token );
+               } else {
+                       if ( isset( $this->cache[$derived_key] ) ) {
+                               $found = true;
+                               return is_object( $this->cache[$derived_key] ) ? clone $this->cache[$derived_key] : $this->cache[$derived_key];
+                       } elseif ( in_array( $group, $this->no_mc_groups ) ) {
+                               return false;
+                       } else {
+                               if ( $byKey )
+                                       $value = $this->m->getByKey( $server_key, $derived_key );
+                               else
+                                       $value = $this->m->get( $derived_key );
+                       }
+               }
+
+               if ( Memcached::RES_SUCCESS === $this->getResultCode() ) {
+                       $this->add_to_internal_cache( $derived_key, $value );
+                       $found = true;
+               }
+
+               return is_object( $value ) ? clone $value : $value;
+       }
+
+       /**
+        * Retrieve object from cache from specified server.
+        *
+        * Gets an object from cache based on $key, $group and $server_key. In order to fully support the $cache_cb and $cas_token
+        * parameters, the runtime cache is ignored by this function if either of those values are set. If either of
+        * those values are set, the request is made directly to the memcached server for proper handling of the
+        * callback and/or token. Note that the $cas_token variable cannot be directly passed to the function. The
+        * variable need to be first defined with a non null value.
+        *
+        * If using the $cache_cb argument, the new value will always have an expiration of time of 0 (forever). This
+        * is a limitation of the Memcached PECL extension.
+        *
+        * @link http://www.php.net/manual/en/memcached.getbykey.php
+        *
+        * @param   string          $server_key The key identifying the server to store the value on.
+        * @param   string          $key        The key under which to store the value.
+        * @param   string          $group      The group value appended to the $key.
+        * @param   bool            $force      Whether or not to force a cache invalidation.
+        * @param   null|bool       $found      Variable passed by reference to determine if the value was found or not.
+        * @param   null|string     $cache_cb   Read-through caching callback.
+        * @param   null|float      $cas_token  The variable to store the CAS token in.
+        * @return  bool|mixed                  Cached object value.
+        */
+       public function getByKey( $server_key, $key, $group = 'default', $force = false, &$found = null, $cache_cb = NULL, &$cas_token = NULL ) {
+               /**
+                * Need to be careful how "get" is called. If you send $cache_cb, and $cas_token, it will hit memcached.
+                * Only send those args if they were sent to this function.
+                */
+               if ( func_num_args() > 5 )
+                       return $this->get( $key, $group, $force, $found, $server_key, true, $cache_cb, $cas_token );
+               else
+                       return $this->get( $key, $group, $force, $found, $server_key, true );
+       }
+
+       /**
+        * Request multiple keys without blocking.
+        *
+        * @link http://www.php.net/manual/en/memcached.getdelayed.php
+        *
+        * @param   string|array    $keys       Array or string of key(s) to request.
+        * @param   string|array    $groups     Array or string of group(s) for the key(s). See buildKeys for more on how these are handled.
+        * @param   bool            $with_cas   Whether to request CAS token values also.
+        * @param   null            $value_cb   The result callback or NULL.
+        * @return  bool                        Returns TRUE on success or FALSE on failure.
+        */
+       public function getDelayed( $keys, $groups = 'default', $with_cas = false, $value_cb = NULL ) {
+               $derived_keys = $this->buildKeys( $keys, $groups );
+               return $this->m->getDelayed( $derived_keys, $with_cas, $value_cb );
+       }
+
+       /**
+        * Request multiple keys without blocking from a specified server.
+        *
+        * @link http://www.php.net/manual/en/memcached.getdelayed.php
+        *
+        * @param   string          $server_key The key identifying the server to store the value on.
+        * @param   string|array    $keys       Array or string of key(s) to request.
+        * @param   string|array    $groups     Array or string of group(s) for the key(s). See buildKeys for more on how these are handled.
+        * @param   bool            $with_cas   Whether to request CAS token values also.
+        * @param   null            $value_cb   The result callback or NULL.
+        * @return  bool                        Returns TRUE on success or FALSE on failure.
+        */
+       public function getDelayedByKey( $server_key, $keys, $groups = 'default', $with_cas = false, $value_cb = NULL ) {
+               $derived_keys = $this->buildKeys( $keys, $groups );
+               return $this->m->getDelayedByKey( $server_key, $derived_keys, $with_cas, $value_cb );
+       }
+
+       /**
+        * Gets multiple values from memcached in one request.
+        *
+        * See the buildKeys method definition to understand the $keys/$groups parameters.
+        *
+        * @link http://www.php.net/manual/en/memcached.getmulti.php
+        *
+        * @param   array           $keys       Array of keys to retrieve.
+        * @param   string|array    $groups     If string, used for all keys. If arrays, corresponds with the $keys array.
+        * @param   string          $server_key The key identifying the server to store the value on.
+        * @param   null|array      $cas_tokens The variable to store the CAS tokens for the found items.
+        * @param   int             $flags      The flags for the get operation.
+        * @return  bool|array                  Returns the array of found items or FALSE on failure.
+        */
+       public function getMulti( $keys, $groups = 'default', $server_key = '', &$cas_tokens = NULL, $flags = NULL ) {
+               $derived_keys = $this->buildKeys( $keys, $groups );
+
+               /**
+                * If either $cas_tokens, or $flags is set, must hit Memcached and bypass runtime cache. Note that
+                * this will purposely ignore no_mc_groups values as they cannot handle CAS tokens or the special
+                * flags; however, if the groups of groups contains a no_mc_group, this is bypassed.
+                */
+               if ( func_num_args() > 3 && ! $this->contains_no_mc_group( $groups ) ) {
+                       if ( ! empty( $server_key ) )
+                               $values = $this->m->getMultiByKey( $server_key, $derived_keys, $cas_tokens, $flags );
+                       else
+                               $values = $this->m->getMulti( $derived_keys, $cas_tokens, $flags );
+               } else {
+                       $values = array();
+                       $need_to_get = array();
+
+                       // Pull out values from runtime cache, or mark for retrieval
+                       foreach ( $derived_keys as $key ) {
+                               if ( isset( $this->cache[$key] ) )
+                                       $values[$key] = $this->cache[$key];
+                               else
+                                       $need_to_get[$key] = $key;
+                       }
+
+                       // Get those keys not found in the runtime cache
+                       if ( ! empty( $need_to_get ) ) {
+                               if ( ! empty( $server_key ) )
+                                       $result = $this->m->getMultiByKey( $server_key, array_keys( $need_to_get ) );
+                               else
+                                       $result = $this->m->getMulti( array_keys( $need_to_get ) );
+                       }
+
+                       // Merge with values found in runtime cache
+                       if ( isset( $result ) && Memcached::RES_SUCCESS === $this->getResultCode() )
+                               $values = array_merge( $values, $result );
+
+                       // If order should be preserved, reorder now
+                       if ( ! empty( $need_to_get ) && $flags === Memcached::GET_PRESERVE_ORDER ) {
+                               $ordered_values = array();
+
+                               foreach ( $derived_keys as $key ) {
+                                       if ( isset( $values[$key] ) )
+                                               $ordered_values[$key] = $values[$key];
+                               }
+
+                               $values = $ordered_values;
+                               unset( $ordered_values );
+                       }
+               }
+
+               // Add the values to the runtime cache
+               $this->cache = array_merge( $this->cache, $values );
+
+               return $values;
+       }
+
+       /**
+        * Gets multiple values from memcached in one request by specified server key.
+        *
+        * See the buildKeys method definition to understand the $keys/$groups parameters.
+        *
+        * @link http://www.php.net/manual/en/memcached.getmultibykey.php
+        *
+        * @param   string          $server_key The key identifying the server to store the value on.
+        * @param   array           $keys       Array of keys to retrieve.
+        * @param   string|array    $groups     If string, used for all keys. If arrays, corresponds with the $keys array.
+        * @param   null|array      $cas_tokens The variable to store the CAS tokens for the found items.
+        * @param   int             $flags      The flags for the get operation.
+        * @return  bool|array                  Returns the array of found items or FALSE on failure.
+        */
+       public function getMultiByKey( $server_key, $keys, $groups = 'default', &$cas_tokens = NULL, $flags = NULL ) {
+               /**
+                * Need to be careful how "getMulti" is called. If you send $cache_cb, and $cas_token, it will hit memcached.
+                * Only send those args if they were sent to this function.
+                */
+               if ( func_num_args() > 3 )
+                       return $this->getMulti( $keys, $groups, $server_key, $cas_tokens, $flags );
+               else
+                       return $this->getMulti( $keys, $groups, $server_key );
+       }
+
+       /**
+        * Retrieve a Memcached option value.
+        *
+        * @link http://www.php.net/manual/en/memcached.getoption.php
+        *
+        * @param   int         $option     One of the Memcached::OPT_* constants.
+        * @return  mixed                   Returns the value of the requested option, or FALSE on error.
+        */
+       public function getOption( $option ) {
+               return $this->m->getOption( $option );
+       }
+
+       /**
+        * Return the result code of the last option.
+        *
+        * @link http://www.php.net/manual/en/memcached.getresultcode.php
+        *
+        * @return  int     Result code of the last Memcached operation.
+        */
+       public function getResultCode() {
+           return $this->m->getResultCode();
+       }
+
+       /**
+        * Return the message describing the result of the last operation.
+        *
+        * @link    http://www.php.net/manual/en/memcached.getresultmessage.php
+        *
+        * @return  string      Message describing the result of the last Memcached operation.
+        */
+       public function getResultMessage() {
+           return $this->m->getResultMessage();
+       }
+
+       /**
+        * Get server information by key.
+        *
+        * @link    http://www.php.net/manual/en/memcached.getserverbykey.php
+        *
+        * @param   string      $server_key     The key identifying the server to store the value on.
+        * @return  array                       Array with host, post, and weight on success, FALSE on failure.
+        */
+       public function getServerByKey( $server_key ) {
+               return $this->m->getServerByKey( $server_key );
+       }
+
+       /**
+        * Get the list of servers in the pool.
+        *
+        * @link    http://www.php.net/manual/en/memcached.getserverlist.php
+        *
+        * @return  array       The list of all servers in the server pool.
+        */
+       public function getServerList() {
+               return $this->m->getServerList();
+       }
+
+       /**
+     * Get server pool statistics.
+        *
+        * @link    http://www.php.net/manual/en/memcached.getstats.php
+        *
+        * @return  array       Array of server statistics, one entry per server.
+        */
+       public function getStats() {
+               return $this->m->getStats();
+       }
+
+       /**
+        * Get server pool memcached version information.
+        *
+        * @link    http://www.php.net/manual/en/memcached.getversion.php
+        *
+        * @return  array       Array of server versions, one entry per server.
+        */
+       public function getVersion() {
+               return $this->m->getVersion();
+       }
+
+       /**
+        * Increment a numeric item's value.
+        *
+        * @link http://www.php.net/manual/en/memcached.increment.php
+        *
+        * @param   string      $key        The key under which to store the value.
+        * @param   int         $offset     The amount by which to increment the item's value.
+        * @param   string      $group      The group value appended to the $key.
+        * @return  int|bool                Returns item's new value on success or FALSE on failure.
+        */
+       public function increment( $key, $offset = 1, $group = 'default' ) {
+               $derived_key = $this->buildKey( $key, $group );
+
+               // Increment values in no_mc_groups
+               if ( in_array( $group, $this->no_mc_groups ) ) {
+
+                       // Only increment if the key already exists and the number is currently 0 or greater (mimics memcached behavior)
+                       if ( isset( $this->cache[$derived_key] ) &&  $this->cache[$derived_key] >= 0 ) {
+
+                               // If numeric, add; otherwise, consider it 0 and do nothing
+                               if ( is_numeric( $this->cache[$derived_key] ) )
+                                       $this->cache[$derived_key] += (int) $offset;
+                               else
+                                       $this->cache[$derived_key] = 0;
+
+                               // Returned value cannot be less than 0
+                               if ( $this->cache[$derived_key] < 0 )
+                                       $this->cache[$derived_key] = 0;
+
+                               return $this->cache[$derived_key];
+                       } else {
+                               return false;
+                       }
+               }
+
+               $result = $this->m->increment( $derived_key, $offset );
+
+               if ( Memcached::RES_SUCCESS === $this->getResultCode() )
+                       $this->add_to_internal_cache( $derived_key, $result );
+
+               return $result;
+       }
+
+       /**
+        * Synonymous with $this->incr.
+        *
+        * Certain plugins expect an "incr" method on the $wp_object_cache object (e.g., Batcache). Since the original
+        * version of this library matched names to the memcached methods, the "incr" method was missing. Adding this
+        * method restores compatibility with plugins expecting an "incr" method.
+        *
+        * @param   string      $key        The key under which to store the value.
+        * @param   int         $offset     The amount by which to increment the item's value.
+        * @param   string      $group      The group value appended to the $key.
+        * @return  int|bool                Returns item's new value on success or FALSE on failure.
+        */
+       public function incr( $key, $offset = 1, $group = 'default' ) {
+               return $this->increment( $key, $offset, $group );
+       }
+
+       /**
+        * Prepend data to an existing item.
+        *
+        * This method should throw an error if it is used with compressed data. This is an expected behavior.
+        * Memcached casts the value to be prepended to the initial value to the type of the initial value. Be
+        * careful as this leads to unexpected behavior at times. For instance, prepending (float) 45.23 to
+        * (int) 23 will result in 45, because the value is first combined (45.2323) then cast to "integer"
+        * (the original value), which will be (int) 45. Due to how memcached treats types, the behavior has been
+        * mimicked in the internal cache to produce similar results and improve consistency. It is recommend
+        * that prepends only occur with data of the same type.
+        *
+        * @link    http://www.php.net/manual/en/memcached.prepend.php
+        *
+        * @param   string    $key          The key under which to store the value.
+        * @param   string    $value        Must be string as prepending mixed values is not well-defined.
+        * @param   string    $group        The group value prepended to the $key.
+        * @param   string    $server_key   The key identifying the server to store the value on.
+        * @param   bool      $byKey        True to store in internal cache by key; false to not store by key
+        * @return  bool                    Returns TRUE on success or FALSE on failure.
+        */
+       public function prepend( $key, $value, $group = 'default', $server_key = '', $byKey = false ) {
+               if ( ! is_string( $value ) && ! is_int( $value ) && ! is_float( $value ) )
+                       return false;
+
+               $derived_key = $this->buildKey( $key, $group );
+
+               // If group is a non-Memcached group, prepend to runtime cache value, not Memcached
+               if ( in_array( $group, $this->no_mc_groups ) ) {
+                       if ( ! isset( $this->cache[$derived_key] ) )
+                               return false;
+
+                       $combined = $this->combine_values( $this->cache[$derived_key], $value, 'pre' );
+                       $this->add_to_internal_cache( $derived_key, $combined );
+                       return true;
+               }
+
+               // Append to Memcached value
+               if ( $byKey )
+                       $result = $this->m->prependByKey( $server_key, $derived_key, $value );
+               else
+                       $result = $this->m->prepend( $derived_key, $value );
+
+               // Store in runtime cache if add was successful
+               if ( Memcached::RES_SUCCESS === $this->getResultCode() ) {
+                       $combined = $this->combine_values( $this->cache[$derived_key], $value, 'pre' );
+                       $this->add_to_internal_cache( $derived_key, $combined );
+               }
+
+               return $result;
+       }
+
+       /**
+        * Append data to an existing item by server key.
+        *
+        * This method should throw an error if it is used with compressed data. This is an expected behavior.
+        * Memcached casts the value to be prepended to the initial value to the type of the initial value. Be
+        * careful as this leads to unexpected behavior at times. For instance, prepending (float) 45.23 to
+        * (int) 23 will result in 45, because the value is first combined (45.2323) then cast to "integer"
+        * (the original value), which will be (int) 45. Due to how memcached treats types, the behavior has been
+        * mimicked in the internal cache to produce similar results and improve consistency. It is recommend
+        * that prepends only occur with data of the same type.
+        *
+        * @link    http://www.php.net/manual/en/memcached.prependbykey.php
+        *
+        * @param   string    $server_key   The key identifying the server to store the value on.
+        * @param   string    $key          The key under which to store the value.
+        * @param   string    $value        Must be string as prepending mixed values is not well-defined.
+        * @param   string    $group        The group value prepended to the $key.
+        * @return  bool                    Returns TRUE on success or FALSE on failure.
+        */
+       public function prependByKey( $server_key, $key, $value, $group = 'default' ) {
+               return $this->prepend( $key, $value, $group, $server_key, true );
+       }
+
+       /**
+        * Replaces a value in cache.
+        *
+        * This method is similar to "add"; however, is does not successfully set a value if
+        * the object's key is not already set in cache.
+        *
+        * @link    http://www.php.net/manual/en/memcached.replace.php
+        *
+        * @param   string      $server_key     The key identifying the server to store the value on.
+        * @param   string      $key            The key under which to store the value.
+        * @param   mixed       $value          The value to store.
+        * @param   string      $group          The group value appended to the $key.
+        * @param   bool        $byKey          True to store in internal cache by key; false to not store by key
+        * @param   int         $expiration     The expiration time, defaults to 0.
+        * @return  bool                        Returns TRUE on success or FALSE on failure.
+        */
+       public function replace( $key, $value, $group = 'default', $expiration = 0, $server_key = '', $byKey = false ) {
+               $derived_key = $this->buildKey( $key, $group );
+               $expiration  = $this->sanitize_expiration( $expiration );
+
+               // If group is a non-Memcached group, save to runtime cache, not Memcached
+               if ( in_array( $group, $this->no_mc_groups ) ) {
+
+                       // Replace won't save unless the key already exists; mimic this behavior here
+                       if ( ! isset( $this->cache[$derived_key] ) )
+                               return false;
+
+                       $this->cache[$derived_key] = $value;
+                       return true;
+               }
+
+               // Save to Memcached
+               if ( $byKey )
+                       $result = $this->m->replaceByKey( $server_key, $derived_key, $value, $expiration );
+               else
+                       $result = $this->m->replace( $derived_key, $value, $expiration );
+
+               // Store in runtime cache if add was successful
+               if ( Memcached::RES_SUCCESS === $this->getResultCode() )
+                       $this->add_to_internal_cache( $derived_key, $value );
+
+               return $result;
+       }
+
+       /**
+        * Replaces a value in cache on a specific server.
+        *
+        * This method is similar to "addByKey"; however, is does not successfully set a value if
+        * the object's key is not already set in cache.
+        *
+        * @link    http://www.php.net/manual/en/memcached.addbykey.php
+        *
+        * @param   string      $server_key     The key identifying the server to store the value on.
+        * @param   string      $key            The key under which to store the value.
+        * @param   mixed       $value          The value to store.
+        * @param   string      $group          The group value appended to the $key.
+        * @param   int         $expiration     The expiration time, defaults to 0.
+        * @return  bool                        Returns TRUE on success or FALSE on failure.
+        */
+       public function replaceByKey( $server_key, $key, $value, $group = 'default', $expiration = 0 ) {
+               return $this->replace( $key, $value, $group, $expiration, $server_key, true );
+       }
+
+       /**
+        * Sets a value in cache.
+        *
+        * The value is set whether or not this key already exists in memcached.
+        *
+        * @link http://www.php.net/manual/en/memcached.set.php
+        *
+        * @param   string      $key        The key under which to store the value.
+        * @param   mixed       $value      The value to store.
+        * @param   string      $group      The group value appended to the $key.
+        * @param   int         $expiration The expiration time, defaults to 0.
+        * @param   string      $server_key The key identifying the server to store the value on.
+        * @param   bool        $byKey      True to store in internal cache by key; false to not store by key
+        * @return  bool                    Returns TRUE on success or FALSE on failure.
+        */
+       public function set( $key, $value, $group = 'default', $expiration = 0, $server_key = '', $byKey = false ) {
+               $derived_key = $this->buildKey( $key, $group );
+               $expiration  = $this->sanitize_expiration( $expiration );
+
+               // If group is a non-Memcached group, save to runtime cache, not Memcached
+               if ( in_array( $group, $this->no_mc_groups ) ) {
+                       $this->add_to_internal_cache( $derived_key, $value );
+                       return true;
+               }
+
+               // Save to Memcached
+               if ( $byKey ) {
+                       $result = $this->m->setByKey( $server_key, $derived_key, $value, $expiration );
+               } else {
+                       $result = $this->m->set( $derived_key, $value, $expiration );
+               }
+
+               // Store in runtime cache if add was successful
+               if ( Memcached::RES_SUCCESS === $this->getResultCode() )
+                       $this->add_to_internal_cache( $derived_key, $value );
+
+               return $result;
+       }
+
+       /**
+        * Sets a value in cache on a specific server.
+        *
+        * The value is set whether or not this key already exists in memcached.
+        *
+        * @link    http://www.php.net/manual/en/memcached.setbykey.php
+        *
+        * @param   string      $server_key     The key identifying the server to store the value on.
+        * @param   string      $key            The key under which to store the value.
+        * @param   mixed       $value          The value to store.
+        * @param   string      $group          The group value appended to the $key.
+        * @param   int         $expiration     The expiration time, defaults to 0.
+        * @return  bool                        Returns TRUE on success or FALSE on failure.
+        */
+       public function setByKey( $server_key, $key, $value, $group = 'default', $expiration = 0 ) {
+               return $this->set( $key, $value, $group, $expiration, $server_key, true );
+       }
+
+       /**
+        * Set multiple values to cache at once.
+        *
+        * By sending an array of $items to this function, all values are saved at once to
+        * memcached, reducing the need for multiple requests to memcached. The $items array
+        * keys and values are what are stored to memcached. The keys in the $items array
+        * are merged with the $groups array/string value via buildKeys to determine the
+        * final key for the object.
+        *
+        * @link    http://www.php.net/manual/en/memcached.setmulti.php
+        *
+        * @param   array           $items          An array of key/value pairs to store on the server.
+        * @param   string|array    $groups         Group(s) to merge with key(s) in $items.
+        * @param   int             $expiration     The expiration time, defaults to 0.
+        * @param   string          $server_key     The key identifying the server to store the value on.
+        * @param   bool            $byKey          True to store in internal cache by key; false to not store by key
+        * @return  bool                            Returns TRUE on success or FALSE on failure.
+        */
+       public function setMulti( $items, $groups = 'default', $expiration = 0, $server_key = '', $byKey = false ) {
+               // Build final keys and replace $items keys with the new keys
+               $derived_keys  = $this->buildKeys( array_keys( $items ), $groups );
+               $expiration    = $this->sanitize_expiration( $expiration );
+               $derived_items = array_combine( $derived_keys, $items );
+
+               // Do not add to memcached if in no_mc_groups
+               foreach ( $derived_items as $derived_key => $value ) {
+
+                       // Get the individual item's group
+                       $key_pieces = explode( ':', $derived_key );
+
+                       // If group is a non-Memcached group, save to runtime cache, not Memcached
+                       if ( in_array( $key_pieces[1], $this->no_mc_groups ) ) {
+                               $this->add_to_internal_cache( $derived_key, $value );
+                               unset( $derived_items[$derived_key] );
+                       }
+               }
+
+               // Save to memcached
+               if ( $byKey )
+                       $result = $this->m->setMultiByKey( $server_key, $derived_items, $expiration );
+               else
+                       $result = $this->m->setMulti( $derived_items, $expiration );
+
+               // Store in runtime cache if add was successful
+               if ( Memcached::RES_SUCCESS === $this->getResultCode() )
+                       $this->cache = array_merge( $this->cache, $derived_items );
+
+               return $result;
+       }
+
+       /**
+        * Set multiple values to cache at once on specified server.
+        *
+        * By sending an array of $items to this function, all values are saved at once to
+        * memcached, reducing the need for multiple requests to memcached. The $items array
+        * keys and values are what are stored to memcached. The keys in the $items array
+        * are merged with the $groups array/string value via buildKeys to determine the
+        * final key for the object.
+        *
+        * @link    http://www.php.net/manual/en/memcached.setmultibykey.php
+        *
+        * @param   string          $server_key     The key identifying the server to store the value on.
+        * @param   array           $items          An array of key/value pairs to store on the server.
+        * @param   string|array    $groups         Group(s) to merge with key(s) in $items.
+        * @param   int             $expiration     The expiration time, defaults to 0.
+        * @return  bool                            Returns TRUE on success or FALSE on failure.
+        */
+       public function setMultiByKey( $server_key, $items, $groups = 'default', $expiration = 0 ) {
+               return $this->setMulti( $items, $groups, $expiration, $server_key, true );
+       }
+
+       /**
+        * Set a Memcached option.
+        *
+        * @link    http://www.php.net/manual/en/memcached.setoption.php
+        *
+        * @param   int         $option     Option name.
+        * @param   mixed       $value      Option value.
+        * @return  bool                Returns TRUE on success or FALSE on failure.
+        */
+       public function setOption( $option, $value ) {
+               return $this->m->setOption( $option, $value );
+       }
+
+       /**
+        * Builds a key for the cached object using the blog_id, key, and group values.
+        *
+        * @author  Ryan Boren   This function is inspired by the original WP Memcached Object cache.
+        * @link    http://wordpress.org/extend/plugins/memcached/
+        *
+        * @param   string      $key        The key under which to store the value.
+        * @param   string      $group      The group value appended to the $key.
+        * @return  string
+        */
+       public function buildKey( $key, $group = 'default' ) {
+               if ( empty( $group ) )
+                       $group = 'default';
+
+               if ( false !== array_search( $group, $this->global_groups ) )
+                       $prefix = $this->global_prefix;
+               else
+                       $prefix = $this->blog_prefix;
+
+               return preg_replace( '/\s+/', '', WP_CACHE_KEY_SALT . "$prefix$group:$key" );
+       }
+
+       /**
+        * Creates an array of keys from passed key(s) and group(s).
+        *
+        * This function takes a string or array of key(s) and group(s) and combines them into a single dimensional
+        * array that merges the keys and groups. If the same number of keys and groups exist, the final keys will
+        * append $groups[n] to $keys[n]. If there are more keys than groups and the $groups parameter is an array,
+        * $keys[n] will be combined with $groups[n] until $groups runs out of values. 'default' will be used for remaining
+        * values. If $keys is an array and $groups is a string, all final values will append $groups to $keys[n].
+        * If both values are strings, they will be combined into a single string. Note that if more $groups are received
+        * than $keys, the method will return an empty array. This method is primarily a helper method for methods
+        * that call memcached with an array of keys.
+        *
+        * @param   string|array    $keys       Key(s) to merge with group(s).
+        * @param   string|array    $groups     Group(s) to merge with key(s).
+        * @return  array                       Array that combines keys and groups into a single set of memcached keys.
+        */
+       public function buildKeys( $keys, $groups = 'default' ) {
+               $derived_keys = array();
+
+               // If strings sent, convert to arrays for proper handling
+               if ( ! is_array( $groups ) )
+                       $groups = (array) $groups;
+
+               if ( ! is_array( $keys ) )
+                       $keys = (array) $keys;
+
+               // If we have equal numbers of keys and groups, merge $keys[n] and $group[n]
+               if ( count( $keys ) == count( $groups ) ) {
+                       for ( $i = 0; $i < count( $keys ); $i++ ) {
+                               $derived_keys[] = $this->buildKey( $keys[$i], $groups[$i] );
+                       }
+
+               // If more keys are received than groups, merge $keys[n] and $group[n] until no more group are left; remaining groups are 'default'
+               } elseif ( count( $keys ) > count( $groups ) ) {
+                       for ( $i = 0; $i < count( $keys ); $i++ ) {
+                               if ( isset( $groups[$i] ) )
+                                       $derived_keys[] = $this->buildKey( $keys[$i], $groups[$i] );
+                               elseif ( count( $groups ) == 1 )
+                                       $derived_keys[] = $this->buildKey( $keys[$i], $groups[0] );
+                               else
+                                       $derived_keys[] = $this->buildKey( $keys[$i], 'default' );
+                       }
+               }
+
+               return $derived_keys;
+       }
+
+       /**
+        * Ensure that a proper expiration time is set.
+        *
+        * Memcached treats any value over 30 days as a timestamp. If a developer sets the expiration for greater than 30
+        * days or less than the current timestamp, the timestamp is in the past and the value isn't cached. This function
+        * detects values in that range and corrects them.
+        *
+        * @param  string|int    $expiration    The dirty expiration time.
+        * @return string|int                   The sanitized expiration time.
+        */
+       public function sanitize_expiration( $expiration ) {
+               if ( $expiration > $this->thirty_days && $expiration <= $this->now ) {
+                       $expiration = $expiration + $this->now;
+               }
+
+               return $expiration;
+       }
+
+       /**
+        * Concatenates two values and casts to type of the first value.
+        *
+        * This is used in append and prepend operations to match how these functions are handled
+        * by memcached. In both cases, whichever value is the original value in the combined value
+        * will dictate the type of the combined value.
+        *
+        * @param   mixed       $original   Original value that dictates the combined type.
+        * @param   mixed       $pended     Value to combine with original value.
+        * @param   string      $direction  Either 'pre' or 'app'.
+        * @return  mixed                   Combined value casted to the type of the first value.
+        */
+       public function combine_values( $original, $pended, $direction ) {
+               $type = gettype( $original );
+
+               // Combine the values based on direction of the "pend"
+               if ( 'pre' == $direction )
+                       $combined = $pended . $original;
+               else
+                       $combined = $original . $pended;
+
+               // Cast type of combined value
+               settype( $combined, $type );
+
+               return $combined;
+       }
+
+       /**
+        * Simple wrapper for saving object to the internal cache.
+        *
+        * @param   string      $derived_key    Key to save value under.
+        * @param   mixed       $value          Object value.
+        */
+       public function add_to_internal_cache( $derived_key, $value ) {
+               if ( is_object( $value ) ) {
+                       $value = clone $value;
+               }
+
+               $this->cache[$derived_key] = $value;
+       }
+
+       /**
+        * Determines if a no_mc_group exists in a group of groups.
+        *
+        * @param   mixed   $groups     The groups to search.
+        * @return  bool                True if a no_mc_group is present; false if a no_mc_group is not present.
+        */
+       public function contains_no_mc_group( $groups ) {
+               if ( is_scalar( $groups ) )
+                       return in_array( $groups, $this->no_mc_groups );
+
+               if ( ! is_array( $groups ) )
+                       return false;
+
+               foreach ( $groups as $group ) {
+                       if ( in_array( $group, $this->no_mc_groups ) )
+                               return true;
+               }
+
+               return false;
+       }
+
+       /**
+        * Add global groups.
+        *
+        * @author  Ryan Boren   This function comes straight from the original WP Memcached Object cache
+        * @link    http://wordpress.org/extend/plugins/memcached/
+        *
+        * @param   array       $groups     Array of groups.
+        * @return  void
+        */
+       public function add_global_groups( $groups ) {
+               if ( ! is_array( $groups ) )
+                       $groups = (array) $groups;
+
+               $this->global_groups = array_merge( $this->global_groups, $groups);
+               $this->global_groups = array_unique( $this->global_groups );
+       }
+
+       /**
+        * Add non-persistent groups.
+        *
+        * @author  Ryan Boren   This function comes straight from the original WP Memcached Object cache
+        * @link    http://wordpress.org/extend/plugins/memcached/
+        *
+        * @param   array       $groups     Array of groups.
+        * @return  void
+        */
+       public function add_non_persistent_groups( $groups ) {
+               if ( ! is_array( $groups ) )
+                       $groups = (array) $groups;
+
+               $this->no_mc_groups = array_merge( $this->no_mc_groups, $groups );
+               $this->no_mc_groups = array_unique( $this->no_mc_groups );
+       }
+
+       /**
+        * Get a value specifically from the internal, run-time cache, not memcached.
+        *
+        * @param   int|string  $key        Key value.
+        * @param   int|string  $group      Group that the value belongs to.
+        * @return  bool|mixed              Value on success; false on failure.
+        */
+       public function get_from_runtime_cache( $key, $group ) {
+               $derived_key = $this->buildKey( $key, $group );
+
+               if ( isset( $this->cache[$derived_key] ) )
+                       return $this->cache[$derived_key];
+
+               return false;
+       }
+
+       /**
+        * Switch blog prefix, which changes the cache that is accessed.
+        *
+        * @param  int     $blog_id    Blog to switch to.
+        * @return void
+        */
+       public function switch_to_blog( $blog_id ) {
+               global $table_prefix;
+               $blog_id           = (int) $blog_id;
+               $this->blog_prefix = ( is_multisite() ? $blog_id : $table_prefix ) . ':';
+       }
+}
</ins></span></pre></div>
<a id="branches47testsphpunittestsajaxCustomizeMenusphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: branches/4.7/tests/phpunit/tests/ajax/CustomizeMenus.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.7/tests/phpunit/tests/ajax/CustomizeMenus.php  2020-11-06 17:34:44 UTC (rev 49531)
+++ branches/4.7/tests/phpunit/tests/ajax/CustomizeMenus.php    2020-11-06 17:37:22 UTC (rev 49532)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -54,7 +54,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( 'administrator' != $role ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        // If we're not an admin, we should get a wp_die(-1).
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $this->setExpectedException( 'WPAjaxDieStopException' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                wp_set_current_user( self::factory()->user->create( array( 'role' => $role ) ) );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -429,7 +429,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( 'administrator' != $role ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        // If we're not an admin, we should get a wp_die(-1).
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $this->setExpectedException( 'WPAjaxDieStopException' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                wp_set_current_user( self::factory()->user->create( array( 'role' => $role ) ) );
</span></span></pre></div>
<a id="branches47testsphpunittestsimageeditor_imagickphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: branches/4.7/tests/phpunit/tests/image/editor_imagick.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.7/tests/phpunit/tests/image/editor_imagick.php 2020-11-06 17:34:44 UTC (rev 49531)
+++ branches/4.7/tests/phpunit/tests/image/editor_imagick.php   2020-11-06 17:37:22 UTC (rev 49532)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -465,7 +465,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $editor = new WP_Image_Editor_Imagick( $file );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertNotInstanceOf( 'WP_Error', $editor );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $editor->load();
</span><span class="cx" style="display: block; padding: 0 10px">                $editor->resize( 5, 5 );
</span><span class="cx" style="display: block; padding: 0 10px">                $save_to_file = tempnam( get_temp_dir(), '' ) . '.png';
</span></span></pre></div>
<a id="branches47toolslocalenvdefaulttemplate"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: branches/4.7/tools/local-env/default.template</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.7/tools/local-env/default.template                             (rev 0)
+++ branches/4.7/tools/local-env/default.template       2020-11-06 17:37:22 UTC (rev 49532)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,32 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+server {
+       index index.php index.html;
+
+       listen 80 default_server;
+       listen [::]:80 default_server;
+
+       server_name localhost;
+
+       client_max_body_size 1g;
+
+       error_log  /var/log/nginx/error.log;
+       access_log /var/log/nginx/access.log;
+
+       root /var/www/${LOCAL_DIR};
+
+       absolute_redirect off;
+
+       location / {
+               try_files $uri $uri/ /index.php?$args;
+       }
+
+       location ~ \.php$ {
+               try_files $uri =404;
+               fastcgi_split_path_info ^(.+\.php)(/.+)$;
+               fastcgi_pass php:9000;
+               fastcgi_index index.php;
+               include fastcgi_params;
+               fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+               fastcgi_param PATH_INFO $fastcgi_path_info;
+               fastcgi_pass_header Authorization;
+       }
+}
</ins></span></pre></div>
<a id="branches47toolslocalenvmysqlinitsql"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: branches/4.7/tools/local-env/mysql-init.sql</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.7/tools/local-env/mysql-init.sql                               (rev 0)
+++ branches/4.7/tools/local-env/mysql-init.sql 2020-11-06 17:37:22 UTC (rev 49532)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,8 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/**
+ * MySQL server init.
+ *
+ * SQL queries in this file will be executed the first time the MySQL server is started.
+ */
+
+CREATE DATABASE IF NOT EXISTS wordpress_develop;
+CREATE DATABASE IF NOT EXISTS wordpress_develop_tests;
</ins></span></pre></div>
<a id="branches47toolslocalenvphpconfigini"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: branches/4.7/tools/local-env/php-config.ini</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.7/tools/local-env/php-config.ini                               (rev 0)
+++ branches/4.7/tools/local-env/php-config.ini 2020-11-06 17:37:22 UTC (rev 49532)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,2 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+upload_max_filesize = 1G
+post_max_size = 1G
</ins></span></pre></div>
<a id="branches47toolslocalenvphpunitconfigini"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: branches/4.7/tools/local-env/phpunit-config.ini</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.7/tools/local-env/phpunit-config.ini                           (rev 0)
+++ branches/4.7/tools/local-env/phpunit-config.ini     2020-11-06 17:37:22 UTC (rev 49532)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,6 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+upload_max_filesize = 1G
+post_max_size = 1G
+
+opcache.enable = 1
+opcache.enable_cli = 1
+opache.file_cache = /tmp/php-opcache
</ins></span></pre></div>
<a id="branches47toolslocalenvscriptsdockerjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: branches/4.7/tools/local-env/scripts/docker.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.7/tools/local-env/scripts/docker.js                            (rev 0)
+++ branches/4.7/tools/local-env/scripts/docker.js      2020-11-06 17:37:22 UTC (rev 49532)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,8 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+const dotenv       = require( 'dotenv' );
+const dotenvExpand = require( 'dotenv-expand' );
+const { execSync } = require( 'child_process' );
+
+dotenvExpand( dotenv.config() );
+
+// Execute any docker-compose command passed to this script.
+execSync( 'docker-compose ' + process.argv.slice( 2 ).join( ' ' ), { stdio: 'inherit' } );
</ins></span></pre></div>
<a id="branches47toolslocalenvscriptsinstalljs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: branches/4.7/tools/local-env/scripts/install.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.7/tools/local-env/scripts/install.js                           (rev 0)
+++ branches/4.7/tools/local-env/scripts/install.js     2020-11-06 17:37:22 UTC (rev 49532)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,47 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+const dotenv       = require( 'dotenv' );
+const dotenvExpand = require( 'dotenv-expand' );
+const wait_on = require( 'wait-on' );
+const { execSync } = require( 'child_process' );
+const { renameSync, readFileSync, writeFileSync } = require( 'fs' );
+
+dotenvExpand( dotenv.config() );
+
+// Create wp-config.php.
+wp_cli( 'config create --dbname=wordpress_develop --dbuser=root --dbpass=password --dbhost=mysql --path=/var/www/src --force' );
+
+// Add the debug settings to wp-config.php.
+// Windows requires this to be done as an additional step, rather than using the --extra-php option in the previous step.
+wp_cli( `config set WP_DEBUG ${process.env.LOCAL_WP_DEBUG} --raw --type=constant` );
+wp_cli( `config set WP_DEBUG_LOG ${process.env.LOCAL_WP_DEBUG_LOG} --raw --type=constant` );
+wp_cli( `config set WP_DEBUG_DISPLAY ${process.env.LOCAL_WP_DEBUG_DISPLAY} --raw --type=constant` );
+wp_cli( `config set SCRIPT_DEBUG ${process.env.LOCAL_SCRIPT_DEBUG} --raw --type=constant` );
+wp_cli( `config set WP_ENVIRONMENT_TYPE ${process.env.LOCAL_WP_ENVIRONMENT_TYPE} --type=constant` );
+
+// Move wp-config.php to the base directory, so it doesn't get mixed up in the src or build directories.
+renameSync( 'src/wp-config.php', 'wp-config.php' );
+
+// Read in wp-tests-config-sample.php, edit it to work with our config, then write it to wp-tests-config.php.
+const testConfig = readFileSync( 'wp-tests-config-sample.php', 'utf8' )
+       .replace( 'youremptytestdbnamehere', 'wordpress_develop_tests' )
+       .replace( 'yourusernamehere', 'root' )
+       .replace( 'yourpasswordhere', 'password' )
+       .replace( 'localhost', 'mysql' )
+       .concat( "\ndefine( 'FS_METHOD', 'direct' );\n" );
+
+writeFileSync( 'wp-tests-config.php', testConfig );
+
+// Once the site is available, install WordPress!
+wait_on( { resources: [ `tcp:localhost:${process.env.LOCAL_PORT}`] } )
+       .then( () => {
+               wp_cli( 'db reset --yes' );
+               wp_cli( `core install --title="WordPress Develop" --admin_user=admin --admin_password=password --admin_email=test@test.com --skip-email --url=http://localhost:${process.env.LOCAL_PORT}` );
+       } );
+
+/**
+ * Runs WP-CLI commands in the Docker environment.
+ *
+ * @param {string} cmd The WP-CLI command to run.
+ */
+function wp_cli( cmd ) {
+       execSync( `docker-compose run --rm cli ${cmd}`, { stdio: 'inherit' } );
+}
</ins></span></pre></div>
<a id="branches47toolslocalenvscriptsstartjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: branches/4.7/tools/local-env/scripts/start.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- branches/4.7/tools/local-env/scripts/start.js                             (rev 0)
+++ branches/4.7/tools/local-env/scripts/start.js       2020-11-06 17:37:22 UTC (rev 49532)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,36 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+const dotenv       = require( 'dotenv' );
+const dotenvExpand = require( 'dotenv-expand' );
+const { execSync } = require( 'child_process' );
+
+dotenvExpand( dotenv.config() );
+
+// Start the local-env containers.
+execSync( 'docker-compose up -d wordpress-develop', { stdio: 'inherit' } );
+
+// If Docker Toolbox is being used, we need to manually forward LOCAL_PORT to the Docker VM.
+if ( process.env.DOCKER_TOOLBOX_INSTALL_PATH ) {
+       // VBoxManage is added to the PATH on every platform except Windows.
+       const vboxmanage = process.env.VBOX_MSI_INSTALL_PATH ? `${ process.env.VBOX_MSI_INSTALL_PATH }/VBoxManage` : 'VBoxManage'
+
+       // Check if the port forwarding is already configured for this port.
+       const vminfoBuffer = execSync( `"${ vboxmanage }" showvminfo "${ process.env.DOCKER_MACHINE_NAME }" --machinereadable` );
+       const vminfo = vminfoBuffer.toString().split( /[\r\n]+/ );
+
+       vminfo.forEach( ( info ) => {
+               if ( ! info.startsWith( 'Forwarding' ) ) {
+                       return;
+               }
+
+               // `info` is in the format: Forwarding(1)="tcp-port8889,tcp,127.0.0.1,8889,,8889"
+               // Parse it down so `rule` only contains the data inside quotes, split by ','.
+               const rule = info.replace( /(^.*?"|"$)/, '' ).split( ',' );
+
+               // Delete rules that are using the port we need.
+               if ( rule[ 3 ] === process.env.LOCAL_PORT || rule[ 5 ] === process.env.LOCAL_PORT ) {
+                       execSync( `"${ vboxmanage }" controlvm "${ process.env.DOCKER_MACHINE_NAME }" natpf1 delete ${ rule[ 0 ] }`, { stdio: 'inherit' } );
+               }
+       } );
+
+       // Add our port forwarding rule.
+       execSync( `"${ vboxmanage }" controlvm "${ process.env.DOCKER_MACHINE_NAME }" natpf1 "tcp-port${ process.env.LOCAL_PORT },tcp,127.0.0.1,${ process.env.LOCAL_PORT },,${ process.env.LOCAL_PORT }"`, { stdio: 'inherit' } );
+}
</ins></span></pre>
</div>
</div>

</body>
</html>