├── 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 |
25 | 26 | 27 | 28 |
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 | 4 | Copyright (C) 2013 by original authors @ fontello.com 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.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 | --------------------------------------------------------------------------------