├── web
├── wp-content
│ ├── object-cache.php
│ ├── themes
│ │ └── phpinfo
│ │ │ ├── index.php
│ │ │ └── style.css
│ └── mu-plugins
│ │ ├── stack-mu-plugin.php
│ │ └── register-theme-directory.php
├── index.php
└── wp-config.php
├── wp-cli.yml
├── src
├── Stack
│ ├── NginxHelper
│ │ ├── index.php
│ │ ├── admin
│ │ │ ├── index.php
│ │ │ ├── icons
│ │ │ │ ├── nginx-icon-32x32.png
│ │ │ │ ├── font
│ │ │ │ │ ├── nginx-fontello.eot
│ │ │ │ │ ├── nginx-fontello.ttf
│ │ │ │ │ ├── nginx-fontello.woff
│ │ │ │ │ └── nginx-fontello.svg
│ │ │ │ ├── config.json
│ │ │ │ └── css
│ │ │ │ │ └── nginx-fontello.css
│ │ │ ├── partials
│ │ │ │ ├── nginx-helper-sidebar-display.php
│ │ │ │ └── nginx-helper-admin-display.php
│ │ │ ├── css
│ │ │ │ └── nginx-helper-admin.css
│ │ │ ├── js
│ │ │ │ └── nginx-helper-admin.js
│ │ │ ├── class-fastcgi-purger.php
│ │ │ ├── class-predis-purger.php
│ │ │ ├── class-phpredis-purger.php
│ │ │ └── class-memcached-purger.php
│ │ ├── includes
│ │ │ ├── index.php
│ │ │ ├── class-nginx-helper-deactivator.php
│ │ │ ├── class-nginx-helper-activator.php
│ │ │ ├── class-nginx-helper-i18n.php
│ │ │ ├── class-nginx-helper-loader.php
│ │ │ └── class-nginx-helper.php
│ │ ├── languages
│ │ │ ├── nginx-helper.mo
│ │ │ └── nginx-helper.po
│ │ ├── .github
│ │ │ └── workflows
│ │ │ │ └── create.yml
│ │ ├── class-nginx-helper-wp-cli-command.php
│ │ ├── composer.json
│ │ ├── uninstall.php
│ │ ├── nginx-helper.php
│ │ ├── composer.lock
│ │ └── readme.txt
│ ├── BlobStore
│ │ ├── Exceptions
│ │ │ └── NotFound.php
│ │ ├── WordPressObjectCache.php
│ │ ├── LocalFilesystem.php
│ │ └── GoogleCloudStorage.php
│ ├── ContentFilterInterface.php
│ ├── BlobStore.php
│ ├── NginxHelperActivator.php
│ ├── Integrations
│ │ ├── WooCommerce.php
│ │ └── WooCommerce
│ │ │ └── WC_Console_Log_Handler.php
│ ├── ContentFilter.php
│ ├── MetricsRegistry.php
│ ├── DNSDiscovery.php
│ ├── QuerySplit.php
│ ├── Config.php
│ ├── URLFixer.php
│ ├── CDNOffloader.php
│ ├── WP_Filesystem_Stack.php
│ ├── MediaStorage.php
│ └── MetricsCollector.php
└── mu-plugin.php
├── config
├── environments
│ ├── staging.php
│ └── development.php
└── application.php
├── common.Makefile
├── .env.example
├── stack-mu-plugin.php
├── var.Makefile
├── .drone.yml
├── README.md
├── composer.json
└── LICENSE
/web/wp-content/object-cache.php:
--------------------------------------------------------------------------------
1 | ../../src/object-cache.php
--------------------------------------------------------------------------------
/wp-cli.yml:
--------------------------------------------------------------------------------
1 | path: web/wp
2 | server:
3 | docroot: web
4 |
--------------------------------------------------------------------------------
/web/wp-content/themes/phpinfo/index.php:
--------------------------------------------------------------------------------
1 | 'purge',
16 | 'nginx_helper_urls' => 'all',
17 | )
18 | );
19 | $nonced_url = wp_nonce_url( $purge_url, 'nginx_helper-purge_all' );
20 | ?>
21 |
22 |
23 |
24 |
29 |
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/admin/icons/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nginx-fontello",
3 | "css_prefix_text": "nginx-helper-",
4 | "css_use_suffix": false,
5 | "hinting": true,
6 | "units_per_em": 1000,
7 | "ascent": 850,
8 | "glyphs": [
9 | {
10 | "uid": "72b1277834cba5b7944b0a6cac7ddb0d",
11 | "css": "rss",
12 | "code": 59395,
13 | "src": "fontawesome"
14 | },
15 | {
16 | "uid": "627abcdb627cb1789e009c08e2678ef9",
17 | "css": "twitter",
18 | "code": 59394,
19 | "src": "fontawesome"
20 | },
21 | {
22 | "uid": "bc50457410acf467b8b5721240768742",
23 | "css": "facebook",
24 | "code": 59393,
25 | "src": "entypo"
26 | },
27 | {
28 | "uid": "b945f4ac2439565661e8e4878e35d379",
29 | "css": "gplus",
30 | "code": 59392,
31 | "src": "entypo"
32 | }
33 | ]
34 | }
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/includes/class-nginx-helper-deactivator.php:
--------------------------------------------------------------------------------
1 | remove_cap( 'Nginx Helper | Config' );
32 | $role->remove_cap( 'Nginx Helper | Purge cache' );
33 |
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | DB_NAME=database_name
2 | DB_USER=database_user
3 | DB_PASSWORD=database_password
4 |
5 | DB_TEST_NAME=test_database_name
6 | DB_TEST_USER=test_database_user
7 | DB_TEST_PASSWORD=test_database_password
8 |
9 | # Optionally, you can use a data source name (DSN)
10 | # When using a DSN, you can remove the DB_NAME, DB_USER, DB_PASSWORD, and DB_HOST variables
11 | # DATABASE_URL=mysql://database_user:database_password@database_host:database_port/database_name
12 |
13 | # Optional variables
14 | # DB_HOST=localhost
15 | # DB_PREFIX=wp_
16 |
17 | WP_ENV=development
18 | WP_HOME=http://example.com
19 | WP_SITEURL=${WP_HOME}/wp
20 |
21 | # Generate your keys here: https://roots.io/salts.html
22 | AUTH_KEY='generateme'
23 | SECURE_AUTH_KEY='generateme'
24 | LOGGED_IN_KEY='generateme'
25 | NONCE_KEY='generateme'
26 | AUTH_SALT='generateme'
27 | SECURE_AUTH_SALT='generateme'
28 | LOGGED_IN_SALT='generateme'
29 | NONCE_SALT='generateme'
30 |
--------------------------------------------------------------------------------
/stack-mu-plugin.php:
--------------------------------------------------------------------------------
1 | purge_all();
39 |
40 | $message = __( 'Purged Everything!', 'nginx-helper' );
41 | WP_CLI::success( $message );
42 |
43 | }
44 |
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rtcamp/nginx-helper",
3 | "description": "Cleans nginx's fastcgi/proxy cache or redis-cache whenever a post is edited/published. Also does a few more things.",
4 | "keywords": ["wordpress", "plugin", "nginx", "nginx-helper", "fastcgi", "redis-cache", "redis", "cache"],
5 | "homepage": "https://rtcamp.com/nginx-helper/",
6 | "license": "GPL-2.0+",
7 | "authors": [{
8 | "name": "rtCamp",
9 | "email": "support@rtcamp.com",
10 | "homepage": "https://rtcamp.com"
11 | }],
12 | "minimum-stability": "dev",
13 | "prefer-stable": true,
14 | "type": "wordpress-plugin",
15 | "support": {
16 | "issues": "https://github.com/rtCamp/nginx-helper/issues",
17 | "forum": "https://wordpress.org/support/plugin/nginx-helper",
18 | "wiki": "https://github.com/rtCamp/nginx-helper/wiki",
19 | "source": "https://github.com/rtCamp/nginx-helper/"
20 | },
21 | "require": {
22 | "php": ">=5.3.2",
23 | "composer/installers": "^1.0"
24 | },
25 | "require-dev": {
26 | "wpreadme2markdown/wpreadme2markdown": "*"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Stack/ContentFilter.php:
--------------------------------------------------------------------------------
1 | enabled()) {
19 | array_push($this->filters, $f);
20 | }
21 | }
22 | if (empty($this->filters)) {
23 | return;
24 | }
25 |
26 | ob_start(array($this, 'filter'));
27 | }
28 |
29 | public function filter($content, $phase)
30 | {
31 | if ($phase & PHP_OUTPUT_HANDLER_FINAL || $phase & PHP_OUTPUT_HANDLER_END) {
32 | foreach ($this->filters as $filter) {
33 | $content = $filter->filter($content);
34 | }
35 | return $content;
36 | }
37 |
38 | return $content;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/uninstall.php:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
31 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/admin/css/nginx-helper-admin.css:
--------------------------------------------------------------------------------
1 | /**
2 | * All of the CSS for your admin-specific functionality should be
3 | * included in this file.
4 | */
5 |
6 | .clearfix {
7 | *zoom: 1;
8 | }
9 | .clearfix:before,
10 | .clearfix:after {
11 | content: " ";
12 | display: table;
13 | }
14 | .clearfix:after {
15 | clear: both;
16 | }
17 | h4 {
18 | margin: 0;
19 | }
20 | .form-table th,
21 | .form-wrap label {
22 | vertical-align: middle;
23 | }
24 | table.rtnginx-table {
25 | border-bottom: 1px solid #EEE;
26 | }
27 | table.rtnginx-table:last-child {
28 | border-bottom: 0;
29 | }
30 | .rtnginx-table p.error {
31 | color: red;
32 | }
33 | pre#map {
34 | background: #e5e5e5 none;
35 | border-radius: 10px;
36 | padding: 10px;
37 | }
38 | #poststuff h2 {
39 | padding: 0 0 0 10px;
40 | margin-top: 0;
41 | }
42 | form#purgeall .button-primary {
43 | margin-bottom: 20px;
44 | box-shadow: inset 0 -2px rgba(0, 0, 0, 0.14);
45 | padding: 15px 30px;
46 | font-size: 1rem;
47 | border: 0;
48 | border-radius: 5px;
49 | color: #FFF;
50 | background: #DD3D36;
51 | height: auto;
52 | }
53 | form#purgeall .button-primary:hover,
54 | form#purgeall .button-primary:focus {
55 | background: #d52c24;
56 | }
57 | .nh-aligncenter {
58 | display: block;
59 | text-align: center;
60 | line-height: 2;
61 | }
62 | .rt-purge_url { width: 100%; }
63 |
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/includes/class-nginx-helper-activator.php:
--------------------------------------------------------------------------------
1 | functional_asset_path();
33 |
34 | if ( ! is_dir( $path ) ) {
35 | mkdir( $path );
36 | }
37 |
38 | if ( ! current_user_can( 'activate_plugins' ) ) {
39 | return;
40 | }
41 |
42 | $role = get_role( 'administrator' );
43 |
44 | if ( empty( $role ) ) {
45 |
46 | update_site_option(
47 | 'rt_wp_nginx_helper_init_check',
48 | __( 'Sorry, you need to be an administrator to use Nginx Helper', 'nginx-helper' )
49 | );
50 |
51 | return;
52 |
53 | }
54 |
55 | $role->add_cap( 'Nginx Helper | Config' );
56 | $role->add_cap( 'Nginx Helper | Purge cache' );
57 |
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/includes/class-nginx-helper-i18n.php:
--------------------------------------------------------------------------------
1 | domain,
46 | false,
47 | dirname( dirname( plugin_basename( __FILE__ ) ) ) . '/languages/'
48 | );
49 |
50 | }
51 |
52 | /**
53 | * Set the domain equal to that of the specified domain.
54 | *
55 | * @since 2.0.0
56 | *
57 | * @param string $domain The domain that represents the locale of this plugin.
58 | */
59 | public function set_domain( $domain ) {
60 | $this->domain = $domain;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/Stack/BlobStore/WordPressObjectCache.php:
--------------------------------------------------------------------------------
1 | cacheGroup = $cacheGroup;
22 | }
23 |
24 | public function get(string $key) : string
25 | {
26 | $found = false;
27 | $result = wp_cache_get($key, $this->cacheGroup, false, $found);
28 | if (false === $found) {
29 | throw new \Stack\BlobStore\Exceptions\NotFound(sprintf("%s not found", $key));
30 | }
31 | return $result;
32 | }
33 |
34 | public function getMeta(string $key)
35 | {
36 | $content = $this->get($key);
37 | return [
38 | "size" => strlen($content),
39 | "atime" => time(),
40 | "ctime" => time(),
41 | "mtime" => time(),
42 | ];
43 | }
44 |
45 | public function set(string $key, string $content)
46 | {
47 | if (!wp_cache_set($key, $content, $this->cacheGroup, 0)) {
48 | throw new \Exception(sprintf("Could not write blob to key '%s'", $key));
49 | }
50 | }
51 |
52 | public function remove(string $key)
53 | {
54 | if (!wp_cache_delete($key, $this->cacheGroup)) {
55 | throw new \Exception(sprintf("Could not remove blob at key '%s'", $key));
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/admin/icons/css/nginx-fontello.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'nginx-fontello';
3 | src: url('../font/nginx-fontello.eot?7388141');
4 | src: url('../font/nginx-fontello.eot?7388141#iefix') format('embedded-opentype'),
5 | url('../font/nginx-fontello.woff?7388141') format('woff'),
6 | url('../font/nginx-fontello.ttf?7388141') format('truetype'),
7 | url('../font/nginx-fontello.svg?7388141#nginx-fontello') format('svg');
8 | font-weight: normal;
9 | font-style: normal;
10 | }
11 | /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
12 | /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
13 | /* // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
14 | @media screen and (-webkit-min-device-pixel-ratio:0) {
15 | @font-face {
16 | font-family: 'nginx-fontello';
17 | src: url('../font/nginx-fontello.svg?7388141#nginx-fontello') format('svg');
18 | }
19 | }
20 | */
21 |
22 | [class^="nginx-helper-"]:before, [class*=" nginx-helper-"]:before {
23 | font-family: "nginx-fontello";
24 | font-style: normal;
25 | font-weight: normal;
26 | speak: none;
27 |
28 | display: inline-block;
29 | text-decoration: inherit;
30 | width: 1em;
31 | margin-right: .2em;
32 | text-align: center;
33 | /* opacity: .8; */
34 |
35 | /* For safety - reset parent styles, that can break glyph codes*/
36 | font-variant: normal;
37 | text-transform: none;
38 |
39 | /* fix buttons height, for twitter bootstrap */
40 | line-height: 1em;
41 |
42 | /* Animation center compensation - margins should be symmetric */
43 | /* remove if not needed */
44 | margin-left: .2em;
45 |
46 | /* you can be more comfortable with increased icons size */
47 | /* font-size: 120%; */
48 |
49 | /* Uncomment for 3D effect */
50 | /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
51 | }
52 |
53 | .nginx-helper-twitter:before { content: '\e802'; } /* '' */
54 | .nginx-helper-facebook:before { content: '\e801'; } /* '' */
55 |
--------------------------------------------------------------------------------
/src/Stack/BlobStore/LocalFilesystem.php:
--------------------------------------------------------------------------------
1 | uploadsDir = $uploadsDir;
21 | }
22 |
23 | public function get(string $key) : string
24 | {
25 | $path = path_join($this->uploadsDir, $key);
26 | if (!file_exists($path)) {
27 | throw new \Stack\BlobStore\Exceptions\NotFound(sprintf("%s not found", $key));
28 | }
29 | $result = file_get_contents($path);
30 | if (false === $result) {
31 | throw new \Exception(sprintf("%s get failed", $key));
32 | }
33 | return $result;
34 | }
35 |
36 | public function getMeta(string $key)
37 | {
38 | $path = path_join($this->uploadsDir, $key);
39 | if (!is_file($path)) {
40 | throw new \Stack\BlobStore\Exceptions\NotFound(sprintf("%s not found", $key));
41 | }
42 | $stat = stat($path);
43 | $content = $this->get($key);
44 | return $stat;
45 | }
46 |
47 | public function set(string $key, string $content)
48 | {
49 | $path = path_join($this->uploadsDir, $key);
50 | $dir = dirname($path);
51 | if (false === wp_mkdir_p($dir)) {
52 | throw new \Exception(sprintf("Could not create directory '%s'", $dir));
53 | }
54 | if (false === file_put_contents($path, $content)) {
55 | throw new \Exception(sprintf("Could not write blob to key '%s'", $key));
56 | }
57 | }
58 |
59 | public function remove(string $key)
60 | {
61 | $path = path_join($this->uploadsDir, $key);
62 | if (false === unlink($path)) {
63 | throw new \Exception(sprintf("Could not remove blob at key '%s'", $key));
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/var.Makefile:
--------------------------------------------------------------------------------
1 | ifndef __VAR_MAKEFILE__
2 |
3 | __VAR_MAKEFILE__ := included
4 |
5 | # Provides a class of targets that ensures variables are
6 | # defined and trigger rebuilds when variable values change.
7 | #
8 | # Usage:
9 | #
10 | # my_target: .build/var/REGISTRY
11 | #
12 | # my_target rebuilds when the value of $(REGISTRY) changes.
13 | # .build/var/REGISTRY also ensures that $(REGISTRY) value
14 | # is non-empty.
15 |
16 |
17 | # The main target that this Makefile offers.
18 | # This is a real file that gets updated when a variable value
19 | # change is detected. This rule does not have a recipe. It
20 | # relies on the %-phony prerequisite to detect the change and
21 | # update the file.
22 | .build/var/%: .build/var/%-required .build/var/%-phony ;
23 |
24 |
25 | .build/var:
26 | mkdir -p "$@"
27 |
28 |
29 | # Since we can't make pattern targets phony, we make them
30 | # effectively phony by depending on this phony target.
31 | .PHONY: var/phony
32 | var/phony: ;
33 |
34 |
35 | # An effectively phony target that always runs to compare the current
36 | # value of the variable (say VARNAME) with the content of the
37 | # corresponding .build/var/VARNAME file. If the contents differ,
38 | # the recipe updates .build/var/VARNAME, which effectively trigger
39 | # rebuilding of targets depending on .build/var.VARNAME.
40 | .build/var/%-phony: var/phony | .build/var
41 | @ \
42 | var_key="$*" ; \
43 | var_val="${$*}" ; \
44 | var_val_old=$$(cat ".build/var/$$var_key" 2> /dev/null) ; \
45 | if [ "$$var_val" != "$$var_val_old" ]; then \
46 | $(call print_notice,$$var_key has been updated) ; \
47 | printf " old value: $$var_val_old\n" ; \
48 | printf " new value: $$var_val\n" ; \
49 | printf "$$var_val" > .build/var/$$var_key ; \
50 | fi
51 |
52 |
53 | # An effectively phony target that verifies that the variable
54 | # has a non-empty value.
55 | .build/var/%-required: var/phony
56 | @ \
57 | var_key="$*" ; \
58 | var_val="${$*}" ; \
59 | if [ "$$var_val" = "" ]; then \
60 | $(call print_notice,Make variable '$*' is required); \
61 | exit 1; \
62 | fi
63 |
64 |
65 | endif
66 |
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/admin/icons/font/nginx-fontello.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.drone.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | name: default
3 |
4 | clone:
5 | disable: true
6 |
7 | steps:
8 | - name: clone
9 | image: plugins/git
10 | settings:
11 | depth: 0
12 | tags: true
13 |
14 | - name: dependencies
15 | image: docker.io/bitpoke/build:v0.7.4
16 | commands:
17 | - composer install --prefer-dist
18 |
19 | - name: lint
20 | image: docker.io/bitpoke/build:v0.7.4
21 | commands:
22 | - make lint
23 | when:
24 | event: [push, pull_request]
25 |
26 | - name: test
27 | image: docker.io/bitpoke/build:v0.7.4
28 | user: root
29 | environment:
30 | DB_TEST_HOST: database
31 | DB_TEST_NAME: wordpress_tests
32 | DB_TEST_PASSWORD: not-so-secure-but-good-for-ci
33 | DB_TEST_USER: wordpress
34 | MEMCACHED_TEST_HOST: memcache:11211
35 | commands:
36 | - make test-runtime
37 | when:
38 | event: [push, pull_request]
39 |
40 | - name: integration test
41 | image: docker.io/bitpoke/build:v0.7.4
42 | user: root
43 | environment:
44 | DB_TEST_HOST: database
45 | DB_TEST_NAME: wordpress_tests
46 | DB_TEST_PASSWORD: not-so-secure-but-good-for-ci
47 | DB_TEST_USER: wordpress
48 | MEMCACHED_TEST_HOST: memcache:11211
49 | commands:
50 | - make test-wp
51 | when:
52 | event: [push, pull_request]
53 |
54 | - name: bundle
55 | pull: always
56 | image: docker.io/bitpoke/build:v0.7.4
57 | user: root
58 | commands:
59 | - make bundle
60 | when:
61 | event: tag
62 |
63 | - name: publish
64 | image: plugins/github-release@sha256:78bf13eda852e815310a5c4faf3ad04fd0c1c07bf661b4f979ab296f044f9cbd
65 | settings:
66 | api_key:
67 | from_secret: GH_TOKEN
68 | files:
69 | - dist/*
70 | checksum:
71 | - md5
72 | - sha1
73 | - sha512
74 | when:
75 | event: tag
76 |
77 | services:
78 | - name: database
79 | image: percona:5.7
80 | environment:
81 | MYSQL_DATABASE: wordpress_tests
82 | MYSQL_PASSWORD: not-so-secure-but-good-for-ci
83 | MYSQL_ROOT_PASSWORD: insecure-root-password-but-good-for-ci
84 | MYSQL_USER: wordpress
85 | when:
86 | event: [push, pull_request]
87 |
88 | - name: memcache
89 | image: memcached:1.5.10-alpine
90 | when:
91 | event: [push, pull_request]
92 |
--------------------------------------------------------------------------------
/src/Stack/MetricsRegistry.php:
--------------------------------------------------------------------------------
1 | registry = new \Prometheus\CollectorRegistry($storage);
15 | $this->registerMetrics($metrics);
16 | }
17 |
18 | public function registerMetrics($metrics = [])
19 | {
20 | foreach ($metrics as $metric => [$kind, $description, $labels]) {
21 | [$namespace, $name] = explode('.', $metric);
22 |
23 | switch ($kind) {
24 | case 'histogram':
25 | $this->metrics[$metric] = $this->registry->getOrRegisterHistogram(
26 | $namespace,
27 | $name,
28 | $description,
29 | $labels
30 | );
31 | break;
32 |
33 | case 'counter':
34 | $this->metrics[$metric] = $this->registry->getOrRegisterCounter(
35 | $namespace,
36 | $name,
37 | $description,
38 | $labels
39 | );
40 | break;
41 |
42 | case 'gauge':
43 | $this->metrics[$metric] = $this->registry->getOrRegisterGauge(
44 | $namespace,
45 | $name,
46 | $description,
47 | $labels
48 | );
49 | break;
50 | }
51 | }
52 | }
53 |
54 | public function getGauge($metric)
55 | {
56 | return $this->metrics[$metric];
57 | }
58 |
59 | public function getCounter($metric)
60 | {
61 | return $this->metrics[$metric];
62 | }
63 |
64 | public function getHistogram($metric)
65 | {
66 | return $this->metrics[$metric];
67 | }
68 |
69 | public function render()
70 | {
71 | $renderer = new \Prometheus\RenderTextFormat();
72 |
73 | return $renderer->render($this->registry->getMetricFamilySamples());
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | stack-mu-plugin
2 | ===
3 | Bitpoke Stack must use plugin for WordPress.
4 |
5 | It provides integration for the [Bitpoke Stack](https://www.bitpoke.io/stack)
6 | functionalities with WordPress and WooCommerce, such as:
7 |
8 | * uploading and serving media files from object storage systems, currently with Google Cloud Storage
9 | * object-cache implementation on top of memcached
10 | * offloading assets to a CDN
11 | * unified handling of logs to stderr, by default
12 | * handling of duplicate, incompatible dependencies through [Jetpack Autoloader](https://github.com/Automattic/jetpack-autoloader)
13 |
14 | ## Install
15 |
16 | ### Bedrock
17 |
18 | When using bedrock, just run:
19 |
20 | ```console
21 | $ composer require bitpoke/stack-mu-plugin
22 | ```
23 |
24 | ### WordPress plugin
25 |
26 | To run as WordPress classic mu-plugin, download the plugin archive from
27 | [https://github.com/bitpoke/stack-mu-plugin/releases](https://github.com/bitpoke/stack-mu-plugin/releases)
28 | and extract it into your `wp-content/mu-plugins` folder.
29 |
30 | Then you need to activate the mu-plugin, by copying `stack-mu-plugin.php` from
31 | `wp-content/mu-plugins/stakc-mu-plugin` into your `wp-content/mu-plugins`
32 | folder.
33 |
34 | ```console
35 | $ cp wp-content/mu-plugins/stack-mu-plugin/stack-mu-plugin.php wp-content/mu-plugins/
36 | ```
37 |
38 | ### WordPress Object Cache
39 |
40 | In order to use the custom object cache, you'll need to copy it into the root of
41 | `WP_CONTENT_DIR` (usually `wp-content`).
42 |
43 | ```console
44 | $ cp wp-content/mu-plugins/stack-mu-plugin/src/object-cache.php wp-content/
45 | ```
46 |
47 | ### Enable and use a CDN for static files
48 |
49 | All that is needed is setting the `CDN_HOST` variable in wp-config.php and of course a CNAME record in your DNS manager pointing to your CDN provider.
50 |
51 | For example, we might use in our config file:
52 |
53 | ```php
54 | define('CDN_HOST', 'cdn.bitpoke.io');
55 | ```
56 |
57 | ## Development
58 |
59 | Clone this repository, copy `.env.example` to `.env` and edit it accordingly.
60 |
61 | To install dependencies just run
62 | ```console
63 | $ make dependencies
64 | ```
65 |
66 | ### Development server
67 |
68 | To start a local development server you need wp-cli installed. To start the
69 | development server, just run
70 |
71 | ```console
72 | $ wp server
73 | ```
74 |
75 | ### Testing
76 |
77 | Running plugin tests:
78 |
79 | ```console
80 | $ make test-wp
81 | ```
82 |
83 | Running integration tests:
84 |
85 | ```console
86 | $ make test-runtime
87 | ```
88 |
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/admin/js/nginx-helper-admin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * File to add JavaScript for nginx-helper.
3 | *
4 | * @package nginx-helper
5 | */
6 |
7 | (function ($) {
8 | 'use strict';
9 |
10 | /**
11 | * All of the code for your admin-specific JavaScript source
12 | * should reside in this file.
13 | *
14 | * Note that this assume you're going to use jQuery, so it prepares
15 | * the $ function reference to be used within the scope of this
16 | * function.
17 | *
18 | * From here, you're able to define handlers for when the DOM is
19 | * ready:
20 | *
21 | * $(function() {
22 | *
23 | * });
24 | *
25 | * Or when the window is loaded:
26 | *
27 | * $( window ).load(function() {
28 | *
29 | * });
30 | *
31 | * ...and so on.
32 | *
33 | * Remember that ideally, we should not attach any more than a single DOM-ready or window-load handler
34 | * for any particular page. Though other scripts in WordPress core, other plugins, and other themes may
35 | * be doing this, we should try to minimize doing that in our own work.
36 | */
37 | $(
38 | function () {
39 |
40 | jQuery( "form#purgeall a" ).click(
41 | function (e) {
42 |
43 | if ( confirm( nginx_helper.purge_confirm_string ) === true ) {
44 | // Continue submitting form.
45 | } else {
46 | e.preventDefault();
47 | }
48 |
49 | }
50 | );
51 |
52 | /**
53 | * Show OR Hide options on option checkbox
54 | *
55 | * @param {type} selector Selector of Checkbox and PostBox
56 | */
57 | function nginx_show_option( selector ) {
58 |
59 | jQuery( '#' + selector ).on(
60 | 'change',
61 | function () {
62 |
63 | if ( jQuery( this ).is( ':checked' ) ) {
64 |
65 | jQuery('.' + selector).not(".hidden").show();
66 |
67 | if ( 'cache_method_redis' === selector ) {
68 | jQuery( '.cache_method_fastcgi' ).hide();
69 | jQuery( '.cache_method_memcached' ).hide();
70 | } else if ( selector === 'cache_method_fastcgi' ) {
71 | jQuery( '.cache_method_redis' ).hide();
72 | jQuery( '.cache_method_memcached' ).hide();
73 | } else if ( selector === 'cache_method_memcached' ) {
74 | jQuery( '.cache_method_redis' ).hide();
75 | jQuery( '.cache_method_fastcgi' ).hide();
76 | }
77 |
78 | } else {
79 | jQuery( '.' + selector ).hide();
80 | }
81 |
82 | }
83 | );
84 |
85 | }
86 |
87 | /* Function call with parameter */
88 | nginx_show_option( 'cache_method_fastcgi' );
89 | nginx_show_option( 'cache_method_redis' );
90 | nginx_show_option( 'cache_method_memcached' );
91 | nginx_show_option( 'enable_map' );
92 | nginx_show_option( 'enable_log' );
93 | nginx_show_option( 'enable_purge' );
94 |
95 | }
96 | );
97 | })( jQuery );
98 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bitpoke/stack-mu-plugin",
3 | "description": "WordPress must-use plugin for Stack",
4 | "license": "Apache-2.0",
5 | "authors": [
6 | {
7 | "name": "Bitpoke",
8 | "email": "hello@bitpoke.io",
9 | "homepage": "http://www.bitpoke.io/stack"
10 | }
11 | ],
12 | "support": {
13 | "issues": "https://github.com/bitpoke/stack-mu-plugin/issues",
14 | "docs": "https://www.bitpoke.io/docs/stack"
15 | },
16 | "type": "wordpress-muplugin",
17 | "repositories": [
18 | {
19 | "type": "composer",
20 | "url": "https://wpackagist.org"
21 | }
22 | ],
23 | "require": {
24 | "php": ">=7.4",
25 | "composer/installers": "^1.9 || ^2.0",
26 | "automattic/jetpack-autoloader": "^5",
27 | "vlucas/phpdotenv": ">=4.1.8 <6",
28 | "oscarotero/env": "^2.1",
29 | "google/cloud-storage": "^1.28",
30 | "google/auth": "^1.21.0",
31 | "promphp/prometheus_client_php": "^2.1"
32 | },
33 | "config": {
34 | "optimize-autoloader": true,
35 | "allow-plugins": {
36 | "roots/wordpress-core-installer": true,
37 | "composer/installers": true,
38 | "dealerdirect/phpcodesniffer-composer-installer": true,
39 | "automattic/jetpack-autoloader": true
40 | }
41 | },
42 | "autoload": {
43 | "psr-4": {
44 | "Stack\\": "src/Stack/"
45 | }
46 | },
47 | "require-dev": {
48 | "squizlabs/php_codesniffer": "^3.6.2",
49 | "dealerdirect/phpcodesniffer-composer-installer": "~0.7.1",
50 | "wp-coding-standards/wpcs": "~2.3.0",
51 | "roave/security-advisories": "dev-latest",
52 | "roots/wordpress": "^6.8",
53 | "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^9",
54 | "roots/wp-config": "1.0.0",
55 | "wpackagist-plugin/debug-bar": "^1.0",
56 | "wpackagist-plugin/debug-bar-console": "^0.3.0",
57 | "wpackagist-plugin/debug-bar-constants": "^2.0",
58 | "wpackagist-plugin/woocommerce": "^8.0",
59 | "yoast/phpunit-polyfills": "^1.0 || ^2.0 || ^3.0 || ^4.0",
60 | "johnkary/phpunit-speedtrap": "^4.0"
61 | },
62 | "scripts": {
63 | "lint": "phpcs",
64 | "test": "phpunit"
65 | },
66 | "extra": {
67 | "wordpress-install-dir": "web/wp",
68 | "installer-paths": {
69 | "web/wp-content/mu-plugins/{$name}/": [
70 | "type:wordpress-muplugin"
71 | ],
72 | "web/wp-content/plugins/{$name}/": [
73 | "type:wordpress-plugin"
74 | ],
75 | "web/wp-content/themes/{$name}/": [
76 | "type:wordpress-theme"
77 | ]
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Stack/Integrations/WooCommerce/WC_Console_Log_Handler.php:
--------------------------------------------------------------------------------
1 | $value) {
43 | if ($key == 'backtrace') {
44 | continue;
45 | }
46 | $ctx .= " $key=" . wp_json_encode($value);
47 | }
48 | return $ctx;
49 | }
50 |
51 | /**
52 | * Builds a log entry text from level, timestamp and message.
53 | *
54 | * @param int $timestamp Log timestamp.
55 | * @param string $level emergency|alert|critical|error|warning|notice|info|debug.
56 | * @param string $message Log message.
57 | * @param array $context Additional information for log handlers.
58 | *
59 | * @return string Formatted log entry.
60 | */
61 | // phpcs:ignore
62 | protected static function format_entry($timestamp, $level, $message, $context)
63 | {
64 | if (defined('STACK_JSON_LOG') && true === STACK_JSON_LOG) {
65 | $time_string = self::format_time($timestamp);
66 | $level_string = strtoupper($level);
67 | $entry = array(
68 | 'timestamp' => $time_string,
69 | 'severity' => strtoupper($level_string),
70 | 'message' => $message,
71 | 'labels' => $context,
72 | );
73 | return wp_json_encode($entry);
74 | } else {
75 | return parent::format_entry($timestamp, $level, $message, $context) . self::format_context($context);
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Stack/DNSDiscovery.php:
--------------------------------------------------------------------------------
1 | $record) {
16 | $hosts[] = $record["ip"];
17 | }
18 | if (count($hosts) > 0) {
19 | shuffle($hosts);
20 | return $hosts[0];
21 | }
22 | return "";
23 | }
24 |
25 | public static function cachedDiscoverSRV(string $host, string $service = "", string $protocol = "tcp", $ttl = -1)
26 | {
27 | if (php_sapi_name() == 'cli') {
28 | return DNSDiscovery::discoverSRV($host, $service, $protocol);
29 | }
30 |
31 | $_host = $host;
32 | if ($service) {
33 | $_host = sprintf("_%s._%s.%s", $service, $protocol, $host);
34 | }
35 | $key = sprintf("%s:%s", DNSDiscovery::$cachePrefix, $_host);
36 |
37 | if ($ttl == -1) {
38 | $ttl = DNSDiscovery::$cacheDefaultTTL;
39 | }
40 |
41 | return apcu_entry($key, function ($key) {
42 | list($_, $host) = explode(':', $key, 2);
43 | return DNSDiscovery::discoverSRV($host);
44 | }, $ttl);
45 | }
46 |
47 | public static function cachedDiscover(string $host, $ttl = -1)
48 | {
49 | if (php_sapi_name() == 'cli') {
50 | return DNSDiscovery::discover($host);
51 | }
52 |
53 | $key = sprintf("%s:%s", DNSDiscovery::$cachePrefix, $host);
54 |
55 | if ($ttl == -1) {
56 | $ttl = DNSDiscovery::$cacheDefaultTTL;
57 | }
58 |
59 | return apcu_entry($key, function ($key) {
60 | list($_, $host) = explode(':', $key, 2);
61 | return DNSDiscovery::discover($host);
62 | }, $ttl);
63 | }
64 |
65 | public static function discoverSRV(string $host, string $service = "", string $protocol = "tcp")
66 | {
67 | if ($service) {
68 | $host = sprintf("_%s._%s.%s", $service, $protocol, $host);
69 | }
70 | $hosts = array();
71 | foreach (dns_get_record($host, DNS_SRV) as $_ => $record) {
72 | $target = DNSDiscovery::resolve($record["target"]);
73 | if ($target) {
74 | $hosts[] = array("priority" => $record["pri"], "host" => $target, "port" => $record["port"]);
75 | }
76 | }
77 | return $hosts;
78 | }
79 |
80 | public static function discover(string $host)
81 | {
82 | $hosts = array();
83 | foreach (dns_get_record($host, DNS_A) as $_ => $record) {
84 | $hosts[] = array("priority" => 10, "host" => $record["ip"], "port" => 0);
85 | }
86 | return $hosts;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/nginx-helper.php:
--------------------------------------------------------------------------------
1 | run();
87 |
88 | // Load WP-CLI command.
89 | if ( defined( 'WP_CLI' ) && WP_CLI ) {
90 |
91 | require_once NGINX_HELPER_BASEPATH . 'class-nginx-helper-wp-cli-command.php';
92 | \WP_CLI::add_command( 'nginx-helper', 'Nginx_Helper_WP_CLI_Command' );
93 |
94 | }
95 |
96 | }
97 | run_nginx_helper();
98 |
--------------------------------------------------------------------------------
/src/Stack/QuerySplit.php:
--------------------------------------------------------------------------------
1 | setup();
27 | $this->addFilters();
28 | }
29 |
30 | public function __destruct()
31 | {
32 | $this->removeFilters();
33 | }
34 |
35 | private function addFilters()
36 | {
37 | add_filter('query', [$this, 'processQuery']);
38 | }
39 |
40 | private function removeFilters()
41 | {
42 | remove_filter('query', [$this, 'processQuery']);
43 | }
44 |
45 | public function processQuery(string $query)
46 | {
47 | // don't need to analize the query if it's already write
48 | // NOTE: this can be optimized to send reads on master per table
49 | if (!$this->srtm) {
50 | $this->analizeQuery($query);
51 | }
52 |
53 | // set the write label on the query
54 | if ($this->srtm === true) {
55 | $query = $query . " " . self::MASTER_LABEL;
56 | }
57 |
58 | return $query;
59 | }
60 |
61 | private function setup()
62 | {
63 | // Send non-idempotent requests to master
64 | if (! in_array($_SERVER['REQUEST_METHOD'], array( 'GET', 'HEAD' ))) {
65 | $this->sendReadsToMaster();
66 | return;
67 | }
68 |
69 | // Cron jobs always go to master so that they can run SQL queries more easily
70 | if (isset($_GET['doing_wp_cron'])) {
71 | $this->sendReadsToMaster();
72 | return;
73 | }
74 |
75 | // Admin requests always go to master
76 | if ('/wp/wp-admin/' == substr($_SERVER['REQUEST_URI'], 0, 13)) {
77 | $this->sendReadsToMaster();
78 | return;
79 | }
80 |
81 | if ('/wp/wp-login.php' == substr($_SERVER['REQUEST_URI'], 0, 16)) {
82 | $this->sendReadsToMaster();
83 | return;
84 | }
85 |
86 | if ('/wp/wp-register.php' == substr($_SERVER['REQUEST_URI'], 0, 19)) {
87 | $this->sendReadsToMaster();
88 | return;
89 | }
90 |
91 | // XML-RPC go to master since they usually are modifying content
92 | if ('/wp/xmlrpc.php' == substr($_SERVER['REQUEST_URI'], 0, 14)) {
93 | $this->sendReadsToMaster();
94 | return;
95 | }
96 | }
97 |
98 | public function sendReadsToMaster()
99 | {
100 | $this->srtm = true;
101 | }
102 |
103 | private function analizeQuery($query)
104 | {
105 | if ($this->isWriteQuery($query)) {
106 | $this->sendReadsToMaster();
107 | }
108 | }
109 |
110 | private function isWriteQuery($q)
111 | {
112 | // Quick and dirty: only SELECT statements are considered read-only.
113 | $q = ltrim($q, "\r\n\t (");
114 |
115 | return ! preg_match('/^(?:SELECT(?!.*FOR UPDATE)|SHOW|DESCRIBE|DESC|EXPLAIN)\s/i', $q);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/Stack/Config.php:
--------------------------------------------------------------------------------
1 | 0) {
16 | return;
17 | }
18 |
19 | $uploads = wp_upload_dir(null, false, false);
20 | $homeURL = home_url();
21 |
22 | /*
23 | * uploads dir relative to webroot
24 | * this takes into account CONTENT_DIR (defined by bedrock setups)
25 | * and defaults to `wp-content/uploads`
26 | */
27 | if ($homeURL === substr($uploads['baseurl'], 0, strlen($homeURL)) &&
28 | !(substr($homeURL, -strlen("/wp")) === "/wp")) {
29 | $relUploadsDir = substr($uploads['baseurl'], strlen($homeURL));
30 | } else {
31 | $relUploadsDir = (defined('CONTENT_DIR') ? CONTENT_DIR : '/wp-content') . '/uploads';
32 | }
33 | $relUploadsDir = ltrim($relUploadsDir, '/');
34 | self::defineFromEnv("STACK_MEDIA_PATH", env('MEDIA_PATH') ?: $relUploadsDir, '/');
35 | self::defineFromEnv("STACK_MEDIA_BUCKET", env('MEDIA_BUCKET') ?: "file://" . $uploads['basedir']);
36 |
37 | self::defineFromEnv("DOBJECT_CACHE_PRELOAD", false);
38 |
39 | self::defineFromEnv("MEMCACHED_HOST", "");
40 | self::defineFromEnv("MEMCACHED_DISCOVERY_HOST", "");
41 |
42 | self::defineFromEnv("STACK_PAGE_CACHE_ENABLED", false);
43 | self::defineFromEnv("STACK_PAGE_CACHE_AUTOMATIC_PLUGIN_ON_OFF", true);
44 | self::defineFromEnv("STACK_PAGE_CACHE_BACKEND", "");
45 | self::defineFromEnv("STACK_PAGE_CACHE_KEY_PREFIX", "");
46 |
47 | if (STACK_PAGE_CACHE_BACKEND == "redis") {
48 | self::defineFromEnv("RT_WP_NGINX_HELPER_REDIS_HOSTNAME", "", "STACK_PAGE_CACHE_REDIS_HOST");
49 | self::defineFromEnv("RT_WP_NGINX_HELPER_REDIS_PORT", "", "STACK_PAGE_CACHE_REDIS_PORT");
50 | self::define("RT_WP_NGINX_HELPER_REDIS_PREFIX", STACK_PAGE_CACHE_KEY_PREFIX);
51 | } elseif (STACK_PAGE_CACHE_BACKEND == "memcached") {
52 | self::defineFromEnv("RT_WP_NGINX_HELPER_MEMCACHED_HOSTNAME", "", "STACK_PAGE_CACHE_MEMCACHED_HOST");
53 | self::defineFromEnv("RT_WP_NGINX_HELPER_MEMCACHED_PORT", "", "STACK_PAGE_CACHE_MEMCACHED_PORT");
54 | self::define("RT_WP_NGINX_HELPER_MEMCACHED_PREFIX", STACK_PAGE_CACHE_KEY_PREFIX);
55 |
56 | $versionedCacheKey = STACK_PAGE_CACHE_KEY_PREFIX . "version";
57 | self::define("RT_WP_NGINX_HELPER_MEMCACHED_VERSIONED_CACHE_KEY", $versionedCacheKey);
58 | }
59 |
60 | self::definePath("GIT_DIR", env("SRC_DIR") ?: "/var/run/presslabs.org/code/src");
61 | self::definePath("GIT_KEY_FILE", "/var/run/secrets/presslabs.org/instance/id_rsa");
62 | self::definePath("GIT_KEY_FILE", (rtrim(env("HOME"), '/') ?: "/var/www") . "/.ssh/id_rsa");
63 |
64 | self::defineFromEnv("STACK_METRICS_ENABLED", true);
65 | self::define('STACK_REST_API_VERSION', '1');
66 |
67 | $runCount++;
68 | }
69 |
70 | public static function defineFromEnv(string $name, $defaultValue, string $envName = "")
71 | {
72 | $envName = $envName ?: $name;
73 | $value = env($envName);
74 | if (false !== $value) {
75 | $value = $value ?: $defaultValue;
76 | }
77 | self::define($name, $value);
78 | }
79 |
80 | public static function definePath(string $name, string $path, string $defaultPath = "")
81 | {
82 | if (file_exists($path)) {
83 | self::define($name, $path);
84 | } elseif (!empty($defaultPath)) {
85 | self::define($name, $defaultPath);
86 | }
87 | }
88 |
89 | public static function define(string $name, $value)
90 | {
91 | defined($name) or define($name, $value);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/Stack/BlobStore/GoogleCloudStorage.php:
--------------------------------------------------------------------------------
1 | client) {
36 | $sup = isset($_ENV['SUPPRESS_GCLOUD_CREDS_WARNING']) && $_ENV['SUPPRESS_GCLOUD_CREDS_WARNING'] == 'true';
37 | $clientConfig = [
38 | 'suppressKeyFileNotice' => $sup,
39 | ];
40 |
41 | $envCreds = isset($_ENV['GOOGLE_CREDENTIALS']) ? $_ENV['GOOGLE_CREDENTIALS'] : '';
42 | if (!empty($envCreds)) {
43 | $envCreds = json_decode($envCreds, true);
44 | $clientConfig['keyFile'] = $envCreds;
45 | }
46 |
47 | $this->client = new StorageClient($clientConfig);
48 | }
49 | return $this->client;
50 | }
51 |
52 | public function __construct(string $bucket, string $prefix = '')
53 | {
54 | $this->client = null;
55 | $this->bucket = $bucket;
56 | $this->prefix = $prefix;
57 | }
58 |
59 | private function normalizePath(string $path) : string
60 | {
61 | return ltrim(path_join($this->prefix, $path), '/');
62 | }
63 |
64 | public function get(string $key) : string
65 | {
66 | try {
67 | $bucket = $this->getClient()->bucket($this->bucket);
68 | $object = $bucket->object($this->normalizePath($key));
69 | $result = $object->downloadAsString();
70 | return $result;
71 | } catch (NotFoundException $e) {
72 | throw new \Stack\BlobStore\Exceptions\NotFound($e->getMessage());
73 | }
74 | }
75 |
76 | public function getMeta(string $key)
77 | {
78 | try {
79 | $bucket = $this->getClient()->bucket($this->bucket);
80 | $object = $bucket->object($this->normalizePath($key));
81 | $info = $object->info();
82 | } catch (NotFoundException $e) {
83 | throw new \Stack\BlobStore\Exceptions\NotFound($e->getMessage());
84 | }
85 | $now = time();
86 | return [
87 | "size" => isset($info['size']) ? $info['size'] : 0,
88 | "atime" => isset($info['updated']) ? strtotime($info['updated']) : $now,
89 | "ctime" => isset($info['timeCreated']) ? strtotime($info['timeCreated']) : $now,
90 | "mtime" => isset($info['updated']) ? strtotime($info['updated']) : $now,
91 | ];
92 | }
93 |
94 | public function set(string $key, string $content)
95 | {
96 | try {
97 | $bucket = $this->getClient()->bucket($this->bucket);
98 | $uploader = $bucket->upload($content, [
99 | 'name' => $this->normalizePath($key),
100 | 'resumable' => false,
101 | ]);
102 | } catch (\Exception $e) {
103 | throw new \Exception($e->getMessage());
104 | }
105 | }
106 |
107 | public function remove(string $key)
108 | {
109 | try {
110 | $bucket = $this->getClient()->bucket($this->bucket);
111 | $object = $bucket->object($this->normalizePath($key));
112 | $object->delete();
113 | } catch (NotFoundException $e) {
114 | throw new \Stack\BlobStore\Exceptions\NotFound($e->getMessage());
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/config/application.php:
--------------------------------------------------------------------------------
1 | load();
30 | $dotenv->required(['WP_HOME', 'WP_SITEURL']);
31 | if (!env('DATABASE_URL')) {
32 | $dotenv->required(['DB_NAME', 'DB_USER', 'DB_PASSWORD']);
33 | }
34 | }
35 |
36 | /**
37 | * Set up our global environment constant and load its config first
38 | * Default: production
39 | */
40 | define('WP_ENV', env('WP_ENV') ?: 'production');
41 |
42 | /**
43 | * URLs
44 | */
45 | Config::define('WP_HOME', env('WP_HOME'));
46 | Config::define('WP_SITEURL', env('WP_SITEURL'));
47 |
48 | /**
49 | * Custom Content Directory
50 | */
51 | Config::define('CONTENT_DIR', '/wp-content');
52 | Config::define('WP_CONTENT_DIR', $webroot_dir . Config::get('CONTENT_DIR'));
53 | Config::define('WP_CONTENT_URL', Config::get('WP_HOME') . Config::get('CONTENT_DIR'));
54 |
55 | /**
56 | * DB settings
57 | */
58 | Config::define('DB_NAME', env('DB_NAME'));
59 | Config::define('DB_USER', env('DB_USER'));
60 | Config::define('DB_PASSWORD', env('DB_PASSWORD'));
61 | Config::define('DB_HOST', env('DB_HOST') ?: 'localhost');
62 | Config::define('DB_CHARSET', 'utf8mb4');
63 | Config::define('DB_COLLATE', '');
64 | $table_prefix = env('DB_PREFIX') ?: 'wp_';
65 |
66 | if (env('DATABASE_URL')) {
67 | $dsn = (object) parse_url(env('DATABASE_URL'));
68 |
69 | Config::define('DB_NAME', substr($dsn->path, 1));
70 | Config::define('DB_USER', $dsn->user);
71 | Config::define('DB_PASSWORD', isset($dsn->pass) ? $dsn->pass : null);
72 | Config::define('DB_HOST', isset($dsn->port) ? "{$dsn->host}:{$dsn->port}" : $dsn->host);
73 | }
74 |
75 | /**
76 | * Authentication Unique Keys and Salts
77 | */
78 | Config::define('AUTH_KEY', env('AUTH_KEY'));
79 | Config::define('SECURE_AUTH_KEY', env('SECURE_AUTH_KEY'));
80 | Config::define('LOGGED_IN_KEY', env('LOGGED_IN_KEY'));
81 | Config::define('NONCE_KEY', env('NONCE_KEY'));
82 | Config::define('AUTH_SALT', env('AUTH_SALT'));
83 | Config::define('SECURE_AUTH_SALT', env('SECURE_AUTH_SALT'));
84 | Config::define('LOGGED_IN_SALT', env('LOGGED_IN_SALT'));
85 | Config::define('NONCE_SALT', env('NONCE_SALT'));
86 |
87 | /**
88 | * Custom Settings
89 | */
90 | Config::define('AUTOMATIC_UPDATER_DISABLED', true);
91 | Config::define('DISABLE_WP_CRON', env('DISABLE_WP_CRON') ?: false);
92 | // Disable the plugin and theme file editor in the admin
93 | Config::define('DISALLOW_FILE_EDIT', true);
94 | // Disable plugin and theme updates and installation from the admin
95 | Config::define('DISALLOW_FILE_MODS', true);
96 |
97 | /**
98 | * Debugging Settings
99 | */
100 | Config::define('WP_DEBUG_DISPLAY', false);
101 | Config::define('SCRIPT_DEBUG', false);
102 | ini_set('display_errors', 0);
103 |
104 | Config::define('WP_DEFAULT_THEME', 'phpinfo');
105 |
106 | /**
107 | * Allow WordPress to detect HTTPS when used behind a reverse proxy or a load balancer
108 | * See https://codex.wordpress.org/Function_Reference/is_ssl#Notes
109 | */
110 | if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
111 | $_SERVER['HTTPS'] = 'on';
112 | }
113 |
114 | $env_config = __DIR__ . '/environments/' . WP_ENV . '.php';
115 |
116 | if (file_exists($env_config)) {
117 | require_once $env_config;
118 | }
119 |
120 | Config::apply();
121 |
122 | /**
123 | * Bootstrap WordPress
124 | */
125 | if (!defined('ABSPATH')) {
126 | define('ABSPATH', $webroot_dir . '/wp/');
127 | }
128 |
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/includes/class-nginx-helper-loader.php:
--------------------------------------------------------------------------------
1 | actions = array();
57 | $this->filters = array();
58 |
59 | }
60 |
61 | /**
62 | * Add a new action to the collection to be registered with WordPress.
63 | *
64 | * @since 2.0.0
65 | *
66 | * @param string $hook The name of the WordPress action that is being registered.
67 | * @param object $component A reference to the instance of the object on which the action is defined.
68 | * @param string $callback The name of the function definition on the $component.
69 | * @param int $priority The priority at which the function should be fired.
70 | * @param int $accepted_args The number of arguments that should be passed to the $callback.
71 | */
72 | public function add_action( $hook, $component, $callback, $priority = 10, $accepted_args = 1 ) {
73 | $this->actions = $this->add( $this->actions, $hook, $component, $callback, $priority, $accepted_args );
74 | }
75 |
76 | /**
77 | * Add a new filter to the collection to be registered with WordPress.
78 | *
79 | * @since 2.0.0
80 | *
81 | * @param string $hook The name of the WordPress filter that is being registered.
82 | * @param object $component A reference to the instance of the object on which the filter is defined.
83 | * @param string $callback The name of the function definition on the $component.
84 | * @param int $priority The priority at which the function should be fired.
85 | * @param int $accepted_args The number of arguments that should be passed to the $callback.
86 | */
87 | public function add_filter( $hook, $component, $callback, $priority = 10, $accepted_args = 1 ) {
88 | $this->filters = $this->add( $this->filters, $hook, $component, $callback, $priority, $accepted_args );
89 | }
90 |
91 | /**
92 | * A utility function that is used to register the actions and hooks into a single
93 | * collection.
94 | *
95 | * @since 2.0.0
96 | *
97 | * @access private
98 | *
99 | * @param array $hooks The collection of hooks that is being registered (that is, actions or filters).
100 | * @param string $hook The name of the WordPress filter that is being registered.
101 | * @param object $component A reference to the instance of the object on which the filter is defined.
102 | * @param string $callback The name of the function definition on the $component.
103 | * @param int $priority The priority at which the function should be fired.
104 | * @param int $accepted_args The number of arguments that should be passed to the $callback.
105 | *
106 | * @return array The collection of actions and filters registered with WordPress.
107 | */
108 | private function add( $hooks, $hook, $component, $callback, $priority, $accepted_args ) {
109 |
110 | $hooks[] = array(
111 | 'hook' => $hook,
112 | 'component' => $component,
113 | 'callback' => $callback,
114 | 'priority' => $priority,
115 | 'accepted_args' => $accepted_args,
116 | );
117 |
118 | return $hooks;
119 |
120 | }
121 |
122 | /**
123 | * Register the filters and actions with WordPress.
124 | *
125 | * @since 2.0.0
126 | */
127 | public function run() {
128 |
129 | foreach ( $this->filters as $hook ) {
130 | add_filter( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] );
131 | }
132 |
133 | foreach ( $this->actions as $hook ) {
134 | add_action( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] );
135 | }
136 |
137 | }
138 |
139 | }
140 |
--------------------------------------------------------------------------------
/src/Stack/URLFixer.php:
--------------------------------------------------------------------------------
1 | coreDirectory = '/' . trim(WP_CORE_DIRECTORY, '/');
28 | }
29 |
30 | $this->addFilters();
31 | }
32 |
33 | /**
34 | * Add filters to verify / fix URLs.
35 | */
36 | public function addFilters()
37 | {
38 | add_filter('option_home', [$this, 'fixHomeURL']);
39 | add_filter('option_siteurl', [$this, 'fixSiteURL']);
40 | add_filter('network_site_url', [$this, 'fixNetworkSiteURL'], 10, 3);
41 | if (is_subdomain_install()) {
42 | add_filter('content_url', [$this, 'fixContentURL']);
43 | add_filter('plugins_url', [$this, 'fixContentURL']);
44 | add_filter('upload_dir', [$this, 'fixUploadsURL']);
45 | }
46 | }
47 |
48 | private function hasSuffix(string $s, string $suffix)
49 | {
50 | return substr($s, -1 * strlen($suffix)) === $suffix;
51 | }
52 |
53 | private function removeSuffix(string $s, string $suffix)
54 | {
55 | if ($this->hasSuffix($s, $suffix)) {
56 | $s = substr($s, 0, -1 * strlen($suffix));
57 | }
58 | return $s;
59 | }
60 |
61 | private function hasPrefix(string $s, string $prefix)
62 | {
63 | return (substr($s, 0, strlen($prefix)) === $prefix);
64 | }
65 |
66 | private function removePrefix(string $s, string $prefix)
67 | {
68 | if ($this->hasPrefix($s, $prefix)) {
69 | $s = substr($s, strlen($prefix));
70 | }
71 | return $s;
72 | }
73 |
74 | /**
75 | * Ensure that content URLs point to the site's domain
76 | *
77 | * We need to do this because we define WP_CONTENT_URL to be the main site's one,
78 | * in order we have a separate wordpress and wp-content directories. For multisites
79 | * on subdomains, having the content server on the main domain may cause cross-origin
80 | * issues for various requests (eg. fonts).
81 | *
82 | * @param string $url the original content URL
83 | * @return string the rewritten URL
84 | */
85 | public function fixContentURL($url)
86 | {
87 | $contentDir = rtrim(defined('CONTENT_DIR') ? CONTENT_DIR : '/wp-content', '/');
88 | $contentURL = rtrim(set_url_scheme(WP_CONTENT_URL), '/');
89 | return str_replace($contentURL, home_url($contentDir), $url);
90 | }
91 |
92 | /**
93 | * Ensure that uploads URLs point to the site's domain
94 | *
95 | * @param array $dir the wp_uploads_dir array
96 | * @return array the array with filtered URLs
97 | */
98 | public function fixUploadsURL($dir)
99 | {
100 | $dir["url"] = $this->fixContentURL($dir["url"]);
101 | $dir["baseurl"] = $this->fixContentURL($dir["baseurl"]);
102 | return $dir;
103 | }
104 |
105 | /**
106 | * Ensure that home URL does not contain the /wp subdirectory.
107 | *
108 | * @param string $value the unchecked home URL
109 | * @return string the verified home URL
110 | */
111 | public function fixHomeURL($value)
112 | {
113 | return $this->removeSuffix($value, $this->coreDirectory);
114 | }
115 |
116 | /**
117 | * Ensure that site URL contains the /wp subdirectory.
118 | *
119 | * @param string $url the unchecked site URL
120 | * @return string the verified site URL
121 | */
122 | public function fixSiteURL($url)
123 | {
124 | if (!$this->hasSuffix($url, $this->coreDirectory) && (is_main_site() || is_subdomain_install())) {
125 | $url .= $this->coreDirectory;
126 | }
127 | return $url;
128 | }
129 |
130 | /**
131 | * Ensure that the network site URL contains the /wp subdirectory.
132 | *
133 | * @param string $url the unchecked network site URL with path appended
134 | * @param string $path the path for the URL
135 | * @param string $scheme the URL scheme
136 | * @return string the verified network site URL
137 | */
138 | public function fixNetworkSiteURL($url, $path, $scheme)
139 | {
140 | $path = ltrim($path, '/');
141 | $url = substr($url, 0, strlen($url) - strlen($path));
142 | $coreDirectory = trim($this->coreDirectory, '/') . '/'; // wp core directory w/ trailing slash
143 |
144 | if (!$this->hasSuffix($url, $coreDirectory)) {
145 | $url .= $coreDirectory;
146 | }
147 |
148 | return $url . $path;
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/Stack/CDNOffloader.php:
--------------------------------------------------------------------------------
1 | cdn_host = $cdn_host;
17 | } elseif (!defined('CDN_HOST')) {
18 | define('CDN_HOST', getenv('CDN_HOST', true) ?: '');
19 | }
20 | $this->cdn_host = apply_filters('bpk_cdn_host', CDN_HOST);
21 | if (empty($this->cdn_host)) {
22 | $this->enabled = false;
23 | return;
24 | }
25 |
26 | $this->hosts = array_unique(apply_filters('bpk_cdn_offload_hosts', array(
27 | parse_url(content_url(), PHP_URL_HOST),
28 | parse_url(includes_url(), PHP_URL_HOST),
29 | )));
30 | if (empty($this->hosts)) {
31 | $this->enabled = false;
32 | return;
33 | }
34 |
35 | $paths = apply_filters('bpk_cdn_offload_paths', array(
36 | parse_url(content_url(), PHP_URL_PATH),
37 | parse_url(includes_url(), PHP_URL_PATH),
38 | parse_url(get_stylesheet_directory_uri(), PHP_URL_PATH),
39 | ));
40 | $paths = array_map(function ($path) {
41 | return trim($path, "/");
42 | }, $paths);
43 | $this->paths = array_unique($paths);
44 |
45 | $this->offload_extensions = array_unique(apply_filters('bpk_cdn_offload_extensions', array(
46 | // images
47 | 'png', 'jpg', 'jpeg', 'tif', 'tiff', 'svg', 'svgz', 'gif', 'webp', 'avif', 'heic', 'bmp',
48 |
49 | // video
50 | 'avi', 'mkv', 'webm', 'mp4', 'mov', 'mpeg', 'mpe', 'swf',
51 |
52 | // font files
53 | 'ttf', 'otf', 'eot', 'woff', 'woff2',
54 |
55 | // media files
56 | 'm3u', 'pls', 'midi', 'mp3', 'flac', 'ogg',
57 |
58 | // web files
59 | 'js', 'css', 'ejs',
60 |
61 | // archives
62 | 'zip', 'tar', 'gz', 'tgz', 'bz2', 'tbz2', 'zst', '7z', 'rar', 'tar.gz', 'tar.bz2',
63 |
64 | // office files
65 | 'doc', 'docx', 'odt', 'csv', 'xls', 'xlsx', 'ods', 'ppt', 'pptx', 'odp', 'odg', 'odf', 'pdf', 'ps', 'eps',
66 | 'pict', 'psd',
67 |
68 | // generic binary file
69 | 'bin', 'iso', 'jar', 'class', 'apk', 'dmg', 'exe'
70 | )));
71 | usort($this->offload_extensions, function ($a, $b) {
72 | return strlen($b) - strlen($a);
73 | });
74 |
75 | $slash = '(?:/|\\\/)'; // match / as well as \/ for JS/JSON escaped paths
76 | # $slash = '/';
77 |
78 | $protocol = "(?:(?:(https?):)?($slash)$slash)";
79 |
80 | $hosts = join('|', array_map(function ($s) {
81 | return preg_quote($s, '#');
82 | }, $this->hosts));
83 |
84 | $paths = join('|', array_map(function ($s) {
85 | return preg_quote($s, '#');
86 | }, $this->paths));
87 | $paths = str_replace('/', $slash, $paths);
88 |
89 | $exts = join('|', array_map(function ($s) {
90 | return preg_quote($s, '#');
91 | }, $this->offload_extensions));
92 |
93 | $path = "$slash(?:$paths)$slash" . '[\\\\/%\w\p{S}\.-]+?' . "\.($exts)";
94 |
95 | $re = "(?<=\A|\s|\b|'|\"|\\\\n)$protocol($hosts)($path)\b";
96 | /* var_dump($re); */
97 | $this->re = "#$re#u";
98 |
99 |
100 | $this->enabled = true;
101 |
102 | add_filter('script_loader_src', array($this, 'filter'));
103 | add_filter('style_loader_src', array($this, 'filter'));
104 | }
105 |
106 | public function enabled(): bool
107 | {
108 | if (!$this->enabled) {
109 | return false;
110 | }
111 | return !apply_filters('cdn_offloader_bypass', false);
112 | }
113 |
114 | public static function offload(string $content = '')
115 | {
116 | $offloader = new static();
117 | return $offloader->filter($content);
118 | }
119 |
120 | public function filter(string $content): string
121 | {
122 | if (!$this->enabled) {
123 | return $content;
124 | }
125 |
126 | $_content = preg_replace_callback($this->re, array($this, 'replace'), $content);
127 |
128 | if (null === $_content) {
129 | trigger_error('CDN offloader preg error: ' . preg_last_error_msg(), E_USER_WARNING);
130 | return $content;
131 | }
132 |
133 | return $_content;
134 | }
135 |
136 | private function replace($matches)
137 | {
138 | $scheme = $matches[1];
139 | $slash = $matches[2];
140 | $request_uri = $matches[4];
141 | $extension = $matches[5];
142 |
143 | // avoid mixed content by always enforcing the proper scheme
144 | $offloaded_scheme = ($scheme == 'https' || is_ssl() ? 'https' : 'http');
145 |
146 | $offloaded = $offloaded_scheme . ":$slash$slash" . $this->cdn_host . $request_uri;
147 |
148 | return apply_filters('bpk_cdn_offload', $offloaded, $matches);
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/admin/class-fastcgi-purger.php:
--------------------------------------------------------------------------------
1 | log( '- Purging URL | ' . $url );
41 |
42 | $parse = wp_parse_url( $url );
43 |
44 | if ( ! isset( $parse['path'] ) ) {
45 | $parse['path'] = '';
46 | }
47 |
48 | switch ( $nginx_helper_admin->options['purge_method'] ) {
49 |
50 | case 'unlink_files':
51 | $_url_purge_base = $parse['scheme'] . '://' . $parse['host'] . $parse['path'];
52 | $_url_purge = $_url_purge_base;
53 |
54 | if ( ! empty( $parse['query'] ) ) {
55 | $_url_purge .= '?' . $parse['query'];
56 | }
57 |
58 | $this->delete_cache_file_for( $_url_purge );
59 |
60 | if ( $feed ) {
61 |
62 | $feed_url = rtrim( $_url_purge_base, '/' ) . '/feed/';
63 | $this->delete_cache_file_for( $feed_url );
64 | $this->delete_cache_file_for( $feed_url . 'atom/' );
65 | $this->delete_cache_file_for( $feed_url . 'rdf/' );
66 |
67 | }
68 | break;
69 |
70 | case 'get_request':
71 | // Go to default case.
72 | default:
73 | $_url_purge_base = $this->purge_base_url() . $parse['path'];
74 | $_url_purge = $_url_purge_base;
75 |
76 | if ( isset( $parse['query'] ) && '' !== $parse['query'] ) {
77 | $_url_purge .= '?' . $parse['query'];
78 | }
79 |
80 | $this->do_remote_get( $_url_purge );
81 |
82 | if ( $feed ) {
83 |
84 | $feed_url = rtrim( $_url_purge_base, '/' ) . '/feed/';
85 | $this->do_remote_get( $feed_url );
86 | $this->do_remote_get( $feed_url . 'atom/' );
87 | $this->do_remote_get( $feed_url . 'rdf/' );
88 |
89 | }
90 | break;
91 |
92 | }
93 |
94 | }
95 |
96 | /**
97 | * Function to custom purge urls.
98 | */
99 | public function custom_purge_urls() {
100 |
101 | global $nginx_helper_admin;
102 |
103 | $parse = wp_parse_url( home_url() );
104 |
105 | $purge_urls = isset( $nginx_helper_admin->options['purge_url'] ) && ! empty( $nginx_helper_admin->options['purge_url'] ) ?
106 | explode( "\r\n", $nginx_helper_admin->options['purge_url'] ) : array();
107 |
108 | /**
109 | * Allow plugins/themes to modify/extend urls.
110 | *
111 | * @param array $purge_urls URLs which needs to be purged.
112 | * @param bool $wildcard If wildcard in url is allowed or not. default false.
113 | */
114 | $purge_urls = apply_filters( 'rt_nginx_helper_purge_urls', $purge_urls, false );
115 |
116 | switch ( $nginx_helper_admin->options['purge_method'] ) {
117 |
118 | case 'unlink_files':
119 | $_url_purge_base = $parse['scheme'] . '://' . $parse['host'];
120 |
121 | if ( is_array( $purge_urls ) && ! empty( $purge_urls ) ) {
122 |
123 | foreach ( $purge_urls as $purge_url ) {
124 |
125 | $purge_url = trim( $purge_url );
126 |
127 | if ( strpos( $purge_url, '*' ) === false ) {
128 |
129 | $purge_url = $_url_purge_base . $purge_url;
130 | $this->log( '- Purging URL | ' . $purge_url );
131 | $this->delete_cache_file_for( $purge_url );
132 |
133 | }
134 | }
135 | }
136 | break;
137 |
138 | case 'get_request':
139 | // Go to default case.
140 | default:
141 | $_url_purge_base = $this->purge_base_url();
142 |
143 | if ( is_array( $purge_urls ) && ! empty( $purge_urls ) ) {
144 |
145 | foreach ( $purge_urls as $purge_url ) {
146 |
147 | $purge_url = trim( $purge_url );
148 |
149 | if ( strpos( $purge_url, '*' ) === false ) {
150 |
151 | $purge_url = $_url_purge_base . $purge_url;
152 | $this->log( '- Purging URL | ' . $purge_url );
153 | $this->do_remote_get( $purge_url );
154 |
155 | }
156 | }
157 | }
158 | break;
159 |
160 | }
161 |
162 | }
163 |
164 | /**
165 | * Purge everything.
166 | */
167 | public function purge_all() {
168 |
169 | $this->unlink_recursive( RT_WP_NGINX_HELPER_CACHE_PATH, false );
170 | $this->log( '* * * * *' );
171 | $this->log( '* Purged Everything!' );
172 | $this->log( '* * * * *' );
173 |
174 | /**
175 | * Fire an action after the FastCGI cache has been purged.
176 | *
177 | * @since 2.1.0
178 | */
179 | do_action( 'rt_nginx_helper_after_fastcgi_purge_all' );
180 | }
181 |
182 | /**
183 | * Constructs the base url to call when purging using the "get_request" method.
184 | *
185 | * @since 2.2.0
186 | *
187 | * @return string
188 | */
189 | private function purge_base_url() {
190 |
191 | $parse = wp_parse_url( home_url() );
192 |
193 | /**
194 | * Filter to change purge suffix for FastCGI cache.
195 | *
196 | * @param string $suffix Purge suffix. Default is purge.
197 | *
198 | * @since 2.2.0
199 | */
200 | $path = apply_filters( 'rt_nginx_helper_fastcgi_purge_suffix', 'purge' );
201 |
202 | // Prevent users from inserting a trailing '/' that could break the url purging.
203 | $path = trim( $path, '/' );
204 |
205 | $purge_url_base = $parse['scheme'] . '://' . $parse['host'] . '/' . $path;
206 |
207 | /**
208 | * Filter to change purge URL base for FastCGI cache.
209 | *
210 | * @param string $purge_url_base Purge URL base.
211 | *
212 | * @since 2.2.0
213 | */
214 | $purge_url_base = apply_filters( 'rt_nginx_helper_fastcgi_purge_url_base', $purge_url_base );
215 |
216 | // Prevent users from inserting a trailing '/' that could break the url purging.
217 | return untrailingslashit( $purge_url_base );
218 |
219 | }
220 |
221 | }
222 |
--------------------------------------------------------------------------------
/src/Stack/WP_Filesystem_Stack.php:
--------------------------------------------------------------------------------
1 | method = 'stack';
27 | $this->errors = new \WP_Error();
28 |
29 | $this->direct = new WP_Filesystem_Direct(null);
30 | }
31 |
32 | protected function log_call($method, $args = [], $result = null)
33 | {
34 | if (!apply_filters('stack_filesystem_debug', false)) {
35 | return;
36 | }
37 | error_log("WP_Filesystem_Stack::$method called with args: " . json_encode($args) .
38 | ($result !== null ? " => " . json_encode($result) : ""));
39 | }
40 |
41 | public function get_contents(...$args)
42 | {
43 | $this->log_call("### " . __FUNCTION__, $args);
44 | return $this->direct->get_contents(...$args);
45 | }
46 |
47 | public function get_contents_array(...$args)
48 | {
49 | $this->log_call(__FUNCTION__, $args);
50 | return $this->direct->get_contents_array(...$args);
51 | }
52 |
53 | public function put_contents(...$args)
54 | {
55 | $this->log_call(__FUNCTION__, $args);
56 | return $this->direct->put_contents(...$args);
57 | }
58 |
59 | public function copy($source, $destination, $overwrite = false, $mode = false)
60 | {
61 | $this->log_call(__FUNCTION__, func_get_args());
62 | if (! $overwrite && $this->exists($destination)) {
63 | return false;
64 | }
65 |
66 | $rtval = copy($source, $destination);
67 |
68 | if ($mode) {
69 | $this->chmod($destination, $mode);
70 | }
71 |
72 | return $rtval;
73 | }
74 |
75 | /**
76 | * @param string $source
77 | * @param string $destination
78 | * @param bool $overwrite
79 | *
80 | * @return bool
81 | */
82 | public function move($source, $destination, $overwrite = false)
83 | {
84 | $this->log_call(__FUNCTION__, func_get_args());
85 | if (! $overwrite && $this->exists($destination)) {
86 | return false;
87 | }
88 |
89 | if ($overwrite && $this->exists($destination) && ! $this->delete($destination, true)) {
90 | // Can't overwrite if the destination couldn't be deleted.
91 | return false;
92 | }
93 |
94 | // Try using rename first. if that fails (for example, source is read only) try copy.
95 | if (@rename($source, $destination)) {
96 | return true;
97 | }
98 |
99 | // Backward compatibility: Only fall back to `::copy()` for single files.
100 | if ($this->is_file($source) && $this->copy($source, $destination, $overwrite) && $this->exists($destination)) {
101 | $this->delete($source);
102 |
103 | return true;
104 | } else {
105 | return false;
106 | }
107 | }
108 |
109 | public function delete(...$args)
110 | {
111 | $this->log_call(__FUNCTION__, $args);
112 | return $this->direct->delete(...$args);
113 | }
114 |
115 | public function size(...$args)
116 | {
117 | $this->log_call(__FUNCTION__, $args);
118 | return $this->direct->size(...$args);
119 | }
120 |
121 | public function exists(...$args)
122 | {
123 | $this->log_call(__FUNCTION__, $args);
124 | return $this->direct->exists(...$args);
125 | }
126 |
127 | public function is_file(...$args)
128 | {
129 | $this->log_call(__FUNCTION__, $args);
130 | return $this->direct->is_file(...$args);
131 | }
132 |
133 | public function is_dir(...$args)
134 | {
135 | $this->log_call(__FUNCTION__, $args);
136 | return $this->direct->is_dir(...$args);
137 | }
138 |
139 | public function is_readable(...$args)
140 | {
141 | $this->log_call(__FUNCTION__, $args);
142 | return $this->direct->is_readable(...$args);
143 | }
144 |
145 | public function is_writable(...$args)
146 | {
147 | $this->log_call(__FUNCTION__, $args);
148 | return $this->direct->is_writable(...$args);
149 | }
150 |
151 | public function atime(...$args)
152 | {
153 | $this->log_call(__FUNCTION__, $args);
154 | return $this->direct->atime(...$args);
155 | }
156 |
157 | public function mtime(...$args)
158 | {
159 | $this->log_call(__FUNCTION__, $args);
160 | return $this->direct->mtime(...$args);
161 | }
162 |
163 | public function touch(...$args)
164 | {
165 | $this->log_call(__FUNCTION__, $args);
166 | return $this->direct->touch(...$args);
167 | }
168 |
169 | public function mkdir(...$args)
170 | {
171 | $this->log_call(__FUNCTION__, $args);
172 | return $this->direct->mkdir(...$args);
173 | }
174 |
175 | public function rmdir(...$args)
176 | {
177 | $this->log_call(__FUNCTION__, $args);
178 | return $this->direct->rmdir(...$args);
179 | }
180 |
181 | public function dirlist(...$args)
182 | {
183 | $this->log_call(__FUNCTION__, $args);
184 | return $this->direct->dirlist(...$args);
185 | }
186 |
187 |
188 | public function cwd(...$args)
189 | {
190 | $this->log_call(__FUNCTION__, $args);
191 | return $this->direct->cwd(...$args);
192 | }
193 |
194 | public function chdir(...$args)
195 | {
196 | $this->log_call(__FUNCTION__, $args);
197 | return $this->direct->chdir(...$args);
198 | }
199 |
200 | public function chgrp(...$args)
201 | {
202 | $this->log_call(__FUNCTION__, $args);
203 | return $this->direct->chgrp(...$args);
204 | }
205 |
206 | public function chmod(...$args)
207 | {
208 | $this->log_call(__FUNCTION__, $args);
209 | return $this->direct->chmod(...$args);
210 | }
211 |
212 | public function chown(...$args)
213 | {
214 | $this->log_call(__FUNCTION__, $args);
215 | return $this->direct->chown(...$args);
216 | }
217 |
218 | public function owner(...$args)
219 | {
220 | $this->log_call(__FUNCTION__, $args);
221 | return $this->direct->owner(...$args);
222 | }
223 |
224 | public function getchmod(...$args)
225 | {
226 | $this->log_call(__FUNCTION__, $args);
227 | return $this->direct->getchmod(...$args);
228 | }
229 |
230 |
231 | public function group(...$args)
232 | {
233 | $this->log_call(__FUNCTION__, $args);
234 | return $this->direct->group(...$args);
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5 | "This file is @generated automatically"
6 | ],
7 | "hash": "f8ee8d46fadaee8c9cc194ef126e7404",
8 | "packages": [
9 | {
10 | "name": "composer/installers",
11 | "version": "v1.0.6",
12 | "source": {
13 | "type": "git",
14 | "url": "https://github.com/composer/installers.git",
15 | "reference": "b3bd071ea114a57212c75aa6a2eef5cfe0cc798f"
16 | },
17 | "dist": {
18 | "type": "zip",
19 | "url": "https://api.github.com/repos/composer/installers/zipball/b3bd071ea114a57212c75aa6a2eef5cfe0cc798f",
20 | "reference": "b3bd071ea114a57212c75aa6a2eef5cfe0cc798f",
21 | "shasum": ""
22 | },
23 | "replace": {
24 | "shama/baton": "*"
25 | },
26 | "require-dev": {
27 | "composer/composer": "1.0.*@dev",
28 | "phpunit/phpunit": "3.7.*"
29 | },
30 | "type": "composer-installer",
31 | "extra": {
32 | "class": "Composer\\Installers\\Installer",
33 | "branch-alias": {
34 | "dev-master": "1.0-dev"
35 | }
36 | },
37 | "autoload": {
38 | "psr-0": {
39 | "Composer\\Installers\\": "src/"
40 | }
41 | },
42 | "notification-url": "https://packagist.org/downloads/",
43 | "license": [
44 | "MIT"
45 | ],
46 | "authors": [
47 | {
48 | "name": "Kyle Robinson Young",
49 | "email": "kyle@dontkry.com",
50 | "homepage": "https://github.com/shama",
51 | "role": "Developer"
52 | }
53 | ],
54 | "description": "A multi-framework Composer library installer",
55 | "homepage": "http://composer.github.com/installers/",
56 | "keywords": [
57 | "TYPO3 CMS",
58 | "TYPO3 Flow",
59 | "TYPO3 Neos",
60 | "agl",
61 | "cakephp",
62 | "codeigniter",
63 | "drupal",
64 | "fuelphp",
65 | "installer",
66 | "joomla",
67 | "kohana",
68 | "laravel",
69 | "li3",
70 | "lithium",
71 | "mako",
72 | "modulework",
73 | "phpbb",
74 | "ppi",
75 | "silverstripe",
76 | "symfony",
77 | "wordpress",
78 | "zend"
79 | ],
80 | "time": "2013-08-20 04:37:09"
81 | }
82 | ],
83 | "packages-dev": [
84 | {
85 | "name": "symfony/console",
86 | "version": "v2.7.1",
87 | "source": {
88 | "type": "git",
89 | "url": "https://github.com/symfony/Console.git",
90 | "reference": "564398bc1f33faf92fc2ec86859983d30eb81806"
91 | },
92 | "dist": {
93 | "type": "zip",
94 | "url": "https://api.github.com/repos/symfony/Console/zipball/564398bc1f33faf92fc2ec86859983d30eb81806",
95 | "reference": "564398bc1f33faf92fc2ec86859983d30eb81806",
96 | "shasum": ""
97 | },
98 | "require": {
99 | "php": ">=5.3.9"
100 | },
101 | "require-dev": {
102 | "psr/log": "~1.0",
103 | "symfony/event-dispatcher": "~2.1",
104 | "symfony/phpunit-bridge": "~2.7",
105 | "symfony/process": "~2.1"
106 | },
107 | "suggest": {
108 | "psr/log": "For using the console logger",
109 | "symfony/event-dispatcher": "",
110 | "symfony/process": ""
111 | },
112 | "type": "library",
113 | "extra": {
114 | "branch-alias": {
115 | "dev-master": "2.7-dev"
116 | }
117 | },
118 | "autoload": {
119 | "psr-4": {
120 | "Symfony\\Component\\Console\\": ""
121 | }
122 | },
123 | "notification-url": "https://packagist.org/downloads/",
124 | "license": [
125 | "MIT"
126 | ],
127 | "authors": [
128 | {
129 | "name": "Fabien Potencier",
130 | "email": "fabien@symfony.com"
131 | },
132 | {
133 | "name": "Symfony Community",
134 | "homepage": "https://symfony.com/contributors"
135 | }
136 | ],
137 | "description": "Symfony Console Component",
138 | "homepage": "https://symfony.com",
139 | "time": "2015-06-10 15:30:22"
140 | },
141 | {
142 | "name": "wpreadme2markdown/wpreadme2markdown",
143 | "version": "2.0.0",
144 | "source": {
145 | "type": "git",
146 | "url": "https://github.com/benbalter/WP-Readme-to-Github-Markdown.git",
147 | "reference": "dceae108111232949affc9107c98276c6fa6c98f"
148 | },
149 | "dist": {
150 | "type": "zip",
151 | "url": "https://api.github.com/repos/benbalter/WP-Readme-to-Github-Markdown/zipball/dceae108111232949affc9107c98276c6fa6c98f",
152 | "reference": "dceae108111232949affc9107c98276c6fa6c98f",
153 | "shasum": ""
154 | },
155 | "require": {
156 | "php": ">= 5.3.3",
157 | "symfony/console": "~2.4"
158 | },
159 | "bin": [
160 | "bin/wp2md"
161 | ],
162 | "type": "library",
163 | "autoload": {
164 | "psr-4": {
165 | "WPReadme2Markdown\\": "src/"
166 | }
167 | },
168 | "notification-url": "https://packagist.org/downloads/",
169 | "license": [
170 | "MIT"
171 | ],
172 | "authors": [
173 | {
174 | "name": "Christian Archer",
175 | "email": "chrstnarchr@aol.com"
176 | },
177 | {
178 | "name": "Benjamin J. Balter"
179 | }
180 | ],
181 | "description": "Convert WordPress Plugin readme.txt to Markdown",
182 | "keywords": [
183 | "converter",
184 | "markdown",
185 | "readme",
186 | "wordpress"
187 | ],
188 | "time": "2014-05-28 21:28:31"
189 | }
190 | ],
191 | "aliases": [],
192 | "minimum-stability": "stable",
193 | "stability-flags": [],
194 | "prefer-stable": false,
195 | "prefer-lowest": false,
196 | "platform": {
197 | "php": ">=5.3.2"
198 | },
199 | "platform-dev": []
200 | }
201 |
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/admin/class-predis-purger.php:
--------------------------------------------------------------------------------
1 | redis_object = new Predis\Client(
43 | array(
44 | 'host' => $nginx_helper_admin->options['redis_hostname'],
45 | 'port' => $nginx_helper_admin->options['redis_port'],
46 | )
47 | );
48 |
49 | try {
50 | $this->redis_object->connect();
51 | } catch ( Exception $e ) {
52 | $this->log( $e->getMessage(), 'ERROR' );
53 | }
54 |
55 | }
56 |
57 | /**
58 | * Purge all.
59 | */
60 | public function purge_all() {
61 |
62 | global $nginx_helper_admin;
63 |
64 | $prefix = trim( $nginx_helper_admin->options['redis_prefix'] );
65 |
66 | $this->log( '* * * * *' );
67 |
68 | // If Purge Cache link click from network admin then purge all.
69 | if ( is_network_admin() ) {
70 |
71 | $this->delete_keys_by_wildcard( $prefix . '*' );
72 | $this->log( '* Purged Everything! * ' );
73 |
74 | } else { // Else purge only site specific cache.
75 |
76 | $parse = wp_parse_url( get_home_url() );
77 | $parse['path'] = empty( $parse['path'] ) ? '/' : $parse['path'];
78 | $this->delete_keys_by_wildcard( $prefix . $parse['scheme'] . 'GET' . $parse['host'] . $parse['path'] . '*' );
79 | $this->log( '* ' . get_home_url() . ' Purged! * ' );
80 |
81 | }
82 |
83 | $this->log( '* * * * *' );
84 |
85 | /**
86 | * Fire an action after the Redis cache has been purged.
87 | *
88 | * @since 2.1.0
89 | */
90 | do_action( 'rt_nginx_helper_after_redis_purge_all' );
91 | }
92 |
93 | /**
94 | * Purge url.
95 | *
96 | * @param string $url URL.
97 | * @param bool $feed Feed or not.
98 | */
99 | public function purge_url( $url, $feed = true ) {
100 |
101 | global $nginx_helper_admin;
102 |
103 | /**
104 | * Filters the URL to be purged.
105 | *
106 | * @since 2.1.0
107 | *
108 | * @param string $url URL to be purged.
109 | */
110 | $url = apply_filters( 'rt_nginx_helper_purge_url', $url );
111 |
112 | $this->log( '- Purging URL | ' . $url );
113 |
114 | $parse = wp_parse_url( $url );
115 |
116 | if ( ! isset( $parse['path'] ) ) {
117 | $parse['path'] = '';
118 | }
119 |
120 | $prefix = $nginx_helper_admin->options['redis_prefix'];
121 | $_url_purge_base = $prefix . $parse['scheme'] . 'GET' . $parse['host'] . $parse['path'];
122 |
123 | /**
124 | * To delete device type caches such as `--mobile`, `--desktop`, `--lowend`, etc.
125 | * This would need $url above to be changed with this filter `rt_nginx_helper_purge_url` by cache key that Nginx sets while generating cache.
126 | *
127 | * For example: If page is accessed from desktop, then cache will be generated by appending `--desktop` to current URL.
128 | * Add this filter in separate plugin or simply in theme's function.php file:
129 | * ```
130 | * add_filter( 'rt_nginx_helper_purge_url', function( $url ) {
131 | * $url = $url . '--*';
132 | * return $url;
133 | * });
134 | * ```
135 | *
136 | * Regardless of what key / suffix is being to store `$device_type` cache , it will be deleted.
137 | *
138 | * @since 2.1.0
139 | */
140 | if ( strpos( $_url_purge_base, '*' ) === false ) {
141 |
142 | $status = $this->delete_single_key( $_url_purge_base );
143 |
144 | if ( $status ) {
145 | $this->log( '- Purge URL | ' . $_url_purge_base );
146 | } else {
147 | $this->log( '- Cache Not Found | ' . $_url_purge_base, 'ERROR' );
148 | }
149 | } else {
150 |
151 | $status = $this->delete_keys_by_wildcard( $_url_purge_base );
152 |
153 | if ( $status ) {
154 | $this->log( '- Purge Wild Card URL | ' . $_url_purge_base . ' | ' . $status . ' url purged' );
155 | } else {
156 | $this->log( '- Cache Not Found | ' . $_url_purge_base, 'ERROR' );
157 | }
158 | }
159 |
160 | }
161 |
162 | /**
163 | * Custom purge urls.
164 | */
165 | public function custom_purge_urls() {
166 |
167 | global $nginx_helper_admin;
168 |
169 | $parse = wp_parse_url( home_url() );
170 | $prefix = $nginx_helper_admin->options['redis_prefix'];
171 | $_url_purge_base = $prefix . $parse['scheme'] . 'GET' . $parse['host'];
172 |
173 | $purge_urls = isset( $nginx_helper_admin->options['purge_url'] ) && ! empty( $nginx_helper_admin->options['purge_url'] ) ?
174 | explode( "\r\n", $nginx_helper_admin->options['purge_url'] ) : array();
175 |
176 | /**
177 | * Allow plugins/themes to modify/extend urls.
178 | *
179 | * @param array $purge_urls URLs which needs to be purged.
180 | * @param bool $wildcard If wildcard in url is allowed or not. default true.
181 | */
182 | $purge_urls = apply_filters( 'rt_nginx_helper_purge_urls', $purge_urls, true );
183 |
184 | if ( is_array( $purge_urls ) && ! empty( $purge_urls ) ) {
185 |
186 | foreach ( $purge_urls as $purge_url ) {
187 |
188 | $purge_url = trim( $purge_url );
189 |
190 | if ( strpos( $purge_url, '*' ) === false ) {
191 |
192 | $purge_url = $_url_purge_base . $purge_url;
193 | $status = $this->delete_single_key( $purge_url );
194 | if ( $status ) {
195 | $this->log( '- Purge URL | ' . $purge_url );
196 | } else {
197 | $this->log( '- Not Found | ' . $purge_url, 'ERROR' );
198 | }
199 | } else {
200 |
201 | $purge_url = $_url_purge_base . $purge_url;
202 | $status = $this->delete_keys_by_wildcard( $purge_url );
203 |
204 | if ( $status ) {
205 | $this->log( '- Purge Wild Card URL | ' . $purge_url . ' | ' . $status . ' url purged' );
206 | } else {
207 | $this->log( '- Not Found | ' . $purge_url, 'ERROR' );
208 | }
209 | }
210 | }
211 | }
212 |
213 | }
214 |
215 | /**
216 | * Single Key Delete Example
217 | * e.g. $key can be nginx-cache:httpGETexample.com/
218 | *
219 | * @param string $key Key to delete cache.
220 | *
221 | * @return mixed
222 | */
223 | public function delete_single_key( $key ) {
224 |
225 | try {
226 | return $this->redis_object->executeRaw( array( 'DEL', $key ) );
227 | } catch ( Exception $e ) {
228 | $this->log( $e->getMessage(), 'ERROR' );
229 | }
230 |
231 | }
232 |
233 | /**
234 | * Delete Keys by wildcard.
235 | * e.g. $key can be nginx-cache:httpGETexample.com*
236 | *
237 | * Lua Script block to delete multiple keys using wildcard
238 | * Script will return count i.e. number of keys deleted
239 | * if return value is 0, that means no matches were found
240 | *
241 | * Call redis eval and return value from lua script
242 | *
243 | * @param string $pattern Pattern.
244 | *
245 | * @return mixed
246 | */
247 | public function delete_keys_by_wildcard( $pattern ) {
248 |
249 | // Lua Script.
250 | $lua = <<redis_object->eval( $lua, 1, $pattern );
262 | } catch ( Exception $e ) {
263 | $this->log( $e->getMessage(), 'ERROR' );
264 | }
265 |
266 | }
267 |
268 | }
269 |
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/admin/class-phpredis-purger.php:
--------------------------------------------------------------------------------
1 | redis_object = new Redis();
42 | $this->redis_object->connect(
43 | $nginx_helper_admin->options['redis_hostname'],
44 | $nginx_helper_admin->options['redis_port'],
45 | 5
46 | );
47 |
48 | } catch ( Exception $e ) {
49 | $this->log( $e->getMessage(), 'ERROR' );
50 | }
51 |
52 | }
53 |
54 | /**
55 | * Purge all cache.
56 | */
57 | public function purge_all() {
58 |
59 | global $nginx_helper_admin;
60 |
61 | $prefix = trim( $nginx_helper_admin->options['redis_prefix'] );
62 |
63 | $this->log( '* * * * *' );
64 |
65 | // If Purge Cache link click from network admin then purge all.
66 | if ( is_network_admin() ) {
67 |
68 | $total_keys_purged = $this->delete_keys_by_wildcard( $prefix . '*' );
69 | $this->log( '* Purged Everything! * ' );
70 |
71 | } else { // Else purge only site specific cache.
72 |
73 | $parse = wp_parse_url( get_home_url() );
74 | $parse['path'] = empty( $parse['path'] ) ? '/' : $parse['path'];
75 | $total_keys_purged = $this->delete_keys_by_wildcard( $prefix . $parse['scheme'] . 'GET' . $parse['host'] . $parse['path'] . '*' );
76 | $this->log( '* ' . get_home_url() . ' Purged! * ' );
77 |
78 | }
79 |
80 | if ( $total_keys_purged ) {
81 | $this->log( "Total {$total_keys_purged} urls purged." );
82 | } else {
83 | $this->log( 'No Cache found.' );
84 | }
85 |
86 | $this->log( '* * * * *' );
87 |
88 | /**
89 | * Fire an action after the Redis cache has been purged.
90 | *
91 | * @since 2.1.0
92 | */
93 | do_action( 'rt_nginx_helper_after_redis_purge_all' );
94 | }
95 |
96 | /**
97 | * Purge url.
98 | *
99 | * @param string $url URL to purge.
100 | * @param bool $feed Feed or not.
101 | */
102 | public function purge_url( $url, $feed = true ) {
103 |
104 | global $nginx_helper_admin;
105 |
106 | /**
107 | * Filters the URL to be purged.
108 | *
109 | * @since 2.1.0
110 | *
111 | * @param string $url URL to be purged.
112 | */
113 | $url = apply_filters( 'rt_nginx_helper_purge_url', $url );
114 |
115 | $parse = wp_parse_url( $url );
116 |
117 | if ( ! isset( $parse['path'] ) ) {
118 | $parse['path'] = '';
119 | }
120 |
121 | $prefix = $nginx_helper_admin->options['redis_prefix'];
122 | $_url_purge_base = $prefix . $parse['scheme'] . 'GET' . $parse['host'] . $parse['path'];
123 |
124 | /**
125 | * To delete device type caches such as `--mobile`, `--desktop`, `--lowend`, etc.
126 | * This would need $url above to be changed with this filter `rt_nginx_helper_purge_url` by cache key that Nginx sets while generating cache.
127 | *
128 | * For example: If page is accessed from desktop, then cache will be generated by appending `--desktop` to current URL.
129 | * Add this filter in separate plugin or simply in theme's function.php file:
130 | * ```
131 | * add_filter( 'rt_nginx_helper_purge_url', function( $url ) {
132 | * $url = $url . '--*';
133 | * return $url;
134 | * });
135 | * ```
136 | *
137 | * Regardless of what key / suffix is being to store `$device_type` cache , it will be deleted.
138 | *
139 | * @since 2.1.0
140 | */
141 | if ( strpos( $_url_purge_base, '*' ) === false ) {
142 |
143 | $status = $this->delete_single_key( $_url_purge_base );
144 |
145 | if ( $status ) {
146 | $this->log( '- Purge URL | ' . $_url_purge_base );
147 | } else {
148 | $this->log( '- Cache Not Found | ' . $_url_purge_base, 'ERROR' );
149 | }
150 | } else {
151 |
152 | $status = $this->delete_keys_by_wildcard( $_url_purge_base );
153 |
154 | if ( $status ) {
155 | $this->log( '- Purge Wild Card URL | ' . $_url_purge_base . ' | ' . $status . ' url purged' );
156 | } else {
157 | $this->log( '- Cache Not Found | ' . $_url_purge_base, 'ERROR' );
158 | }
159 | }
160 |
161 | $this->log( '* * * * *' );
162 |
163 | }
164 |
165 | /**
166 | * Custom purge urls.
167 | */
168 | public function custom_purge_urls() {
169 |
170 | global $nginx_helper_admin;
171 |
172 | $parse = wp_parse_url( home_url() );
173 | $prefix = $nginx_helper_admin->options['redis_prefix'];
174 | $_url_purge_base = $prefix . $parse['scheme'] . 'GET' . $parse['host'];
175 |
176 | $purge_urls = isset( $nginx_helper_admin->options['purge_url'] ) && ! empty( $nginx_helper_admin->options['purge_url'] ) ?
177 | explode( "\r\n", $nginx_helper_admin->options['purge_url'] ) : array();
178 |
179 | /**
180 | * Allow plugins/themes to modify/extend urls.
181 | *
182 | * @param array $purge_urls URLs which needs to be purged.
183 | * @param bool $wildcard If wildcard in url is allowed or not. default true.
184 | */
185 | $purge_urls = apply_filters( 'rt_nginx_helper_purge_urls', $purge_urls, true );
186 |
187 | if ( is_array( $purge_urls ) && ! empty( $purge_urls ) ) {
188 |
189 | foreach ( $purge_urls as $purge_url ) {
190 |
191 | $purge_url = trim( $purge_url );
192 |
193 | if ( strpos( $purge_url, '*' ) === false ) {
194 |
195 | $purge_url = $_url_purge_base . $purge_url;
196 | $status = $this->delete_single_key( $purge_url );
197 |
198 | if ( $status ) {
199 | $this->log( '- Purge URL | ' . $purge_url );
200 | } else {
201 | $this->log( '- Cache Not Found | ' . $purge_url, 'ERROR' );
202 | }
203 | } else {
204 |
205 | $purge_url = $_url_purge_base . $purge_url;
206 | $status = $this->delete_keys_by_wildcard( $purge_url );
207 |
208 | if ( $status ) {
209 | $this->log( '- Purge Wild Card URL | ' . $purge_url . ' | ' . $status . ' url purged' );
210 | } else {
211 | $this->log( '- Cache Not Found | ' . $purge_url, 'ERROR' );
212 | }
213 | }
214 | }
215 | }
216 |
217 | }
218 |
219 | /**
220 | * Single Key Delete Example
221 | * e.g. $key can be nginx-cache:httpGETexample.com/
222 | *
223 | * @param string $key Key.
224 | *
225 | * @return int
226 | */
227 | public function delete_single_key( $key ) {
228 |
229 | try {
230 | return $this->redis_object->del( $key );
231 | } catch ( Exception $e ) {
232 | $this->log( $e->getMessage(), 'ERROR' );
233 | }
234 |
235 | }
236 |
237 | /**
238 | * Delete Keys by wildcard.
239 | * e.g. $key can be nginx-cache:httpGETexample.com*
240 | *
241 | * Lua Script block to delete multiple keys using wildcard
242 | * Script will return count i.e. number of keys deleted
243 | * if return value is 0, that means no matches were found
244 | *
245 | * Call redis eval and return value from lua script
246 | *
247 | * @param string $pattern pattern.
248 | *
249 | * @return mixed
250 | */
251 | public function delete_keys_by_wildcard( $pattern ) {
252 |
253 | // Lua Script.
254 | $lua = <<redis_object->eval( $lua, array( $pattern ), 1 );
266 | } catch ( Exception $e ) {
267 | $this->log( $e->getMessage(), 'ERROR' );
268 | }
269 |
270 | }
271 |
272 | }
273 |
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/admin/class-memcached-purger.php:
--------------------------------------------------------------------------------
1 | memcached_object = new Memcached();
42 | $this->connect(
43 | $nginx_helper_admin->options['memcached_hostname'],
44 | $nginx_helper_admin->options['memcached_port']
45 | );
46 |
47 | } catch ( Exception $e ) {
48 | $this->log( $e->getMessage(), 'ERROR' );
49 | }
50 |
51 | }
52 |
53 | public function connect($host , $port){
54 | // https://www.php.net/manual/en/memcached.addserver.php#110003
55 | $servers = $this->memcached_object->getServerList();
56 | if(is_array($servers)) {
57 | foreach ($servers as $server) {
58 | if($server['host'] == $host and $server['port'] == $port)
59 | return true;
60 | }
61 | }
62 | return $this->memcached_object->addServer($host , $port);
63 | }
64 |
65 | /**
66 | * Purge all cache.
67 | */
68 | public function purge_all() {
69 |
70 | global $nginx_helper_admin;
71 |
72 | $prefix = trim( $nginx_helper_admin->options['memcached_prefix'] );
73 | $versioned_cache_key = trim( $nginx_helper_admin->options['memcached_versioned_cache_key'] );
74 |
75 | $this->log( '* * * * *' );
76 | /**
77 | * There are a couple of reasons for why the cache version is being removed and not changed:
78 | *
79 | * 1. By deciding the version in the nginx conf, the case where memcached is restarted and the version
80 | * key is lost is handled. Memcached might also decide to evict the key if memory constrained, but that
81 | * should not happen as it is frequently accessed and should be set with no expiration time.
82 | *
83 | * 2. It lets the user decide what the version will be. One might choose the timestamp in seconds or
84 | * a totally random key for example. It's not a good idea to increment the key based on the previous version
85 | * as resetting to 0 might cause old cached pages to be served again.
86 | */
87 | $this->memcached_object->delete($versioned_cache_key);
88 |
89 | $result_code = $this->memcached_object->getResultCode();
90 | if ( Memcached::RES_SUCCESS === $result_code || Memcached::RES_NOTFOUND === $result_code ) {
91 | $this->log( ' * Purged cache by invalidating the cache version. * ' );
92 | } else {
93 | $this->log( ' * Failed to invalidate cache version * ', 'ERROR' );
94 | }
95 |
96 | $this->log( '* * * * *' );
97 |
98 | /**
99 | * Fire an action after the Memcached cache has been purged.
100 | *
101 | * @since 2.1.0
102 | */
103 | do_action( 'rt_nginx_helper_after_memcached_purge_all' );
104 | }
105 |
106 | /**
107 | * Purge url.
108 | *
109 | * @param string $url URL to purge.
110 | * @param bool $feed Feed or not.
111 | */
112 | public function purge_url( $url, $feed = true ) {
113 |
114 | global $nginx_helper_admin;
115 |
116 | /**
117 | * Filters the URL to be purged.
118 | *
119 | * @since 2.1.0
120 | *
121 | * @param string $url URL to be purged.
122 | */
123 | $url = apply_filters( 'rt_nginx_helper_purge_url', $url );
124 |
125 | $parse = wp_parse_url( $url );
126 |
127 | if ( ! isset( $parse['path'] ) ) {
128 | $parse['path'] = '';
129 | }
130 |
131 | $prefix = $nginx_helper_admin->options['memcached_prefix'];
132 | $versioned_cache_key = trim( $nginx_helper_admin->options['memcached_versioned_cache_key'] );
133 | $query_string_version_key_prefix = trim( $nginx_helper_admin->options['memcached_query_string_version_key_prefix'] );
134 | $key_uid = $parse['scheme'] . 'GET' . $parse['host'] . $parse['path'];
135 | $query_string_version_key = $query_string_version_key_prefix . $key_uid;
136 |
137 | $version = $this->memcached_object->get($versioned_cache_key);
138 | if ( '' === $version ) {
139 | $this->log( '- Cache version empty or not found | ' . $versioned_cache_key, 'ERROR' );
140 | return;
141 | }
142 |
143 | $this->memcached_object->delete($query_string_version_key);
144 |
145 | $result_code = $this->memcached_object->getResultCode();
146 | if ( Memcached::RES_SUCCESS === $result_code || Memcached::RES_NOTFOUND === $result_code ) {
147 | $this->log( '- Invalidated query string version | ' . $query_string_version_key );
148 | } else {
149 | $this->log( '- Failed to invalidate query string version | ' . $query_string_version_key, 'ERROR' );
150 | }
151 |
152 | $_url_purge_base = $prefix . $version . ':' . $key_uid;
153 |
154 | /**
155 | * To delete device type caches such as `--mobile`, `--desktop`, `--lowend`, etc.
156 | * This would need $url above to be changed with this filter `rt_nginx_helper_purge_url` by cache key that Nginx sets while generating cache.
157 | *
158 | * For example: If page is accessed from desktop, then cache will be generated by appending `--desktop` to current URL.
159 | * Add this filter in separate plugin or simply in theme's function.php file:
160 | * ```
161 | * add_filter( 'rt_nginx_helper_purge_url', function( $url ) {
162 | * $url = $url . '--*';
163 | * return $url;
164 | * });
165 | * ```
166 | *
167 | * Beware that when using the versioned cache option, wildcard purging does not work.
168 | *
169 | * Regardless of what key / suffix is being to store `$device_type` cache , it will be deleted.
170 | *
171 | * @since 2.1.0
172 | */
173 | $status = $this->delete_single_key( $_url_purge_base );
174 |
175 | if ( $status ) {
176 | $this->log( '- Purge URL | ' . $_url_purge_base );
177 | } else {
178 | $this->log( '- Cache Not Found | ' . $_url_purge_base, 'ERROR' );
179 | }
180 |
181 | $this->log( '* * * * *' );
182 | }
183 |
184 | /**
185 | * Custom purge urls.
186 | */
187 | public function custom_purge_urls() {
188 |
189 | global $nginx_helper_admin;
190 |
191 | $parse = wp_parse_url( home_url() );
192 | $prefix = $nginx_helper_admin->options['memcached_prefix'];
193 | $versioned_cache_key = trim( $nginx_helper_admin->options['memcached_versioned_cache_key'] );
194 | $version = "";
195 |
196 | $version = $this->memcached_object->get($versioned_cache_key);
197 | if ( '' === $version ) {
198 | $this->log( '- Cache version empty or not found | ' . $versioned_cache_key, 'ERROR' );
199 | return;
200 | }
201 |
202 | $_url_purge_base = $prefix . $version . ':' . $parse['scheme'] . 'GET' . $parse['host'];
203 |
204 | $purge_urls = isset( $nginx_helper_admin->options['purge_url'] ) && ! empty( $nginx_helper_admin->options['purge_url'] ) ?
205 | explode( "\r\n", $nginx_helper_admin->options['purge_url'] ) : array();
206 |
207 | /**
208 | * Allow plugins/themes to modify/extend urls.
209 | *
210 | * @param array $purge_urls URLs which needs to be purged.
211 | * @param bool $wildcard If wildcard in url is allowed or not. default true.
212 | */
213 | $purge_urls = apply_filters( 'rt_nginx_helper_purge_urls', $purge_urls, true );
214 |
215 | if ( is_array( $purge_urls ) && ! empty( $purge_urls ) ) {
216 |
217 | foreach ( $purge_urls as $purge_url ) {
218 | $purge_url = $_url_purge_base . trim( $purge_url );
219 | $status = $this->delete_single_key( $purge_url );
220 |
221 | if ( $status ) {
222 | $this->log( '- Purge URL | ' . $purge_url );
223 | } else {
224 | $this->log( '- Cache Not Found | ' . $purge_url, 'ERROR' );
225 | }
226 | }
227 | }
228 |
229 | }
230 |
231 | /**
232 | * Delete cache by single key.
233 | *
234 | * @param string $key The cache key to be deleted.
235 | * e.g. $key can be nginx-cache:httpGETexample.com/
236 | *
237 | * @return bool True if deleted, False otherwise.
238 | */
239 | public function delete_single_key( $key ) {
240 |
241 | try {
242 | return $this->memcached_object->delete( $key );
243 | } catch ( Exception $e ) {
244 | $this->log( $e->getMessage(), 'ERROR' );
245 | }
246 |
247 | }
248 |
249 | }
250 |
--------------------------------------------------------------------------------
/src/Stack/MediaStorage.php:
--------------------------------------------------------------------------------
1 | relUploadsDir = trim(defined('STACK_MEDIA_PATH') ? STACK_MEDIA_PATH : 'wp-content/uploads', '/');
24 |
25 | $parts = parse_url(STACK_MEDIA_BUCKET);
26 |
27 | switch ($parts['scheme']) {
28 | case 'objcache':
29 | $blobStore = new \Stack\BlobStore\WordPressObjectCache();
30 | break;
31 | case 'gs':
32 | case 'gcs':
33 | $blobStore = new \Stack\BlobStore\GoogleCloudStorage($parts['host'], $parts['path'] ?: '');
34 | break;
35 | case 'file':
36 | case '':
37 | $blobStore = $this->getLocalFilesystemBlobStore($parts['path']);
38 | break;
39 | default:
40 | wp_die('Invalid protocol ' . $parts['scheme'] . ' for media storage.');
41 | }
42 |
43 | $this->blobStore = $blobStore;
44 | $fs = \Stack\MediaFilesystem\StreamWrapper::register($this->blobStore, "media");
45 | $this->register();
46 | }
47 |
48 | /*
49 | * Returns a LocalFilesystem BlobStore taking into account WordPress particularities
50 | */
51 | private function getLocalFilesystemBlobStore(string $path = '')
52 | {
53 | if ($this->endsWith($path, '/' . $this->relUploadsDir)) {
54 | $path = substr($path, 0, -strlen('/' . $this->relUploadsDir));
55 | }
56 | return new \Stack\BlobStore\LocalFilesystem($path);
57 | }
58 |
59 | public function register()
60 | {
61 | add_filter('upload_dir', [$this, 'filterUploadDir']);
62 | add_filter('wp_delete_file', [$this, 'filterDeleteFile'], PHP_INT_MIN);
63 | add_filter('wp_delete_file', [$this, 'deleteFile'], PHP_INT_MAX);
64 | add_filter('wp_image_editors', [$this, 'filterImageEditors']);
65 | add_action('init', [$this, 'serveImage']);
66 | add_filter('filesystem_method', [$this, 'filesystemMethod'], PHP_INT_MAX);
67 | add_filter('request_filesystem_credentials', [$this, 'filesystemCredentials'], PHP_INT_MAX, 3);
68 | }
69 |
70 | public function unregister()
71 | {
72 | remove_filter('upload_dir', [$this, 'filterUploadDir']);
73 | remove_filter('wp_delete_file', [$this, 'filterDeleteFile'], PHP_INT_MIN);
74 | remove_filter('wp_delete_file', [$this, 'deleteFile'], PHP_INT_MAX);
75 | remove_filter('wp_image_editors', [$this, 'filterImageEditors']);
76 | remove_action('init', [$this, 'serveImage']);
77 | remove_filter('filesystem_method', [$this, 'filesystemMethod'], PHP_INT_MAX);
78 | remove_filter('request_filesystem_credentials', [$this, 'filesystemCredentials'], PHP_INT_MAX, 3);
79 | }
80 |
81 | public function filesystemMethod()
82 | {
83 | // Force WordPress to use our custom filesystem method
84 | return 'stack';
85 | }
86 |
87 | public function filesystemCredentials($credentials, $form_post, $type)
88 | {
89 | return [ new \WP_Filesystem_Direct(null) ];
90 | }
91 |
92 | public function serveImage()
93 | {
94 | if (!isset($_SERVER['HTTP_HOST'])) {
95 | return;
96 | }
97 |
98 | $upload = wp_upload_dir();
99 | $request = (is_ssl() ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
100 | if ($this->startsWith($request, $upload['baseurl'])) {
101 | $path = substr($request, strlen($upload['baseurl']));
102 | $filetype = wp_check_filetype($path);
103 | $fullPath = 'media://' . $this->relUploadsDir . $path;
104 |
105 | if (empty($filetype['ext'])) {
106 | wp_die("Directory listing disabled.", "UNAUTHORIZED", 403);
107 | } elseif (!file_exists($fullPath)) {
108 | wp_die("Not found.", "NOT FOUND", 404);
109 | } else {
110 | header('Content-Type: ' . $filetype['type']);
111 | readfile($fullPath);
112 | die();
113 | }
114 | }
115 | }
116 |
117 | public function filterImageEditors(array $image_editors) : array
118 | {
119 | $editors = array();
120 | foreach ($image_editors as $editor) {
121 | if ($editor != 'WP_Image_Editor_Imagick') {
122 | $editors []= $editor;
123 | }
124 | }
125 | return $editors;
126 | }
127 |
128 | /**
129 | * Filter used to set the uplaods directory.
130 | * We use it in order to append the ://: in front of file's path.
131 | */
132 | public function filterUploadDir(array $uploads) : array
133 | {
134 | $basedir = untrailingslashit($this->getUploadsDir());
135 |
136 | // taken from https://developer.wordpress.org/reference/functions/_wp_upload_dir/
137 | // If multisite (and if not the main site in a post-MU network)
138 | // NOTICE: we support only post-MU network setups
139 | if (is_multisite() && !(is_main_network() && is_main_site() && defined('MULTISITE'))) {
140 | $suffix = '/sites/' . get_current_blog_id();
141 | if (!$this->endsWith($basedir, $suffix)) {
142 | $basedir .= $suffix;
143 | }
144 | }
145 |
146 | $uploads['basedir'] = $basedir;
147 | $uploads['path'] = untrailingslashit($basedir . '/' . ltrim($uploads['subdir'], '/'));
148 |
149 | return $uploads;
150 | }
151 |
152 | /**
153 | * Unlink files starting with 'media://'
154 | *
155 | * This is needed because WordPress thinks a path starts with '://' is
156 | * not an absolute path and manipulate it in a wrong way before unlinking
157 | * intermediate files.
158 | *
159 | * TODO: Use `path_is_absolute` filter when a bug below is resolved:
160 | * https://core.trac.wordpress.org/ticket/38907#ticket
161 | *
162 | * Because path_join() doesn't recognize :// as an absolute path, we need
163 | * to remove ://:/ since for thumbnail will multiply such prefixes
164 | * eg: ://:/wp-content/uploads/ftp://:/wp-content/uploads/...
165 | */
166 | public function filterDeleteFile(string $filePath) : string
167 | {
168 | $baseDir = $this->getUploadsDir();
169 |
170 | if ($this->startsWith($filePath, $baseDir)) {
171 | while ($this->startsWith($filePath, $baseDir)) {
172 | $filePath = $this->removePrefix($filePath, $baseDir);
173 | }
174 | $filePath = $baseDir . $filePath;
175 | }
176 |
177 | return $filePath;
178 | }
179 |
180 | // unlink() does not clear the cache if you are performing file_exists() on a remote file
181 | // http://php.net/manual/en/function.clearstatcache.php#105888
182 | public function deleteFile(string $filePath) : string
183 | {
184 | if (empty($filePath)) {
185 | return $filePath;
186 | }
187 |
188 | if ($this->startsWith($filePath, 'media://')) {
189 | @unlink($filePath);
190 | clearstatcache();
191 | return '';
192 | }
193 |
194 | return $filePath;
195 | }
196 |
197 | private function startsWith(string $haystack, string $needle) : bool
198 | {
199 | $length = strlen($needle);
200 | return (substr($haystack, 0, $length) === $needle);
201 | }
202 |
203 | private function endsWith(string $haystack, string $needle) : bool
204 | {
205 | $length = strlen($needle);
206 | if ($length == 0) {
207 | return true;
208 | }
209 |
210 | return (substr($haystack, -$length) === $needle);
211 | }
212 |
213 |
214 | private function removePrefix(string $path, string $prefix) : string
215 | {
216 | return str_replace($prefix, '', $path);
217 | }
218 |
219 | /**
220 | * Returns a remote path, using the current prefix.
221 | */
222 | public function getPath(string $remotePath) : string
223 | {
224 | return sprintf("media://%s", $remotePath);
225 | }
226 |
227 | /**
228 | * Returns the remote upload path, using the current prefix.
229 | */
230 | private function getUploadsDir() : string
231 | {
232 | return $this->getPath($this->relUploadsDir);
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/src/Stack/MetricsCollector.php:
--------------------------------------------------------------------------------
1 | metrics = new MetricsRegistry(
23 | array(
24 | 'wp.requests' => array(
25 | 'counter',
26 | 'Number of requests',
27 | ['request_type']
28 | ),
29 | 'wp.page_generation_time' => array(
30 | 'histogram',
31 | 'Page generation time, in seconds',
32 | ['request_type']
33 | ),
34 | 'wp.peak_memory' => array(
35 | 'histogram',
36 | 'Peak memory per request, in bytes',
37 | ['request_type']
38 | ),
39 | 'wpdb.query_time' => array(
40 | 'histogram',
41 | 'Total MySQL query time per request, in seconds',
42 | ['request_type']
43 | ),
44 | 'wpdb.num_queries' => array(
45 | 'histogram',
46 | 'Total number of MySQL queries per request',
47 | ['request_type']
48 | ),
49 | 'wpdb.num_slow_queries' => array(
50 | 'histogram',
51 | 'Number of MySQL slow queries per request',
52 | ['request_type']
53 | ),
54 | 'wpdb.slow_query_treshold' => array(
55 | 'gauge',
56 | 'The treshold for counting slow queries, in seconds',
57 | []
58 | ),
59 | 'woocommerce.orders' => array(
60 | 'counter',
61 | 'Number of completed WooCommerce orders',
62 | []
63 | ),
64 | 'woocommerce.checkouts' => array(
65 | 'counter',
66 | 'Number of started WooCommerce checkouts',
67 | []
68 | )
69 | )
70 | );
71 |
72 | $this->registerHooks();
73 | $this->initWpdbStats();
74 | }
75 |
76 | public function initWpdbStats()
77 | {
78 | $this->wpdbStats['slow_query_treshold'] = defined('SLOW_QUERY_THRESHOLD') ? SLOW_QUERY_THRESHOLD : 2000;
79 | $this->wpdbStats['query_time'] = 0;
80 | $this->wpdbStats['num_queries'] = 0;
81 | $this->wpdbStats['num_slow_queries'] = 0;
82 | }
83 |
84 | public function collectRequestMetrics()
85 | {
86 | $requestType = $this::getRequestType();
87 | $requestTime = $this::getRequestTime();
88 | $peakMemory = memory_get_peak_usage();
89 |
90 | $this->metrics->getCounter('wp.requests')->incBy(
91 | 1,
92 | [$requestType]
93 | );
94 | $this->metrics->getHistogram('wp.peak_memory')->observe(
95 | $peakMemory,
96 | [$requestType]
97 | );
98 | $this->metrics->getHistogram('wp.page_generation_time')->observe(
99 | $requestTime,
100 | [$requestType]
101 | );
102 |
103 | if ($this::canCollectWpdbMetrics()) {
104 | $this->metrics->getHistogram('wpdb.query_time')->observe(
105 | $this->wpdbStats['query_time'],
106 | [$requestType]
107 | );
108 | $this->metrics->getHistogram('wpdb.num_queries')->observe(
109 | $this->wpdbStats['num_queries'],
110 | [$requestType]
111 | );
112 | $this->metrics->getHistogram('wpdb.num_slow_queries')->observe(
113 | $this->wpdbStats['num_slow_queries'],
114 | [$requestType]
115 | );
116 | $this->metrics->getGauge('wpdb.slow_query_treshold')->set(
117 | $this->wpdbStats['slow_query_treshold']
118 | );
119 | }
120 | }
121 |
122 | public function registerEndpoint()
123 | {
124 | $namespace = 'stack/v' . STACK_REST_API_VERSION;
125 | $base = 'metrics';
126 |
127 | register_rest_route($namespace, '/' . $base, array(
128 | array(
129 | 'methods' => \WP_REST_Server::READABLE,
130 | 'callback' => [$this, 'render'],
131 | 'permission_callback' => '__return_true',
132 | )
133 | ));
134 |
135 | add_filter('rest_pre_echo_response', [$this, 'preEchoResponse'], 9999, 3);
136 | }
137 |
138 | public function preEchoResponse($result, $server, $request)
139 | {
140 | if ($request->get_route() == "/stack/v" . STACK_REST_API_VERSION . "/metrics" && is_string($result)) {
141 | echo $result;
142 | return null;
143 | }
144 |
145 | return $result;
146 | }
147 |
148 | public function render()
149 | {
150 | $response = new WP_REST_Response($this->metrics->render());
151 |
152 | $response->header('Content-type', \Prometheus\RenderTextFormat::MIME_TYPE);
153 | $response->header('Cache-Control', 'no-cache,max-age=0');
154 |
155 | return $response;
156 | }
157 |
158 | public function collectWpdbStats($queryData, $query, $queryTime, $queryCallstack, $queryStart)
159 | {
160 | if (!$this::canCollectWpdbMetrics()) {
161 | return;
162 | }
163 |
164 | $this->wpdbStats['num_queries'] += 1;
165 | $this->wpdbStats['query_time'] += $queryTime;
166 |
167 | if ($queryTime > $this->wpdbStats['slow_query_treshold']) {
168 | $this->wpdbStats['num_slow_queries'] += 1;
169 | }
170 |
171 | return $queryData;
172 | }
173 |
174 | public function trackWoocomerceOrder($orderId, $oldStatus, $newStatus)
175 | {
176 | if ($newStatus == 'completed') {
177 | $this->metrics['woocommerce.orders']->incBy(1);
178 | }
179 | }
180 |
181 | public function trackWoocomerceCheckout()
182 | {
183 | $this->metrics['woocommerce.checkouts']->incBy(1);
184 | }
185 |
186 | private function registerHooks()
187 | {
188 | add_action('rest_api_init', [$this, 'registerEndpoint']);
189 | add_action('shutdown', [$this, 'collectRequestMetrics']);
190 |
191 | if ($this::canCollectWpdbMetrics()) {
192 | add_filter('log_query_custom_data', [$this, 'collectWpdbStats'], 10, 5);
193 | }
194 |
195 | if ($this::canCollectWoocommerceMetrics()) {
196 | add_action('woocommerce_checkout_billing', [$this, 'trackWoocomerceCheckout']);
197 | add_action('woocommerce_order_status_changed', [$this, 'trackWoocomerceOrder'], 10, 3);
198 | }
199 | }
200 |
201 | private function isWoocommerce()
202 | {
203 | return function_exists('is_woocommerce') && is_woocommerce();
204 | }
205 |
206 | private function canCollectWpdbMetrics()
207 | {
208 | return defined('SAVEQUERIES') && SAVEQUERIES;
209 | }
210 |
211 | private function canCollectWoocommerceMetrics()
212 | {
213 | return $this::isWoocommerce();
214 | }
215 |
216 | private function getRequestType()
217 | {
218 | if (defined('DOING_CRON') && DOING_CRON) {
219 | return 'cron';
220 | }
221 | if (defined('REST_REQUEST') && REST_REQUEST) {
222 | return 'api';
223 | }
224 | if (defined('DOING_AJAX') && DOING_AJAX) {
225 | return 'admin-ajax';
226 | }
227 | if ($this::isWoocommerce()) {
228 | if (function_exists('is_shop') && is_shop()) {
229 | return 'shop';
230 | }
231 | if (function_exists('is_product_category') && is_product_category()) {
232 | return 'product_category';
233 | }
234 | if (function_exists('is_product') && is_product()) {
235 | return 'product';
236 | }
237 | if (function_exists('is_cart') && is_cart()) {
238 | return 'checkout';
239 | }
240 | if (function_exists('is_checkout') && is_checkout()) {
241 | return 'checkout';
242 | }
243 | if (function_exists('is_account_page') && is_account_page()) {
244 | return 'is_account_page';
245 | }
246 | }
247 | if (is_admin()) {
248 | return 'admin';
249 | }
250 | if (is_search()) {
251 | return 'search';
252 | }
253 | if (is_front_page() || is_home()) {
254 | return 'frontpage';
255 | }
256 | if (is_singular()) {
257 | return 'singular';
258 | }
259 | if (is_archive()) {
260 | return 'archive';
261 | }
262 |
263 | return 'other';
264 | }
265 |
266 | private function getRequestTime()
267 | {
268 | global $timestart, $timeend;
269 | $precision = 12;
270 | $timeend = microtime(true);
271 | $timetotal = $timeend - $timestart;
272 | return number_format($timetotal, $precision);
273 | }
274 | }
275 |
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/includes/class-nginx-helper.php:
--------------------------------------------------------------------------------
1 | plugin_name = 'nginx-helper';
80 | $this->version = '2.2.2';
81 | $this->minimum_wp = '3.0';
82 |
83 | if ( ! $this->required_wp_version() ) {
84 | return;
85 | }
86 |
87 | if ( ! defined( 'RT_WP_NGINX_HELPER_CACHE_PATH' ) ) {
88 | define( 'RT_WP_NGINX_HELPER_CACHE_PATH', '/var/run/nginx-cache' );
89 | }
90 |
91 | $this->load_dependencies();
92 | $this->set_locale();
93 | $this->define_admin_hooks();
94 | }
95 |
96 | /**
97 | * Load the required dependencies for this plugin.
98 | *
99 | * Include the following files that make up the plugin:
100 | *
101 | * - Nginx_Helper_Loader. Orchestrates the hooks of the plugin.
102 | * - Nginx_Helper_i18n. Defines internationalization functionality.
103 | * - Nginx_Helper_Admin. Defines all hooks for the admin area.
104 | *
105 | * Create an instance of the loader which will be used to register the hooks
106 | * with WordPress.
107 | *
108 | * @since 2.0.0
109 | * @access private
110 | */
111 | private function load_dependencies() {
112 |
113 | /**
114 | * The class responsible for orchestrating the actions and filters of the
115 | * core plugin.
116 | */
117 | require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-nginx-helper-loader.php';
118 |
119 | /**
120 | * The class responsible for defining internationalization functionality
121 | * of the plugin.
122 | */
123 | require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-nginx-helper-i18n.php';
124 |
125 | /**
126 | * The class responsible for defining all actions that required for purging urls.
127 | */
128 | require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-purger.php';
129 |
130 | /**
131 | * The class responsible for defining all actions that occur in the admin area.
132 | */
133 | require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-nginx-helper-admin.php';
134 |
135 | /**
136 | * The class responsible for defining all actions that occur in the public-facing
137 | * side of the site.
138 | */
139 | $this->loader = new Nginx_Helper_Loader();
140 |
141 | }
142 |
143 | /**
144 | * Define the locale for this plugin for internationalization.
145 | *
146 | * Uses the Nginx_Helper_i18n class in order to set the domain and to register the hook
147 | * with WordPress.
148 | *
149 | * @since 2.0.0
150 | * @access private
151 | */
152 | private function set_locale() {
153 |
154 | $plugin_i18n = new Nginx_Helper_i18n();
155 | $plugin_i18n->set_domain( $this->get_plugin_name() );
156 |
157 | $this->loader->add_action( 'plugins_loaded', $plugin_i18n, 'load_plugin_textdomain' );
158 |
159 | }
160 |
161 | /**
162 | * Register all of the hooks related to the admin area functionality of the plugin.
163 | *
164 | * @since 2.0.0
165 | * @access private
166 | */
167 | private function define_admin_hooks() {
168 |
169 | global $nginx_helper_admin, $nginx_purger;
170 |
171 | $nginx_helper_admin = new Nginx_Helper_Admin( $this->get_plugin_name(), $this->get_version() );
172 |
173 | // Defines global variables.
174 | if ( ! empty( $nginx_helper_admin->options['cache_method'] ) && 'enable_redis' === $nginx_helper_admin->options['cache_method'] ) {
175 |
176 | if ( class_exists( 'Redis' ) ) { // Use PHP5-Redis extension if installed.
177 |
178 | require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-phpredis-purger.php';
179 | $nginx_purger = new PhpRedis_Purger();
180 |
181 | } else {
182 |
183 | require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-predis-purger.php';
184 | $nginx_purger = new Predis_Purger();
185 |
186 | }
187 | } else if ( ! empty( $nginx_helper_admin->options['cache_method'] ) && 'enable_memcached' === $nginx_helper_admin->options['cache_method'] ) {
188 |
189 | require_once plugin_dir_path(dirname(__FILE__)) . 'admin/class-memcached-purger.php';
190 | $nginx_purger = new Memcached_Purger();
191 |
192 | } else {
193 |
194 | require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-fastcgi-purger.php';
195 | $nginx_purger = new FastCGI_Purger();
196 |
197 | }
198 |
199 | $this->loader->add_action( 'admin_enqueue_scripts', $nginx_helper_admin, 'enqueue_styles' );
200 | $this->loader->add_action( 'admin_enqueue_scripts', $nginx_helper_admin, 'enqueue_scripts' );
201 |
202 | if ( is_multisite() ) {
203 | $this->loader->add_action( 'network_admin_menu', $nginx_helper_admin, 'nginx_helper_admin_menu' );
204 | $this->loader->add_filter( 'network_admin_plugin_action_links_' . NGINX_HELPER_BASENAME, $nginx_helper_admin, 'nginx_helper_settings_link' );
205 | } else {
206 | $this->loader->add_action( 'admin_menu', $nginx_helper_admin, 'nginx_helper_admin_menu' );
207 | $this->loader->add_filter( 'plugin_action_links_' . NGINX_HELPER_BASENAME, $nginx_helper_admin, 'nginx_helper_settings_link' );
208 | }
209 |
210 | if ( ! empty( $nginx_helper_admin->options['enable_purge'] ) ) {
211 | $this->loader->add_action( 'admin_bar_menu', $nginx_helper_admin, 'nginx_helper_toolbar_purge_link', 100 );
212 | }
213 |
214 | $this->loader->add_action( 'wp_ajax_rt_get_feeds', $nginx_helper_admin, 'nginx_helper_get_feeds' );
215 |
216 | $this->loader->add_action( 'shutdown', $nginx_helper_admin, 'add_timestamps', 99999 );
217 | $this->loader->add_action( 'add_init', $nginx_helper_admin, 'update_map' );
218 |
219 | // Add actions to purge.
220 | $this->loader->add_action( 'wp_insert_comment', $nginx_purger, 'purge_post_on_comment', 200, 2 );
221 | $this->loader->add_action( 'transition_comment_status', $nginx_purger, 'purge_post_on_comment_change', 200, 3 );
222 | $this->loader->add_action( 'transition_post_status', $nginx_helper_admin, 'set_future_post_option_on_future_status', 20, 3 );
223 | $this->loader->add_action( 'delete_post', $nginx_helper_admin, 'unset_future_post_option_on_delete', 20, 1 );
224 | $this->loader->add_action( 'edit_attachment', $nginx_purger, 'purge_image_on_edit', 100, 1 );
225 | $this->loader->add_action( 'wpmu_new_blog', $nginx_helper_admin, 'update_new_blog_options', 10, 1 );
226 | $this->loader->add_action( 'transition_post_status', $nginx_purger, 'purge_on_post_moved_to_trash', 20, 3 );
227 | $this->loader->add_action( 'edit_term', $nginx_purger, 'purge_on_term_taxonomy_edited', 20, 3 );
228 | $this->loader->add_action( 'delete_term', $nginx_purger, 'purge_on_term_taxonomy_edited', 20, 3 );
229 | $this->loader->add_action( 'check_ajax_referer', $nginx_purger, 'purge_on_check_ajax_referer', 20 );
230 | $this->loader->add_action( 'admin_bar_init', $nginx_helper_admin, 'purge_all' );
231 |
232 | // expose action to allow other plugins to purge the cache.
233 | $this->loader->add_action( 'rt_nginx_helper_purge_all', $nginx_purger, 'purge_all' );
234 | }
235 |
236 | /**
237 | * Run the loader to execute all of the hooks with WordPress.
238 | *
239 | * @since 2.0.0
240 | */
241 | public function run() {
242 | $this->loader->run();
243 | }
244 |
245 | /**
246 | * The name of the plugin used to uniquely identify it within the context of
247 | * WordPress and to define internationalization functionality.
248 | *
249 | * @since 2.0.0
250 | * @return string The name of the plugin.
251 | */
252 | public function get_plugin_name() {
253 | return $this->plugin_name;
254 | }
255 |
256 | /**
257 | * The reference to the class that orchestrates the hooks with the plugin.
258 | *
259 | * @since 2.0.0
260 | *
261 | * @return Nginx_Helper_Loader Orchestrates the hooks of the plugin.
262 | */
263 | public function get_loader() {
264 | return $this->loader;
265 | }
266 |
267 | /**
268 | * Retrieve the version number of the plugin.
269 | *
270 | * @since 2.0.0
271 | * @return string The version number of the plugin.
272 | */
273 | public function get_version() {
274 | return $this->version;
275 | }
276 |
277 | /**
278 | * Check wp version.
279 | *
280 | * @since 2.0.0
281 | *
282 | * @global string $wp_version
283 | *
284 | * @return boolean
285 | */
286 | public function required_wp_version() {
287 |
288 | global $wp_version;
289 |
290 | $wp_ok = version_compare( $wp_version, $this->minimum_wp, '>=' );
291 |
292 | if ( false === $wp_ok ) {
293 |
294 | add_action( 'admin_notices', array( &$this, 'display_notices' ) );
295 | add_action( 'network_admin_notices', array( &$this, 'display_notices' ) );
296 | return false;
297 |
298 | }
299 |
300 | return true;
301 |
302 | }
303 |
304 | /**
305 | * Dispay plugin notices.
306 | */
307 | public function display_notices() {
308 | ?>
309 |
310 |
311 |
312 | minimum_wp )
317 | );
318 | ?>
319 |
320 |
321 |
322 | \n"
8 | "Language-Team: LANGUAGE \n"
9 | "MIME-Version: 1.0\n"
10 | "Content-Type: text/plain; charset=UTF-8\n"
11 | "Content-Transfer-Encoding: 8bit\n"
12 | "POT-Creation-Date: 2020-04-20T18:52:09+00:00\n"
13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
14 | "X-Generator: WP-CLI 2.2.0\n"
15 | "X-Domain: nginx-helper\n"
16 |
17 | #. Plugin Name of the plugin
18 | #: admin/class-nginx-helper-admin.php:177
19 | #: admin/class-nginx-helper-admin.php:178
20 | #: admin/class-nginx-helper-admin.php:188
21 | #: admin/class-nginx-helper-admin.php:189
22 | msgid "Nginx Helper"
23 | msgstr ""
24 |
25 | #. Plugin URI of the plugin
26 | msgid "https://rtcamp.com/nginx-helper/"
27 | msgstr ""
28 |
29 | #. Description of the plugin
30 | msgid "Cleans nginx's fastcgi/proxy cache or redis-cache whenever a post is edited/published. Also does few more things."
31 | msgstr ""
32 |
33 | #. Author of the plugin
34 | msgid "rtCamp"
35 | msgstr ""
36 |
37 | #. Author URI of the plugin
38 | msgid "https://rtcamp.com"
39 | msgstr ""
40 |
41 | #: includes/class-nginx-helper-activator.php:49
42 | msgid "Sorry, you need to be an administrator to use Nginx Helper"
43 | msgstr ""
44 |
45 | #. translators: %s is Minimum WP version.
46 | #: includes/class-nginx-helper.php:311
47 | msgid "Sorry, Nginx Helper requires WordPress %s or higher"
48 | msgstr ""
49 |
50 | #: admin/class-nginx-helper-admin.php:88
51 | msgid "General"
52 | msgstr ""
53 |
54 | #: admin/class-nginx-helper-admin.php:92
55 | msgid "Support"
56 | msgstr ""
57 |
58 | #: admin/class-nginx-helper-admin.php:160
59 | msgid "Purging entire cache is not recommended. Would you like to continue?"
60 | msgstr ""
61 |
62 | #: admin/class-nginx-helper-admin.php:212
63 | msgid "Purge Cache"
64 | msgstr ""
65 |
66 | #: admin/class-nginx-helper-admin.php:215
67 | msgid "Purge Current Page"
68 | msgstr ""
69 |
70 | #: admin/class-nginx-helper-admin.php:341
71 | msgid "Settings"
72 | msgstr ""
73 |
74 | #: admin/class-nginx-helper-admin.php:404
75 | msgid "No items"
76 | msgstr ""
77 |
78 | #: admin/class-nginx-helper-admin.php:415
79 | msgid "Posted "
80 | msgstr ""
81 |
82 | #: admin/class-nginx-helper-admin.php:740
83 | msgid "Purge initiated"
84 | msgstr ""
85 |
86 | #: admin/class-purger.php:686
87 | msgid "Purging homepage (WPML) "
88 | msgstr ""
89 |
90 | #: admin/class-purger.php:691
91 | msgid "Purging homepage "
92 | msgstr ""
93 |
94 | #: admin/class-purger.php:710
95 | msgid "Purging personal urls"
96 | msgstr ""
97 |
98 | #: admin/class-purger.php:718
99 | msgid "No personal urls available"
100 | msgstr ""
101 |
102 | #: admin/class-purger.php:734
103 | msgid "Purging category archives"
104 | msgstr ""
105 |
106 | #. translators: %d: Category ID.
107 | #: admin/class-purger.php:743
108 | msgid "Purging category '%d'"
109 | msgstr ""
110 |
111 | #: admin/class-purger.php:761
112 | msgid "Purging tags archives"
113 | msgstr ""
114 |
115 | #: admin/class-purger.php:769
116 | #: admin/class-purger.php:869
117 | msgid "Purging tag '%1$s' ( id %2$d )"
118 | msgstr ""
119 |
120 | #: admin/class-purger.php:788
121 | msgid "Purging post custom taxonomies related"
122 | msgstr ""
123 |
124 | #. translators: %s: Post taxonomy name.
125 | #. translators: %s: Taxonomy name.
126 | #: admin/class-purger.php:802
127 | #: admin/class-purger.php:902
128 | msgid "Purging custom taxonomy '%s'"
129 | msgstr ""
130 |
131 | #. translators: %s: Post taxonomy name.
132 | #. translators: %s: Taxonomy name.
133 | #: admin/class-purger.php:816
134 | #: admin/class-purger.php:918
135 | msgid "Your built-in taxonomy '%s' has param '_builtin' set to false."
136 | msgstr ""
137 |
138 | #: admin/class-purger.php:820
139 | #: admin/class-purger.php:922
140 | msgid "No custom taxonomies"
141 | msgstr ""
142 |
143 | #: admin/class-purger.php:833
144 | msgid "Purging all categories"
145 | msgstr ""
146 |
147 | #: admin/class-purger.php:841
148 | msgid "Purging category '%1$s' ( id %2$d )"
149 | msgstr ""
150 |
151 | #: admin/class-purger.php:847
152 | msgid "No categories archives"
153 | msgstr ""
154 |
155 | #: admin/class-purger.php:861
156 | msgid "Purging all tags"
157 | msgstr ""
158 |
159 | #: admin/class-purger.php:874
160 | msgid "No tags archives"
161 | msgstr ""
162 |
163 | #: admin/class-purger.php:888
164 | msgid "Purging all custom taxonomies"
165 | msgstr ""
166 |
167 | #: admin/class-purger.php:949
168 | msgid "Purging all posts, pages and custom post types."
169 | msgstr ""
170 |
171 | #: admin/class-purger.php:964
172 | msgid "Purging post id '%1$d' ( post type '%2$s' )"
173 | msgstr ""
174 |
175 | #: admin/class-purger.php:969
176 | msgid "No posts"
177 | msgstr ""
178 |
179 | #: admin/class-purger.php:983
180 | msgid "Purging all date-based archives."
181 | msgstr ""
182 |
183 | #: admin/class-purger.php:1000
184 | msgid "Purging all daily archives."
185 | msgstr ""
186 |
187 | #: admin/class-purger.php:1023
188 | msgid "Purging daily archive '%1$s/%2$s/%3$s'"
189 | msgstr ""
190 |
191 | #: admin/class-purger.php:1034
192 | msgid "No daily archives"
193 | msgstr ""
194 |
195 | #: admin/class-purger.php:1046
196 | msgid "Purging all monthly archives."
197 | msgstr ""
198 |
199 | #: admin/class-purger.php:1074
200 | msgid "Purging monthly archive '%1$s/%2$s'"
201 | msgstr ""
202 |
203 | #: admin/class-purger.php:1079
204 | msgid "No monthly archives"
205 | msgstr ""
206 |
207 | #: admin/class-purger.php:1091
208 | msgid "Purging all yearly archives."
209 | msgstr ""
210 |
211 | #. translators: %s: Year to purge cache.
212 | #: admin/class-purger.php:1119
213 | msgid "Purging yearly archive '%s'"
214 | msgstr ""
215 |
216 | #: admin/class-purger.php:1124
217 | msgid "No yearly archives"
218 | msgstr ""
219 |
220 | #: admin/class-purger.php:1136
221 | msgid "Let's purge everything!"
222 | msgstr ""
223 |
224 | #: admin/class-purger.php:1142
225 | msgid "Everything purged!"
226 | msgstr ""
227 |
228 | #: admin/class-purger.php:1165
229 | msgid "Term taxonomy edited or deleted"
230 | msgstr ""
231 |
232 | #: admin/class-purger.php:1172
233 | msgid "Term taxonomy '%1$s' edited, (tt_id '%2$d', term_id '%3$d', taxonomy '%4$s')"
234 | msgstr ""
235 |
236 | #: admin/class-purger.php:1176
237 | msgid "A term taxonomy has been deleted from taxonomy '%1$s', (tt_id '%2$d', term_id '%3$d')"
238 | msgstr ""
239 |
240 | #: admin/class-purger.php:1204
241 | msgid "Widget saved, moved or removed in a sidebar"
242 | msgstr ""
243 |
244 | #: admin/partials/nginx-helper-admin-display.php:21
245 | msgid "Nginx Settings"
246 | msgstr ""
247 |
248 | #: admin/partials/nginx-helper-support-options.php:18
249 | msgid "Support Forums"
250 | msgstr ""
251 |
252 | #: admin/partials/nginx-helper-support-options.php:24
253 | msgid "Free Support"
254 | msgstr ""
255 |
256 | #: admin/partials/nginx-helper-support-options.php:27
257 | msgid "Free Support Forum"
258 | msgstr ""
259 |
260 | #: admin/partials/nginx-helper-support-options.php:28
261 | #: admin/partials/nginx-helper-support-options.php:38
262 | msgid "Link to forum"
263 | msgstr ""
264 |
265 | #: admin/partials/nginx-helper-support-options.php:34
266 | msgid "Premium Support"
267 | msgstr ""
268 |
269 | #: admin/partials/nginx-helper-support-options.php:37
270 | msgid "Premium Support Forum"
271 | msgstr ""
272 |
273 | #: admin/partials/nginx-helper-sidebar-display.php:26
274 | msgid "Purge Entire Cache"
275 | msgstr ""
276 |
277 | #: admin/partials/nginx-helper-sidebar-display.php:31
278 | msgid "Need Help?"
279 | msgstr ""
280 |
281 | #: admin/partials/nginx-helper-sidebar-display.php:38
282 | msgid "Please use our"
283 | msgstr ""
284 |
285 | #: admin/partials/nginx-helper-sidebar-display.php:40
286 | msgid "free support forum"
287 | msgstr ""
288 |
289 | #: admin/partials/nginx-helper-sidebar-display.php:50
290 | msgid "Getting Social is Good"
291 | msgstr ""
292 |
293 | #: admin/partials/nginx-helper-sidebar-display.php:54
294 | msgid "Become a fan on Facebook"
295 | msgstr ""
296 |
297 | #: admin/partials/nginx-helper-sidebar-display.php:55
298 | msgid "Follow us on Twitter"
299 | msgstr ""
300 |
301 | #: admin/partials/nginx-helper-sidebar-display.php:61
302 | msgid "Useful Links"
303 | msgstr ""
304 |
305 | #: admin/partials/nginx-helper-sidebar-display.php:66
306 | msgid "WordPress-Nginx Solutions"
307 | msgstr ""
308 |
309 | #: admin/partials/nginx-helper-sidebar-display.php:69
310 | msgid "WordPress Theme Devleopment"
311 | msgstr ""
312 |
313 | #: admin/partials/nginx-helper-sidebar-display.php:72
314 | msgid "WordPress Plugin Development"
315 | msgstr ""
316 |
317 | #: admin/partials/nginx-helper-sidebar-display.php:75
318 | msgid "WordPress Consultancy"
319 | msgstr ""
320 |
321 | #: admin/partials/nginx-helper-sidebar-display.php:78
322 | msgid "easyengine (ee)"
323 | msgstr ""
324 |
325 | #: admin/partials/nginx-helper-sidebar-display.php:85
326 | msgid "Click to toggle"
327 | msgstr ""
328 |
329 | #: admin/partials/nginx-helper-sidebar-display.php:86
330 | msgid "Latest News"
331 | msgstr ""
332 |
333 | #: admin/partials/nginx-helper-sidebar-display.php:87
334 | msgid "Loading..."
335 | msgstr ""
336 |
337 | #: admin/partials/nginx-helper-general-options.php:56
338 | msgid "Log file size must be a number."
339 | msgstr ""
340 |
341 | #: admin/partials/nginx-helper-general-options.php:66
342 | msgid "Settings saved."
343 | msgstr ""
344 |
345 | #: admin/partials/nginx-helper-general-options.php:93
346 | msgid "Purging Options"
347 | msgstr ""
348 |
349 | #: admin/partials/nginx-helper-general-options.php:100
350 | msgid "Enable Purge"
351 | msgstr ""
352 |
353 | #: admin/partials/nginx-helper-general-options.php:110
354 | msgid "Caching Method"
355 | msgstr ""
356 |
357 | #: admin/partials/nginx-helper-general-options.php:122
358 | msgid "nginx Fastcgi cache"
359 | msgstr ""
360 |
361 | #: admin/partials/nginx-helper-general-options.php:124
362 | msgid "External settings for nginx"
363 | msgstr ""
364 |
365 | #: admin/partials/nginx-helper-general-options.php:125
366 | msgid "requires external settings for nginx"
367 | msgstr ""
368 |
369 | #: admin/partials/nginx-helper-general-options.php:135
370 | msgid "Redis cache"
371 | msgstr ""
372 |
373 | #: admin/partials/nginx-helper-general-options.php:145
374 | msgid "Purge Method"
375 | msgstr ""
376 |
377 | #: admin/partials/nginx-helper-general-options.php:155
378 | #: admin/partials/nginx-helper-general-options.php:335
379 | msgid "when a post/page/custom post is published."
380 | msgstr ""
381 |
382 | #: admin/partials/nginx-helper-general-options.php:165
383 | msgid "Using a GET request to"
384 | msgstr ""
385 |
386 | #: admin/partials/nginx-helper-general-options.php:166
387 | msgid "(Default option)"
388 | msgstr ""
389 |
390 | #. translators: %s Nginx cache purge module link.
391 | #: admin/partials/nginx-helper-general-options.php:177
392 | msgid "Uses the %s module."
393 | msgstr ""
394 |
395 | #: admin/partials/nginx-helper-general-options.php:195
396 | msgid "Delete local server cache files"
397 | msgstr ""
398 |
399 | #: admin/partials/nginx-helper-general-options.php:201
400 | msgid "Checks for matching cache file in RT_WP_NGINX_HELPER_CACHE_PATH. Does not require any other modules. Requires that the cache be stored on the same server as WordPress. You must also be using the default nginx cache options (levels=1:2) and (fastcgi_cache_key \"$scheme$request_method$host$request_uri\")."
401 | msgstr ""
402 |
403 | #: admin/partials/nginx-helper-general-options.php:216
404 | msgid "Redis Settings"
405 | msgstr ""
406 |
407 | #: admin/partials/nginx-helper-general-options.php:221
408 | msgid "Hostname"
409 | msgstr ""
410 |
411 | #: admin/partials/nginx-helper-general-options.php:228
412 | #: admin/partials/nginx-helper-general-options.php:243
413 | #: admin/partials/nginx-helper-general-options.php:258
414 | msgid "Overridden by constant variables."
415 | msgstr ""
416 |
417 | #: admin/partials/nginx-helper-general-options.php:236
418 | msgid "Port"
419 | msgstr ""
420 |
421 | #: admin/partials/nginx-helper-general-options.php:251
422 | msgid "Prefix"
423 | msgstr ""
424 |
425 | #: admin/partials/nginx-helper-general-options.php:271
426 | msgid "Purging Conditions"
427 | msgstr ""
428 |
429 | #: admin/partials/nginx-helper-general-options.php:276
430 | msgid "Purge Homepage:"
431 | msgstr ""
432 |
433 | #: admin/partials/nginx-helper-general-options.php:283
434 | msgid "when a post/page/custom post is modified or added."
435 | msgstr ""
436 |
437 | #: admin/partials/nginx-helper-general-options.php:292
438 | #: admin/partials/nginx-helper-general-options.php:419
439 | msgid "when a post (or page/custom post) is modified or added."
440 | msgstr ""
441 |
442 | #: admin/partials/nginx-helper-general-options.php:304
443 | msgid "when an existing post/page/custom post is modified."
444 | msgstr ""
445 |
446 | #: admin/partials/nginx-helper-general-options.php:313
447 | msgid "when a published post (or page/custom post) is trashed"
448 | msgstr ""
449 |
450 | #: admin/partials/nginx-helper-general-options.php:327
451 | msgid "Purge Post/Page/Custom Post Type:"
452 | msgstr ""
453 |
454 | #: admin/partials/nginx-helper-general-options.php:344
455 | msgid "when a post is published."
456 | msgstr ""
457 |
458 | #: admin/partials/nginx-helper-general-options.php:356
459 | #: admin/partials/nginx-helper-general-options.php:453
460 | msgid "when a comment is approved/published."
461 | msgstr ""
462 |
463 | #: admin/partials/nginx-helper-general-options.php:365
464 | #: admin/partials/nginx-helper-general-options.php:462
465 | msgid "when a comment is approved/published."
466 | msgstr ""
467 |
468 | #: admin/partials/nginx-helper-general-options.php:377
469 | #: admin/partials/nginx-helper-general-options.php:474
470 | msgid "when a comment is unapproved/deleted."
471 | msgstr ""
472 |
473 | #: admin/partials/nginx-helper-general-options.php:386
474 | #: admin/partials/nginx-helper-general-options.php:483
475 | msgid "when a comment is unapproved/deleted."
476 | msgstr ""
477 |
478 | #: admin/partials/nginx-helper-general-options.php:400
479 | msgid "Purge Archives:"
480 | msgstr ""
481 |
482 | #: admin/partials/nginx-helper-general-options.php:402
483 | msgid "(date, category, tag, author, custom taxonomies)"
484 | msgstr ""
485 |
486 | #: admin/partials/nginx-helper-general-options.php:410
487 | msgid "when an post/page/custom post is modified or added"
488 | msgstr ""
489 |
490 | #: admin/partials/nginx-helper-general-options.php:431
491 | msgid "when an existing post/page/custom post is trashed."
492 | msgstr ""
493 |
494 | #: admin/partials/nginx-helper-general-options.php:440
495 | msgid "when a published post (or page/custom post) is trashed."
496 | msgstr ""
497 |
498 | #: admin/partials/nginx-helper-general-options.php:496
499 | msgid "Custom Purge URL:"
500 | msgstr ""
501 |
502 | #: admin/partials/nginx-helper-general-options.php:502
503 | msgid "Add one URL per line. URL should not contain domain name."
504 | msgstr ""
505 |
506 | #: admin/partials/nginx-helper-general-options.php:505
507 | msgid "Eg: To purge http://example.com/sample-page/ add /sample-page/ in above textarea."
508 | msgstr ""
509 |
510 | #: admin/partials/nginx-helper-general-options.php:509
511 | msgid "'*' will only work with redis cache server."
512 | msgstr ""
513 |
514 | #: admin/partials/nginx-helper-general-options.php:519
515 | msgid "Debug Options"
516 | msgstr ""
517 |
518 | #: admin/partials/nginx-helper-general-options.php:529
519 | msgid "Enable Nginx Map."
520 | msgstr ""
521 |
522 | #: admin/partials/nginx-helper-general-options.php:538
523 | msgid "Enable Logging"
524 | msgstr ""
525 |
526 | #: admin/partials/nginx-helper-general-options.php:546
527 | msgid "Enable Nginx Timestamp in HTML"
528 | msgstr ""
529 |
530 | #: admin/partials/nginx-helper-general-options.php:560
531 | msgid "Nginx Map"
532 | msgstr ""
533 |
534 | #: admin/partials/nginx-helper-general-options.php:569
535 | msgid "Can't write on map file."
536 | msgstr ""
537 |
538 | #. translators: %s file url.
539 | #: admin/partials/nginx-helper-general-options.php:574
540 | #: admin/partials/nginx-helper-general-options.php:646
541 | msgid "Check you have write permission on %s"
542 | msgstr ""
543 |
544 | #: admin/partials/nginx-helper-general-options.php:591
545 | msgid "Nginx Map path to include in nginx settings"
546 | msgstr ""
547 |
548 | #: admin/partials/nginx-helper-general-options.php:592
549 | msgid "(recommended)"
550 | msgstr ""
551 |
552 | #: admin/partials/nginx-helper-general-options.php:605
553 | msgid "Or,"
554 | msgstr ""
555 |
556 | #: admin/partials/nginx-helper-general-options.php:606
557 | msgid "Text to manually copy and paste in nginx settings"
558 | msgstr ""
559 |
560 | #: admin/partials/nginx-helper-general-options.php:607
561 | msgid "(if your network is small and new sites are not added frequently)"
562 | msgstr ""
563 |
564 | #: admin/partials/nginx-helper-general-options.php:625
565 | msgid "Logging Options"
566 | msgstr ""
567 |
568 | #: admin/partials/nginx-helper-general-options.php:641
569 | msgid "Can't write on log file."
570 | msgstr ""
571 |
572 | #: admin/partials/nginx-helper-general-options.php:663
573 | msgid "Logs path"
574 | msgstr ""
575 |
576 | #: admin/partials/nginx-helper-general-options.php:675
577 | msgid "View Log"
578 | msgstr ""
579 |
580 | #: admin/partials/nginx-helper-general-options.php:680
581 | msgid "Log"
582 | msgstr ""
583 |
584 | #: admin/partials/nginx-helper-general-options.php:687
585 | msgid "Log level"
586 | msgstr ""
587 |
588 | #: admin/partials/nginx-helper-general-options.php:692
589 | msgid "None"
590 | msgstr ""
591 |
592 | #: admin/partials/nginx-helper-general-options.php:693
593 | msgid "Info"
594 | msgstr ""
595 |
596 | #: admin/partials/nginx-helper-general-options.php:694
597 | msgid "Warning"
598 | msgstr ""
599 |
600 | #: admin/partials/nginx-helper-general-options.php:695
601 | msgid "Error"
602 | msgstr ""
603 |
604 | #: admin/partials/nginx-helper-general-options.php:702
605 | msgid "Max log file size"
606 | msgstr ""
607 |
608 | #: admin/partials/nginx-helper-general-options.php:708
609 | msgid "Mb"
610 | msgstr ""
611 |
612 | #: admin/partials/nginx-helper-general-options.php:725
613 | msgid "Save All Changes"
614 | msgstr ""
615 |
616 | #: class-nginx-helper-wp-cli-command.php:40
617 | msgid "Purged Everything!"
618 | msgstr ""
619 |
--------------------------------------------------------------------------------
/src/Stack/NginxHelper/readme.txt:
--------------------------------------------------------------------------------
1 | === Nginx Helper ===
2 | Contributors: rtcamp, rahul286, saurabhshukla, manishsongirkar36, faishal, desaiuditd, darren-slatten, jk3us, daankortenbach, telofy, pjv, llonchj, jinnko, weskoop, bcole808, gungeekatx, rohanveer, chandrapatel, gagan0123, ravanh, michaelbeil, samedwards, niwreg, entr, nuvoPoint, iam404, rittesh.patel, vishalkakadiya, BhargavBhandari90, vincent-lu, murrayjbrown, bryant1410, 1gor, matt-h, pySilver, johan-chassaing, dotsam, sanketio, petenelson, nathanielks, rigagoogoo, dslatten, jinschoi, kelin1003, vaishuagola27, rahulsprajapati, Joel-James, utkarshpatel, gsayed786, shashwatmittal, sudhiryadav, thrijith, stayallive, jaredwsmith, abhijitrakas, umeshnevase
3 | Donate Link: http://rtcamp.com/donate/
4 | Tags: nginx, cache, purge, nginx map, nginx cache, maps, fastcgi, proxy, redis, redis-cache, rewrite, permalinks
5 | License: GPLv2 or later (of-course)
6 | License URI: http://www.gnu.org/licenses/gpl-2.0.html
7 | Requires at least: 3.0
8 | Tested up to: 5.4
9 | Stable tag: 2.2.2
10 |
11 | Cleans nginx's fastcgi/proxy cache or redis-cache whenever a post is edited/published. Also does a few more things.
12 |
13 | == Description ==
14 |
15 | 1. Removes `index.php` from permalinks when using WordPress with nginx.
16 | 1. Adds support for purging redis-cache when used as full-page cache created using [nginx-srcache-module](https://github.com/openresty/srcache-nginx-module#caching-with-redis)
17 | 1. Adds support for nginx fastcgi_cache_purge & proxy_cache_purge directive from [module](https://github.com/FRiCKLE/ngx_cache_purge "ngx_cache_purge module"). Provides settings so you can customize purging rules.
18 | 1. Adds support for nginx `map{..}` on a WordPress-multisite network installation. Using it, Nginx can serve PHP file uploads even if PHP/MySQL crashes. Please check the tutorial list below for related Nginx configurations.
19 |
20 | = Tutorials =
21 |
22 | You will need to follow one or more tutorials below to get desired functionality:
23 |
24 | * [Nginx Map + WordPress-Multisite + Static Files Handling](https://easyengine.io/wordpress-nginx/tutorials/multisite/static-files-handling/)
25 | * [Nginx + WordPress + fastcgi_purge_cache](https://easyengine.io/wordpress-nginx/tutorials/single-site/fastcgi-cache-with-purging/)
26 | * [Nginx + WordPress-Multisite (Subdirectories) + fastcgi_purge_cache](https://easyengine.io/wordpress-nginx/tutorials/multisite/subdirectories/fastcgi-cache-with-purging/)
27 | * [Nginx + WordPress-Multisite (Subdomains/domain-mapping) + fastcgi_purge_cache](https://easyengine.io/wordpress-nginx/tutorials/multisite/subdomains/fastcgi-cache-with-purging/)
28 | * [Other WordPress-Nginx Tutorials](https://easyengine.io/wordpress-nginx/tutorials/)
29 |
30 |
31 | == Installation ==
32 |
33 | Automatic Installation
34 |
35 | 1. Log in to your WordPress admin panel, navigate to the Plugins menu and click Add New.
36 | 1. In the search field type “Nginx Helper” and click Search Plugins. From the search results, pick Nginx Helper and click Install Now. Wordpress will ask you to confirm to complete the installation.
37 |
38 | Manual Installation
39 |
40 | 1. Extract the zip file.
41 | 1. Upload them to `/wp-content/plugins/` directory on your WordPress installation.
42 | 1. Then activate the Plugin from Plugins page.
43 |
44 | For proper configuration, check out our **tutorial list** in the [Description tab](http://wordpress.org/extend/plugins/nginx-helper).
45 |
46 | == Frequently Asked Questions ==
47 |
48 | **Important** - Please refer to [https://easyengine.io/nginx-helper/faq](https://easyengine.io/nginx-helper/faq) for up-to-date FAQs.
49 |
50 | = FAQ - Installation/Comptability =
51 |
52 | **Q. Will this work out of the box?**
53 |
54 | No. You need to make some changes at the Nginx end. Please check our [tutorial list](https://easyengine.io/wordpress-nginx/tutorials/).
55 |
56 | = FAQ - Nginx Fastcgi Cache Purge =
57 |
58 | **Q. There's a 'purge all' button? Does it purge the whole site?**
59 |
60 | Yes, it does. It physically empties the cache directory. It is set by default to `/var/run/nginx-cache/`.
61 |
62 | If your cache directory is different, you can override this in your wp-config.php by adding
63 | `define('RT_WP_NGINX_HELPER_CACHE_PATH','/var/run/nginx-cache/');`
64 |
65 | Replace the path with your own.
66 |
67 | **Q. Does it work for custom posts and taxonomies?**
68 |
69 | Yes. It handles all post-types the same way.
70 |
71 | **Q. How do I know my Nginx config is correct for fastcgi purging?**
72 |
73 | Manually purging any page from the cache, by following instructions in the previous answer.
74 |
75 | Version 1.3.4 onwards, Nginx Helper adds a comment at the end of the HTML source ('view source' in your favourite browser):
76 | `<!--Cached using Nginx-Helper on 2012-10-08 07:01:45. It took 42 queries executed in 0.280 seconds.-->`. This shows the time when the page was last cached. This date/time will be reset whenever this page is purged and refreshed in the cache. Just check this comment before and after a manual purge.
77 |
78 | As long as you don't purge the page (or make changes that purge it from the cache), the timestamp will remain as is, even if you keep refreshing the page. This means the page was served from the cache and it's working!
79 |
80 | The rest shows you the database queries and time saved on loading this page. (This would have been the additional resource load, if you weren't using fast-cgi-cache.)
81 |
82 |
83 | **Q. I need to flush a cached page immediately! How do I do that?**
84 |
85 | Nginx helper plugin handles usual scenarios, when a page in the cache will need purging. For example, when a post is edited or a comment is approved on a post.
86 |
87 | To purge a page immediately, follow these instructions:
88 |
89 | * Let's say we have a page at the following domain: http://yoursite.com/about.
90 | * Between the domain name and the rest of the URL, insert '/purge/'.
91 | * So, in the above example, the purge URL will be http://yoursite.com/purge/about.
92 | * Just open this in a browser and the page will be purged instantly.
93 | * Needless to say, this won't work, if you have a page or taxonomy called 'purge'.
94 |
95 | = FAQ - Nginx Redis Cache =
96 |
97 | **Q. Can I override the redis hostname, port and prefix?**
98 |
99 | Yes, you can force override the redis hostname, port or prefix by defining constant in wp-config.php. For example:
100 |
101 | ```
102 | define('RT_WP_NGINX_HELPER_REDIS_HOSTNAME', '10.0.0.1');
103 |
104 | define('RT_WP_NGINX_HELPER_REDIS_PORT', '6000');
105 |
106 | define('RT_WP_NGINX_HELPER_REDIS_PREFIX', 'page-cache:');
107 | ```
108 |
109 | = FAQ - Nginx Memcached Cache =
110 |
111 | **Q. Can I override the memcached hostname, port, prefix and versioned cache key?**
112 |
113 | Yes, you can force override the memcached hostname, port or prefix by defining constant in wp-config.php. For example:
114 |
115 | ```
116 | define('RT_WP_NGINX_HELPER_MEMCACHED_HOSTNAME', '10.0.0.1');
117 |
118 | define('RT_WP_NGINX_HELPER_MEMCACHED_PORT', '6000');
119 |
120 | define('RT_WP_NGINX_HELPER_MEMCACHED_PREFIX', 'page-cache:');
121 |
122 | define('RT_WP_NGINX_HELPER_MEMCACHED_VERSIONED_CACHE_KEY', 'page-cache:version');
123 | ```
124 |
125 | = FAQ - Nginx Map =
126 |
127 | **Q. My multisite already uses `WPMU_ACCEL_REDIRECT`. Do I still need Nginx Map?**
128 |
129 | Definitely. `WPMU_ACCEL_REDIRECT` reduces the load on PHP, but it still ask WordPress i.e. PHP/MySQL to do some work for static files e.g. images in your post. Nginx map lets nginx handle files on its own bypassing wordpress which gives you much better performance without using a CDN.
130 |
131 | **Q. I am using X plugin. Will it work on Nginx?**
132 |
133 | Most likely yes. A wordpress plugin, if not using explicitly any Apache-only mod, should work on Nginx. Some plugin may need some extra work.
134 |
135 |
136 | = Still need help! =
137 |
138 | Please post your problem in [our free support forum](http://community.rtcamp.com/c/wordpress-nginx).
139 |
140 | == Screenshots ==
141 | 1. Nginx plugin settings
142 | 2. Remaining settings
143 |
144 | == Changelog ==
145 |
146 | = 2.2.2 =
147 | * Add action `rt_nginx_helper_after_purge_all` to fire after the entire cache has been purged whatever caching type is used. [#232](https://github.com/rtCamp/nginx-helper/pull/232) - by [Julien-prrs](https://github.com/Julien-prrs)
148 | * Fix issue where settings not saved because the button's value localized (for any language). [#236](https://github.com/rtCamp/nginx-helper/pull/236) - by [umeshnevase](https://github.com/umeshnevase)
149 | * Fix issue where "Custom Purge URL" option displays previous value. [#240](https://github.com/rtCamp/nginx-helper/issues/240), [#241](https://github.com/rtCamp/nginx-helper/pull/241) - by [KirillGritcenko](https://github.com/KirillGritcenko)
150 | * Tested with WordPress 5.4
151 |
152 | = 2.2.1 =
153 | * Fix timeout issue on FastCGI cache purge. [#229](https://github.com/rtCamp/nginx-helper/pull/229) - by [chandrapatel](https://github.com/chandrapatel), [thrijith](https://github.com/thrijith)
154 |
155 | = 2.2.0 =
156 | * Add filter `rt_nginx_helper_fastcgi_purge_suffix` to change purge suffix for FastCGI cache. [#141](https://github.com/rtCamp/nginx-helper/pull/141) - by [stayallive](https://github.com/stayallive)
157 | * Add filter `rt_nginx_helper_fastcgi_purge_url_base` to change purge URL base for FastCGI cache. [#141](https://github.com/rtCamp/nginx-helper/pull/141) - by [stayallive](https://github.com/stayallive)
158 | * Update our code to be in line with WordPress Coding standards in various places. [#209](https://github.com/rtCamp/nginx-helper/pull/209), [#225](https://github.com/rtCamp/nginx-helper/pull/225) - by [abhijitrakas](https://github.com/abhijitrakas), [chandrapatel](https://github.com/chandrapatel)
159 | * Check and verify purging is enabled before purging cache. [#168](https://github.com/rtCamp/nginx-helper/pull/168) - by [jaredwsmith](https://github.com/jaredwsmith)
160 | * Hide Purge Cache button in admin bar when purge is disabled. [#218](https://github.com/rtCamp/nginx-helper/issues/218), [#219](https://github.com/rtCamp/nginx-helper/pull/219) - by [mbautista](https://github.com/mbautista), [chandrapatel](https://github.com/chandrapatel)
161 | * Don't add Nginx Timestamp on WordPress login page. [#204](https://github.com/rtCamp/nginx-helper/issues/204), [#220](https://github.com/rtCamp/nginx-helper/pull/220) - by [peixotorms](https://github.com/peixotorms), [chandrapatel](https://github.com/chandrapatel)
162 |
163 | = 2.1.0 =
164 | * Add wildcard cache key deletion for device type cache purge. [#203](https://github.com/rtCamp/nginx-helper/pull/203) - by [pradeep910](https://github.com/pradeep910)
165 | * Add filter `rt_nginx_helper_purge_url` to filter the URL to be purged. [#182](https://github.com/rtCamp/nginx-helper/pull/182) - by [todeveni](https://github.com/todeveni)
166 | * Add filter `rt_nginx_helper_purge_cached_file` to filter the cached file name. [#182](https://github.com/rtCamp/nginx-helper/pull/182) - by [todeveni](https://github.com/todeveni)
167 | * Add filter `rt_nginx_helper_remote_purge_url` to filter remote URL to be purged. [#182](https://github.com/rtCamp/nginx-helper/pull/182) - by [todeveni](https://github.com/todeveni)
168 | * Add action `rt_nginx_helper_after_fastcgi_purge_all` to fire after the FastCGI cache has been purged. [#182](https://github.com/rtCamp/nginx-helper/pull/182) - by [todeveni](https://github.com/todeveni)
169 | * Add action `rt_nginx_helper_after_redis_purge_all` to fire after the Redis cache has been purged. [#182](https://github.com/rtCamp/nginx-helper/pull/182) - by [todeveni](https://github.com/todeveni)
170 | * Add action `rt_nginx_helper_purged_file` to fire an action after deleting file from cache. [#182](https://github.com/rtCamp/nginx-helper/pull/182) - by [todeveni](https://github.com/todeveni)
171 | * Add action `rt_nginx_helper_before_remote_purge_url` to fire an action before purging remote URL. [#182](https://github.com/rtCamp/nginx-helper/pull/182) - by [todeveni](https://github.com/todeveni)
172 | * Add action `rt_nginx_helper_after_remote_purge_url` to fire an action after remote purge request. [#182](https://github.com/rtCamp/nginx-helper/pull/182) - by [todeveni](https://github.com/todeveni)
173 | * Fix issue with post purge on new comments. [#175](https://github.com/rtCamp/nginx-helper/pull/175) - by [jinschoi](https://github.com/jinschoi)
174 | * Fix Nginx Timestamp being added to invalid content type. [#200](https://github.com/rtCamp/nginx-helper/pull/200) - by [thrijith](https://github.com/thrijith)
175 | * Handle filesize exception while truncating nginx.log file. [#206](https://github.com/rtCamp/nginx-helper/pull/206) - by [peterjanes](https://github.com/peterjanes)
176 |
177 | = 2.0.3 =
178 | * Update article link for fastcgi cache purge. [#187](https://github.com/rtCamp/nginx-helper/pull/187) - by [gagan0123](https://github.com/gagan0123)
179 | * Fix map generation issue on `SUBDOMAIN_INSTALL`. [#189](https://github.com/rtCamp/nginx-helper/pull/189) - by [ChrisHardie](https://github.com/ChrisHardie)
180 |
181 | = 2.0.2 =
182 | * Fix undefined error when we install the plugin for the first time and if Redis is not available. [#162](https://github.com/rtCamp/nginx-helper/pull/162) - by [Joel-James](https://github.com/Joel-James)
183 | * Remove extra spacing for nginx map section. [#169](https://github.com/rtCamp/nginx-helper/pull/169) - by [ShashwatMittal](https://github.com/ShashwatMittal)
184 | * Purge Cache menu in front-end admibar now purge current page. [#173](https://github.com/rtCamp/nginx-helper/pull/173) - by [imranhsayed](https://github.com/imranhsayed)
185 | * Fix issue where cache is not cleared when page is swiched from publish to draft. [#174](https://github.com/rtCamp/nginx-helper/pull/174) - by [imranhsayed](https://github.com/imranhsayed)
186 | * Fix an issue where custom purge url option does not show newlines when using multiple urls. [#184](https://github.com/rtCamp/nginx-helper/issues/184) - by [mist-webit](https://github.com/mist-webit)
187 |
188 | = 2.0.1 =
189 | * Fix settings url for multisite: use network_admin_url to get network correct settings url. [#163](https://github.com/rtCamp/nginx-helper/pull/163) - by [Joel-James](https://github.com/Joel-James)
190 | * Fix php error with arbitrary statement in empty - Prior to PHP 5.5. [#165](https://github.com/rtCamp/nginx-helper/pull/165) - by [PatelUtkarsh](https://github.com/PatelUtkarsh)
191 |
192 | = 2.0.0 =
193 | * Fix typo causing failure to purge on trashed comment. [#159](https://github.com/rtCamp/nginx-helper/pull/159) - by [jinschoi](https://github.com/jinschoi)
194 | * Refactor Plugin structure and remove unused code. Initial code by [chandrapatel](https://github.com/chandrapatel), [#153](https://github.com/rtCamp/nginx-helper/pull/153) - by [jinschoi](https://github.com/kelin1003),
195 | * Run phpcs and fix warning. [#158](https://github.com/rtCamp/nginx-helper/pull/158)
196 | * Make compatible with EasyEngine v4.
197 |
198 | = 1.9.12 =
199 | * Allow override Redis host/port/prefix by defining constant in wp-config.php [#152](https://github.com/rtCamp/nginx-helper/pull/152) - by [vincent-lu](https://github.com/vincent-lu)
200 |
201 | = 1.9.11 =
202 | * Fixed issue where permalinks without trailing slash does not purging [#124](https://github.com/rtCamp/nginx-helper/issues/124) - by Patrick
203 | * Check whether role exist or not before removing capability. [#134](https://github.com/rtCamp/nginx-helper/pull/134) - by [1gor](https://github.com/1gor)
204 |
205 | = 1.9.10 =
206 | * Fixed issue where Nginx cache folders deleted on purge. [#123](https://github.com/rtCamp/nginx-helper/pull/123) - by [johan-chassaing](https://github.com/johan-chassaing)
207 | * Fixed Redis purge all feature for installation where WordPress lives in a separate folder. [#130](https://github.com/rtCamp/nginx-helper/pull/130) - by [pySilver](https://github.com/pySilver)
208 |
209 | = 1.9.9 =
210 | * Fix wp_redirect issue. [#131](https://github.com/rtCamp/nginx-helper/pull/131) - by [matt-h](https://github.com/matt-h)
211 |
212 | = 1.9.8 =
213 | * Fixed homepage cache cleared when WPML plugin used [#116](https://github.com/rtCamp/nginx-helper/pull/116) - by [Niwreg](https://profiles.wordpress.org/niwreg/)
214 | * Fixed Purge Cache clears the whole Redis cache [#113](https://github.com/rtCamp/nginx-helper/issues/113) - by HansVanEijsden
215 | * One log file for all site in WPMU.
216 | * Single site Redis cache purge when click on Purge Cache button in WPMU [#122](https://github.com/rtCamp/nginx-helper/pull/122) - by Lars Støttrup Nielsen
217 | * Fixed notices and warnings.
218 |
219 | = 1.9.7 =
220 | * Remove timestamp if cron or wp-cli [#114](https://github.com/rtCamp/nginx-helper/pull/114) - by [samedwards](https://profiles.wordpress.org/samedwards/)
221 | * Fixed notices and warnings.
222 |
223 | = 1.9.6 =
224 | * Fixed cache purging on post publish.
225 | * Error fixed when redis server not installed.
226 |
227 | = 1.9.5 =
228 | Added custom purge URL option.
229 |
230 | = 1.9.4 =
231 | * Added redis server connection timeout.
232 | * Added RedisException handling.
233 |
234 | = 1.9.3 =
235 | * Added PhpRedis API support.
236 | * Added redis-lua script support to purge complete cache very fast.
237 | * Added composer.json support
238 | * Fixed cache purging link in admin bar.
239 | * Updated the initial settings to include the 'purge_method' [#99](https://github.com/rtCamp/nginx-helper/pull/99) - by
240 | [gagan0123](https://github.com/gagan0123)
241 |
242 | = 1.9.2 =
243 | Fix purging for Redis cache and FastCGI cache
244 |
245 | = 1.9.1 =
246 | Fix purging for custom post types
247 |
248 | = 1.9 =
249 | Added Redis cache purge support.
250 |
251 | = 1.8.13 =
252 | Fixed PHP notice for an undefined index when "Enable Logging" is not set.
253 |
254 | = 1.8.12 =
255 | Updated readme and changelog
256 |
257 | = 1.8.11 =
258 | Fix url escaping [#82](https://github.com/rtCamp/nginx-helper/pull/82) - by
259 | [javisperez](https://github.com/javisperez)
260 |
261 | = 1.8.10 =
262 | * Security bug fix
263 |
264 | = 1.8.9 =
265 | * Default setting fix and wp-cli example correction - by [bcole808](https://profiles.wordpress.org/bcole808/)
266 |
267 | = 1.8.8 =
268 | * Added option to purge cache without nginx purge module - by [bcole808](https://profiles.wordpress.org/bcole808/)
269 |
270 | = 1.8.7 =
271 | * Added action `rt_nginx_helper_purge_all` to purge cache from other plugins - by [gungeekatx](https://profiles.wordpress.org/gungeekatx/)
272 |
273 | = 1.8.6 =
274 | * Removed wercker.yml from plugin zip/svn.
275 | * Updated readme
276 |
277 | = 1.8.5 =
278 | * Added WP_CLI support - by [Udit Desai](https://profiles.wordpress.org/desaiuditd/)
279 |
280 | = 1.8.4 =
281 | * Fix undefined index issue and correct "purge_archive_on_del" key
282 |
283 | = 1.8.3 =
284 | * Tested with WordPress 4.0
285 | * Fix issue #69
286 |
287 | = 1.8.1 =
288 | * Tested with wordpress 3.9.1
289 | * Fix confilct with Mailchimp's Social plugin
290 |
291 | = 1.8 =
292 | * New admin UI
293 | * Fix missing wp_sanitize_redirect function call
294 |
295 | = 1.7.6 =
296 | * Update Backend UI
297 | * Added Language Support
298 |
299 | = 1.7.5 =
300 | * Fixed option name mismatch issue to purge homepage on delete.
301 |
302 | = 1.7.4 =
303 | * Disable purge and stamp by default.
304 |
305 | = 1.7.3 =
306 | * Suppressed `unlink` related error-messages which can be safely ignored.
307 | * Fixed a bug in purge-all option.
308 |
309 | = 1.7.2 =
310 | * [pjv](http://profiles.wordpress.org/pjv/) fixed bug in logging file.
311 |
312 | = 1.7.1 =
313 | * Fixes bug in true purge and admin screen.
314 |
315 | = 1.7 =
316 | * True full cache purge added.
317 | * Map file location changed to uploads' directory to fix http://rtcamp.com/support/topic/plugin-update-removes-map-file/
318 | * Log file location also changed to uploads' directory.
319 |
320 | = 1.6.13 =
321 | * [pjv](http://profiles.wordpress.org/pjv/) changed the way home URL is accessed. Instead of site option, the plugin now uses home_URL() function.
322 |
323 | = 1.6.12 =
324 | * [telofy](http://wordpress.org/support/profile/telofy) added purging of atom and RDF feeds.
325 |
326 | = 1.6.11 =
327 | * Removed comments from Admin screens since, it was interfering with media uploads in 3.5 up.
328 |
329 | = 1.6.10 =
330 | * Cleaned up code.
331 | * Added credits for code.
332 | * Improved attachment purging.
333 |
334 | = 1.6.9 =
335 | * Added Faux to Purge all buttons, to avoid misleading users.
336 |
337 | = 1.6.8 =
338 | * [daankortenbach](http://profiles.wordpress.org/daankortenbach) added Purge Cache link to wp-admin bar
339 |
340 | = 1.6.7 =
341 | * [jk3us](http://profiles.wordpress.org/jk3us) added better content-type detection for cache verification comments
342 |
343 | = 1.6.6 =
344 | * [darren-slatten](http://profiles.wordpress.org/darren-slatten/) added Manual 'Purge all URLs' functionality
345 |
346 | = 1.6.5 =
347 | * Fixed typo that interfered with archive purge settings. Thanks to [Daan Kortenbach](http://profiles.wordpress.org/daankortenbach/) for pointing this out.
348 |
349 | = 1.6.4 =
350 | * Improved code for map generation to better conventions since the nesting confused some servers.
351 | * Added map update process to admin_init for frequent refreshes.
352 |
353 | = 1.6.3 =
354 | * Fixed duplicate entries.
355 |
356 | = 1.6.2 =
357 | * Another bug fix in the revised code for improved multisite and multidomain mapping.
358 |
359 | = 1.6.1 =
360 | * Fixed bug in the revised code for improved multisite and multidomain mapping.
361 |
362 | = 1.6 =
363 | * Revised code for improved multisite and multidomain mapping.
364 |
365 | = 1.5 =
366 | * Timestamp now only gets added to content-type text/html
367 | * Added option to toggle timestamp creation
368 |
369 | = 1.4 =
370 | * Fixed bug related to nomenclature of comment status that caused purge to fail.
371 |
372 | = 1.3.9 =
373 | * Removed extraneous headers.
374 |
375 | = 1.3.8 =
376 |
377 | * Fixed bug in single post/page/post-type purging code. Thanks to Greg for pointing this out here: http://rtcamp.com/support/topic/updating-post-nginx-helper-purge-cache-post/.
378 |
379 | = 1.3.7 =
380 |
381 | * Changed the action hook, back to 'shutdown' from 'wp_footer' to add verification comments.
382 | * Added a check to prevent adding comments to ajax requests,
383 |
384 | = 1.3.6 =
385 |
386 | * Changed the action hook, from 'shutdown' to 'wp_footer' to add verification comments. This was interfering with other plugins.
387 |
388 | = 1.3.5 =
389 |
390 | * Improved Readme.
391 | * Improved cache verification comments.
392 |
393 | = 1.3.4 =
394 |
395 | * Fixed duplicate entries generated for maps (Harmless, but doesn't look good!)
396 | * Added timestamp html comments for cache verification, as described here: http://rtcamp.com/wordpress-nginx/tutorials/checklist/
397 |
398 | = 1.3.3 =
399 |
400 | * Fixed map generation for multi domain installs using domain mapping plugin, where blog ids were not displayed.
401 |
402 | = 1.3.2 =
403 |
404 | * Fixed map generation for multi domain installs with domain mapping plugin.
405 |
406 | = 1.3.1 =
407 |
408 | * Minor fixes for directory structure and file names.
409 |
410 | = 1.3 =
411 |
412 | * Improved Readme.
413 |
414 | = 1.2 =
415 |
416 | * Fixed map generation error.
417 | * Fixed purging logic.
418 | * Fixed UI where purge settings were lost on disabling and re-enabling purge.
419 | * Minor Ui rearrangement.
420 |
421 | = 1.1 =
422 |
423 | * Improved readme.txt. Added Screenshots.
424 |
425 | = 1.0 =
426 |
427 | * First release
428 |
429 | == Upgrade Notice ==
430 |
431 | = 2.2.2 =
432 | Nginx Helper 2.2.2, Add new "rt_nginx_helper_after_purge_all" action and fixes issues where settings not saved because the button's value localized (for any language) and "Custom Purge URL" option displays previous value.
433 |
--------------------------------------------------------------------------------