├── .coveralls.yml ├── .dev-lib ├── .editorconfig ├── .env.sample ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .gitmodules ├── .jscsrc ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── bin ├── build.sh ├── deploy.sh ├── down ├── mysql-export ├── mysql-import ├── phpcbf ├── phpcs ├── phpunit ├── plugin ├── run ├── shared └── up ├── composer.json ├── config ├── mysql │ └── wptests.sql ├── nginx │ ├── conf.d │ │ └── local.conf │ └── ssl │ │ ├── local.cert │ │ └── local.key └── php │ └── conf.d │ └── wordpress.ini ├── data └── .gitkeep ├── docker-compose.yml ├── docker └── php │ └── Dockerfile ├── logs └── .gitkeep ├── package.json ├── readme.md ├── wp-config ├── proxy.php ├── redis.php ├── security.php └── theme.php ├── wp-content ├── advanced-cache.php ├── mu-plugins │ ├── advanced-post-cache.php │ └── opcache.php ├── object-cache.php └── plugins │ └── foo-bar │ ├── foo-bar.php │ ├── instance.php │ ├── php │ ├── class-exception.php │ ├── class-plugin-base.php │ └── class-plugin.php │ ├── readme.md │ └── tests │ ├── class-test-foo-bar.php │ └── php │ ├── class-test-plugin-base.php │ └── class-test-plugin.php └── wp-tests ├── phpunit-bootstrap.php ├── phpunit.xml.dist └── wp-tests-config.php /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | coverage_clover: build/logs/clover.xml 3 | json_path: build/logs/coveralls-upload.json 4 | -------------------------------------------------------------------------------- /.dev-lib: -------------------------------------------------------------------------------- 1 | PROJECT_TYPE=site 2 | PHPUNIT_CONFIG='/var/www/html/wp-tests/phpunit.xml.dist' 3 | WPCS_STANDARD=WordPress-Core,WordPress-Extra,WordPress-Docs,WordPress-VIP 4 | PHPCS_IGNORE='node_modules/*,div-lib/*,vendor/*,wordpress/*,mu-plugins/*,advanced-cache.php,db.php,opchache/*,query-monitor/*,wp-redis/*,twentyseventeen/*' 5 | DEFAULT_BASE_BRANCH=develop -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | dev-lib/.editorconfig -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | # MySQL 2 | MYSQL_ROOT_PASSWORD=root 3 | MYSQL_DATABASE=wordpress 4 | MYSQL_USER=wordpress 5 | MYSQL_PASSWORD=wordpress 6 | 7 | # WordPress 8 | WP_ENV=dev 9 | WP_DB_HOST=mysql 10 | WP_DB_NAME=wordpress 11 | WP_DB_USER=wordpress 12 | WP_DB_PASSWORD=wordpress 13 | WP_THEME_NAME=twentyseventeen 14 | WP_DOMAIN=wp.local 15 | WP_ADMIN_USER=wordpress 16 | WP_ADMIN_PASSWORD=wordpress 17 | WP_ADMIN_EMAIL=dev@wp.local 18 | WP_VERSION=4.7.3 19 | WP_TITLE="WP Docker" 20 | WP_DEBUG=1 21 | FORCE_SSL_ADMIN=1 22 | 23 | # Xdebug 24 | XDEBUG_CONFIG=remote_host=127.0.0.1 idekey=PHPSTORM 25 | PHP_IDE_CONFIG=serverName=wp.local -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dev-lib/.eslintignore -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | dev-lib/.eslintrc -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # WordPress 2 | wordpress 3 | wp-config/secrets.php 4 | wp-content/themes/twentyseventeen 5 | wp-content/upgrade 6 | wp-content/uploads 7 | 8 | # Test Suite 9 | wp-tests/coverage 10 | wp-tests/data 11 | wp-tests/includes 12 | 13 | # Docker 14 | .env 15 | data/* 16 | docker-custom.yml 17 | logs/* 18 | 19 | # Composer 20 | composer.lock 21 | wp-content/db.php 22 | wp-content/plugins/opcache 23 | wp-content/plugins/query-monitor 24 | wp-content/plugins/wp-redis 25 | vendor 26 | 27 | # Development 28 | .DS_Store 29 | .idea 30 | node_modules 31 | 32 | # DB Migrations 33 | config/mysql/* 34 | !config/mysql/wptests.sql 35 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dev-lib"] 2 | path = dev-lib 3 | url = https://github.com/xwp/wp-dev-lib.git 4 | branch = master 5 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | dev-lib/.jscsrc -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | **/*.min.js 2 | **/node_modules/** 3 | **/vendor/** 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | dev-lib/.jshintrc -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | language: 4 | - php 5 | 6 | php: 7 | - 7.1.3 8 | 9 | node_js: 10 | - stable 11 | 12 | env: 13 | - WP_VERSION=latest WP_MULTISITE=0 14 | 15 | install: 16 | - sudo apt-get install -y libxml2-utils 17 | - export TRAVIS_PHPUNIT_CONFIG=${TRAVIS_BUILD_DIR}/wp-tests/phpunit.xml.dist 18 | - export DEV_LIB_PATH=dev-lib 19 | - if [ ! -e "$DEV_LIB_PATH" ] && [ -L .travis.yml ]; then export DEV_LIB_PATH=$( dirname $( readlink .travis.yml ) ); fi 20 | - source $DEV_LIB_PATH/travis.install.sh 21 | 22 | script: 23 | - source $DEV_LIB_PATH/travis.script.sh 24 | 25 | after_script: 26 | - source $DEV_LIB_PATH/travis.after_script.sh 27 | 28 | deploy: 29 | - provider: script 30 | script: bin/deploy.sh 31 | on: 32 | branch: master -------------------------------------------------------------------------------- /bin/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | USAGE="[--help|-h] [--with-tests|-t] [--deploy|-d]" 4 | 5 | ## Process cli params 6 | for p in "$@"; 7 | do 8 | case $p in 9 | --help|-h) 10 | echo "Usage: $0 ${USAGE}" 11 | exit 2; 12 | ;; 13 | --with-tests|-t) 14 | TASK="tests" 15 | ;; 16 | --deploy|-d) 17 | TASK="deploy" 18 | ;; 19 | *) 20 | echo "Invalid parameter ${p}" 21 | echo "Usage: $0 ${USAGE}" 22 | exit 2; 23 | ;; 24 | esac 25 | done 26 | 27 | export WP_CORE_DIR="/var/www/html/wordpress" 28 | export WP_CONTENT_DIR="/var/www/html/wp-content" 29 | export WP_TESTS_DIR="/var/www/html/wp-tests" 30 | 31 | # allow any of these "Authentication Unique Keys and Salts." to be specified via 32 | # environment variables with a "WP_" prefix (ie, "WP_AUTH_KEY") 33 | uniqueEnvs=( 34 | AUTH_KEY 35 | SECURE_AUTH_KEY 36 | LOGGED_IN_KEY 37 | NONCE_KEY 38 | AUTH_SALT 39 | SECURE_AUTH_SALT 40 | LOGGED_IN_SALT 41 | NONCE_SALT 42 | ) 43 | envs=( 44 | MYSQL_DATABASE 45 | MYSQL_USER 46 | MYSQL_PASSWORD 47 | WP_DB_HOST 48 | WP_DB_USER 49 | WP_DB_PASSWORD 50 | WP_DB_NAME 51 | WP_TABLE_PREFIX 52 | WP_DEBUG 53 | WP_DOMAIN 54 | WP_TITLE 55 | WP_ADMIN_USER 56 | WP_ADMIN_PASSWORD 57 | WP_ADMIN_EMAIL 58 | WP_VERSION 59 | "${uniqueEnvs[@]/#/WP_}" 60 | ) 61 | 62 | function is_active_theme() { 63 | RESULT=`wp theme list \ 64 | --status=active \ 65 | --fields=name \ 66 | --format=csv \ 67 | --allow-root \ 68 | --path=${WP_CORE_DIR} \ 69 | | tail -1 \ 70 | 2>/dev/null` 71 | 72 | if [ "$RESULT" != "${WP_THEME_NAME}" ]; then 73 | return 0 74 | else 75 | return 1 76 | fi 77 | } 78 | 79 | function is_db_up() { 80 | RESULT=`mysql \ 81 | -h ${WP_DB_HOST%:*} \ 82 | -P${WP_DB_HOST#*:} \ 83 | -u ${WP_DB_USER} \ 84 | -p${WP_DB_PASSWORD} \ 85 | --skip-column-names \ 86 | -e "SHOW DATABASES LIKE '${WP_DB_NAME}'" \ 87 | 2>/dev/null` 88 | 89 | if [ "$RESULT" == "${WP_DB_NAME}" ]; then 90 | return 0 91 | else 92 | return 1 93 | fi 94 | } 95 | 96 | until is_db_up; do 97 | echo "Waiting for database to become available..." 98 | sleep 5 99 | done 100 | 101 | echo "Database is available. Continuing..." 102 | 103 | # Download WordPress 104 | if [ ! -e wp-config.php ] || [ ${WP_VERSION} != $(wp core version --allow-root --path=${WP_CORE_DIR}) ]; then 105 | wp core download \ 106 | --allow-root \ 107 | --path=${WP_CORE_DIR} \ 108 | --version="${WP_VERSION}" \ 109 | --force 110 | fi 111 | 112 | # Build config 113 | echo 114 | echo "Setup wp-config.php..." 115 | 116 | # version 4.4.1 decided to switch to windows line endings, that breaks our seds and awks 117 | # https://github.com/docker-library/wordpress/issues/116 118 | # https://github.com/WordPress/WordPress/commit/1acedc542fba2482bab88ec70d4bea4b997a92e4 119 | sed -ri -e 's/\r$//' wp-config* 120 | 121 | if [ ! -e wp-config.php ]; then 122 | awk '/^\/\*.*stop editing.*\*\/$/ && c == 0 { c = 1; system("cat") } { print }' wp-config-sample.php > wp-config.php <<'EOPHP' 123 | // Load the config files. 124 | foreach ( glob( '/var/www/html/wp-config/*.php' ) as $config ) { 125 | require( $config ); 126 | } 127 | 128 | // Set the content directory. 129 | define( 'WP_CONTENT_DIR', "/var/www/html/wp-content" ); 130 | 131 | EOPHP 132 | chown www-data:www-data wp-config.php 133 | fi 134 | 135 | # see http://stackoverflow.com/a/2705678/433558 136 | sed_escape_lhs() { 137 | echo "$@" | sed -e 's/[]\/$*.^|[]/\\&/g' 138 | } 139 | sed_escape_rhs() { 140 | echo "$@" | sed -e 's/[\/&]/\\&/g' 141 | } 142 | php_escape() { 143 | php -r 'var_export(('$2') $argv[1]);' -- "$1" 144 | } 145 | set_config() { 146 | key="$1" 147 | value="$2" 148 | var_type="${3:-string}" 149 | start="(['\"])$(sed_escape_lhs "$key")\2\s*," 150 | end="\);" 151 | if [ "${key:0:1}" = '$' ]; then 152 | start="^(\s*)$(sed_escape_lhs "$key")\s*=" 153 | end=";" 154 | fi 155 | sed -ri -e "s/($start\s*).*($end)$/\1$(sed_escape_rhs "$(php_escape "$value" "$var_type")")\3/" wp-config.php 156 | } 157 | 158 | set_config 'DB_HOST' "${WP_DB_HOST}" 159 | set_config 'DB_USER' "${WP_DB_USER}" 160 | set_config 'DB_PASSWORD' "${WP_DB_PASSWORD}" 161 | set_config 'DB_NAME' "${WP_DB_NAME}" 162 | 163 | for unique in "${uniqueEnvs[@]}"; do 164 | uniqVar="WP_$unique" 165 | if [ -n "${!uniqVar}" ]; then 166 | set_config "$unique" "${!uniqVar}" 167 | else 168 | # if not specified, let's generate a random value 169 | currentVal="$(sed -rn -e "s/define\((([\'\"])$unique\2\s*,\s*)(['\"])(.*)\3\);/\4/p" wp-config.php)" 170 | if [ "$currentVal" = 'put your unique phrase here' ]; then 171 | set_config "$unique" "$(head -c1m /dev/urandom | sha1sum | cut -d' ' -f1)" 172 | fi 173 | fi 174 | done 175 | 176 | if [ "$WP_TABLE_PREFIX" ]; then 177 | set_config '$table_prefix' "$WP_TABLE_PREFIX" 178 | fi 179 | 180 | if [ "$WP_DEBUG" ]; then 181 | set_config 'WP_DEBUG' 1 boolean 182 | fi 183 | 184 | if [ "$WP_ENV" ]; then 185 | awk '/^\/\*.*stop editing.*\*\/$/ && c == 0 { c = 1; system("cat") } { print }' wp-config.php > wp-config.tmp <<'EOPHP' 186 | // Set the environment. 187 | define( 'WP_ENV', getenv('WP_ENV') ); 188 | EOPHP 189 | mv wp-config.tmp wp-config.php 190 | fi 191 | 192 | # Install Core 193 | if ! $(wp core is-installed --allow-root --path=${WP_CORE_DIR}); then 194 | echo 195 | echo "Installing WordPress..." 196 | wp core install \ 197 | --allow-root \ 198 | --path=${WP_CORE_DIR} \ 199 | --url=${WP_DOMAIN} \ 200 | --title="${WP_TITLE}" \ 201 | --admin_user=${WP_ADMIN_USER} \ 202 | --admin_password=${WP_ADMIN_PASSWORD} \ 203 | --admin_email=${WP_ADMIN_EMAIL} \ 204 | --skip-email 205 | fi 206 | 207 | if [ "${TASK}" == "tests" ]; then 208 | 209 | # Generate the tests SVN tag 210 | if [[ ${WP_VERSION} =~ [0-9]+\.[0-9]+(\.[0-9]+)? ]]; then 211 | WP_TESTS_TAG="tags/${WP_VERSION}" 212 | elif [[ ${WP_VERSION} == 'nightly' || ${WP_VERSION} == 'trunk' ]]; then 213 | WP_TESTS_TAG="trunk" 214 | else 215 | # http serves a single offer, whereas https serves multiple. we only want one 216 | download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json 217 | grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json 218 | LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') 219 | if [[ -z "$LATEST_VERSION" ]]; then 220 | echo "Latest WordPress version could not be found" 221 | exit 1 222 | fi 223 | WP_TESTS_TAG="tags/$LATEST_VERSION" 224 | fi 225 | 226 | # Set up testing suite if it doesn't yet exist 227 | echo 228 | if [ ! -d $WP_TESTS_DIR ]; then 229 | echo "Creating WordPress Test Suite Directory..." 230 | 231 | # set up testing suite 232 | mkdir -p $WP_TESTS_DIR 233 | fi 234 | 235 | echo "Updating WordPress Test Suite..." 236 | 237 | svn co https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes --trust-server-cert 238 | svn co https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data --trust-server-cert 239 | fi 240 | 241 | # Ensure the plugin and theme directories exist 242 | mkdir -p ${WP_CONTENT_DIR}/themes 243 | mkdir -p ${WP_CONTENT_DIR}/plugins 244 | 245 | # Checking out the default theme 246 | echo "Checking out default theme..." 247 | wp theme install ${WP_THEME_NAME} --allow-root --force 248 | 249 | if [ ! is_active_theme ]; then 250 | wp theme activate ${WP_THEME_NAME} --allow-root 251 | fi 252 | 253 | echo 254 | echo "Activating Plugins..." 255 | 256 | wp plugin activate \ 257 | opcache \ 258 | query-monitor \ 259 | wp-redis \ 260 | --allow-root 261 | 262 | echo 263 | echo "Done!" 264 | 265 | echo 266 | grep "${WP_DOMAIN}" /etc/hosts > /dev/null || echo "Be sure to add '127.0.0.1 ${WP_DOMAIN}' to your /etc/hosts file" 267 | 268 | # Let's clear out the relevant environment variables (so that stray "phpinfo()" calls don't leak secrets from our code) 269 | for e in "${envs[@]}"; do 270 | if [[ "XDEBUG_CONFIG|PHP_IDE_CONFIG|WP_ENV" =~ "$e" ]]; then 271 | continue 272 | fi 273 | unset "$e" 274 | done 275 | 276 | php-fpm -F 277 | -------------------------------------------------------------------------------- /bin/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -v 5 | 6 | docker login -u $DOCKER_USER -p $DOCKER_PASS 7 | docker image build -t $DOCKER_REPO:latest -t $DOCKER_REPO:7.1-fpm-alpine ./docker/php 8 | docker push $DOCKER_REPO 9 | -------------------------------------------------------------------------------- /bin/down: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Stop and remove containers 4 | echo 5 | echo "Stopping and removing containers..." 6 | docker-compose down 7 | 8 | echo 9 | echo "Done" 10 | -------------------------------------------------------------------------------- /bin/mysql-export: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S") 4 | 5 | while IFS= read -r line 6 | do 7 | export $(echo -e "$line" | sed -e 's/[[:space:]]*$//') 8 | done < <(docker-compose run --rm php env | grep WP_DB_) 9 | 10 | mkdir -p config/mysql/backups 11 | 12 | echo 13 | echo "Exporting the '${WP_DB_NAME}' database..." 14 | echo "Generating config/mysql/backups/${WP_DB_NAME}-${TIMESTAMP}.sql..." 15 | 16 | docker-compose run --rm php mysqldump --opt \ 17 | -h ${WP_DB_HOST%:*} \ 18 | -P${WP_DB_HOST#*:} \ 19 | -u ${WP_DB_USER} \ 20 | -p${WP_DB_PASSWORD} \ 21 | ${WP_DB_NAME} \ 22 | > config/mysql/backups/${WP_DB_NAME}-${TIMESTAMP}.sql 23 | 24 | echo "Done!" 25 | echo 26 | -------------------------------------------------------------------------------- /bin/mysql-import: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$*" ]; then 4 | echo "Waring: The SQL file path is a required parameter" 5 | else 6 | export TIMESTAMP=$(date +"%Y-%m-%d_%H:%M:%S") 7 | 8 | while IFS= read -r line 9 | do 10 | export $(echo -e "$line" | sed -e 's/[[:space:]]*$//') 11 | done < <(docker-compose run --rm php env | grep WP_DB_) 12 | 13 | echo 14 | echo "Importing into the '${WP_DB_NAME}' database..." 15 | 16 | docker-compose run --rm php mysql \ 17 | -h ${WP_DB_HOST%:*} \ 18 | -P${WP_DB_HOST#*:} \ 19 | -u ${WP_DB_USER} \ 20 | -p${WP_DB_PASSWORD} \ 21 | ${WP_DB_NAME} \ 22 | < "$@" 23 | 24 | echo "Done!" 25 | echo 26 | fi 27 | -------------------------------------------------------------------------------- /bin/phpcbf: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source "$(dirname "$0")/shared" 4 | 5 | sniffs phpcbf $@ 6 | -------------------------------------------------------------------------------- /bin/phpcs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source "$(dirname "$0")/shared" 4 | 5 | sniffs phpcs $@ 6 | -------------------------------------------------------------------------------- /bin/phpunit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$*" ]; then 4 | 5 | while IFS= read -r line 6 | do 7 | export $(echo -e "$line" | sed -e 's/[[:space:]]*$//' -e "s/'//g") 8 | done < <(cat .dev-lib | grep PHPUNIT_CONFIG) 9 | 10 | docker-compose run --rm php phpunit -c ${PHPUNIT_CONFIG} 11 | else 12 | docker-compose run --rm php phpunit "$@" 13 | fi 14 | 15 | -------------------------------------------------------------------------------- /bin/plugin: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Usage: bin/plugin "Hello World" 4 | # Creates a directory "hello-world" in the plugins directory, 5 | # performing substitutions on the scaffold "foo-bar" plugin. 6 | 7 | set -e 8 | 9 | if [ $# != 1 ]; then 10 | echo "You must only supply one argument, the plugin name." 11 | exit 1 12 | fi 13 | 14 | name="$1" 15 | if [ -z "$name" ]; then 16 | echo "Provide name argument" 17 | exit 1 18 | fi 19 | 20 | if ! perl -pe '/^[A-Z][a-z0-9]*( [A-Z][a-z0-9]*)*$/ || exit 1;' > /dev/null <<< "$name"; then 21 | echo "Malformed name argument '$name'. Please use title case words separated by spaces. No hypens." 22 | exit 1 23 | fi 24 | 25 | slug=$( perl -pe '$_ = lc; s/ /-/g' <<< "$name" ) 26 | prefix=$( perl -pe '$_ = lc; s/ /_/g' <<< "$name" ) 27 | namespace=$( perl -pe 's/ //g' <<< "$name" ) 28 | class=$( perl -pe 's/ /_/g' <<< "$name" ) 29 | 30 | plugins_path="wp-content/plugins" 31 | src_path="$plugins_path/foo-bar" 32 | dest_path="$plugins_path/$slug" 33 | 34 | echo "Name: $name" 35 | echo "Slug: $slug" 36 | echo "Prefix: $prefix" 37 | echo "NS: $namespace" 38 | echo "Class: $class" 39 | 40 | cp -r $src_path $dest_path 41 | 42 | cd "$dest_path" 43 | mv foo-bar.php "$slug.php" 44 | cd tests 45 | mv test-foo-bar.php "test-$slug.php" 46 | cd .. 47 | 48 | perl -p -i'' -e "s/Foo Bar/$name/g" $( find */ -type f ) *.* 49 | perl -p -i'' -e "s/foo-bar/$slug/g" $( find */ -type f ) *.* 50 | perl -p -i'' -e "s/foo_bar/$prefix/g" $( find */ -type f ) *.* 51 | perl -p -i'' -e "s/FooBar/$namespace/g" $( find */ -type f ) *.* 52 | perl -p -i'' -e "s/Foo_Bar/$class/g" $( find */ -type f ) *.* 53 | 54 | echo "Plugin is located at:" 55 | pwd 56 | -------------------------------------------------------------------------------- /bin/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose run --rm php $@ 4 | -------------------------------------------------------------------------------- /bin/shared: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function sniffs { 4 | if [ -z "$2" ]; then 5 | echo "Waring: The file path is a required parameter" 6 | else 7 | COMMAND="phpcs" 8 | if [ $1 = 'phpcbf' ]; then 9 | COMMAND="phpcbf --no-patch" 10 | fi 11 | 12 | if [[ $2 != *"/var/www/html/"* ]]; then 13 | FILE_PATH="/var/www/html/$2" 14 | else 15 | FILE_PATH="$2" 16 | fi 17 | 18 | # Get the phpcs vars 19 | while IFS= read -r line 20 | do 21 | export $(echo -e "$line" | sed -e 's/[[:space:]]*$//' -e "s/'//g") 22 | done < <(cat .dev-lib | grep PHPCS_) 23 | 24 | while IFS= read -r line 25 | do 26 | export $(echo -e "$line" | sed -e 's/[[:space:]]*$//' -e "s/'//g") 27 | done < <(cat .dev-lib | grep WPCS_) 28 | 29 | docker-compose run --rm php ${COMMAND} --ignore=${PHPCS_IGNORE} --standard=${WPCS_STANDARD} ${FILE_PATH} 30 | fi 31 | } 32 | -------------------------------------------------------------------------------- /bin/up: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PARAMS="" 4 | if [ -z "$*" ]; then 5 | PARAMS="--build" 6 | fi 7 | 8 | # Build containers 9 | echo 10 | echo "Building containers..." 11 | docker-compose pull 12 | if [ -e "docker-custom.yml" ]; then 13 | docker-compose -f docker-compose.yml -f docker-custom.yml up ${PARAMS} $@ 14 | else 15 | docker-compose up ${PARAMS} $@ 16 | fi 17 | 18 | echo 19 | echo "Done" 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xwp/wp-docker", 3 | "license": "GPLv2", 4 | "repositories": [ 5 | { 6 | "type": "composer", 7 | "url": "https://wpackagist.org" 8 | } 9 | ], 10 | "authors": [ 11 | { 12 | "name": "Derek Herman", 13 | "email": "derek.herman@xwp.co" 14 | } 15 | ], 16 | "require": { 17 | "satooshi/php-coveralls": "dev-master", 18 | "wpackagist-plugin/opcache": "0.3.1", 19 | "wpackagist-plugin/query-monitor": "2.13.4", 20 | "wpackagist-plugin/wp-redis": "0.6.1" 21 | }, 22 | "extra": { 23 | "installer-paths": { 24 | "wp-content/plugins/{$name}/": ["type:wordpress-plugin"] 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /config/mysql/wptests.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS wptests; 2 | GRANT ALL ON wptests.* TO 'wptests'@'%' IDENTIFIED BY 'wptests'; 3 | FLUSH PRIVILEGES; -------------------------------------------------------------------------------- /config/nginx/conf.d/local.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default; 3 | listen 443 ssl; 4 | server_name _; 5 | 6 | root /var/www/html/wordpress; 7 | index index.php index.html; 8 | 9 | ssl_certificate /etc/ssl/local.cert; 10 | ssl_certificate_key /etc/ssl/local.key; 11 | 12 | access_log /srv/logs/nginx/access.log; 13 | error_log /srv/logs/nginx/error.log; 14 | 15 | # Block all requests to hidden files 16 | location ~ /\. { 17 | deny all; 18 | access_log off; 19 | } 20 | 21 | # Block PHP files in the uploads directory 22 | location ~* /(?:uploads|files)/.*.php$ { 23 | deny all; 24 | access_log off; 25 | } 26 | 27 | # upload config 28 | location ~ ^/wp-admin/(media-upload|media-new|async-upload|admin|admin-post|themes).php$ { 29 | client_max_body_size 128M; 30 | fastcgi_pass php:9000; 31 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 32 | include fastcgi_params; 33 | } 34 | 35 | location ~ ^/wp-content/(.*)$ { 36 | alias /var/www/html/wp-content/$1; 37 | expires 7d; 38 | } 39 | 40 | # Directives to send expires headers and turn off 404 error logging. 41 | location ~* .(?:js|css|png|jpg|jpeg|gif|ico|woff|ttf|svg)$ { 42 | expires max; 43 | log_not_found off; 44 | } 45 | 46 | location / { 47 | try_files $uri $uri/ /index.php?$args; 48 | } 49 | 50 | if (!-e $request_filename) { 51 | # Add trailing slash to */wp-admin requests. 52 | rewrite /wp-admin$ $scheme://$host$uri/ permanent; 53 | } 54 | 55 | location ~ \.php$ { 56 | try_files $uri /index.php; 57 | fastcgi_pass php:9000; 58 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 59 | include fastcgi_params; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /config/nginx/ssl/local.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDRTCCAi2gAwIBAgIJANtrQOrKX7iSMA0GCSqGSIb3DQEBBQUAMCAxHjAcBgNV 3 | BAMTFWJlYWNoYm9keWxpdmUudmFncmFudDAeFw0xNjA2MTUwMDExMzhaFw0yNjA2 4 | MTMwMDExMzhaMCAxHjAcBgNVBAMTFWJlYWNoYm9keWxpdmUudmFncmFudDCCASIw 5 | DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ3HVEGz5uqwhYuvUYW7y98Appk+ 6 | LE+RGMJY6+kkytHudrkE3BvAhBeKnBfj7oXP4BkcIsE12udCuirOFkFE4+Y+Rg7v 7 | VwSnnaXpq9qWbaDlqFXk4zT6oTeT9FhA4a3q0DD4D/tqH9pyq+YWHnBs8kiJg8VA 8 | DGeAFT3Q3hz1D78V9gQ8A7U9twYqETmyT9aiyeKmTYqU+QlhkHPdOjCffir5I4nj 9 | 2VueGbZWphEGZ0N+tfcvvX2eF94trhFir5sxW8Kb1Fz3JZkR6x1YnjSNeOhJsfo8 10 | qtfp9gP/w//nGpRZ72IX66p3uf/K1gP1hqzSUfIbTV3PBKkDGipcddxF1ckCAwEA 11 | AaOBgTB/MB0GA1UdDgQWBBTCI1x8KOM9d/NgMKerxpGBmqRyeTBQBgNVHSMESTBH 12 | gBTCI1x8KOM9d/NgMKerxpGBmqRyeaEkpCIwIDEeMBwGA1UEAxMVYmVhY2hib2R5 13 | bGl2ZS52YWdyYW50ggkA22tA6spfuJIwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B 14 | AQUFAAOCAQEAlum5OVVGBw+C842PxNa7UYJ0/syik2LQRY4Lx8SBdqzcLQbvhkgb 15 | +UvudMehyKS74Cvp8kBeEOxW3LlcdhA+vy+ZDfUuiC2agCNuJ0y9eX+7ZFXGrtSK 16 | xmntWN5ivmGeBO8ZuciXuywbckK1wjPuEHG3RFhhDxuRPl+qdzGvn0WSSbZxkLy4 17 | 5u+IaP2N2QofRZNZfyyXvNd6+WP+j/48azny5mFpbhQ8dbXe3BARwmpfiySjVzbo 18 | QOn/NnSNmVJH804EXyFFgQJNcMiQ9/uQQeSn6ZmZPq4MEV/SjBucRJOE2+Z/EFNU 19 | CCXBssSNuNfA9rLXmyCsdigPrv209OPfBg== 20 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /config/nginx/ssl/local.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAncdUQbPm6rCFi69RhbvL3wCmmT4sT5EYwljr6STK0e52uQTc 3 | G8CEF4qcF+Puhc/gGRwiwTXa50K6Ks4WQUTj5j5GDu9XBKedpemr2pZtoOWoVeTj 4 | NPqhN5P0WEDhrerQMPgP+2of2nKr5hYecGzySImDxUAMZ4AVPdDeHPUPvxX2BDwD 5 | tT23BioRObJP1qLJ4qZNipT5CWGQc906MJ9+KvkjiePZW54ZtlamEQZnQ3619y+9 6 | fZ4X3i2uEWKvmzFbwpvUXPclmRHrHVieNI146Emx+jyq1+n2A//D/+calFnvYhfr 7 | qne5/8rWA/WGrNJR8htNXc8EqQMaKlx13EXVyQIDAQABAoIBAF4oodDgExPGKryU 8 | 8RrVWzKHieT0JK+LUPJS7N7jNSMZo8KA8vag6nO+Ja9gOG2lBAEYOGmDwVK4ELAQ 9 | 81kaSsOSMG8jSJQfj8Z+8C9wDJaz18UC5tEsLWAkrPKqLcSD2KEQuUD6MW5Fdu7G 10 | Dn5bli/R2VCzC3QNDSAp8RPo7+/BtIGtEfAXftw/Zi78/axp17muNYcCBY+oJN8s 11 | NqAARv3sDfmcEsgNfezLZjLtXPjC3IYYMrmctVLTB1A5MfCnOI1P13buOLJP5/b9 12 | yJhtYNZUhFIxzh7GSt5CRXycY4vpe+IOs+NRLrt92qwn1fCvPzz6kV3AvBJc5rrB 13 | 8KM3noECgYEAyYdHYhMyQmHnqZIR/yYKFOU7d6f1C306GL0LdyA3x5iudFnwNF1t 14 | sXxh6ebT6nRONuRRYwx9Et0fCISTL9ieWZY0bO6HCnhMI27XkISbqz+qFFX+vhVi 15 | pR+nCPKOBSHaY5bcv+QQQ95Mdf/3KVgd0rl4/wbvMw/TxF3OqxMvFlECgYEAyGzK 16 | gdqJMBy6JRxoz9HlgQ4BBR9c9RwO7oaGvTRY0n/rFPIH/5TZ20Leaj1TcdTuUDBT 17 | NEoif86LIlZ8gFtP0wySGgNWHcgwUaDQCKpf4HjnmUBex/NtVt71ca2bsIHuqCUz 18 | jWuV0p0sTo1d3nM0q8FRXnVw5M1aPXjEdsDx0fkCgYBODbYpFzRFH7RhKfEMeIg5 19 | FZvVuVigbY0d3cJeDj1scgdHizoMng8JShqGRF/Zk9hjTET1bvXWY4xMeUSkqGqU 20 | WifN9QialoMuyhR1pdbBGfRe0fvLVW8Sc1L3lTJKbJUwM1Mmg0eFccj9fvkUq0tJ 21 | vYpOhDoK9dtYV9jey5xWMQKBgQDFY1+Nw8yYtbIeGnyfL/j3wPMAOIM0Yw3RnGvW 22 | q1dO+OxtFHEMJWzppFGoD/2+fha2ouFO/jQ76w7cIpE7WLKlCBxbwi1t51qvCdHY 23 | tvL2AB0XsW2nPvbQN4VDD9flXhHNR9Yd0Xccle6s7k0kaBHXleytOryUminloKrc 24 | yelfAQKBgQCbhNxTGTFzQn3jWkTnb6YvnoUw3VG0C1xCfULvMbTXlrtJ0NZOKUOp 25 | Bw/TP5qnXJZ8kiRq86AjJDEORJErxEE+d9veeHrtlqWC5hIxvBfRtQw30tgfymxg 26 | R1uD1pfUcF53zu9TSJpR1CN1zSij90B7tlFCle8J8N2nCnvJM7lABQ== 27 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /config/php/conf.d/wordpress.ini: -------------------------------------------------------------------------------- 1 | date.timezone = "America/Los_Angeles" 2 | short_open_tag = Off 3 | session.auto_start = Off 4 | file_uploads = On 5 | memory_limit = 64M 6 | upload_max_filesize = 64M 7 | post_max_size = 64M 8 | max_execution_time = 30 9 | xdebug.remote_enable = On 10 | xdebug.remote_autostart = Off 11 | xdebug.cli_color = 0 12 | xdebug.profiler_enable = 0 13 | xdebug.remote_handler = dbgp 14 | xdebug.remote_mode = req 15 | xdebug.remote_log = "/srv/logs/xdebug/xdebug.log" 16 | xdebug.remote_port = 9000 -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwp/wp-docker/8e6df708d20ac0a8ab2a017d33c7f0521fbe130f/data/.gitkeep -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | 5 | data: 6 | image: tianon/true 7 | restart: on-failure 8 | volumes: 9 | - ./wordpress:/var/www/html/wordpress 10 | - ./wp-content:/var/www/html/wp-content 11 | - ./wp-config:/var/www/html/wp-config 12 | - ./wp-tests:/var/www/html/wp-tests 13 | - ./data/redis:/data 14 | - ./data/mysql:/var/lib/mysql 15 | - ./logs/nginx:/srv/logs/nginx 16 | - ./logs/php:/srv/logs/php-fpm 17 | - ./logs/xdebug:/srv/logs/xdebug 18 | - ./config/mysql:/docker-entrypoint-initdb.d 19 | - ./config/nginx/conf.d/local.conf:/etc/nginx/conf.d/default.conf:ro 20 | - ./config/nginx/ssl:/etc/ssl 21 | - ./config/php/conf.d/wordpress.ini:/usr/local/etc/php/conf.d/wordpress.ini 22 | - ./bin/build.sh:/usr/local/bin/build.sh 23 | 24 | redis: 25 | image: redis:3.2-alpine 26 | restart: on-failure 27 | ports: 28 | - 6379:6379 29 | volumes_from: 30 | - data 31 | networks: 32 | - backend 33 | 34 | mysql: 35 | image: mariadb:10.1 36 | restart: on-failure 37 | ports: 38 | - 3306:3306 39 | volumes_from: 40 | - data 41 | environment: 42 | MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} 43 | MYSQL_DATABASE: ${MYSQL_DATABASE} 44 | MYSQL_USER: ${MYSQL_USER} 45 | MYSQL_PASSWORD: ${MYSQL_PASSWORD} 46 | networks: 47 | - backend 48 | 49 | php: 50 | image: xwpco/php:7.1-fpm-alpine 51 | restart: on-failure 52 | links: 53 | - mysql 54 | - redis 55 | volumes_from: 56 | - data 57 | environment: 58 | WP_ENV: ${WP_ENV} 59 | WP_DB_HOST: ${WP_DB_HOST} 60 | WP_DB_NAME: ${WP_DB_NAME} 61 | WP_DB_USER: ${WP_DB_USER} 62 | WP_DB_PASSWORD: ${WP_DB_PASSWORD} 63 | WP_THEME_NAME: ${WP_THEME_NAME} 64 | WP_DOMAIN: ${WP_DOMAIN} 65 | WP_ADMIN_USER: ${WP_ADMIN_USER} 66 | WP_ADMIN_PASSWORD: ${WP_ADMIN_PASSWORD} 67 | WP_ADMIN_EMAIL: ${WP_ADMIN_EMAIL} 68 | WP_VERSION: ${WP_VERSION} 69 | WP_TITLE: ${WP_TITLE} 70 | WP_DEBUG: ${WP_DEBUG} 71 | XDEBUG_CONFIG: ${XDEBUG_CONFIG} 72 | PHP_IDE_CONFIG: ${PHP_IDE_CONFIG} 73 | FORCE_SSL_ADMIN: ${FORCE_SSL_ADMIN} 74 | command: /bin/ash -c "/usr/local/bin/build.sh --with-tests && php-fpm" 75 | networks: 76 | - backend 77 | 78 | nginx: 79 | image: nginx:1.11-alpine 80 | restart: on-failure 81 | volumes_from: 82 | - data 83 | links: 84 | - php 85 | ports: 86 | - 80:80 87 | - 443:443 88 | networks: 89 | - backend 90 | - frontend 91 | 92 | networks: 93 | frontend: 94 | driver: bridge 95 | backend: 96 | driver: bridge 97 | -------------------------------------------------------------------------------- /docker/php/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.1-fpm-alpine 2 | 3 | ENV PHPREDIS_VERSION 3.1.2 4 | 5 | RUN apk add --no-cache --virtual .build-deps \ 6 | $PHPIZE_DEPS \ 7 | curl \ 8 | curl-dev \ 9 | freetype-dev \ 10 | icu \ 11 | icu-dev \ 12 | libintl \ 13 | libjpeg-turbo-dev \ 14 | libmcrypt-dev \ 15 | libpng-dev \ 16 | libxml2-dev \ 17 | 18 | && apk add --no-cache --virtual .persistent-deps \ 19 | bash \ 20 | grep \ 21 | sed \ 22 | git \ 23 | mariadb-client \ 24 | subversion \ 25 | 26 | && docker-php-ext-configure gd \ 27 | --with-png-dir=/usr \ 28 | --with-jpeg-dir=/usr \ 29 | 30 | && docker-php-ext-install \ 31 | bcmath \ 32 | curl \ 33 | exif \ 34 | gd \ 35 | iconv \ 36 | intl \ 37 | mbstring \ 38 | mcrypt \ 39 | mysqli \ 40 | opcache \ 41 | zip \ 42 | 43 | && { \ 44 | echo 'opcache.memory_consumption=128'; \ 45 | echo 'opcache.interned_strings_buffer=8'; \ 46 | echo 'opcache.max_accelerated_files=4000'; \ 47 | echo 'opcache.revalidate_freq=60'; \ 48 | echo 'opcache.fast_shutdown=1'; \ 49 | echo 'opcache.enable_cli=1'; \ 50 | } > /usr/local/etc/php/conf.d/opcache-recommended.ini \ 51 | 52 | # Install php-redis 53 | && curl -L -o /tmp/redis.tar.gz https://github.com/phpredis/phpredis/archive/$PHPREDIS_VERSION.tar.gz \ 54 | && mkdir /tmp/redis \ 55 | && tar -xf /tmp/redis.tar.gz -C /tmp/redis \ 56 | && rm /tmp/redis.tar.gz \ 57 | && ( \ 58 | cd /tmp/redis/phpredis-$PHPREDIS_VERSION \ 59 | && phpize \ 60 | && ./configure \ 61 | && make -j$(nproc) \ 62 | && make install \ 63 | ) \ 64 | && rm -r /tmp/redis \ 65 | && docker-php-ext-enable redis \ 66 | 67 | # Install xdebug 68 | && yes | pecl install xdebug \ 69 | && echo "zend_extension=$(find /usr/local/lib/php/extensions/ -name xdebug.so)" > /usr/local/etc/php/conf.d/xdebug.ini \ 70 | && echo "xdebug.remote_enable=on" >> /usr/local/etc/php/conf.d/xdebug.ini \ 71 | && echo "xdebug.remote_autostart=off" >> /usr/local/etc/php/conf.d/xdebug.ini \ 72 | 73 | && find /usr/local/lib/php/extensions -name '*.a' -delete \ 74 | && find /usr/local/lib/php/extensions -name '*.so' -exec strip --strip-all '{}' \; \ 75 | 76 | && find /usr/local \ 77 | \( -type d -a -name test -o -name tests \) \ 78 | -o \( -type f -a -name '*.pyc' -o -name '*.pyo' \) \ 79 | -exec rm -rf '{}' + \ 80 | 81 | && runDeps="$( \ 82 | scanelf --needed --nobanner --recursive /usr/local \ 83 | | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \ 84 | | sort -u \ 85 | | xargs -r apk info --installed \ 86 | | sort -u \ 87 | )" \ 88 | && apk add --virtual .run-deps $runDeps \ 89 | && apk del .build-deps \ 90 | && rm -rf /var/lib/apk/lists/* /usr/share/doc/* /usr/share/man/* /usr/share/info/* /var/cache/apk/* 91 | 92 | # Install wp-cli 93 | RUN curl -o /usr/local/bin/wp -SL https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar \ 94 | && chmod +x /usr/local/bin/wp 95 | 96 | # Install PHPUnit 97 | RUN curl https://phar.phpunit.de/phpunit-5.7.5.phar -L -o phpunit.phar \ 98 | && chmod +x phpunit.phar \ 99 | && mv phpunit.phar /usr/local/bin/phpunit 100 | 101 | # Install phpcs & wpcs standard 102 | RUN curl -o /usr/local/bin/phpcs -SL https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar \ 103 | && chmod +x /usr/local/bin/phpcs \ 104 | && git clone -b master --depth=1 https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards.git /usr/local/bin/wpcs \ 105 | && /usr/local/bin/phpcs --config-set show_progress 1 \ 106 | && /usr/local/bin/phpcs --config-set colors 1 \ 107 | && /usr/local/bin/phpcs --config-set installed_paths /usr/local/bin/wpcs 108 | 109 | # Install phpcbf 110 | RUN curl -o /usr/local/bin/phpcbf -SL https://squizlabs.github.io/PHP_CodeSniffer/phpcbf.phar \ 111 | && chmod +x /usr/local/bin/phpcbf 112 | 113 | WORKDIR /var/www/html/wordpress/ 114 | 115 | CMD ["php-fpm"] -------------------------------------------------------------------------------- /logs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwp/wp-docker/8e6df708d20ac0a8ab2a017d33c7f0521fbe130f/logs/.gitkeep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wp-docker", 3 | "version": "1.0.0", 4 | "description": "A docker environment for WordPress site development.", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/xwp/wp-docker.git" 8 | }, 9 | "author": "XWP", 10 | "license": "GPL-3.0", 11 | "bugs": { 12 | "url": "https://github.com/xwp/wp-docker/issues" 13 | }, 14 | "homepage": "https://github.com/xwp/wp-docker#readme", 15 | "devDependencies": { 16 | "eslint": "^3.15.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # WP Docker (Beta) 2 | 3 | [![Build Status](https://travis-ci.org/xwp/wp-docker.svg?branch=master)](https://travis-ci.org/xwp/wp-docker) [![Coverage Status](https://coveralls.io/repos/github/xwp/wp-docker/badge.svg?branch=master)](https://coveralls.io/github/xwp/wp-docker?branch=master) 4 | 5 | A docker environment for WordPress site development. 6 | 7 | ## Requirements 8 | 9 | * docker-compose 10 | * composer 11 | * npm 12 | 13 | ## Usage 14 | 15 | Clone Repository: 16 | 17 | ``` 18 | git clone --recursive git@github.com:xwp/wp-docker.git 19 | ``` 20 | 21 | Install Plugins: 22 | 23 | ``` 24 | composer install 25 | ``` 26 | 27 | Add Environment Variables: 28 | 29 | ``` 30 | cp .env.sample .env 31 | ``` 32 | This creates a new `.env` file from the `.env.sample` file and is not checked into `git`, change the settings as needed. 33 | 34 | Start: 35 | 36 | ``` 37 | bin/up {optional:parameters} 38 | ``` 39 | 40 | The optional parameters give you the ability to append configurations to the script. However, the `bin/up` script by default adds the `--build` parameter if nothing is passed to it. For example, you could run Docker in daemon mode by adding `-d` and that would be the only parameter. So if you want the default behaviour plus daemon mode you would do: 41 | 42 | ``` 43 | bin/up --build -d 44 | ``` 45 | 46 | Although it's not recommended, you can alternatively use: 47 | 48 | ``` 49 | docker-compose up 50 | ``` 51 | 52 | Stop: 53 | 54 | ``` 55 | bin/down 56 | ``` 57 | 58 | Alternatively use: 59 | 60 | ``` 61 | docker-compose down 62 | ``` 63 | 64 | ## MySQL 65 | 66 | Docker will execute files with extensions `.sh`, `.sql` and `.sql.gz` that are found in the `mysql` directory. Files will be executed in alphabetical order. You can easily populate your mariadb service by [mounting a SQL dump into that directory](https://docs.docker.com/engine/tutorials/dockervolumes/#mount-a-host-file-as-a-data-volume) and provide [custom images](https://docs.docker.com/reference/builder/) with contributed data. SQL files will be imported by default to the database specified by the `MYSQL_DATABASE` variable. However, it may not be the ideal workflow to load the DB this way so there are additional custom import and export capabilities. 67 | 68 | Import: 69 | 70 | ``` 71 | bin/mysql-import {path-to-file}.sql 72 | ``` 73 | 74 | Running the `bin/mysql-import` bash script will import an SQL file into the database which is defined in the `MYSQL_DATABASE` environment variable. All you need to do is supply a path to the SQL file. 75 | 76 | Export: 77 | 78 | ``` 79 | bin/mysql-export 80 | ``` 81 | 82 | Running the `bin/mysql-export` bash script will create a backup of the database inside the `mysql/backups` directory with the format `{db-name}-{timestamp}.sql` 83 | 84 | ## PHPUnit 85 | 86 | ``` 87 | bin/phpunit 88 | ``` 89 | 90 | Running the default command without any parameters will automatically run the testsuite. As well, the `pre-commit` hook will also run the testsuite for the plugins automatically. Both use the `PHPUNIT_CONFIG` variable found in the `.dev-lib` configuration file. 91 | 92 | The `bin/phpunit` bash script is a wrapper for `phpunit` inside Docker and excepts all the [same parameters](https://phpunit.de/manual/current/en/textui.html). The following will manually run the unit tests for the plugins. 93 | 94 | ``` 95 | bin/phpunit -c /var/www/html/wp-tests/phpunit.xml.dist 96 | ``` 97 | 98 | You can additionally add a coverage clover by doing the following. 99 | 100 | ``` 101 | bin/phpunit -c ../wp-tests/phpunit.xml.dist --coverage-html ../wp-tests/coverage 102 | ``` 103 | 104 | Notice that relative paths work, as well. This is because the current working directory when running test in Docker is the host machines `{project_root}/bin` directory. 105 | 106 | ## PHPCS 107 | 108 | ``` 109 | bin/phpcs {path} 110 | ``` 111 | 112 | Performs preset PHP Coding Standard and WordPress sniffs. The `bin/phpcs` bash script is a wrapper for `phpcs` inside Docker with the parameters already supplied. The script requires/accepts an absolute (docker) or relative (host) path to a directory or file. Configurations are automatically set by parameters in the `.dev-lib` file. 113 | 114 | ## PHPCBF 115 | 116 | ``` 117 | bin/phpcbf {path} 118 | ``` 119 | 120 | To automatically fix as many sniff violations as possible, use the `phpcbf` command in place of the `phpcs` command. The `bin/phpcbf` bash script is a wrapper for `phpcbf` inside Docker with the parameters already supplied. The script requires/accepts an absolute (docker) or relative (host) path to a directory or file. Configurations are automatically set by parameters in the `.dev-lib` file. 121 | 122 | 123 | ## Run Commands 124 | 125 | ``` 126 | bin/run {command} 127 | ``` 128 | 129 | The `bin/run` bash script is a wrapper for the following `docker-compose` script. This is essential to interacting with the `php` service and its linked services. You can quicky run commands even when the Docker container is not `up`. 130 | 131 | 1. `docker-compose run --rm php ` 132 | 133 | You could also do this manually with `docker exec` by doing the following. 134 | 135 | 1. `docker ps` 136 | 1. Get the ID of the PHP container 137 | 1. `docker exec -it ` 138 | 139 | ## Plugin Scaffolding 140 | 141 | Foo Bar is a built-in template plugin for scaffolding WordPress plugins. The [`bin/plugin`](bin/plugin) bash script will copy the `foo-bar` plugin and make the necessary replacements via: 142 | 143 | ```bash 144 | bin/plugin "Hello World" 145 | ``` 146 | 147 | This will create a plugin `hello-world` in the `wp-content/plugins` directory and will greatly speed up plugin development inside this repo. 148 | 149 | Be sure to add your new plugin to the `testsuite` inside the [`wp-tests/phpunit.xml.dist`](wp-tests/phpunit.xml.dist) file to ensure your PHPUnit tests are included in the `pre-commit` hook and `bin/phpunit` script. It is required that you run this script from this repositories root directory. 150 | -------------------------------------------------------------------------------- /wp-config/proxy.php: -------------------------------------------------------------------------------- 1 | 'redis', 13 | 'port' => 6379, 14 | 'auth' => 'redis', 15 | 'database' => 0, 16 | ); 17 | -------------------------------------------------------------------------------- /wp-config/security.php: -------------------------------------------------------------------------------- 1 | cancel = true; 16 | } 17 | 18 | // Variants can be set by functions which use early-set globals like $_SERVER to run simple tests. 19 | // Functions defined in WordPress, plugins, and themes are not available and MUST NOT be used. 20 | // Example: vary_cache_on_function('return preg_match("/feedburner/i", $_SERVER["HTTP_USER_AGENT"]);'); 21 | // This will cause batcache to cache a variant for requests from Feedburner. 22 | // Tips for writing $function: 23 | // X_X DO NOT use any functions from your theme or plugins. Those files have not been included. Fatal error. 24 | // X_X DO NOT use any WordPress functions except is_admin() and is_multisite(). Fatal error. 25 | // X_X DO NOT include or require files from anywhere without consulting expensive professionals first. Fatal error. 26 | // X_X DO NOT use $wpdb, $blog_id, $current_user, etc. These have not been initialized. 27 | // ^_^ DO understand how create_function works. This is how your code is used: create_function('', $function); 28 | // ^_^ DO remember to return something. The return value determines the cache variant. 29 | function vary_cache_on_function($function) { 30 | global $batcache; 31 | 32 | if ( preg_match('/include|require|echo|(?add_variant($function); 39 | } 40 | 41 | class batcache { 42 | // This is the base configuration. You can edit these variables or move them into your wp-config.php file. 43 | var $max_age = 300; // Expire batcache items aged this many seconds (zero to disable batcache) 44 | 45 | var $remote = 0; // Zero disables sending buffers to remote datacenters (req/sec is never sent) 46 | 47 | var $times = 2; // Only batcache a page after it is accessed this many times... (two or more) 48 | var $seconds = 120; // ...in this many seconds (zero to ignore this and use batcache immediately) 49 | 50 | var $group = 'batcache'; // Name of memcached group. You can simulate a cache flush by changing this. 51 | 52 | var $unique = array(); // If you conditionally serve different content, put the variable values here. 53 | 54 | var $vary = array(); // Array of functions for create_function. The return value is added to $unique above. 55 | 56 | var $headers = array(); // Add headers here as name=>value or name=>array(values). These will be sent with every response from the cache. 57 | 58 | var $cache_redirects = false; // Set true to enable redirect caching. 59 | var $redirect_status = false; // This is set to the response code during a redirect. 60 | var $redirect_location = false; // This is set to the redirect location. 61 | 62 | var $use_stale = true; // Is it ok to return stale cached response when updating the cache? 63 | var $uncached_headers = array('transfer-encoding'); // These headers will never be cached. Apply strtolower. 64 | 65 | var $debug = true; // Set false to hide the batcache info 66 | 67 | var $cache_control = true; // Set false to disable Last-Modified and Cache-Control headers 68 | 69 | var $cancel = false; // Change this to cancel the output buffer. Use batcache_cancel(); 70 | 71 | var $noskip_cookies = array( 'wordpress_test_cookie' ); // Names of cookies - if they exist and the cache would normally be bypassed, don't bypass it 72 | 73 | var $query = ''; 74 | var $genlock = false; 75 | var $do = false; 76 | 77 | function __construct( $settings ) { 78 | if ( is_array( $settings ) ) foreach ( $settings as $k => $v ) 79 | $this->$k = $v; 80 | } 81 | 82 | function is_ssl() { 83 | if ( isset($_SERVER['HTTPS']) ) { 84 | if ( 'on' == strtolower($_SERVER['HTTPS']) ) 85 | return true; 86 | if ( '1' == $_SERVER['HTTPS'] ) 87 | return true; 88 | } elseif ( isset($_SERVER['SERVER_PORT']) && ( '443' == $_SERVER['SERVER_PORT'] ) ) { 89 | return true; 90 | } 91 | return false; 92 | } 93 | 94 | function status_header( $status_header, $status_code ) { 95 | $this->status_header = $status_header; 96 | $this->status_code = $status_code; 97 | 98 | return $status_header; 99 | } 100 | 101 | function redirect_status( $status, $location ) { 102 | if ( $this->cache_redirects ) { 103 | $this->redirect_status = $status; 104 | $this->redirect_location = $location; 105 | } 106 | 107 | return $status; 108 | } 109 | 110 | function do_headers( $headers1, $headers2 = array() ) { 111 | // Merge the arrays of headers into one 112 | $headers = array(); 113 | $keys = array_unique( array_merge( array_keys( $headers1 ), array_keys( $headers2 ) ) ); 114 | foreach ( $keys as $k ) { 115 | $headers[$k] = array(); 116 | if ( isset( $headers1[$k] ) && isset( $headers2[$k] ) ) 117 | $headers[$k] = array_merge( (array) $headers2[$k], (array) $headers1[$k] ); 118 | elseif ( isset( $headers2[$k] ) ) 119 | $headers[$k] = (array) $headers2[$k]; 120 | else 121 | $headers[$k] = (array) $headers1[$k]; 122 | $headers[$k] = array_unique( $headers[$k] ); 123 | } 124 | // These headers take precedence over any previously sent with the same names 125 | foreach ( $headers as $k => $values ) { 126 | $clobber = true; 127 | foreach ( $values as $v ) { 128 | header( "$k: $v", $clobber ); 129 | $clobber = false; 130 | } 131 | } 132 | } 133 | 134 | function configure_groups() { 135 | // Configure the memcached client 136 | if ( ! $this->remote ) 137 | if ( function_exists('wp_cache_add_no_remote_groups') ) 138 | wp_cache_add_no_remote_groups(array($this->group)); 139 | if ( function_exists('wp_cache_add_global_groups') ) 140 | wp_cache_add_global_groups(array($this->group)); 141 | } 142 | 143 | // Defined here because timer_stop() calls number_format_i18n() 144 | function timer_stop($display = 0, $precision = 3) { 145 | global $timestart, $timeend; 146 | $mtime = microtime(); 147 | $mtime = explode(' ',$mtime); 148 | $mtime = $mtime[1] + $mtime[0]; 149 | $timeend = $mtime; 150 | $timetotal = $timeend-$timestart; 151 | $r = number_format($timetotal, $precision); 152 | if ( $display ) 153 | echo $r; 154 | return $r; 155 | } 156 | 157 | function ob($output) { 158 | // PHP5 and objects disappearing before output buffers? 159 | wp_cache_init(); 160 | 161 | // Remember, $wp_object_cache was clobbered in wp-settings.php so we have to repeat this. 162 | $this->configure_groups(); 163 | 164 | if ( $this->cancel !== false ) { 165 | wp_cache_delete( "{$this->url_key}_genlock", $this->group ); 166 | return $output; 167 | } 168 | 169 | // Do not batcache blank pages unless they are HTTP redirects 170 | $output = trim($output); 171 | if ( $output === '' && (!$this->redirect_status || !$this->redirect_location) ) { 172 | wp_cache_delete( "{$this->url_key}_genlock", $this->group ); 173 | return; 174 | } 175 | 176 | // Do not cache 5xx responses 177 | if ( isset( $this->status_code ) && intval($this->status_code / 100) == 5 ) { 178 | wp_cache_delete( "{$this->url_key}_genlock", $this->group ); 179 | return $output; 180 | } 181 | 182 | $this->do_variants($this->vary); 183 | $this->generate_keys(); 184 | 185 | // Construct and save the batcache 186 | $this->cache = array( 187 | 'output' => $output, 188 | 'time' => isset( $_SERVER['REQUEST_TIME'] ) ? $_SERVER['REQUEST_TIME'] : time(), 189 | 'timer' => $this->timer_stop(false, 3), 190 | 'headers' => array(), 191 | 'status_header' => $this->status_header, 192 | 'redirect_status' => $this->redirect_status, 193 | 'redirect_location' => $this->redirect_location, 194 | 'version' => $this->url_version 195 | ); 196 | 197 | foreach ( headers_list() as $header ) { 198 | list($k, $v) = array_map('trim', explode(':', $header, 2)); 199 | $this->cache['headers'][$k][] = $v; 200 | } 201 | 202 | if ( !empty( $this->cache['headers'] ) && !empty( $this->uncached_headers ) ) { 203 | foreach ( $this->uncached_headers as $header ) 204 | unset( $this->cache['headers'][$header] ); 205 | } 206 | 207 | foreach ( $this->cache['headers'] as $header => $values ) { 208 | // Do not cache if cookies were set 209 | if ( strtolower( $header ) === 'set-cookie' ) { 210 | wp_cache_delete( "{$this->url_key}_genlock", $this->group ); 211 | return $output; 212 | } 213 | 214 | foreach ( (array) $values as $value ) 215 | if ( preg_match('/^Cache-Control:.*max-?age=(\d+)/i', "$header: $value", $matches) ) 216 | $this->max_age = intval($matches[1]); 217 | } 218 | 219 | $this->cache['max_age'] = $this->max_age; 220 | 221 | wp_cache_set($this->key, $this->cache, $this->group, $this->max_age + $this->seconds + 30); 222 | 223 | // Unlock regeneration 224 | wp_cache_delete("{$this->url_key}_genlock", $this->group); 225 | 226 | if ( $this->cache_control ) { 227 | // Don't clobber Last-Modified header if already set, e.g. by WP::send_headers() 228 | if ( !isset($this->cache['headers']['Last-Modified']) ) 229 | header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $this->cache['time'] ) . ' GMT', true ); 230 | if ( !isset($this->cache['headers']['Cache-Control']) ) 231 | header("Cache-Control: max-age=$this->max_age, must-revalidate", false); 232 | } 233 | 234 | $this->do_headers( $this->headers ); 235 | 236 | // Add some debug info just before debug ) { 238 | $this->add_debug_just_cached(); 239 | } 240 | 241 | // Pass output to next ob handler 242 | batcache_stats( 'batcache', 'total_page_views' ); 243 | return $this->cache['output']; 244 | } 245 | 246 | function add_variant($function) { 247 | $key = md5($function); 248 | $this->vary[$key] = $function; 249 | } 250 | 251 | function do_variants($dimensions = false) { 252 | // This function is called without arguments early in the page load, then with arguments during the OB handler. 253 | if ( $dimensions === false ) 254 | $dimensions = wp_cache_get("{$this->url_key}_vary", $this->group); 255 | else 256 | wp_cache_set("{$this->url_key}_vary", $dimensions, $this->group, $this->max_age + 10); 257 | 258 | if ( is_array($dimensions) ) { 259 | ksort($dimensions); 260 | foreach ( $dimensions as $key => $function ) { 261 | $fun = create_function('', $function); 262 | $value = $fun(); 263 | $this->keys[$key] = $value; 264 | } 265 | } 266 | } 267 | 268 | function generate_keys() { 269 | // ksort($this->keys); // uncomment this when traffic is slow 270 | $this->key = md5(serialize($this->keys)); 271 | $this->req_key = $this->key . '_reqs'; 272 | } 273 | 274 | function add_debug_just_cached() { 275 | $generation = $this->cache['timer']; 276 | $bytes = strlen( serialize( $this->cache ) ); 277 | $html = <<max_age} seconds 281 | --> 282 | 283 | HTML; 284 | $this->add_debug_html_to_output( $html ); 285 | } 286 | 287 | function add_debug_from_cache() { 288 | $seconds_ago = time() - $this->cache['time']; 289 | $generation = $this->cache['timer']; 290 | $serving = $this->timer_stop( false, 3 ); 291 | $expires = $this->cache['max_age'] - time() + $this->cache['time']; 292 | $html = << 299 | 300 | HTML; 301 | $this->add_debug_html_to_output( $html ); 302 | } 303 | 304 | function add_debug_html_to_output( $debug_html ) { 305 | // Casing on the Content-Type header is inconsistent 306 | foreach ( array( 'Content-Type', 'Content-type' ) as $key ) { 307 | if ( isset( $this->cache['headers'][ $key ][0] ) && 0 !== strpos( $this->cache['headers'][ $key ][0], 'text/html' ) ) 308 | return; 309 | } 310 | 311 | $head_position = strpos( $this->cache['output'], 'cache['output'] .= "\n$debug_html"; 316 | } 317 | } 318 | 319 | global $batcache; 320 | // Pass in the global variable which may be an array of settings to override defaults. 321 | $batcache = new batcache($batcache); 322 | 323 | if ( ! defined( 'WP_CONTENT_DIR' ) ) 324 | return; 325 | 326 | // Never batcache interactive scripts or API endpoints. 327 | if ( in_array( 328 | basename( $_SERVER['SCRIPT_FILENAME'] ), 329 | array( 330 | 'wp-app.php', 331 | 'xmlrpc.php', 332 | 'wp-cron.php', 333 | ) ) ) 334 | return; 335 | 336 | // Never batcache WP javascript generators 337 | if ( strstr( $_SERVER['SCRIPT_FILENAME'], 'wp-includes/js' ) ) 338 | return; 339 | 340 | // Only cache HEAD and GET requests. 341 | if ((isset($_SERVER['REQUEST_METHOD']) && !in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD')))) { 342 | return; 343 | } 344 | 345 | // Never batcache when cookies indicate a cache-exempt visitor. 346 | if ( is_array( $_COOKIE) && ! empty( $_COOKIE ) ) { 347 | foreach ( array_keys( $_COOKIE ) as $batcache->cookie ) { 348 | if ( ! in_array( $batcache->cookie, $batcache->noskip_cookies ) && ( substr( $batcache->cookie, 0, 2 ) == 'wp' || substr( $batcache->cookie, 0, 9 ) == 'wordpress' || substr( $batcache->cookie, 0, 14 ) == 'comment_author' ) ) { 349 | batcache_stats( 'batcache', 'cookie_skip' ); 350 | return; 351 | } 352 | } 353 | } 354 | 355 | if ( ! include_once( WP_CONTENT_DIR . '/object-cache.php' ) ) 356 | return; 357 | 358 | wp_cache_init(); // Note: wp-settings.php calls wp_cache_init() which clobbers the object made here. 359 | 360 | if ( ! is_object( $wp_object_cache ) ) 361 | return; 362 | 363 | // Now that the defaults are set, you might want to use different settings under certain conditions. 364 | 365 | /* Example: if your documents have a mobile variant (a different document served by the same URL) you must tell batcache about the variance. Otherwise you might accidentally cache the mobile version and serve it to desktop users, or vice versa. 366 | $batcache->unique['mobile'] = is_mobile_user_agent(); 367 | */ 368 | 369 | /* Example: never batcache for this host 370 | if ( $_SERVER['HTTP_HOST'] == 'do-not-batcache-me.com' ) 371 | return; 372 | */ 373 | 374 | /* Example: batcache everything on this host regardless of traffic level 375 | if ( $_SERVER['HTTP_HOST'] == 'always-batcache-me.com' ) 376 | return; 377 | */ 378 | 379 | /* Example: If you sometimes serve variants dynamically (e.g. referrer search term highlighting) you probably don't want to batcache those variants. Remember this code is run very early in wp-settings.php so plugins are not yet loaded. You will get a fatal error if you try to call an undefined function. Either include your plugin now or define a test function in this file. 380 | if ( include_once( 'plugins/searchterm-highlighter.php') && referrer_has_search_terms() ) 381 | return; 382 | */ 383 | 384 | // Disabled 385 | if ( $batcache->max_age < 1 ) 386 | return; 387 | 388 | // Make sure we can increment. If not, turn off the traffic sensor. 389 | if ( ! method_exists( $GLOBALS['wp_object_cache'], 'incr' ) ) 390 | $batcache->times = 0; 391 | 392 | // Necessary to prevent clients using cached version after login cookies set. If this is a problem, comment it out and remove all Last-Modified headers. 393 | header('Vary: Cookie', false); 394 | 395 | // Things that define a unique page. 396 | if ( isset( $_SERVER['QUERY_STRING'] ) ) 397 | parse_str($_SERVER['QUERY_STRING'], $batcache->query); 398 | 399 | $batcache->keys = array( 400 | 'host' => $_SERVER['HTTP_HOST'], 401 | 'method' => $_SERVER['REQUEST_METHOD'], 402 | 'path' => ( $batcache->pos = strpos($_SERVER['REQUEST_URI'], '?') ) ? substr($_SERVER['REQUEST_URI'], 0, $batcache->pos) : $_SERVER['REQUEST_URI'], 403 | 'query' => $batcache->query, 404 | 'extra' => $batcache->unique 405 | ); 406 | 407 | if ( $batcache->is_ssl() ) 408 | $batcache->keys['ssl'] = true; 409 | 410 | // Recreate the permalink from the URL 411 | $batcache->permalink = 'http://' . $batcache->keys['host'] . $batcache->keys['path'] . ( isset($batcache->keys['query']['p']) ? "?p=" . $batcache->keys['query']['p'] : '' ); 412 | $batcache->url_key = md5($batcache->permalink); 413 | $batcache->configure_groups(); 414 | $batcache->url_version = (int) wp_cache_get("{$batcache->url_key}_version", $batcache->group); 415 | $batcache->do_variants(); 416 | $batcache->generate_keys(); 417 | 418 | // Get the batcache 419 | $batcache->cache = wp_cache_get($batcache->key, $batcache->group); 420 | 421 | if ( isset( $batcache->cache['version'] ) && $batcache->cache['version'] != $batcache->url_version ) { 422 | // Always refresh the cache if a newer version is available. 423 | $batcache->do = true; 424 | } else if ( $batcache->seconds < 1 || $batcache->times < 2 ) { 425 | // Are we only caching frequently-requested pages? 426 | $batcache->do = true; 427 | } else { 428 | // No batcache item found, or ready to sample traffic again at the end of the batcache life? 429 | if ( !is_array($batcache->cache) || time() >= $batcache->cache['time'] + $batcache->max_age - $batcache->seconds ) { 430 | wp_cache_add($batcache->req_key, 0, $batcache->group); 431 | $batcache->requests = wp_cache_incr($batcache->req_key, 1, $batcache->group); 432 | 433 | if ( $batcache->requests >= $batcache->times && 434 | time() >= $batcache->cache['time'] + $batcache->cache['max_age'] 435 | ) { 436 | wp_cache_delete( $batcache->req_key, $batcache->group ); 437 | $batcache->do = true; 438 | } else { 439 | $batcache->do = false; 440 | } 441 | } 442 | } 443 | 444 | // Obtain cache generation lock 445 | if ( $batcache->do ) 446 | $batcache->genlock = wp_cache_add("{$batcache->url_key}_genlock", 1, $batcache->group, 10); 447 | 448 | if ( isset( $batcache->cache['time'] ) && // We have cache 449 | ! $batcache->genlock && // We have not obtained cache regeneration lock 450 | ( 451 | time() < $batcache->cache['time'] + $batcache->cache['max_age'] || // Batcached page that hasn't expired || 452 | ( $batcache->do && $batcache->use_stale ) // Regenerating it in another request and can use stale cache 453 | ) 454 | ) { 455 | // Issue redirect if cached and enabled 456 | if ( $batcache->cache['redirect_status'] && $batcache->cache['redirect_location'] && $batcache->cache_redirects ) { 457 | $status = $batcache->cache['redirect_status']; 458 | $location = $batcache->cache['redirect_location']; 459 | // From vars.php 460 | $is_IIS = (strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false || strpos($_SERVER['SERVER_SOFTWARE'], 'ExpressionDevServer') !== false); 461 | 462 | $batcache->do_headers( $batcache->headers ); 463 | if ( $is_IIS ) { 464 | header("Refresh: 0;url=$location"); 465 | } else { 466 | if ( php_sapi_name() != 'cgi-fcgi' ) { 467 | $texts = array( 468 | 300 => 'Multiple Choices', 469 | 301 => 'Moved Permanently', 470 | 302 => 'Found', 471 | 303 => 'See Other', 472 | 304 => 'Not Modified', 473 | 305 => 'Use Proxy', 474 | 306 => 'Reserved', 475 | 307 => 'Temporary Redirect', 476 | ); 477 | $protocol = $_SERVER["SERVER_PROTOCOL"]; 478 | if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol ) 479 | $protocol = 'HTTP/1.0'; 480 | if ( isset($texts[$status]) ) 481 | header("$protocol $status " . $texts[$status]); 482 | else 483 | header("$protocol 302 Found"); 484 | } 485 | header("Location: $location"); 486 | } 487 | exit; 488 | } 489 | 490 | // Respect ETags served with feeds. 491 | $three04 = false; 492 | if ( isset( $SERVER['HTTP_IF_NONE_MATCH'] ) && isset( $batcache->cache['headers']['ETag'][0] ) && $_SERVER['HTTP_IF_NONE_MATCH'] == $batcache->cache['headers']['ETag'][0] ) 493 | $three04 = true; 494 | 495 | // Respect If-Modified-Since. 496 | elseif ( $batcache->cache_control && isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ) { 497 | $client_time = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']); 498 | if ( isset($batcache->cache['headers']['Last-Modified'][0]) ) 499 | $cache_time = strtotime($batcache->cache['headers']['Last-Modified'][0]); 500 | else 501 | $cache_time = $batcache->cache['time']; 502 | 503 | if ( $client_time >= $cache_time ) 504 | $three04 = true; 505 | } 506 | 507 | // Use the batcache save time for Last-Modified so we can issue "304 Not Modified" but don't clobber a cached Last-Modified header. 508 | if ( $batcache->cache_control && !isset($batcache->cache['headers']['Last-Modified'][0]) ) { 509 | header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $batcache->cache['time'] ) . ' GMT', true ); 510 | header('Cache-Control: max-age=' . ($batcache->cache['max_age'] - time() + $batcache->cache['time']) . ', must-revalidate', true); 511 | } 512 | 513 | // Add some debug info just before 514 | if ( $batcache->debug ) { 515 | $batcache->add_debug_from_cache(); 516 | } 517 | 518 | $batcache->do_headers( $batcache->headers, $batcache->cache['headers'] ); 519 | 520 | if ( $three04 ) { 521 | header("HTTP/1.1 304 Not Modified", true, 304); 522 | die; 523 | } 524 | 525 | if ( !empty($batcache->cache['status_header']) ) 526 | header($batcache->cache['status_header'], true); 527 | 528 | batcache_stats( 'batcache', 'total_cached_views' ); 529 | 530 | // Have you ever heard a death rattle before? 531 | die($batcache->cache['output']); 532 | } 533 | 534 | // Didn't meet the minimum condition? 535 | if ( ! $batcache->do || ! $batcache->genlock ) 536 | return; 537 | 538 | //WordPress 4.7 changes how filters are hooked. Since WordPress 4.6 add_filter can be used in advanced-cache.php. Previous behaviour is kept for backwards compatability with WP < 4.6 539 | if ( function_exists( 'add_filter' ) ) { 540 | add_filter( 'status_header', array( &$batcache, 'status_header' ), 10, 2 ); 541 | add_filter( 'wp_redirect_status', array( &$batcache, 'redirect_status' ), 10, 2 ); 542 | } else { 543 | $wp_filter['status_header'][10]['batcache'] = array( 'function' => array(&$batcache, 'status_header'), 'accepted_args' => 2 ); 544 | $wp_filter['wp_redirect_status'][10]['batcache'] = array( 'function' => array(&$batcache, 'redirect_status'), 'accepted_args' => 2 ); 545 | } 546 | 547 | ob_start(array(&$batcache, 'ob')); 548 | 549 | // It is safer to omit the final PHP closing tag. 550 | -------------------------------------------------------------------------------- /wp-content/mu-plugins/advanced-post-cache.php: -------------------------------------------------------------------------------- 1 | CACHE_GROUP_PREFIX, 'advanced_post_cache' ); 39 | } 40 | 41 | $this->setup_for_blog(); 42 | 43 | add_action( 'switch_blog', array( $this, 'setup_for_blog' ), 10, 2 ); 44 | 45 | add_filter( 'posts_request', array( &$this, 'posts_request' ) ); // Short circuits if cached 46 | add_filter( 'posts_results', array( &$this, 'posts_results' ) ); // Collates if cached, primes cache if not 47 | 48 | add_filter( 'post_limits_request', array( &$this, 'post_limits_request' ), 999, 2 ); // Checks to see if we need to worry about found_posts 49 | 50 | add_filter( 'found_posts_query', array( &$this, 'found_posts_query' ) ); // Short circuits if cached 51 | add_filter( 'found_posts', array( &$this, 'found_posts' ) ); // Reads from cache if cached, primes cache if not 52 | } 53 | 54 | function setup_for_blog( $new_blog_id = false, $previous_blog_id = false ) { 55 | if ( $new_blog_id && $new_blog_id == $previous_blog_id ) { 56 | return; 57 | } 58 | 59 | $this->cache_incr = wp_cache_get( 'advanced_post_cache', 'cache_incrementors' ); // Get and construct current cache group name 60 | if ( !is_numeric( $this->cache_incr ) ) { 61 | $now = time(); 62 | wp_cache_set( 'advanced_post_cache', $now, 'cache_incrementors' ); 63 | $this->cache_incr = $now; 64 | } 65 | $this->cache_group = $this->CACHE_GROUP_PREFIX . $this->cache_incr; 66 | } 67 | 68 | /* Advanced Post Cache API */ 69 | 70 | /** 71 | * Flushes the cache by incrementing the cache group 72 | */ 73 | function flush_cache() { 74 | // Cache flushes have been disabled 75 | if ( !$this->do_flush_cache ) 76 | return; 77 | 78 | // Bail on post preview 79 | if ( is_admin() && isset( $_POST['wp-preview'] ) && 'dopreview' == $_POST['wp-preview'] ) 80 | return; 81 | 82 | // Bail on autosave 83 | if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) 84 | return; 85 | 86 | // We already flushed once this page load, and have not put anything into the cache since. 87 | // OTHER processes may have put something into the cache! In theory, this could cause stale caches. 88 | // We do this since clean_post_cache() (which fires the action this method attaches to) is called RECURSIVELY for all descendants. 89 | // if ( !$this->need_to_flush_cache ) 90 | // return; 91 | 92 | $this->cache_incr = wp_cache_incr( 'advanced_post_cache', 1, 'cache_incrementors' ); 93 | if ( 10 < strlen( $this->cache_incr ) ) { 94 | wp_cache_set( 'advanced_post_cache', 0, 'cache_incrementors' ); 95 | $this->cache_incr = 0; 96 | } 97 | $this->cache_group = $this->CACHE_GROUP_PREFIX . $this->cache_incr; 98 | $this->need_to_flush_cache = false; 99 | } 100 | 101 | 102 | /* Cache Reading/Priming Functions */ 103 | 104 | /** 105 | * Determines (by hash of SQL) if query is cached. 106 | * If cached: Return query of needed post IDs. 107 | * Otherwise: Returns query unchanged. 108 | */ 109 | function posts_request( $sql ) { 110 | global $wpdb; 111 | 112 | $this->cache_key = md5( $sql ); // init 113 | $this->all_post_ids = wp_cache_get( $this->cache_key, $this->cache_group ); 114 | if ( 'NA' !== $this->found_posts ) 115 | $this->found_posts = wp_cache_get( "{$this->cache_key}_found", $this->cache_group ); 116 | 117 | if ( $this->all_post_ids xor $this->found_posts ) 118 | $this->cache_func = 'wp_cache_set'; 119 | else 120 | $this->cache_func = 'wp_cache_add'; 121 | 122 | $this->cached_post_ids = array(); // re-init 123 | $this->cached_posts = array(); // re-init 124 | 125 | // Query is cached 126 | if ( $this->found_posts && is_array( $this->all_post_ids ) ) { 127 | if ( function_exists( 'wp_cache_get_multi' ) ) { 128 | $this->cached_posts = wp_cache_get_multi( array( 'posts' => $this->all_post_ids ) ); 129 | } else { 130 | $this->cached_posts = array(); 131 | foreach ( $this->all_post_ids as $pid ) { 132 | $this->cached_posts[] = wp_cache_get( $pid, 'posts' ); 133 | } 134 | } 135 | 136 | $this->cached_posts = array_filter( $this->cached_posts ); 137 | 138 | foreach ( $this->cached_posts as $post ) { 139 | if ( !empty( $post ) ) 140 | $this->cached_post_ids[] = $post->ID; 141 | } 142 | $uncached_post_ids = array_diff( $this->all_post_ids, $this->cached_post_ids ); 143 | 144 | if ( $uncached_post_ids ) 145 | return "SELECT * FROM $wpdb->posts WHERE ID IN(" . join( ',', array_map( 'absint', $uncached_post_ids ) ) . ")"; 146 | return ''; 147 | } 148 | 149 | return $sql; 150 | } 151 | 152 | /** 153 | * If cached: Collates posts returned by SQL query with posts that are already cached. Orders correctly. 154 | * Otherwise: Primes cache with data for current posts WP_Query. 155 | */ 156 | function posts_results( $posts ) { 157 | if ( $this->found_posts && is_array( $this->all_post_ids ) ) { // is cached 158 | $collated_posts = array(); 159 | foreach ( $this->cached_posts as $post ) 160 | $posts[] = $post; 161 | 162 | foreach ( $posts as $post ) { 163 | $loc = array_search( $post->ID, $this->all_post_ids ); 164 | if ( is_numeric( $loc ) && -1 < $loc ) 165 | $collated_posts[$loc] = $post; 166 | } 167 | ksort( $collated_posts ); 168 | return array_map( 'get_post', array_values( $collated_posts ) ); 169 | } 170 | 171 | $post_ids = array(); 172 | foreach ( (array) $posts as $post ) 173 | $post_ids[] = $post->ID; 174 | 175 | if ( !$post_ids ) 176 | return array(); 177 | 178 | call_user_func( $this->cache_func, $this->cache_key, $post_ids, $this->cache_group ); 179 | $this->need_to_flush_cache = true; 180 | 181 | return array_map( 'get_post', $posts ); 182 | } 183 | 184 | /** 185 | * If $limits is empty, WP_Query never calls the found_rows stuff, so we set $this->found_rows to 'NA' 186 | */ 187 | function post_limits_request( $limits, $query ) { 188 | if ( empty( $limits ) || ( isset( $query->query_vars['no_found_rows'] ) && $query->query_vars['no_found_rows'] ) ) 189 | $this->found_posts = 'NA'; 190 | else 191 | $this->found_posts = false; // re-init 192 | return $limits; 193 | } 194 | 195 | /** 196 | * If cached: Blanks SELECT FOUND_ROWS() query. This data is already stored in cache. 197 | * Otherwise: Returns query unchanged. 198 | */ 199 | function found_posts_query( $sql ) { 200 | if ( $this->found_posts && is_array( $this->all_post_ids ) ) // is cached 201 | return ''; 202 | return $sql; 203 | } 204 | 205 | /** 206 | * If cached: Returns cached result of FOUND_ROWS() query. 207 | * Otherwise: Returs result unchanged 208 | */ 209 | function found_posts( $found_posts ) { 210 | if ( $this->found_posts && is_array( $this->all_post_ids ) ) // is cached 211 | return (int) $this->found_posts; 212 | 213 | call_user_func( $this->cache_func, "{$this->cache_key}_found", (int) $found_posts, $this->cache_group ); 214 | $this->need_to_flush_cache = true; 215 | 216 | return $found_posts; 217 | } 218 | } 219 | 220 | global $advanced_post_cache_object; 221 | $advanced_post_cache_object = new Advanced_Post_Cache; 222 | 223 | function clear_advanced_post_cache() { 224 | global $advanced_post_cache_object; 225 | $advanced_post_cache_object->flush_cache(); 226 | } 227 | 228 | function do_clear_advanced_post_cache() { 229 | $GLOBALS['advanced_post_cache_object']->do_flush_cache = true; 230 | } 231 | 232 | function dont_clear_advanced_post_cache() { 233 | $GLOBALS['advanced_post_cache_object']->do_flush_cache = false; 234 | } 235 | 236 | add_action( 'clean_term_cache', 'clear_advanced_post_cache' ); 237 | add_action( 'clean_post_cache', 'clear_advanced_post_cache' ); 238 | 239 | // Don't clear Advanced Post Cache for a new comment - temp core hack 240 | // http://core.trac.wordpress.org/ticket/15565 241 | add_action( 'wp_updating_comment_count', 'dont_clear_advanced_post_cache' ); 242 | add_action( 'wp_update_comment_count' , 'do_clear_advanced_post_cache' ); -------------------------------------------------------------------------------- /wp-content/mu-plugins/opcache.php: -------------------------------------------------------------------------------- 1 | =' ) ) { 34 | require_once __DIR__ . '/instance.php'; 35 | } else { 36 | if ( defined( 'WP_CLI' ) ) { 37 | WP_CLI::warning( _foo_bar_php_version_text() ); 38 | } else { 39 | add_action( 'admin_notices', '_foo_bar_php_version_error' ); 40 | } 41 | } 42 | 43 | /** 44 | * Admin notice for incompatible versions of PHP. 45 | */ 46 | function _foo_bar_php_version_error() { 47 | printf( '

%s

', esc_html( _foo_bar_php_version_text() ) ); 48 | } 49 | 50 | /** 51 | * String describing the minimum PHP version. 52 | * 53 | * @return string 54 | */ 55 | function _foo_bar_php_version_text() { 56 | return __( 'Foo Bar plugin error: Your version of PHP is too old to run this plugin. You must be running PHP 5.3 or higher.', 'foo-bar' ); 57 | } 58 | -------------------------------------------------------------------------------- /wp-content/plugins/foo-bar/instance.php: -------------------------------------------------------------------------------- 1 | locate_plugin(); 72 | $this->slug = $location['dir_basename']; 73 | $this->dir_path = $location['dir_path']; 74 | $this->dir_url = $location['dir_url']; 75 | 76 | spl_autoload_register( array( $this, 'autoload' ) ); 77 | $this->add_doc_hooks(); 78 | } 79 | 80 | /** 81 | * Plugin_Base destructor. 82 | */ 83 | function __destruct() { 84 | $this->remove_doc_hooks(); 85 | } 86 | 87 | /** 88 | * Get reflection object for this class. 89 | * 90 | * @return \ReflectionObject 91 | */ 92 | public function get_object_reflection() { 93 | static $reflection; 94 | 95 | if ( empty( $reflection ) ) { 96 | $reflection = new \ReflectionObject( $this ); 97 | } 98 | 99 | return $reflection; 100 | } 101 | 102 | /** 103 | * Autoload for classes that are in the same namespace as $this. 104 | * 105 | * @param string $class Class name. 106 | * @return void 107 | */ 108 | public function autoload( $class ) { 109 | if ( ! isset( $this->autoload_matches_cache[ $class ] ) ) { 110 | if ( ! preg_match( '/^(?P.+)\\\\(?P[^\\\\]+)$/', $class, $matches ) ) { 111 | $matches = false; 112 | } 113 | 114 | $this->autoload_matches_cache[ $class ] = $matches; 115 | } else { 116 | $matches = $this->autoload_matches_cache[ $class ]; 117 | } 118 | 119 | if ( empty( $matches ) ) { 120 | return false; 121 | } 122 | 123 | if ( $this->get_object_reflection()->getNamespaceName() !== $matches['namespace'] ) { 124 | return false; 125 | } 126 | 127 | $class_name = $matches['class']; 128 | $class_path = \trailingslashit( $this->dir_path ); 129 | 130 | if ( $this->autoload_class_dir ) { 131 | $class_path .= \trailingslashit( $this->autoload_class_dir ); 132 | } 133 | 134 | $class_path .= sprintf( 'class-%s.php', strtolower( str_replace( '_', '-', $class_name ) ) ); 135 | 136 | if ( is_readable( $class_path ) ) { 137 | require_once $class_path; 138 | } 139 | } 140 | 141 | /** 142 | * Version of plugin_dir_url() which works for plugins installed in the plugins directory, 143 | * and for plugins bundled with themes. 144 | * 145 | * @throws Exception If the plugin is not located in the expected location. 146 | * @return array 147 | */ 148 | public function locate_plugin() { 149 | $file_name = $this->get_object_reflection()->getFileName(); 150 | 151 | if ( '/' !== \DIRECTORY_SEPARATOR ) { 152 | $file_name = str_replace( \DIRECTORY_SEPARATOR, '/', $file_name ); // Windows compat. 153 | } 154 | 155 | $plugin_dir = preg_replace( '#(.*plugins[^/]*/[^/]+)(/.*)?#', '$1', $file_name, 1, $count ); 156 | 157 | if ( 0 === $count ) { 158 | throw new Exception( "Class not located within a directory tree containing 'plugins': $file_name" ); 159 | } 160 | 161 | // Make sure that we can reliably get the relative path inside of the content directory. 162 | $plugin_path = $this->relative_path( $plugin_dir, 'wp-content', \DIRECTORY_SEPARATOR ); 163 | 164 | if ( '' === $plugin_path ) { 165 | throw new Exception( 'Plugin dir is not inside of the `wp-content` directory' ); 166 | } 167 | 168 | $dir_url = content_url( trailingslashit( $plugin_path ) ); 169 | $dir_path = $plugin_dir; 170 | $dir_basename = basename( $plugin_dir ); 171 | 172 | return compact( 'dir_url', 'dir_path', 'dir_basename' ); 173 | } 174 | 175 | /** 176 | * Relative Path 177 | * 178 | * Returns a relative path from a specified starting position of a full path 179 | * 180 | * @param string $path The full path to start with. 181 | * @param string $start The directory after which to start creating the relative path. 182 | * @param string $sep The directory seperator. 183 | * 184 | * @return string 185 | */ 186 | public function relative_path( $path, $start, $sep ) { 187 | $path = explode( $sep, untrailingslashit( $path ) ); 188 | 189 | if ( count( $path ) > 0 ) { 190 | foreach ( $path as $p ) { 191 | array_shift( $path ); 192 | 193 | if ( $p === $start ) { 194 | break; 195 | } 196 | } 197 | } 198 | 199 | return implode( $sep, $path ); 200 | } 201 | 202 | /** 203 | * Hooks a function on to a specific filter. 204 | * 205 | * @param string $name The hook name. 206 | * @param array $callback The class object and method. 207 | * @param array $args An array with priority and arg_count. 208 | * 209 | * @return mixed 210 | */ 211 | public function add_filter( $name, $callback, $args = array() ) { 212 | // Merge defaults. 213 | $args = array_merge( array( 214 | 'priority' => 10, 215 | 'arg_count' => PHP_INT_MAX, 216 | ), $args ); 217 | 218 | return $this->_add_hook( 'filter', $name, $callback, $args ); 219 | } 220 | 221 | /** 222 | * Hooks a function on to a specific action. 223 | * 224 | * @param string $name The hook name. 225 | * @param array $callback The class object and method. 226 | * @param array $args An array with priority and arg_count. 227 | * 228 | * @return mixed 229 | */ 230 | public function add_action( $name, $callback, $args = array() ) { 231 | // Merge defaults. 232 | $args = array_merge( array( 233 | 'priority' => 10, 234 | 'arg_count' => PHP_INT_MAX, 235 | ), $args ); 236 | 237 | return $this->_add_hook( 'action', $name, $callback, $args ); 238 | } 239 | 240 | /** 241 | * Hooks a function on to a specific action/filter. 242 | * 243 | * @param string $type The hook type. Options are action/filter. 244 | * @param string $name The hook name. 245 | * @param array $callback The class object and method. 246 | * @param array $args An array with priority and arg_count. 247 | * 248 | * @return mixed 249 | */ 250 | protected function _add_hook( $type, $name, $callback, $args = array() ) { 251 | $priority = isset( $args['priority'] ) ? $args['priority'] : 10; 252 | $arg_count = isset( $args['arg_count'] ) ? $args['arg_count'] : PHP_INT_MAX; 253 | $fn = sprintf( '\add_%s', $type ); 254 | $retval = \call_user_func( $fn, $name, $callback, $priority, $arg_count ); 255 | 256 | return $retval; 257 | } 258 | 259 | /** 260 | * Add actions/filters from the methods of a class based on DocBlocks. 261 | * 262 | * @param object $object The class object. 263 | */ 264 | public function add_doc_hooks( $object = null ) { 265 | if ( is_null( $object ) ) { 266 | $object = $this; 267 | } 268 | 269 | $class_name = get_class( $object ); 270 | 271 | if ( isset( $this->_called_doc_hooks[ $class_name ] ) ) { 272 | $notice = sprintf( 'The add_doc_hooks method was already called on %s. Note that the Plugin_Base constructor automatically calls this method.', $class_name ); 273 | 274 | // @codingStandardsIgnoreStart 275 | trigger_error( esc_html( $notice ), \E_USER_NOTICE ); 276 | // @codingStandardsIgnoreEnd 277 | 278 | return; 279 | } 280 | 281 | $this->_called_doc_hooks[ $class_name ] = true; 282 | 283 | $reflector = new \ReflectionObject( $object ); 284 | 285 | foreach ( $reflector->getMethods() as $method ) { 286 | $doc = $method->getDocComment(); 287 | $arg_count = $method->getNumberOfParameters(); 288 | 289 | if ( preg_match_all( '#\* @(?Pfilter|action)\s+(?P[a-z0-9\-\._]+)(?:,\s+(?P\d+))?#', $doc, $matches, PREG_SET_ORDER ) ) { 290 | foreach ( $matches as $match ) { 291 | $type = $match['type']; 292 | $name = $match['name']; 293 | $priority = empty( $match['priority'] ) ? 10 : intval( $match['priority'] ); 294 | $callback = array( $object, $method->getName() ); 295 | call_user_func( array( $this, "add_{$type}" ), $name, $callback, compact( 'priority', 'arg_count' ) ); 296 | } 297 | } 298 | } 299 | } 300 | 301 | /** 302 | * Removes the added DocBlock hooks. 303 | * 304 | * @param object $object The class object. 305 | */ 306 | public function remove_doc_hooks( $object = null ) { 307 | if ( is_null( $object ) ) { 308 | $object = $this; 309 | } 310 | 311 | $class_name = get_class( $object ); 312 | $reflector = new \ReflectionObject( $object ); 313 | 314 | foreach ( $reflector->getMethods() as $method ) { 315 | $doc = $method->getDocComment(); 316 | 317 | if ( preg_match_all( '#\* @(?Pfilter|action)\s+(?P[a-z0-9\-\._]+)(?:,\s+(?P\d+))?#', $doc, $matches, PREG_SET_ORDER ) ) { 318 | foreach ( $matches as $match ) { 319 | $type = $match['type']; 320 | $name = $match['name']; 321 | $priority = empty( $match['priority'] ) ? 10 : intval( $match['priority'] ); 322 | $callback = array( $object, $method->getName() ); 323 | call_user_func( "remove_{$type}", $name, $callback, $priority ); 324 | } 325 | } 326 | } 327 | 328 | unset( $this->_called_doc_hooks[ $class_name ] ); 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /wp-content/plugins/foo-bar/php/class-plugin.php: -------------------------------------------------------------------------------- 1 | config = apply_filters( 'foo_bar_plugin_config', $this->config, $this ); 26 | } 27 | 28 | /** 29 | * Register scripts. 30 | * 31 | * @action wp_enqueue_scripts 32 | */ 33 | public function register_scripts() {} 34 | 35 | /** 36 | * Register styles. 37 | * 38 | * @action wp_enqueue_scripts 39 | */ 40 | public function register_styles() {} 41 | } 42 | -------------------------------------------------------------------------------- /wp-content/plugins/foo-bar/readme.md: -------------------------------------------------------------------------------- 1 | # Foo Bar 2 | 3 | Foo Bar is a built-in template plugin for scaffolding WordPress plugins. 4 | 5 | **Contributors:** [xwp](https://profiles.wordpress.org/xwp) 6 | **Requires at least:** 4.4 7 | **Tested up to:** 4.7.3 8 | **Stable tag:** trunk (master) 9 | **License:** [GPLv2 or later](http://www.gnu.org/licenses/gpl-2.0.html) 10 | 11 | ## Description ## 12 | 13 | Foo Bar is a built-in template plugin for scaffolding WordPress plugins. The [`bin/plugin`](../../../bin/plugin) bash script will copy the `foo-bar` plugin and make the necessary replacements via: 14 | 15 | ```bash 16 | bin/plugin "Hello World" 17 | ``` 18 | 19 | This will create a plugin `hello-world` in the `wp-content/plugins` directory and will greatly speed up plugin development inside this repo. 20 | 21 | Be sure to add your new plugin to the `testsuite` inside the [`wp-tests/phpunit.xml.dist`](../../../wp-tests/phpunit.xml.dist) file to ensure your PHPUnit tests are included in the `pre-commit` hook and `bin/phpunit` script. It is required that you run this script from this repositories root directory. -------------------------------------------------------------------------------- /wp-content/plugins/foo-bar/tests/class-test-foo-bar.php: -------------------------------------------------------------------------------- 1 | assertContains( '
', $buffer ); 28 | } 29 | 30 | /** 31 | * Test _foo_bar_php_version_text(). 32 | * 33 | * @see _foo_bar_php_version_text() 34 | */ 35 | public function test_foo_bar_php_version_text() { 36 | $this->assertContains( 'Foo Bar plugin error:', _foo_bar_php_version_text() ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /wp-content/plugins/foo-bar/tests/php/class-test-plugin-base.php: -------------------------------------------------------------------------------- 1 | plugin = get_plugin_instance(); 32 | } 33 | 34 | /** 35 | * Test autoload. 36 | * 37 | * @see Plugin_Base::autoload() 38 | */ 39 | public function test_autoload() { 40 | $object = new Test_Doc_Hooks(); 41 | 42 | $retval = $this->plugin->autoload( get_class( $object ) ); 43 | $this->assertNull( $retval ); 44 | 45 | $retval = $this->plugin->autoload( 'OtherNameSpace\Test' ); 46 | $this->assertFalse( $retval ); 47 | } 48 | 49 | /** 50 | * Test locate_plugin. 51 | * 52 | * @see Plugin_Base::locate_plugin() 53 | */ 54 | public function test_locate_plugin() { 55 | $location = $this->plugin->locate_plugin(); 56 | 57 | $this->assertEquals( 'foo-bar', $location['dir_basename'] ); 58 | $this->assertContains( 'plugins/foo-bar', $location['dir_path'] ); 59 | $this->assertContains( 'plugins/foo-bar', $location['dir_url'] ); 60 | } 61 | 62 | /** 63 | * Test relative_path. 64 | * 65 | * @see Plugin_Base::relative_path() 66 | */ 67 | public function test_relative_path() { 68 | $this->assertEquals( 'plugins/foo-bar', $this->plugin->relative_path( '/srv/www/wordpress-develop/src/wp-content/plugins/foo-bar', 'wp-content', '/' ) ); 69 | $this->assertEquals( 'themes/twentysixteen/plugins/foo-bar', $this->plugin->relative_path( '/srv/www/wordpress-develop/src/wp-content/themes/twentysixteen/plugins/foo-bar', 'wp-content', '/' ) ); 70 | } 71 | 72 | 73 | /** 74 | * Test add_doc_hooks(). 75 | * 76 | * @see Plugin_Base::add_doc_hooks() 77 | */ 78 | public function test_add_doc_hooks() { 79 | $object = new Test_Doc_Hooks(); 80 | 81 | $this->assertFalse( has_action( 'init', array( $object, 'init_action' ) ) ); 82 | $this->assertFalse( has_action( 'the_content', array( $object, 'the_content_filter' ) ) ); 83 | $this->plugin->add_doc_hooks( $object ); 84 | $this->assertEquals( 10, has_action( 'init', array( $object, 'init_action' ) ) ); 85 | $this->assertEquals( 10, has_action( 'the_content', array( $object, 'the_content_filter' ) ) ); 86 | $this->plugin->remove_doc_hooks( $object ); 87 | } 88 | 89 | /** 90 | * Test add_doc_hooks(). 91 | * 92 | * @see Plugin_Base::add_doc_hooks() 93 | */ 94 | public function test_add_doc_hooks_error() { 95 | $object = new Test_Doc_Hooks(); 96 | $that = $this; 97 | 98 | $this->plugin->add_doc_hooks( $object ); 99 | 100 | // @codingStandardsIgnoreStart 101 | set_error_handler( function ( $errno, $errstr ) use ( $that, $object ) { 102 | $that->assertEquals( sprintf( 'The add_doc_hooks method was already called on %s. Note that the Plugin_Base constructor automatically calls this method.', get_class( $object ) ), $errstr ); 103 | $that->assertEquals( \E_USER_NOTICE, $errno ); 104 | } ); 105 | // @codingStandardsIgnoreEnd 106 | 107 | $this->plugin->add_doc_hooks( $object ); 108 | restore_error_handler(); 109 | 110 | $this->plugin->remove_doc_hooks( $object ); 111 | } 112 | 113 | /** 114 | * Test remove_doc_hooks(). 115 | * 116 | * @see Plugin_Base::remove_doc_hooks() 117 | */ 118 | public function test_remove_doc_hooks() { 119 | $object = new Test_Doc_Hooks(); 120 | 121 | $this->plugin->add_doc_hooks( $object ); 122 | $this->assertEquals( 10, has_action( 'init', array( $object, 'init_action' ) ) ); 123 | $this->assertEquals( 10, has_action( 'the_content', array( $object, 'the_content_filter' ) ) ); 124 | $this->plugin->remove_doc_hooks( $object ); 125 | $this->assertFalse( has_action( 'init', array( $object, 'init_action' ) ) ); 126 | $this->assertFalse( has_action( 'the_content', array( $object, 'the_content_filter' ) ) ); 127 | } 128 | 129 | /** 130 | * Test remove_doc_hooks(). 131 | * 132 | * @see Plugin_Base::remove_doc_hooks() 133 | */ 134 | public function test_remove_doc_hooks_alt() { 135 | $object = new Test_Doc_Hooks_Alt(); 136 | 137 | $this->plugin->add_doc_hooks( $object ); 138 | $this->assertEquals( 10, has_action( 'init', array( $object, 'init_action' ) ) ); 139 | $cache = clone $object; 140 | $object = null; 141 | $this->assertFalse( has_action( 'the_content', array( $cache, 'the_content_filter' ) ) ); 142 | } 143 | } 144 | 145 | /** 146 | * Test_Doc_Hooks class. 147 | */ 148 | class Test_Doc_Hooks { 149 | 150 | /** 151 | * Load this on the init action hook. 152 | * 153 | * @action init 154 | */ 155 | public function init_action() {} 156 | 157 | /** 158 | * Load this on the the_content filter hook. 159 | * 160 | * @filter the_content 161 | * 162 | * @param string $content The content. 163 | * @return string 164 | */ 165 | public function the_content_filter( $content ) { 166 | return $content; 167 | } 168 | } 169 | 170 | /** 171 | * Test_Doc_Hooks_Alt class. 172 | */ 173 | class Test_Doc_Hooks_Alt extends Plugin_Base { 174 | 175 | /** 176 | * Load this on the init action hook. 177 | * 178 | * @action init 179 | */ 180 | public function init_action() {} 181 | } 182 | 183 | -------------------------------------------------------------------------------- /wp-content/plugins/foo-bar/tests/php/class-test-plugin.php: -------------------------------------------------------------------------------- 1 | assertEquals( 9, has_action( 'after_setup_theme', array( $plugin, 'init' ) ) ); 26 | $this->assertEquals( 10, has_action( 'wp_enqueue_scripts', array( $plugin, 'register_scripts' ) ) ); 27 | $this->assertEquals( 10, has_action( 'wp_enqueue_scripts', array( $plugin, 'register_styles' ) ) ); 28 | } 29 | 30 | /** 31 | * Test for init() method. 32 | * 33 | * @see Plugin::init() 34 | */ 35 | public function test_init() { 36 | $plugin = get_plugin_instance(); 37 | 38 | add_filter( 'foo_bar_plugin_config', array( $this, 'filter_config' ), 10, 2 ); 39 | 40 | $plugin->init(); 41 | $this->assertInternalType( 'array', $plugin->config ); 42 | $this->assertArrayHasKey( 'foo', $plugin->config ); 43 | } 44 | 45 | /** 46 | * Filter to test 'foo_bar_plugin_config'. 47 | * 48 | * @see Plugin::init() 49 | * @param array $config Plugin config. 50 | * @param Plugin_Base $plugin Plugin instance. 51 | * @return array 52 | */ 53 | public function filter_config( $config, $plugin ) { 54 | unset( $config, $plugin ); // Test should actually use these. 55 | 56 | return array( 57 | 'foo' => 'bar', 58 | ); 59 | } 60 | 61 | /* Put other test functions here... */ 62 | } 63 | -------------------------------------------------------------------------------- /wp-tests/phpunit-bootstrap.php: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | ../wp-content/plugins/foo-bar/tests/ 12 | 13 | 14 | 15 | 16 | 17 | ../wp-content/plugins 18 | 19 | ../wp-content/plugins/opcache 20 | ../wp-content/plugins/query-monitor 21 | ../wp-content/plugins/wp-redis 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /wp-tests/wp-tests-config.php: -------------------------------------------------------------------------------- 1 |