├── .sonarcloud.properties ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── apc └── proxy.php ├── bin └── install-wp-tests.sh ├── cachify.php ├── css ├── admin-bar-flush.css └── dashboard.css ├── images └── symbols.svg ├── inc ├── cachify.settings-footer.php ├── cachify.settings.php ├── class-cachify-backend.php ├── class-cachify-cli.php ├── class-cachify-db.php ├── class-cachify-hdd.php ├── class-cachify-memcached.php ├── class-cachify-noop.php ├── class-cachify-redis.php ├── class-cachify.php └── setup │ ├── cachify.hdd.htaccess.php │ ├── cachify.hdd.nginx.php │ └── cachify.memcached.nginx.php ├── js └── admin-bar-flush.js ├── phpunit.xml ├── readme.txt └── tests ├── bootstrap.php ├── test-cachify-db.php ├── test-cachify-hdd.php ├── test-cachify-memcached.php ├── test-cachify-noop.php ├── test-cachify-redis.php └── test-cachify.php /.sonarcloud.properties: -------------------------------------------------------------------------------- 1 | # Path to sources. 2 | sonar.sources=inc,js,css,cachify.php 3 | sonar.exclusions=**/*.min.css,**/*.min.js 4 | #sonar.inclusions= 5 | 6 | # Path to tests. 7 | sonar.tests=tests 8 | #sonar.test.exclusions= 9 | #sonar.test.inclusions= 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). 3 | 4 | ## 2.4.1 5 | 6 | * Fix: validation of Redis context parameters array in server hook no longer fails (#315) (#317) 7 | * Enhance: move some settings related code out of main class (#321) 8 | * Maintenance: Tested up to WordPress 6.7 9 | 10 | 11 | ## 2.4.0 12 | 13 | Requires PHP 5.6 and WordPress 4.7 or above 14 | 15 | * New: introduce Redis (also KeyDB and Valkey) support using the _phpredis_ module (#253), (#252, props @newtovaux) 16 | * New: add `cachify_modify_output` filter 17 | * New: add `cachify_create_gzip_files` to disable creation of static GZip files (#262, props @angcl) 18 | * New: add hooks `cachify_removed_cache_by_url` and `cachify_flushed_total_cache` for additional actions after clearing (#294, props @ouun) 19 | * Removed: APC support (#304) 20 | * Enhance: adjust styling for setup instructions (#215, props @timse201) 21 | * Enhance: update hooks for Multisite initialization in WordPress 5.1 and above (#246, props @ouun) 22 | * Enhance: rework flush hooks and add some third-party triggers for Autoptimize and WooCommerce (#225, props @timse201) 23 | * Enhance: clean up some internal error suppressions (#256) 24 | * Enhance: inform user on cache clear in admin bar (#257, props @angcl) 25 | * Enhance: do not flush the cache for post revisions (#261, props @angcl) 26 | * Enhance: prevent unnecessary cache clearing in some cases (#223) (#224, props @timse201) 27 | * Enhance: remove empty directories when clearing the HDD cache (#289) 28 | * Enhance: introduce common interface for caching backends (#298, props @lloc) 29 | * Enhance: enhance examples for .htaccess and nginx configuration (#302) 30 | * Enhance: show admin notice instead of silent fallback to DB cache, if selected backend is unavailable (#305) 31 | * Enhance: disable gzip creation of required PHP extension is missing (#308) 32 | * Enhance: various internal code clean ups 33 | * Fix: invalidate cache when permalink changes (#285, #286, props @raffaelj) 34 | * Fix: remove empty directories when pruning the HDD cache (#289) 35 | * Fix: correctly add user-agent to robots.txt (#282) (#283) 36 | * Fix: exclude _sitemap.xml_ from caching (#242) (#254) 37 | * Fix: prevent cache generation of non-GET requests (#200) (#258) 38 | * Fix: prevent cache generation of requests with status different from 200 OK (#266) (#267, props @karlkowald) 39 | * Fix: prevent cache generation of non-HTML responses when using content negotiation (#265) (#273, props @Ancocodet) 40 | * Fix: fix styling for various dark mode plugins (#264) (#278) 41 | * Fix: fix SVG markup for icons in dashboard widget (#269, props @Latz) 42 | * Fix: added missing .gz suffix in htaccess (#287) (#291, props @raffaelj) 43 | * Fix: fix some brand names and unify spelling (#297, props @pedro-mendonca) 44 | * Maintenance: Tested up to WordPress 6.6 45 | 46 | 47 | ## 2.3.2 48 | * Fix: enforce WordPress environment for caching modules (#221, props timse201) 49 | * Fix: Remove unnecessary build artifacts from plugin deployment (#226) 50 | * Fix: Fix input sanitization for APC proxy (#240) (#241) 51 | * Maintenance: Remove unused language folder (#214, props timse201) 52 | * Maintenance: Update documentation links (#211, #212, props timse201) 53 | * Maintenance: Update documentation links (#213, props timse201) 54 | * Maintenance: More precise tags in README file (#216, props timse201) 55 | * Maintenance: Tested up to WordPress 5.8 56 | 57 | ## 2.3.1 58 | * Fix: clean up unused parameter evaluation after publishing a post to prevent PHP notice (#187) (#188) 59 | * Fix: correct minor spelling mistakes (#193, props timse201) 60 | * Fix: update support links (#194, props timse201) 61 | 62 | ## 2.3.0 63 | * New: WP-CLI integration (#165, props derweili) 64 | * New: `cachify_flush_cache_hooks` filter added to modify all hooks that flush the cache 65 | * New: Flush cache when a user is created / updated / deleted 66 | * New: Flush cache when a term is created / updated / deleted (#169, props derweili) 67 | * New: Cache behavior after post modification is now configurable in plugin settings (#176) 68 | * Enhance: Cache exceptions/User-Agents translation (#52, props timse201) 69 | * Enhance: Readme FAQ (#51, props timse201) 70 | * Enhance: sizeable exclusion boxes + placeholder (#53, props timse201) 71 | * Enhance: FAQ and Support links (#55, props timse201) 72 | * Enhance: Add text caption to "flush cache" button 73 | * Enhance: Icon font converted to SVG (#64) 74 | * Enhance: Improved HDD cache invalidation for hierarchical post types (#71, props Syberspace) 75 | * Enhance: Unified and shortened HTML signature across all caching methods (#108) (#109) 76 | * Security: Tabnabbing prevention (#55, props timse201) 77 | * Maintenance: Tested up to WordPress 5.4 78 | 79 | 80 | ## 2.2.4 81 | * Fixes caching for mixed HTTPS and HTTP setups 82 | * Fixes an issue with the icon styling in the admin toolbar 83 | * Ensures compatibility with the latest WordPress version 84 | 85 | ## 2.2.3 86 | * New: Generated a POT file 87 | * New: Added German formal translation 88 | * Updated, translated + formatted README.md 89 | * Updated expired link URLs in plugin and languages files 90 | * Updated plugin authors 91 | 92 | ## 2.2.2 93 | * Fix: parameter return by filter dashboard_glance_items 94 | * Generous use of the filter esc_html 95 | 96 | ## 2.2.1 97 | * Fix for the PHP notice "Call to undefined function is_plugin_active_for_network" on WordPress Multisite 98 | 99 | ## 2.2.0 100 | * Toolbar: Display of the "Flush the Cachify cache" button on the frontend 101 | * Toolbar: Controlling the display of the "Flush the Cachify cache" button via hook 102 | 103 | ## 2.1.9 104 | * Vervollständigung des Cachify-Pfades in `robots.txt`: `Disallow: /wp-content/cache/cachify/` 105 | * *Release-Zeitaufwand (Development & QA): 0,75 Stunden* 106 | 107 | ## 2.1.8 108 | * HHVM-Unterstützung für die *Memcached* Caching-Methode (Danke, [Ulrich Block](http://www.ulrich-block.de)) 109 | 110 | ## 2.1.7 111 | * Cache-Leerung bei Custom Post Types 112 | * Einführung zusätzlicher Sicherheitsabfragen 113 | * Code-Refactoring 114 | 115 | ## 2.1.6 116 | * Button "Cache leeren" sichtbar sowohl in WP 3.8 wie WP 3.9 117 | 118 | ## 2.1.5 119 | * Support für WordPress 3.9 120 | * Button "Cache leeren" sichtbar bei jeder Bildschirmauflösung 121 | 122 | ## 2.1.4 123 | * Support für WordPress 3.8.1 124 | 125 | ## 2.1.3 126 | * Manuelle Auswahl: Beim Artikel-Update nur den Seitencache löschen 127 | * Lokalisierung der Plugin-Dateien 128 | * Umbauten am Plugin-Core 129 | * Detaillierter auf [Google+](https://plus.google.com/+SergejMüller/posts/By2PEtRMk8g) 130 | 131 | ## 2.1.2 132 | * Optimierung für WordPress 3.8 133 | * Option hinzugefügt: Neue Kommentare lösen einen Cache-Reset aus 134 | 135 | ## 2.1.1 136 | * Hook `cachify_skip_cache` für die Steuerung der Cache-Generierung 137 | * Support für das MP6 Plugin 138 | * Detaillierter auf [Google+](https://plus.google.com/110569673423509816572/posts/S1mpFsG3NZC) 139 | 140 | ## 2.1.0 141 | * Cache-Leerung bei Plugin-Deaktivierung 142 | 143 | ## 2.0.9 144 | * Quelltext-Minimierung als Selektbox und Hook 145 | * Interne Umstellung auf Konstanten 146 | 147 | ## 2.0.7 148 | * Unterstützung für Memcached 149 | * WordPress 3.6 Support 150 | * Cache-Neuaufbau beim Theme-Wechsel 151 | * Quelltext-Optimierungen 152 | 153 | ## 2.0.6 154 | * Cache-Neuaufbau einer Blogseite nur bei Kommentaren, die freigegeben sind 155 | 156 | ## 2.0.5 157 | * Cache-Leerung nach einem WordPress-Upgrade 158 | * Keine Cache-Ausgabe für Jetpack Mobile Theme 159 | * Abfrage auf eingeloggte Nutzer bei APC als Caching-Methode 160 | * Änderung der Systemvoraussetzungen 161 | * Cache-Reset nach WordPress-Update 162 | 163 | ## 2.0.4 164 | * Bessere Trennung der Cache-Gesamtgröße im Dashboard-Widget "Auf einen Blick" 165 | 166 | ## 2.0.3 167 | * Cache-Leerung beim Veröffentlichen verfügbarer Custom Post Types 168 | * Noindex in der von WordPress generierten `robots.txt` für den Ordner mit HDD-Cache 169 | * Hook `cachify_flush_cache` zum Leeren des Cache aus Drittanwendungen 170 | 171 | ## 2.0.2 172 | * Unterstützung für WordPress 3.4 173 | * Hochauflösende Icons für iPad & Co. 174 | * Anpassungen für ältere PHP5-Versionen 175 | * Entfernung des Plugin-Icons aus der Sidebar 176 | 177 | ## 2.0.1 178 | * Verbesserter Autoload-Prozess 179 | * Diverse Umbenennungen der Optionen 180 | * Cache-Neuaufbau bei geplanten Beiträgen (Cachify DB) 181 | 182 | ## 2.0 183 | * Überarbeitung der GUI 184 | * Source Code-Modularisierung 185 | * Cache-Größe auf dem Dashboard 186 | * Festplatte als Ablageort für Cache 187 | * Produktseite online: http://cachify.de 188 | * Cache-Neuaufbau bei Kommentarstatusänderungen 189 | * APC-Anforderungen: APC 3.0.0, empfohlen 3.1.4 190 | * Optional: Kein Cache für kommentierende Nutzer 191 | * Schnellübersicht der Optionen als Inline-Hilfe 192 | * Mindestanforderungen: WordPress 3.1 & PHP 5.1.2 193 | 194 | ## 1.5.1 195 | * `zlib.output_compression = Off` für Apache Webserver 196 | 197 | ## 1.5 198 | * Überarbeitung des Regexp für HTML-Minify 199 | * Reduzierung des Toolbar-Buttons auf das Icon 200 | * Formatierung und Kommentierung des Quelltextes 201 | 202 | ## 1.4 203 | * Xmas Edition 204 | 205 | ## 1.3 206 | * Unterstützung für APC (Alternative PHP Cache) 207 | * Umpositionierung des Admin Bar Buttons 208 | 209 | ## 1.2.1 210 | * Icon für die "Cache leeren" Schaltfläche in der Admin Bar 211 | 212 | ## 1.2 213 | * Schaltfläche "Cache leeren" in der Adminbar (ab WordPress 3.1) 214 | * `flush_cache` auf public gesetzt, um von [wpSEO](http://wpseo.de "WordPress SEO Plugin") ansprechen zu können 215 | * Ausführliche Tests unter WordPress 3.3 216 | 217 | ## 1.1 218 | * Interne Prüfung auf fehlerhafte Cache-Generierung 219 | * Anpassungen an der Code-Struktur 220 | * Entfernung der Inline-Hilfe 221 | * Verknüpfung der Online-Hilfe mit Optionen 222 | 223 | ## 1.0 224 | * Leerung des Cache beim Aktualisieren von statischen Seiten 225 | * Seite mit Plugin-Einstellungen 226 | * Inline-Dokumentation in der Optionsseite 227 | * Ausschluss von Passwort-geschützten Seiten 228 | * WordPress 3.2 Support 229 | * Unterstützung der WordPress Multisite Blogs 230 | * Umstellung auf den template_redirect-Hook (Plugin-Kompatibilität) 231 | * Interne Code-Bereinigung 232 | 233 | ## 0.9.2 234 | * HTML-Kompression 235 | * Flattr-Link 236 | 237 | ## 0.9.1 238 | * Cache-Reset bei geplanten Beiträgen 239 | * Unterstützung für das Carrington-Mobile Theme 240 | 241 | ## 0.9 242 | * Workaround für Redirects 243 | 244 | ## 0.8 245 | * Blacklist für PostIDs 246 | * Blacklist für UserAgents 247 | * Ausnahme für WP Touch 248 | * Ausgabe des Zeitpunktes der Generierung 249 | * Umbenennung der Konstanten 250 | 251 | ## 0.7 252 | * Ausgabe des Speicherverbrauchs 253 | 254 | ## 0.6 255 | * Live auf wordpress.org 256 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cachify # 2 | Smart, efficient cache solution for WordPress. Use DB, HDD, Redis or Memcached for storing your blog pages. Make WordPress faster! 3 | 4 | ## Description ## 5 | *Cachify* optimizes your page loads by caching posts, pages and custom post types as static content. You can choose between caching via database, on the web server’s hard drive (HDD), Memcached (only on Nginx) or Redis. Whenever a page or post is loaded, it can be pulled directly from the cache. The amount of database queries and PHP requests will dramatically decrease towards zero, depending on the caching method you chose. 6 | 7 | ### Features ### 8 | * Works with custom post types. 9 | * Caching methods: DB, HDD, Redis and Memcached. 10 | * “Flush Cache” button in the WordPress toolbar. 11 | * Ready for WordPress Multisite. 12 | * Optional compression of HTML markup. 13 | * White lists for posts and user agents. 14 | * Manual and automatic cache reset. 15 | * Automatic cache management. 16 | * Dashboard widget for cached objects. 17 | * Settings for Apache and Nginx servers. 18 | * Extendability via hooks/filters. 19 | 20 | ### Support ### 21 | * Community support via the [support forums on wordpress.org](https://wordpress.org/support/plugin/cachify/) 22 | * We don’t handle support via e-mail, Twitter, GitHub issues etc. 23 | 24 | ### Contribute ### 25 | * Active development of this plugin is handled [on GitHub](https://github.com/pluginkollektiv/cachify). 26 | * Pull requests for documented bugs are highly appreciated. 27 | * If you think you’ve found a bug (e.g. you’re experiencing unexpected behavior), please post at the [support forums](https://wordpress.org/support/plugin/cachify/) first. 28 | * If you want to help us translate this plugin you can do so [on WordPress Translate](https://translate.wordpress.org/projects/wp-plugins/cachify/). 29 | 30 | ### Donate 31 | [Donate to us via Paypal](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=TD4AMD2D8EMZW) 32 | 33 | ### Credits ### 34 | * Author: [Sergej Müller](https://sergejmueller.github.io) 35 | * Maintainers: [pluginkollektiv](https://pluginkollektiv.org) 36 | 37 | 38 | ## Installation ## 39 | * If you don’t know how to install a plugin for WordPress, [here’s how](https://wordpress.org/support/article/managing-plugins/#installing-plugins). 40 | 41 | ### Requirements ### 42 | * PHP 5.6 or greater 43 | * WordPress 4.7 or greater 44 | * Memcached in Nginx (optional) 45 | * Redis (optional, via the phpredis module) 46 | 47 | 48 | ## Frequently Asked Questions ## 49 | Please have a look [in the FAQ pages](https://cachify.pluginkollektiv.org/documentation/faq/). 50 | 51 | A complete documentation is available in the [online handbook](https://cachify.pluginkollektiv.org/documentation/). 52 | 53 | 54 | ## Changelog ## 55 | [Changelog](./CHANGELOG.md) 56 | -------------------------------------------------------------------------------- /apc/proxy.php: -------------------------------------------------------------------------------- 1 | [db-host] [wp-version] [skip-database-creation]" 5 | exit 1 6 | fi 7 | 8 | DB_NAME=$1 9 | DB_USER=$2 10 | DB_PASS=$3 11 | DB_HOST=${4-localhost} 12 | WP_VERSION=${5-latest} 13 | SKIP_DB_CREATE=${6-false} 14 | 15 | TMPDIR=${TMPDIR-/tmp} 16 | TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//") 17 | WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib} 18 | WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress} 19 | 20 | download() { 21 | if [ `which curl` ]; then 22 | curl -s "$1" > "$2"; 23 | elif [ `which wget` ]; then 24 | wget -nv -O "$2" "$1" 25 | else 26 | echo "Error: Neither curl nor wget is installed." 27 | exit 1 28 | fi 29 | } 30 | 31 | # Check if svn is installed 32 | check_svn_installed() { 33 | if ! command -v svn > /dev/null; then 34 | echo "Error: svn is not installed. Please install svn and try again." 35 | exit 1 36 | fi 37 | } 38 | 39 | if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then 40 | WP_BRANCH=${WP_VERSION%\-*} 41 | WP_TESTS_TAG="branches/$WP_BRANCH" 42 | 43 | elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then 44 | WP_TESTS_TAG="branches/$WP_VERSION" 45 | elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then 46 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then 47 | # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x 48 | WP_TESTS_TAG="tags/${WP_VERSION%??}" 49 | else 50 | WP_TESTS_TAG="tags/$WP_VERSION" 51 | fi 52 | elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 53 | WP_TESTS_TAG="trunk" 54 | else 55 | # http serves a single offer, whereas https serves multiple. we only want one 56 | download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json 57 | grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json 58 | LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') 59 | if [[ -z "$LATEST_VERSION" ]]; then 60 | echo "Latest WordPress version could not be found" 61 | exit 1 62 | fi 63 | WP_TESTS_TAG="tags/$LATEST_VERSION" 64 | fi 65 | set -ex 66 | 67 | install_wp() { 68 | 69 | if [ -d $WP_CORE_DIR ]; then 70 | return; 71 | fi 72 | 73 | mkdir -p $WP_CORE_DIR 74 | 75 | if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 76 | mkdir -p $TMPDIR/wordpress-trunk 77 | rm -rf $TMPDIR/wordpress-trunk/* 78 | check_svn_installed 79 | svn export --quiet https://core.svn.wordpress.org/trunk $TMPDIR/wordpress-trunk/wordpress 80 | mv $TMPDIR/wordpress-trunk/wordpress/* $WP_CORE_DIR 81 | else 82 | if [ $WP_VERSION == 'latest' ]; then 83 | local ARCHIVE_NAME='latest' 84 | elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then 85 | # https serves multiple offers, whereas http serves single. 86 | download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json 87 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then 88 | # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x 89 | LATEST_VERSION=${WP_VERSION%??} 90 | else 91 | # otherwise, scan the releases and get the most up to date minor version of the major release 92 | local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'` 93 | LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1) 94 | fi 95 | if [[ -z "$LATEST_VERSION" ]]; then 96 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 97 | else 98 | local ARCHIVE_NAME="wordpress-$LATEST_VERSION" 99 | fi 100 | else 101 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 102 | fi 103 | download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz 104 | tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR 105 | fi 106 | 107 | download https://raw.githubusercontent.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php 108 | } 109 | 110 | install_test_suite() { 111 | # portable in-place argument for both GNU sed and Mac OSX sed 112 | if [[ $(uname -s) == 'Darwin' ]]; then 113 | local ioption='-i.bak' 114 | else 115 | local ioption='-i' 116 | fi 117 | 118 | # set up testing suite if it doesn't yet exist 119 | if [ ! -d $WP_TESTS_DIR ]; then 120 | # set up testing suite 121 | mkdir -p $WP_TESTS_DIR 122 | rm -rf $WP_TESTS_DIR/{includes,data} 123 | check_svn_installed 124 | svn export --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes 125 | svn export --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data 126 | fi 127 | 128 | if [ ! -f wp-tests-config.php ]; then 129 | download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php 130 | # remove all forward slashes in the end 131 | WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") 132 | sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php 133 | sed $ioption "s:__DIR__ . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php 134 | sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php 135 | sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php 136 | sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php 137 | sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php 138 | fi 139 | 140 | # Modify the WP_UnitTestCase class to use the polyfilled version for PHPUnit cross-compatibility. 141 | # This is a dirty "backport" of the polyfills used in WP 5.9 and might fail with future updates. 142 | if [ ! -f "$WP_TESTS_DIR"/includes/abstract-testcase.php ]; then 143 | local testcase_file="$WP_TESTS_DIR"/includes/testcase.php 144 | sed $ioption 's/class WP_UnitTestCase extends PHPUnit_Framework_TestCase /class WP_UnitTestCase extends Yoast\\PHPUnitPolyfills\\TestCases\\TestCase /' "$testcase_file" 145 | sed $ioption 's/setUpBeforeClass[(][)]/set_up_before_class()/g' "$testcase_file" 146 | sed $ioption 's/tearDownAfterClass[(][)]/tear_down_after_class()/g' "$testcase_file" 147 | sed $ioption 's/setUp[(][)]/set_up()/g' "$testcase_file" 148 | sed $ioption 's/tearDown[(][)]/tear_down()/g' "$testcase_file" 149 | fi 150 | 151 | } 152 | 153 | recreate_db() { 154 | shopt -s nocasematch 155 | if [[ $1 =~ ^(y|yes)$ ]] 156 | then 157 | mysqladmin drop $DB_NAME -f --user="$DB_USER" --password="$DB_PASS"$EXTRA 158 | create_db 159 | echo "Recreated the database ($DB_NAME)." 160 | else 161 | echo "Leaving the existing database ($DB_NAME) in place." 162 | fi 163 | shopt -u nocasematch 164 | } 165 | 166 | create_db() { 167 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA 168 | } 169 | 170 | install_db() { 171 | 172 | if [ ${SKIP_DB_CREATE} = "true" ]; then 173 | return 0 174 | fi 175 | 176 | # parse DB_HOST for port or socket references 177 | local PARTS=(${DB_HOST//\:/ }) 178 | local DB_HOSTNAME=${PARTS[0]}; 179 | local DB_SOCK_OR_PORT=${PARTS[1]}; 180 | local EXTRA="" 181 | 182 | if ! [ -z $DB_HOSTNAME ] ; then 183 | if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then 184 | EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" 185 | elif ! [ -z $DB_SOCK_OR_PORT ] ; then 186 | EXTRA=" --socket=$DB_SOCK_OR_PORT" 187 | elif ! [ -z $DB_HOSTNAME ] ; then 188 | EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" 189 | fi 190 | fi 191 | 192 | # create database 193 | if [ $(mysql --user="$DB_USER" --password="$DB_PASS"$EXTRA --execute='show databases;' | grep ^$DB_NAME$) ] 194 | then 195 | echo "Reinstalling will delete the existing test database ($DB_NAME)" 196 | read -p 'Are you sure you want to proceed? [y/N]: ' DELETE_EXISTING_DB 197 | recreate_db $DELETE_EXISTING_DB 198 | else 199 | create_db 200 | fi 201 | } 202 | 203 | install_wp 204 | install_test_suite 205 | install_db 206 | -------------------------------------------------------------------------------- /cachify.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /inc/cachify.settings-footer.php: -------------------------------------------------------------------------------- 1 | 11 | 12 |

13 | 14 | • 15 | • 16 | • 17 |

18 | -------------------------------------------------------------------------------- /inc/cachify.settings.php: -------------------------------------------------------------------------------- 1 | __( 'Database', 'cachify' ), 16 | ); 17 | if ( Cachify_HDD::is_available() ) { 18 | $method_select[ Cachify::METHOD_HDD ] = __( 'Hard disk', 'cachify' ); 19 | } 20 | if ( Cachify_MEMCACHED::is_available() ) { 21 | $method_select[ Cachify::METHOD_MMC ] = __( 'Memcached', 'cachify' ); 22 | } 23 | if ( Cachify_REDIS::is_available() ) { 24 | $method_select[ Cachify::METHOD_REDIS ] = __( 'Redis', 'cachify' ); 25 | } 26 | 27 | /* 28 | * Minify cache dropdown 29 | */ 30 | $minify_select = array( 31 | Cachify::MINIFY_DISABLED => __( 'No minify', 'cachify' ), 32 | Cachify::MINIFY_HTML_ONLY => __( 'HTML', 'cachify' ), 33 | Cachify::MINIFY_HTML_JS => __( 'HTML + Inline JavaScript', 'cachify' ), 34 | ); 35 | 36 | ?> 37 | 38 |
39 | 40 | 41 | 42 | 45 | 52 | 53 | 54 | 55 | 58 | 85 | 86 | 87 | 88 | 91 | 119 | 120 | 121 | 122 | 125 | 140 | 141 | 142 | 143 | 146 | 155 | 156 | 157 | 158 | 161 | 165 | 166 | 167 | 168 | 171 | 175 | 176 |
43 | 44 | 46 | 51 |
56 | 57 | 59 | 60 | 61 | 62 |

63 | 64 | 65 |

66 | "' . esc_html__( 'Flush the Cachify cache', 'cachify' ) . '"' 71 | ); 72 | ?> 73 |

74 | 75 | 78 | 79 |

80 | 81 | 82 | 83 |

84 |
89 | 90 | 92 |
93 | 97 | 98 |
99 | 100 | 104 |

105 | 106 |

107 | 108 |
109 | 110 | 114 |

115 | 116 |

117 |
118 |
123 | 124 | 126 |
127 | 131 | 132 |
133 | 134 | 138 |
139 |
144 | 145 | 147 | 154 |
159 | 160 | 162 | /> 163 | 164 |
169 | 170 | 172 | /> 173 | 174 |
177 | 178 | 179 |
180 | -------------------------------------------------------------------------------- /inc/class-cachify-backend.php: -------------------------------------------------------------------------------- 1 | false ) ); 27 | 28 | Cachify::flush_total_cache( $assoc_args['all-methods'] ); 29 | 30 | if ( $assoc_args['all-methods'] ) { 31 | WP_CLI::success( 'All Cachify caches flushed' ); 32 | } else { 33 | WP_CLI::success( 'Cachify cache flushed' ); 34 | } 35 | } 36 | 37 | /** 38 | * Get cache size 39 | * 40 | * @param array $args the CLI arguments as array. 41 | * @param array $assoc_args the CLI arguments as associative array. 42 | * 43 | * @since 2.3.0 44 | */ 45 | public static function get_cache_size( $args, $assoc_args ) { 46 | // Set default arguments. 47 | $assoc_args = wp_parse_args( $assoc_args, array( 'raw' => false ) ); 48 | 49 | $cache_size = Cachify::get_cache_size(); 50 | 51 | if ( $assoc_args['raw'] ) { 52 | $message = $cache_size; 53 | } else { 54 | $message = "The cache size is $cache_size bytes"; 55 | } 56 | 57 | WP_CLI::line( $message ); 58 | } 59 | 60 | /** 61 | * Register CLI Commands 62 | * 63 | * @since 2.3.0 64 | */ 65 | public static function add_commands() { 66 | // Add flush command. 67 | WP_CLI::add_command( 68 | 'cachify flush', 69 | array( 70 | 'Cachify_CLI', 71 | 'flush_cache', 72 | ), 73 | array( 74 | 'shortdesc' => 'Flush site cache', 75 | 'synopsis' => array( 76 | array( 77 | 'type' => 'flag', 78 | 'name' => 'all-methods', 79 | 'description' => 'Flush all caching methods', 80 | 'optional' => true, 81 | ), 82 | ), 83 | ) 84 | ); 85 | 86 | // Add cache-size command. 87 | WP_CLI::add_command( 88 | 'cachify cache-size', 89 | array( 90 | 'Cachify_CLI', 91 | 'get_cache_size', 92 | ), 93 | array( 94 | 'shortdesc' => 'Get the size of the cache in bytes', 95 | 'synopsis' => array( 96 | array( 97 | 'type' => 'flag', 98 | 'name' => 'raw', 99 | 'description' => 'Raw size output in bytes', 100 | 'optional' => true, 101 | ), 102 | ), 103 | ) 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /inc/class-cachify-db.php: -------------------------------------------------------------------------------- 1 | $data, 61 | 'meta' => array( 62 | 'queries' => self::_page_queries(), 63 | 'timer' => self::_page_timer(), 64 | 'memory' => self::_page_memory(), 65 | 'time' => current_time( 'timestamp' ), 66 | ), 67 | ), 68 | $lifetime 69 | ); 70 | } 71 | 72 | /** 73 | * Read item from cache 74 | * 75 | * @param string $hash Hash of the entry. 76 | * 77 | * @return mixed Content of the entry 78 | * 79 | * @since 2.0 80 | */ 81 | public static function get_item( $hash ) { 82 | return get_transient( $hash ); 83 | } 84 | 85 | /** 86 | * Delete item from cache 87 | * 88 | * @param string $hash Hash of the entry. 89 | * @param string $url URL of the entry [optional]. 90 | * 91 | * @since 2.0 92 | */ 93 | public static function delete_item( $hash, $url = '' ) { 94 | delete_transient( $hash ); 95 | } 96 | 97 | /** 98 | * Clear the cache 99 | * 100 | * @since 2.0 101 | */ 102 | public static function clear_cache() { 103 | /* Init */ 104 | global $wpdb; 105 | 106 | /* Clear */ 107 | $wpdb->query( 'DELETE FROM `' . $wpdb->options . "` WHERE `option_name` LIKE ('\_transient%.cachify')" ); 108 | } 109 | 110 | /** 111 | * Print the cache 112 | * 113 | * @param bool $sig_detail Show details in signature. 114 | * @param array $cache Array of cache values. 115 | * 116 | * @since 2.0 117 | * @since 2.3.0 added $sig_detail parameter 118 | */ 119 | public static function print_cache( $sig_detail, $cache ) { 120 | /* No array? */ 121 | if ( ! is_array( $cache ) ) { 122 | return; 123 | } 124 | 125 | /* Content */ 126 | echo $cache['data']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 127 | 128 | /* Signature - might contain runtime information, so it's generated at this point */ 129 | if ( isset( $cache['meta'] ) ) { 130 | // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 131 | echo self::_cache_signature( $sig_detail, $cache['meta'] ); 132 | } 133 | 134 | /* Quit */ 135 | exit; 136 | } 137 | 138 | /** 139 | * Get the cache size 140 | * 141 | * @return int Column size 142 | * 143 | * @since 2.0 144 | */ 145 | public static function get_stats() { 146 | /* Init */ 147 | global $wpdb; 148 | 149 | /* Read */ 150 | return intval( 151 | $wpdb->get_var( 152 | 'SELECT SUM( CHAR_LENGTH(option_value) ) FROM `' . $wpdb->options . "` WHERE `option_name` LIKE ('\_transient%.cachify')" 153 | ) 154 | ); 155 | } 156 | 157 | /** 158 | * Generate signature 159 | * 160 | * @param bool $detail Show details in signature. 161 | * @param array $meta Content of metadata. 162 | * 163 | * @return string Signature string 164 | * 165 | * @since 2.0 166 | * @since 2.3.0 added $detail parameter 167 | */ 168 | private static function _cache_signature( $detail, $meta ) { 169 | /* No array? */ 170 | if ( ! is_array( $meta ) ) { 171 | return ''; 172 | } 173 | 174 | if ( $detail ) { 175 | return sprintf( 176 | "\n\n", 177 | 'Cachify | https://cachify.pluginkollektiv.org', 178 | 'DB Cache', 179 | date_i18n( 180 | 'd.m.Y H:i:s', 181 | $meta['time'] 182 | ), 183 | sprintf( 184 | 'Without Cachify: %d DB queries, %s seconds, %s', 185 | $meta['queries'], 186 | $meta['timer'], 187 | $meta['memory'] 188 | ), 189 | sprintf( 190 | 'With Cachify: %d DB queries, %s seconds, %s', 191 | self::_page_queries(), 192 | self::_page_timer(), 193 | self::_page_memory() 194 | ) 195 | ); 196 | } else { 197 | return sprintf( 198 | "\n\n", 199 | 'Cachify | https://cachify.pluginkollektiv.org', 200 | __( 'Generated', 'cachify' ), 201 | date_i18n( 202 | 'd.m.Y H:i:s', 203 | $meta['time'] 204 | ) 205 | ); 206 | } 207 | } 208 | 209 | /** 210 | * Return query count 211 | * 212 | * @return int Number of queries 213 | * 214 | * @since 0.1 215 | */ 216 | private static function _page_queries() { 217 | return $GLOBALS['wpdb']->num_queries; 218 | } 219 | 220 | /** 221 | * Return execution time 222 | * 223 | * @return string Execution time in seconds 224 | * 225 | * @since 0.1 226 | */ 227 | private static function _page_timer() { 228 | return timer_stop( 0, 2 ); 229 | } 230 | 231 | /** 232 | * Return memory consumption 233 | * 234 | * @return string Formatted memory size 235 | * 236 | * @since 0.7 237 | */ 238 | private static function _page_memory() { 239 | return ( function_exists( 'memory_get_usage' ) ? size_format( memory_get_usage(), 2 ) : 0 ); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /inc/class-cachify-hdd.php: -------------------------------------------------------------------------------- 1 | ", 166 | 'Cachify | https://cachify.pluginkollektiv.org', 167 | ( $detail ? 'HDD Cache' : __( 'Generated', 'cachify' ) ), 168 | date_i18n( 169 | 'd.m.Y H:i:s', 170 | current_time( 'timestamp' ) 171 | ) 172 | ); 173 | } 174 | 175 | /** 176 | * Initialize caching process 177 | * 178 | * @param string $data Cache content. 179 | * 180 | * @since 2.0 181 | */ 182 | private static function _create_files( $data ) { 183 | $file_path = self::_file_path(); 184 | 185 | /* Create directory */ 186 | if ( ! wp_mkdir_p( $file_path ) ) { 187 | trigger_error( esc_html( __METHOD__ . ": Unable to create directory {$file_path}." ), E_USER_WARNING ); 188 | return; 189 | } 190 | /* Write to file */ 191 | self::_create_file( self::_file_html( $file_path ), $data ); 192 | 193 | /** 194 | * Filter that allows to enable/disable gzip file creation 195 | * 196 | * @param bool $create_gzip_files Whether to create gzip files. Default is `true` 197 | */ 198 | if ( self::is_gzip_enabled() ) { 199 | self::_create_file( self::_file_gzip( $file_path ), gzencode( $data, 9 ) ); 200 | } 201 | } 202 | 203 | /** 204 | * Create cache file 205 | * 206 | * @param string $file Path to cache file. 207 | * @param string $data Cache content. 208 | * 209 | * @since 2.0 210 | */ 211 | private static function _create_file( $file, $data ) { 212 | /* Writable? */ 213 | $handle = @fopen( $file, 'wb' ); 214 | if ( ! $handle ) { 215 | trigger_error( esc_html( __METHOD__ . ": Could not write file {$file}." ), E_USER_WARNING ); 216 | return; 217 | } 218 | 219 | /* Write */ 220 | fwrite( $handle, $data ); 221 | fclose( $handle ); 222 | clearstatcache(); 223 | 224 | /* Permissions */ 225 | $stat = @stat( dirname( $file ) ); 226 | $perms = $stat['mode'] & 0007777; 227 | $perms = $perms & 0000666; 228 | @chmod( $file, $perms ); 229 | clearstatcache(); 230 | } 231 | 232 | /** 233 | * Clear directory 234 | * 235 | * @param string $dir Directory path. 236 | * @param bool $recursive true for clearing subdirectories as well. 237 | * 238 | * @since 2.0 239 | */ 240 | private static function _clear_dir( $dir, $recursive = false ) { 241 | // Remove trailing slash. 242 | $dir = untrailingslashit( $dir ); 243 | 244 | // Is directory? 245 | if ( ! is_dir( $dir ) ) { 246 | return; 247 | } 248 | 249 | // List directory contents. 250 | $objects = array_diff( 251 | scandir( $dir ), 252 | array( '..', '.' ) 253 | ); 254 | 255 | // Loop over items. 256 | foreach ( $objects as $object ) { 257 | // Expand path. 258 | $object = $dir . DIRECTORY_SEPARATOR . $object; 259 | 260 | if ( is_dir( $object ) ) { 261 | if ( $recursive ) { 262 | // Recursively clear the directory. 263 | self::_clear_dir( $object, $recursive ); 264 | } elseif ( self::_user_can_delete( $object ) && 0 === count( glob( trailingslashit( $object ) . '*' ) ) ) { 265 | // Delete the directory, if empty. 266 | @rmdir( $object ); 267 | } 268 | } elseif ( self::_user_can_delete( $object ) ) { 269 | // Delete the file. 270 | unlink( $object ); 271 | } 272 | } 273 | 274 | // Remove directory, if empty. 275 | if ( self::_user_can_delete( $dir ) && 0 === count( glob( trailingslashit( $dir ) . '*' ) ) ) { 276 | @rmdir( $dir ); 277 | } 278 | 279 | // Clean up. 280 | clearstatcache(); 281 | } 282 | 283 | /** 284 | * Get directory size 285 | * 286 | * @param string $dir Directory path. 287 | * 288 | * @return int|false Directory size 289 | * 290 | * @since 2.0 291 | */ 292 | public static function _dir_size( $dir = '.' ) { 293 | /* Is directory? */ 294 | if ( ! is_dir( $dir ) ) { 295 | return false; 296 | } 297 | 298 | /* Read */ 299 | $objects = array_diff( 300 | scandir( $dir ), 301 | array( '..', '.' ) 302 | ); 303 | 304 | /* Empty? */ 305 | if ( empty( $objects ) ) { 306 | return false; 307 | } 308 | 309 | /* Init */ 310 | $size = 0; 311 | 312 | /* Loop over items */ 313 | foreach ( $objects as $object ) { 314 | /* Expand path */ 315 | $object = $dir . DIRECTORY_SEPARATOR . $object; 316 | 317 | /* Directory or file */ 318 | if ( is_dir( $object ) ) { 319 | $size += self::_dir_size( $object ); 320 | } else { 321 | $size += filesize( $object ); 322 | } 323 | } 324 | 325 | return $size; 326 | } 327 | 328 | /** 329 | * Path to cache file 330 | * 331 | * @param string $path Request URI or permalink [optional]. 332 | * 333 | * @return string Path to cache file 334 | * 335 | * @since 2.0 336 | */ 337 | private static function _file_path( $path = null ) { 338 | $prefix = is_ssl() ? 'https-' : ''; 339 | 340 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated 341 | $path_parts = wp_parse_url( $path ? $path : wp_unslash( $_SERVER['REQUEST_URI'] ) ); 342 | 343 | $path = sprintf( 344 | '%s%s%s%s%s', 345 | CACHIFY_CACHE_DIR, 346 | DIRECTORY_SEPARATOR, 347 | $prefix, 348 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated 349 | strtolower( wp_unslash( $_SERVER['HTTP_HOST'] ) ), 350 | $path_parts['path'] 351 | ); 352 | 353 | if ( validate_file( $path ) > 0 ) { 354 | wp_die( 'Invalid file path.' ); 355 | } 356 | 357 | return trailingslashit( $path ); 358 | } 359 | 360 | /** 361 | * Path to HTML file 362 | * 363 | * @param string $file_path File path [optional]. 364 | * 365 | * @return string Path to HTML file 366 | * 367 | * @since 2.0 368 | */ 369 | private static function _file_html( $file_path = '' ) { 370 | return ( empty( $file_path ) ? self::_file_path() : $file_path ) . 'index.html'; 371 | } 372 | 373 | /** 374 | * Path to GZIP file 375 | * 376 | * @param string $file_path File path [optional]. 377 | * 378 | * @return string Path to GZIP file 379 | * 380 | * @since 2.0 381 | */ 382 | private static function _file_gzip( $file_path = '' ) { 383 | return ( empty( $file_path ) ? self::_file_path() : $file_path ) . 'index.html.gz'; 384 | } 385 | 386 | /** 387 | * Does the user has the right to delete this file? 388 | * 389 | * @param string $file the file name. 390 | * 391 | * @return bool 392 | */ 393 | private static function _user_can_delete( $file ) { 394 | if ( ! is_file( $file ) && ! is_dir( $file ) ) { 395 | return false; 396 | } 397 | 398 | if ( 0 !== strpos( $file, CACHIFY_CACHE_DIR ) ) { 399 | return false; 400 | } 401 | 402 | // If its just a single blog, the user has the right to delete this file. 403 | // But also, if you are in the network admin, you should be able to delete all files. 404 | if ( ! is_multisite() || is_network_admin() ) { 405 | return true; 406 | } 407 | 408 | if ( is_dir( $file ) ) { 409 | $file = trailingslashit( $file ); 410 | } 411 | 412 | $ssl_prefix = is_ssl() ? 'https-' : ''; 413 | $current_blog = get_blog_details( get_current_blog_id() ); 414 | $blog_path = CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . $ssl_prefix . $current_blog->domain . $current_blog->path; 415 | 416 | if ( 0 !== strpos( $file, $blog_path ) ) { 417 | return false; 418 | } 419 | 420 | // We are on a subdirectory installation and the current blog is in a subdirectory. 421 | if ( '/' !== $current_blog->path ) { 422 | return true; 423 | } 424 | 425 | // If we are on the root blog in a subdirectory multisite, we check if the current dir is the root dir. 426 | $root_site_dir = CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . $ssl_prefix . DOMAIN_CURRENT_SITE . DIRECTORY_SEPARATOR; 427 | if ( $root_site_dir === $file ) { 428 | return false; 429 | } 430 | 431 | // If we are on the root blog in a subdirectory multisite, we check, if the current file is part of another blog. 432 | global $wpdb; 433 | $results = $wpdb->get_col( 434 | $wpdb->prepare( 435 | 'select path from ' . $wpdb->base_prefix . 'blogs where domain = %s && blog_id != %d', 436 | $current_blog->domain, 437 | $current_blog->blog_id 438 | ) 439 | ); 440 | foreach ( $results as $site ) { 441 | $forbidden_path = CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . $ssl_prefix . $current_blog->domain . $site; 442 | if ( 0 === strpos( $file, $forbidden_path ) ) { 443 | return false; 444 | } 445 | } 446 | 447 | return true; 448 | } 449 | } 450 | -------------------------------------------------------------------------------- /inc/class-cachify-memcached.php: -------------------------------------------------------------------------------- 1 | set( 74 | self::_file_path(), 75 | $data . self::_cache_signature( $sig_detail ), 76 | $lifetime 77 | ); 78 | } 79 | 80 | /** 81 | * Read item from cache 82 | * 83 | * @param string $hash Hash of the entry. 84 | * 85 | * @return mixed Content of the entry 86 | * 87 | * @since 2.0.7 88 | */ 89 | public static function get_item( $hash ) { 90 | /* Server connect */ 91 | if ( ! self::_connect_server() ) { 92 | return null; 93 | } 94 | 95 | /* Get item */ 96 | return self::$_memcached->get( 97 | self::_file_path() 98 | ); 99 | } 100 | 101 | /** 102 | * Delete item from cache 103 | * 104 | * @param string $hash Hash of the entry. 105 | * @param string $url URL of the entry [optional]. 106 | * 107 | * @since 2.0.7 108 | */ 109 | public static function delete_item( $hash, $url = '' ) { 110 | /* Server connect */ 111 | if ( ! self::_connect_server() ) { 112 | return; 113 | } 114 | 115 | /* Delete */ 116 | self::$_memcached->delete( 117 | self::_file_path( $url ) 118 | ); 119 | } 120 | 121 | /** 122 | * Clear the cache 123 | * 124 | * @since 2.0.7 125 | */ 126 | public static function clear_cache() { 127 | /* Server connect */ 128 | if ( ! self::_connect_server() ) { 129 | return; 130 | } 131 | 132 | if ( ! self::$_memcached instanceof Memcached ) { 133 | return; 134 | } 135 | 136 | /* Flush */ 137 | self::$_memcached->flush(); 138 | } 139 | 140 | /** 141 | * Print the cache 142 | * 143 | * @param bool $sig_detail Show details in signature. 144 | * @param array $cache Array of cache values. 145 | * 146 | * @since 2.0.7 147 | */ 148 | public static function print_cache( $sig_detail, $cache ) { 149 | // Not supported. 150 | } 151 | 152 | /** 153 | * Get the cache size 154 | * 155 | * @return mixed Cache size 156 | * 157 | * @since 2.0.7 158 | */ 159 | public static function get_stats() { 160 | /* Server connect */ 161 | if ( ! self::_connect_server() ) { 162 | return null; 163 | } 164 | 165 | /* Info */ 166 | $data = self::$_memcached->getStats(); 167 | 168 | /* No stats? */ 169 | if ( empty( $data ) ) { 170 | return null; 171 | } 172 | 173 | /* Get first key */ 174 | $data = $data[ key( $data ) ]; 175 | 176 | /* Empty */ 177 | if ( empty( $data['bytes'] ) ) { 178 | return null; 179 | } 180 | 181 | return $data['bytes']; 182 | } 183 | 184 | /** 185 | * Generate signature 186 | * 187 | * @param bool $detail Show details in signature. 188 | * 189 | * @return string Signature string 190 | * 191 | * @since 2.0.7 192 | * @since 2.3.0 added $detail parameter 193 | */ 194 | private static function _cache_signature( $detail ) { 195 | return sprintf( 196 | "\n\n", 197 | 'Cachify | https://cachify.pluginkollektiv.org', 198 | ( $detail ? 'Memcached' : __( 'Generated', 'cachify' ) ), 199 | date_i18n( 200 | 'd.m.Y H:i:s', 201 | current_time( 'timestamp' ) 202 | ) 203 | ); 204 | } 205 | 206 | /** 207 | * Path of cache file 208 | * 209 | * @param string $path Request URI or permalink [optional]. 210 | * 211 | * @return string Path to cache file 212 | * 213 | * @since 2.0.7 214 | */ 215 | private static function _file_path( $path = null ) { 216 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated 217 | $path_parts = wp_parse_url( $path ? $path : wp_unslash( $_SERVER['REQUEST_URI'] ) ); 218 | 219 | return trailingslashit( 220 | sprintf( 221 | '%s%s', 222 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated 223 | wp_unslash( $_SERVER['HTTP_HOST'] ), 224 | $path_parts['path'] 225 | ) 226 | ); 227 | } 228 | 229 | /** 230 | * Connect to Memcached server 231 | * 232 | * @hook array cachify_memcached_servers Array with memcached servers 233 | * 234 | * @return bool TRUE on success 235 | * 236 | * @since 2.0.7 237 | */ 238 | private static function _connect_server() { 239 | /* Not enabled? */ 240 | if ( ! self::is_available() ) { 241 | return false; 242 | } 243 | 244 | /* Already connected */ 245 | if ( is_object( self::$_memcached ) ) { 246 | return true; 247 | } 248 | 249 | /* Init */ 250 | self::$_memcached = new Memcached(); 251 | 252 | /* Set options */ 253 | if ( defined( 'HHVM_VERSION' ) ) { 254 | self::$_memcached->setOption( Memcached::OPT_COMPRESSION, false ); 255 | self::$_memcached->setOption( Memcached::OPT_BUFFER_WRITES, true ); 256 | self::$_memcached->setOption( Memcached::OPT_BINARY_PROTOCOL, true ); 257 | } else { 258 | self::$_memcached->setOptions( 259 | array( 260 | Memcached::OPT_COMPRESSION => false, 261 | Memcached::OPT_BUFFER_WRITES => true, 262 | Memcached::OPT_BINARY_PROTOCOL => true, 263 | ) 264 | ); 265 | } 266 | 267 | /* Connect */ 268 | self::$_memcached->addServers( 269 | (array) apply_filters( 270 | 'cachify_memcached_servers', 271 | array( 272 | array( 273 | '127.0.0.1', 274 | 11211, 275 | ), 276 | ) 277 | ) 278 | ); 279 | 280 | return true; 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /inc/class-cachify-noop.php: -------------------------------------------------------------------------------- 1 | unavailable_method = $unavailable_method; 34 | } 35 | 36 | /** 37 | * Availability check 38 | * 39 | * @return bool TRUE when installed 40 | */ 41 | public static function is_available() { 42 | return true; 43 | } 44 | 45 | /** 46 | * Caching method as string 47 | * 48 | * @return string Caching method 49 | */ 50 | public static function stringify_method() { 51 | return 'NOOP'; 52 | } 53 | 54 | /** 55 | * Store item in cache 56 | * 57 | * @param string $hash Hash of the entry. 58 | * @param string $data Content of the entry. 59 | * @param int $lifetime Lifetime of the entry. 60 | * @param bool $sig_detail Show details in signature. 61 | */ 62 | public static function store_item( $hash, $data, $lifetime, $sig_detail ) { 63 | // NOOP. 64 | } 65 | 66 | /** 67 | * Read item from cache 68 | * 69 | * @param string $hash Hash of the entry. 70 | * 71 | * @return false No content 72 | */ 73 | public static function get_item( $hash ) { 74 | return false; 75 | } 76 | 77 | /** 78 | * Delete item from cache 79 | * 80 | * @param string $hash Hash of the entry. 81 | * @param string $url URL of the entry [optional]. 82 | */ 83 | public static function delete_item( $hash, $url = '' ) { 84 | // NOOP. 85 | } 86 | 87 | /** 88 | * Clear the cache 89 | */ 90 | public static function clear_cache() { 91 | // NOOP. 92 | } 93 | 94 | /** 95 | * Print the cache 96 | * 97 | * @param bool $sig_detail Show details in signature. 98 | * @param array $cache Array of cache values. 99 | */ 100 | public static function print_cache( $sig_detail, $cache ) { 101 | // NOOP. 102 | } 103 | 104 | /** 105 | * Get the cache size 106 | * 107 | * @return int Column size 108 | */ 109 | public static function get_stats() { 110 | return 0; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /inc/class-cachify-redis.php: -------------------------------------------------------------------------------- 1 | set( 65 | self::_file_path(), 66 | $data . self::_cache_signature( $sig_detail ), 67 | $lifetime 68 | ); 69 | } 70 | 71 | /** 72 | * Read item from cache 73 | * 74 | * @param string $hash Hash of the entry. 75 | * @return mixed Content of the entry 76 | */ 77 | public static function get_item( $hash ) { 78 | /* Server connect */ 79 | if ( ! self::_connect_server() ) { 80 | return null; 81 | } 82 | 83 | /* Get item */ 84 | return self::$_redis->get( 85 | self::_file_path() 86 | ); 87 | } 88 | 89 | /** 90 | * Delete item from cache 91 | * 92 | * @param string $hash Hash of the entry [ignored]. 93 | * @param string $url URL of the entry. 94 | */ 95 | public static function delete_item( $hash, $url ) { 96 | /* Server connect */ 97 | if ( ! self::_connect_server() ) { 98 | return; 99 | } 100 | 101 | /* Delete */ 102 | self::$_redis->del( 103 | self::_file_path( $url ) 104 | ); 105 | } 106 | 107 | /** 108 | * Clear the cache 109 | * 110 | * @return void 111 | */ 112 | public static function clear_cache() { 113 | /* Server connect */ 114 | if ( ! self::_connect_server() ) { 115 | return; 116 | } 117 | 118 | /* Flush */ 119 | @self::$_redis->flushAll(); 120 | } 121 | 122 | /** 123 | * Print the cache 124 | * 125 | * @param bool $sig_detail Show details in signature. 126 | * @param string $cache Cached content. 127 | */ 128 | public static function print_cache( $sig_detail, $cache ) { 129 | echo $cache; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 130 | exit; 131 | } 132 | 133 | /** 134 | * Get the cache size 135 | * 136 | * @return integer Directory size 137 | */ 138 | public static function get_stats() { 139 | /* Server connect */ 140 | if ( ! self::_connect_server() ) { 141 | return null; 142 | } 143 | 144 | /* Info */ 145 | $data = self::$_redis->info( 'MEMORY' ); 146 | 147 | /* No stats? */ 148 | if ( empty( $data ) ) { 149 | return null; 150 | } 151 | 152 | /* Empty */ 153 | if ( empty( $data['used_memory_dataset'] ) ) { 154 | return null; 155 | } 156 | 157 | return $data['used_memory_dataset']; 158 | } 159 | 160 | /** 161 | * Generate signature 162 | * 163 | * @param bool $detail Show details in signature. 164 | * @return string Signature string 165 | */ 166 | private static function _cache_signature( $detail ) { 167 | return sprintf( 168 | "\n\n", 169 | 'Cachify | https://cachify.pluginkollektiv.org', 170 | ( $detail ? 'Redis Cache' : __( 'Generated', 'cachify' ) ), 171 | date_i18n( 172 | 'd.m.Y H:i:s', 173 | current_time( 'timestamp' ) 174 | ) 175 | ); 176 | } 177 | 178 | /** 179 | * Path of cache file 180 | * 181 | * @param string $path Request URI or permalink [optional]. 182 | * @return string Path to cache file 183 | */ 184 | private static function _file_path( $path = null ) { 185 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated 186 | $path_parts = wp_parse_url( $path ? $path : wp_unslash( $_SERVER['REQUEST_URI'] ) ); 187 | 188 | return trailingslashit( 189 | sprintf( 190 | '%s%s', 191 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated 192 | wp_unslash( $_SERVER['HTTP_HOST'] ), 193 | $path_parts['path'] 194 | ) 195 | ); 196 | } 197 | 198 | /** 199 | * Connect to Redis server 200 | * 201 | * @return boolean true/false TRUE on success 202 | */ 203 | private static function _connect_server() { 204 | /* Not enabled? */ 205 | if ( ! self::is_available() ) { 206 | return false; 207 | } 208 | 209 | /* Have object and it thinks it's connected to a server */ 210 | if ( is_object( self::$_redis ) && self::$_redis->isConnected() ) { 211 | return true; 212 | } 213 | 214 | /* Init */ 215 | self::$_redis = new Redis(); 216 | 217 | /** 218 | * Filter hook to adjust Redis connection parameters 219 | * 220 | * @param array $redis_server Redis connection parameters. 221 | * 222 | * @see Redis::connect() For supported parameters. 223 | * 224 | * @since 2.4.0 225 | */ 226 | $con = apply_filters( 'cachify_redis_servers', array( 'localhost' ) ); 227 | $con = self::sanitize_con_parameters( $con ); 228 | 229 | if ( false === $con ) { 230 | return false; 231 | } 232 | 233 | // Establish connection. 234 | try { 235 | self::$_redis->connect( ...$con ); 236 | 237 | if ( ! self::$_redis->isConnected() ) { 238 | return false; 239 | } 240 | } catch ( Exception $e ) { 241 | return false; 242 | } 243 | 244 | return true; 245 | } 246 | 247 | /** 248 | * Sanitize Redis connection parameters. 249 | * 250 | * @param mixed $con Connection parameters (from hook). 251 | * 252 | * @return array|false Array of connection arguments or FALSE, if invalid. 253 | */ 254 | private static function sanitize_con_parameters( $con ) { 255 | if ( is_string( $con ) ) { 256 | return array( $con ); 257 | } elseif ( is_array( $con ) && ! empty( $con ) ) { 258 | $con[0] = strval( $con[0] ); // Host or socket path. 259 | if ( count( $con ) > 1 ) { 260 | $con[1] = intval( $con[1] ); // Port number. 261 | } 262 | if ( count( $con ) > 2 ) { 263 | $con[2] = floatval( $con[2] ); // Socket timeout in seconds. 264 | } 265 | if ( count( $con ) > 3 && ! is_null( $con[3] ) ) { 266 | $con[3] = strval( $con[3] ); // Persistent connection ID. 267 | } 268 | if ( count( $con ) > 4 ) { 269 | $con[4] = intval( $con[4] ); // Retry interval in milliseconds. 270 | } 271 | if ( count( $con ) > 5 ) { 272 | $con[5] = floatval( $con[5] ); // Read timeout in seconds. 273 | } 274 | if ( count( $con ) > 6 && ! is_null( $con[6] ) && ! is_array( $con[6] ) ) { 275 | return false; // Context parameters, e.g. authentication (since PhpRedis 5.3). 276 | } 277 | if ( count( $con ) > 7 ) { 278 | $con = array_slice( $con, 0, 7 ); // Trim excessive parameters. 279 | } 280 | 281 | return $con; 282 | } else { 283 | return false; 284 | } 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /inc/class-cachify.php: -------------------------------------------------------------------------------- 1 | blog_id ); 231 | 232 | /* Install */ 233 | self::_install_backend(); 234 | 235 | /* Switch back */ 236 | restore_current_blog(); 237 | } 238 | 239 | /** 240 | * Actual installation of the options 241 | * 242 | * @since 1.0 243 | */ 244 | private static function _install_backend() { 245 | add_option( 246 | 'cachify', 247 | array() 248 | ); 249 | 250 | /* Flush */ 251 | self::flush_total_cache( true ); 252 | } 253 | 254 | /** 255 | * Uninstalling of the plugin per MU blog. 256 | * 257 | * @since 1.0 258 | */ 259 | public static function on_uninstall() { 260 | /* Global */ 261 | global $wpdb; 262 | 263 | /* Multisite & Network */ 264 | if ( is_multisite() && ! empty( $_GET['networkwide'] ) ) { 265 | /* Alter Blog */ 266 | $old = $wpdb->blogid; 267 | 268 | /* Blog IDs */ 269 | $ids = self::_get_blog_ids(); 270 | 271 | /* Loop */ 272 | foreach ( $ids as $id ) { 273 | switch_to_blog( $id ); 274 | self::_uninstall_backend(); 275 | } 276 | 277 | /* Switch back */ 278 | switch_to_blog( $old ); 279 | } else { 280 | self::_uninstall_backend(); 281 | } 282 | } 283 | 284 | /** 285 | * Uninstalling of the plugin for WPMS site. 286 | * 287 | * @param int|WP_Site $old_site Old site ID or object. 288 | * 289 | * @since 1.0 290 | * @since 2.4.0 supports WP_Site argument 291 | */ 292 | public static function uninstall_later( $old_site ) { 293 | /* No network plugin */ 294 | if ( ! is_plugin_active_for_network( CACHIFY_BASE ) ) { 295 | return; 296 | } 297 | 298 | /* Switch to blog */ 299 | switch_to_blog( is_int( $old_site ) ? $old_site : $old_site->blog_id ); 300 | 301 | /* Install */ 302 | self::_uninstall_backend(); 303 | 304 | /* Switch back */ 305 | restore_current_blog(); 306 | } 307 | 308 | /** 309 | * Actual uninstalling of the plugin 310 | * 311 | * @since 1.0 312 | */ 313 | private static function _uninstall_backend() { 314 | /* Option */ 315 | delete_option( 'cachify' ); 316 | 317 | /* Flush cache */ 318 | self::flush_total_cache( true ); 319 | } 320 | 321 | /** 322 | * Get IDs of installed blogs 323 | * 324 | * @return array Blog IDs 325 | * 326 | * @since 1.0 327 | */ 328 | private static function _get_blog_ids() { 329 | /* Global */ 330 | global $wpdb; 331 | 332 | return $wpdb->get_col( "SELECT blog_id FROM `$wpdb->blogs`" ); 333 | } 334 | 335 | /** 336 | * Register the styles 337 | * 338 | * @since 2.4.0 339 | */ 340 | public static function register_styles() { 341 | /* Register dashboard CSS */ 342 | wp_register_style( 343 | 'cachify-dashboard', 344 | plugins_url( 'css/dashboard.min.css', CACHIFY_FILE ), 345 | array(), 346 | filemtime( plugin_dir_path( CACHIFY_FILE ) . 'css/dashboard.min.css' ) 347 | ); 348 | 349 | /* Register admin bar flush CSS */ 350 | wp_register_style( 351 | 'cachify-admin-bar-flush', 352 | plugins_url( 'css/admin-bar-flush.min.css', CACHIFY_FILE ), 353 | array(), 354 | filemtime( plugin_dir_path( CACHIFY_FILE ) . 'css/admin-bar-flush.min.css' ) 355 | ); 356 | } 357 | 358 | /** 359 | * Register the scripts 360 | * 361 | * @since 2.4.0 362 | */ 363 | public static function register_scripts() { 364 | /* Register admin bar flush script */ 365 | wp_register_script( 366 | 'cachify-admin-bar-flush', 367 | plugins_url( 'js/admin-bar-flush.min.js', CACHIFY_FILE ), 368 | array(), 369 | filemtime( plugin_dir_path( CACHIFY_FILE ) . 'js/admin-bar-flush.min.js' ), 370 | true 371 | ); 372 | } 373 | 374 | /** 375 | * Register the language file 376 | * 377 | * @since 2.1.3 378 | */ 379 | public static function register_textdomain() { 380 | load_plugin_textdomain( 'cachify' ); 381 | } 382 | 383 | /** 384 | * Set default options 385 | * 386 | * @since 2.0 387 | */ 388 | private static function _set_default_vars() { 389 | /* Options */ 390 | self::$options = self::_get_options(); 391 | 392 | if ( self::METHOD_APC === self::$options['use_apc'] ) { 393 | /* APC */ 394 | add_action( 'admin_notices', array( __CLASS__, 'admin_notice_unavailable' ) ); 395 | self::$method = new Cachify_NOOP( 'APC' ); 396 | } elseif ( self::METHOD_HDD === self::$options['use_apc'] ) { 397 | /* HDD */ 398 | if ( Cachify_HDD::is_available() ) { 399 | self::$method = new Cachify_HDD(); 400 | } else { 401 | add_action( 'admin_notices', array( __CLASS__, 'admin_notice_unavailable' ) ); 402 | self::$method = new Cachify_NOOP( Cachify_HDD::stringify_method() ); 403 | } 404 | } elseif ( self::METHOD_MMC === self::$options['use_apc'] ) { 405 | /* Memcached */ 406 | if ( Cachify_MEMCACHED::is_available() ) { 407 | self::$method = new Cachify_MEMCACHED(); 408 | } else { 409 | add_action( 'admin_notices', array( __CLASS__, 'admin_notice_unavailable' ) ); 410 | self::$method = new Cachify_NOOP( Cachify_MEMCACHED::stringify_method() ); 411 | } 412 | } elseif ( self::METHOD_REDIS === self::$options['use_apc'] ) { 413 | /* Redis */ 414 | if ( Cachify_REDIS::is_available() ) { 415 | self::$method = new Cachify_REDIS(); 416 | } else { 417 | add_action( 'admin_notices', array( __CLASS__, 'admin_notice_unavailable' ) ); 418 | self::$method = new Cachify_NOOP( Cachify_REDIS::stringify_method() ); 419 | } 420 | } else { 421 | /* Database */ 422 | self::$method = new Cachify_DB(); 423 | } 424 | } 425 | 426 | /** 427 | * Show admin notice if caching backend is unavailable. 428 | * 429 | * @since 2.4.0 430 | */ 431 | public static function admin_notice_unavailable() { 432 | if ( current_user_can( 'manage_options' ) ) { 433 | $unavailable_method = '-'; 434 | if ( self::$method instanceof Cachify_NOOP ) { 435 | $unavailable_method = self::$method->unavailable_method; 436 | } 437 | 438 | printf( 439 | '

%1$s

%2$s

%3$s

', 440 | esc_html__( 'Cachify backend not available', 'cachify' ), 441 | esc_html( 442 | sprintf( 443 | /* translators: Name of the caching backend inserted for placeholder */ 444 | __( 'The configured caching backend is not available: %s', 'cachify' ), 445 | $unavailable_method 446 | ) 447 | ), 448 | wp_kses( 449 | sprintf( 450 | /* translators: Link to Cachify settings page inserted at placeholder */ 451 | __( 'Please check your server configuration and visit the settings page to chose a different method.', 'cachify' ), 452 | add_query_arg( array( 'page' => 'cachify' ), admin_url( 'options-general.php' ) ) 453 | ), 454 | array( 'a' => array( 'href' => array() ) ) 455 | ) 456 | ); 457 | } 458 | } 459 | 460 | /** 461 | * Get options 462 | * 463 | * @return array Array of option values 464 | * 465 | * @since 2.0 466 | */ 467 | private static function _get_options() { 468 | return wp_parse_args( 469 | get_option( 'cachify' ), 470 | array( 471 | 'only_guests' => 1, 472 | 'compress_html' => self::MINIFY_DISABLED, 473 | 'cache_expires' => 12, 474 | 'without_ids' => '', 475 | 'without_agents' => '', 476 | 'use_apc' => self::METHOD_DB, 477 | 'reset_on_post' => 1, 478 | 'reset_on_comment' => 0, 479 | 'sig_detail' => 0, 480 | 'change_robots_txt' => 1, 481 | ) 482 | ); 483 | } 484 | 485 | /** 486 | * Modify robots.txt 487 | * 488 | * @param string $output The robots.txt output. 489 | * 490 | * @since 1.0 491 | * @since 2.1.9 492 | */ 493 | public static function robots_txt( $output ) { 494 | if ( ! self::$options['change_robots_txt'] ) { 495 | return $output; 496 | } 497 | /* HDD only */ 498 | if ( self::METHOD_HDD === self::$options['use_apc'] ) { 499 | $output .= "\nUser-agent: *\nDisallow: */cache/cachify/\n"; 500 | } 501 | 502 | return $output; 503 | } 504 | 505 | /** 506 | * HDD Cache expiration cron action. 507 | * 508 | * @since 2.4.0 509 | */ 510 | public static function run_hdd_cache_cron() { 511 | Cachify_HDD::clear_cache(); 512 | } 513 | 514 | /** 515 | * Add cache expiration cron schedule. 516 | * 517 | * @param array $schedules Array of previously added non-default schedules. 518 | * 519 | * @return array Array of non-default schedules with our tasks added. 520 | * 521 | * @since 2.4.0 522 | */ 523 | public static function add_cron_cache_expiration( $schedules ) { 524 | $schedules['cachify_cache_expire'] = array( 525 | 'interval' => self::$options['cache_expires'] * 3600, 526 | 'display' => esc_html__( 'Cachify expire', 'cachify' ), 527 | ); 528 | return $schedules; 529 | } 530 | 531 | /** 532 | * Add the action links 533 | * 534 | * @param array $data Initial array with action links. 535 | * 536 | * @return array Merged array with action links. 537 | * 538 | * @since 1.0 539 | */ 540 | public static function action_links( $data ) { 541 | /* Permissions? */ 542 | if ( ! current_user_can( 'manage_options' ) ) { 543 | return $data; 544 | } 545 | 546 | return array_merge( 547 | $data, 548 | array( 549 | sprintf( 550 | '%s', 551 | add_query_arg( 552 | array( 553 | 'page' => 'cachify', 554 | ), 555 | admin_url( 'options-general.php' ) 556 | ), 557 | esc_html__( 'Settings', 'cachify' ) 558 | ), 559 | ) 560 | ); 561 | } 562 | 563 | /** 564 | * Meta links of the plugin 565 | * 566 | * @param array $input Initial array with meta links. 567 | * @param string $page Current page. 568 | * 569 | * @return array Merged array with meta links. 570 | * 571 | * @since 0.5 572 | */ 573 | public static function row_meta( $input, $page ) { 574 | /* Permissions */ 575 | if ( CACHIFY_BASE !== $page ) { 576 | return $input; 577 | } 578 | 579 | return array_merge( 580 | $input, 581 | array( 582 | '' . esc_html__( 'Donate', 'cachify' ) . '', 583 | '' . esc_html__( 'Support', 'cachify' ) . '', 584 | ) 585 | ); 586 | } 587 | 588 | /** 589 | * Add cache properties to dashboard 590 | * 591 | * @param array $items Initial array with dashboard items. 592 | * 593 | * @return array Merged array with dashboard items. 594 | * 595 | * @since 2.0.0 596 | */ 597 | public static function add_dashboard_count( $items = array() ) { 598 | /* Skip */ 599 | if ( ! current_user_can( 'manage_options' ) ) { 600 | return $items; 601 | } 602 | 603 | /* Cache size */ 604 | $size = self::get_cache_size(); 605 | 606 | /* Caching method */ 607 | $method = call_user_func( 608 | array( 609 | self::$method, 610 | 'stringify_method', 611 | ) 612 | ); 613 | 614 | /* Output of the cache size */ 615 | $cachesize = ( 0 === $size ) 616 | ? esc_html__( 'Empty Cache', 'cachify' ) : 617 | /* translators: %s: cache size */ 618 | sprintf( esc_html__( '%s Cache', 'cachify' ), size_format( $size ) ); 619 | 620 | /* Right now item */ 621 | $items[] = sprintf( 622 | ' 623 | %s', 626 | add_query_arg( 627 | array( 628 | 'page' => 'cachify', 629 | ), 630 | admin_url( 'options-general.php' ) 631 | ), 632 | sprintf( 633 | /* translators: 1: "Caching method label"; 2: Actual method. */ 634 | esc_html__( '%1$s: %2$s', 'cachify' ), 635 | esc_html__( 'Caching method', 'cachify' ), 636 | esc_attr( strtolower( $method ) ) 637 | ), 638 | esc_attr( $method ), 639 | plugins_url( 'images/symbols.svg', CACHIFY_FILE ), 640 | esc_attr( strtolower( $method ) ), 641 | plugins_url( 'images/symbols.svg', CACHIFY_FILE ), 642 | esc_attr( strtolower( $method ) ), 643 | $cachesize 644 | ); 645 | 646 | return $items; 647 | } 648 | 649 | /** 650 | * Get the cache size 651 | * 652 | * @return int Cache size in bytes. 653 | * 654 | * @since 2.0.6 655 | */ 656 | public static function get_cache_size() { 657 | $size = get_transient( 'cachify_cache_size' ); 658 | if ( ! $size ) { 659 | /* Read */ 660 | $size = (int) call_user_func( 661 | array( 662 | self::$method, 663 | 'get_stats', 664 | ) 665 | ); 666 | 667 | /* Save */ 668 | set_transient( 669 | 'cachify_cache_size', 670 | $size, 671 | 60 * 15 672 | ); 673 | } 674 | 675 | return $size; 676 | } 677 | 678 | /** 679 | * Add flush icon to admin bar menu 680 | * 681 | * @hook mixed cachify_user_can_flush_cache 682 | * 683 | * @param object $wp_admin_bar Object of menu items. 684 | * 685 | * @since 1.2 686 | * @since 2.2.2 687 | * @since 2.4.0 Adjust icon for flush request via AJAX 688 | */ 689 | public static function add_flush_icon( $wp_admin_bar ) { 690 | /* Quit */ 691 | if ( ! is_admin_bar_showing() || ! apply_filters( 'cachify_user_can_flush_cache', current_user_can( 'manage_options' ) ) ) { 692 | return; 693 | } 694 | 695 | /* Enqueue style */ 696 | wp_enqueue_style( 'cachify-admin-bar-flush' ); 697 | 698 | /* Print area for aria live updates */ 699 | echo ''; 700 | /* Check if the flush action was used without AJAX */ 701 | $dashicon_class = 'dashicons-trash'; 702 | if ( isset( $_GET['_cachify'] ) && 'flushed' === $_GET['_cachify'] ) { 703 | $dashicon_class = self::get_dashicon_success_class(); 704 | } 705 | 706 | /* Add menu item */ 707 | $wp_admin_bar->add_menu( 708 | array( 709 | 'id' => 'cachify', 710 | 'href' => wp_nonce_url( add_query_arg( '_cachify', 'flush' ), '_cachify__flush_nonce' ), // esc_url in /wp-includes/class-wp-admin-bar.php#L438. 711 | 'parent' => 'top-secondary', 712 | 'title' => '' . 713 | '' . 714 | __( 715 | 'Flush site cache', 716 | 'cachify' 717 | ) . 718 | '', 719 | 'meta' => array( 720 | 'title' => esc_html__( 'Flush the Cachify cache', 'cachify' ), 721 | ), 722 | ) 723 | ); 724 | } 725 | 726 | /** 727 | * Returns the dashicon class for the success state in admin bar flush button 728 | * 729 | * @return string 730 | * 731 | * @since 2.4.0 732 | */ 733 | public static function get_dashicon_success_class() { 734 | global $wp_version; 735 | if ( version_compare( $wp_version, '5.2', '<' ) ) { 736 | return 'dashicons-yes'; 737 | } 738 | 739 | return 'dashicons-yes-alt'; 740 | } 741 | 742 | /** 743 | * Add a script to query the REST endpoint and animate the flush icon in admin bar menu 744 | * 745 | * @hook mixed cachify_user_can_flush_cache ? 746 | * 747 | * @param object $wp_admin_bar Object of menu items. 748 | * 749 | * @since 2.4.0 750 | */ 751 | public static function add_flush_icon_script( $wp_admin_bar ) { 752 | /* Quit */ 753 | if ( ! is_admin_bar_showing() || ! apply_filters( 'cachify_user_can_flush_cache', current_user_can( 'manage_options' ) ) ) { 754 | return; 755 | } 756 | 757 | /* Enqueue script */ 758 | wp_enqueue_script( 'cachify-admin-bar-flush' ); 759 | 760 | /* Localize script */ 761 | wp_localize_script( 762 | 'cachify-admin-bar-flush', 763 | 'cachify_admin_bar_flush_ajax_object', 764 | array( 765 | 'url' => esc_url_raw( rest_url( self::REST_NAMESPACE . '/' . self::REST_ROUTE_FLUSH ) ), 766 | 'nonce' => wp_create_nonce( 'wp_rest' ), 767 | 'flushing' => __( 'Flushing cache', 'cachify' ), 768 | 'flushed' => __( 'Cache flushed successfully', 'cachify' ), 769 | 'dashicon_success' => self::get_dashicon_success_class(), 770 | ) 771 | ); 772 | } 773 | 774 | 775 | /** 776 | * Registers an REST endpoint for the flush operation 777 | * 778 | * @since 2.4.0 779 | */ 780 | public static function add_flush_rest_endpoint() { 781 | register_rest_route( 782 | self::REST_NAMESPACE, 783 | self::REST_ROUTE_FLUSH, 784 | array( 785 | 'methods' => WP_REST_Server::DELETABLE, 786 | 'callback' => array( 787 | __CLASS__, 788 | 'flush_cache', 789 | ), 790 | 'permission_callback' => array( 791 | __CLASS__, 792 | 'user_can_manage_options', 793 | ), 794 | ) 795 | ); 796 | } 797 | 798 | /** 799 | * Check if user can manage options 800 | * 801 | * @return bool 802 | * 803 | * @since 2.4.0 804 | */ 805 | public static function user_can_manage_options() { 806 | return current_user_can( 'manage_options' ); 807 | } 808 | 809 | /** 810 | * Process plugin's meta actions 811 | * 812 | * @hook mixed cachify_user_can_flush_cache 813 | * 814 | * @param array $data Metadata of the plugin. 815 | * 816 | * @since 0.5 817 | * @since 2.2.2 818 | * @since 2.4.0 Extract cache flushing to own method and always redirect to referer with new value for `_cachify` param. 819 | */ 820 | public static function process_flush_request( $data ) { 821 | /* Skip if not a flush request */ 822 | if ( empty( $_GET['_cachify'] ) || 'flush' !== $_GET['_cachify'] ) { 823 | return; 824 | } 825 | 826 | /* Check nonce */ 827 | if ( empty( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), '_cachify__flush_nonce' ) ) { 828 | return; 829 | } 830 | 831 | /* Skip if not necessary */ 832 | if ( ! is_admin_bar_showing() || ! apply_filters( 'cachify_user_can_flush_cache', current_user_can( 'manage_options' ) ) ) { 833 | return; 834 | } 835 | 836 | /* Load on demand */ 837 | if ( ! function_exists( 'is_plugin_active_for_network' ) ) { 838 | require_once ABSPATH . 'wp-admin/includes/plugin.php'; 839 | } 840 | 841 | /* Flush cache */ 842 | self::flush_cache(); 843 | 844 | wp_safe_redirect( 845 | add_query_arg( 846 | '_cachify', 847 | 'flushed', 848 | wp_get_referer() 849 | ) 850 | ); 851 | 852 | exit(); 853 | } 854 | 855 | /** 856 | * Flush cache 857 | * 858 | * @since 2.4.0 859 | */ 860 | public static function flush_cache() { 861 | /* Flush cache */ 862 | if ( is_multisite() && is_network_admin() ) { 863 | /* Old blog */ 864 | $old = $GLOBALS['wpdb']->blogid; 865 | 866 | /* Blog IDs */ 867 | $ids = self::_get_blog_ids(); 868 | 869 | /* Loop over blogs */ 870 | foreach ( $ids as $id ) { 871 | switch_to_blog( $id ); 872 | self::flush_total_cache(); 873 | } 874 | 875 | /* Switch back to old blog */ 876 | switch_to_blog( $old ); 877 | 878 | /* Notice */ 879 | if ( is_admin() ) { 880 | add_action( 'network_admin_notices', array( __CLASS__, 'flush_notice' ) ); 881 | } 882 | } else { 883 | self::flush_total_cache(); 884 | 885 | /* Notice */ 886 | if ( is_admin() ) { 887 | add_action( 'admin_notices', array( __CLASS__, 'flush_notice' ) ); 888 | } 889 | } 890 | 891 | /* Reschedule HDD Cache Cron */ 892 | if ( self::METHOD_HDD === self::$options['use_apc'] ) { 893 | $timestamp = wp_next_scheduled( 'hdd_cache_cron' ); 894 | if ( false !== $timestamp ) { 895 | wp_reschedule_event( $timestamp, 'cachify_cache_expire', 'hdd_cache_cron' ); 896 | wp_unschedule_event( $timestamp, 'hdd_cache_cron' ); 897 | } 898 | } 899 | 900 | if ( ! is_admin() ) { 901 | wp_safe_redirect( 902 | remove_query_arg( 903 | '_cachify', 904 | wp_get_referer() 905 | ) 906 | ); 907 | 908 | exit(); 909 | } 910 | } 911 | 912 | /** 913 | * Notice after successful flushing of the cache 914 | * 915 | * @hook mixed cachify_user_can_flush_cache 916 | * 917 | * @since 1.2 918 | * @since 2.2.2 919 | */ 920 | public static function flush_notice() { 921 | /* No admin */ 922 | if ( ! is_admin_bar_showing() || ! apply_filters( 'cachify_user_can_flush_cache', current_user_can( 'manage_options' ) ) ) { 923 | return; 924 | } 925 | 926 | printf( 927 | '

%s

', 928 | esc_html__( 'Cachify cache is flushed.', 'cachify' ) 929 | ); 930 | } 931 | 932 | /** 933 | * Remove page from cache or flush on comment edit 934 | * 935 | * @param int $id Comment ID. 936 | * 937 | * @since 0.1.0 938 | * @since 2.1.2 939 | * 940 | * @deprecated 2.4.0 Use comment_edit($id, $comment) instead. 941 | */ 942 | public static function edit_comment( $id ) { 943 | self::comment_edit( $id, array( 'comment_approved' => 1 ) ); 944 | } 945 | 946 | /** 947 | * Remove page from cache or flush on comment edit. 948 | * 949 | * @param integer $id Comment ID. 950 | * @param array $comment Comment data. 951 | * 952 | * @since 2.4.0 Replacement for edit_comment($id) with additional comment parameter. 953 | */ 954 | public static function comment_edit( $id, $comment ) { 955 | $approved = (int) $comment['comment_approved']; 956 | 957 | /* Approved comment? */ 958 | if ( 1 === $approved ) { 959 | if ( self::$options['reset_on_comment'] ) { 960 | self::flush_total_cache(); 961 | } else { 962 | self::remove_page_cache_by_post_id( 963 | get_comment( $id )->comment_post_ID 964 | ); 965 | } 966 | } 967 | } 968 | 969 | /** 970 | * Remove page from cache or flush on new comment 971 | * 972 | * @param mixed $approved Comment status. 973 | * @param array $comment Array of properties. 974 | * 975 | * @return mixed Comment status. 976 | * 977 | * @since 0.1 978 | * @since 2.1.2 979 | * @since 2.4.0 Replacement for edit_comment($id) with additional comment parameter. 980 | */ 981 | public static function pre_comment( $approved, $comment ) { 982 | self::new_comment( $comment['comment_ID'], $approved ); 983 | 984 | return $approved; 985 | } 986 | 987 | /** 988 | * Remove page from cache or flush on new comment 989 | * 990 | * @param integer|string $id Comment ID. 991 | * @param integer|string $approved Comment status. 992 | * 993 | * @since 0.1.0 994 | * @since 2.1.2 995 | * @since 2.4.0 Renamed with ID parameter instead of comment array. 996 | */ 997 | public static function new_comment( $id, $approved ) { 998 | /* Approved comment? */ 999 | if ( 1 === $approved ) { 1000 | if ( self::$options['reset_on_comment'] ) { 1001 | self::flush_total_cache(); 1002 | } else { 1003 | self::remove_page_cache_by_post_id( get_comment( $id )->comment_post_ID ); 1004 | } 1005 | } 1006 | } 1007 | 1008 | /** 1009 | * Remove page from cache or flush on comment edit 1010 | * 1011 | * @param string $new_status New status. 1012 | * @param string $old_status Old status. 1013 | * @param object $comment The comment. 1014 | * 1015 | * @since 0.1 1016 | * @since 2.1.2 1017 | * 1018 | * @deprecated 2.4.0 Use comment_status($new_status, $old_status, $comment) instead. 1019 | */ 1020 | public static function touch_comment( $new_status, $old_status, $comment ) { 1021 | self::comment_status( $new_status, $old_status, $comment ); 1022 | } 1023 | 1024 | /** 1025 | * Remove page from cache or flush on comment edit. 1026 | * 1027 | * @param string $new_status New status. 1028 | * @param string $old_status Old status. 1029 | * @param WP_Comment $comment The comment. 1030 | * 1031 | * @since 0.1 1032 | * @since 2.1.2 1033 | * @since 2.4.0 Renamed from touch_comment(). 1034 | */ 1035 | public static function comment_status( $new_status, $old_status, $comment ) { 1036 | if ( 'approved' === $old_status || 'approved' === $new_status ) { 1037 | if ( self::$options['reset_on_comment'] ) { 1038 | self::flush_total_cache(); 1039 | } else { 1040 | self::remove_page_cache_by_post_id( $comment->comment_post_ID ); 1041 | } 1042 | } 1043 | } 1044 | 1045 | /** 1046 | * Generate publish hook for custom post types 1047 | * 1048 | * @since 2.0.3 1049 | * @since 2.1.7 Make the function public 1050 | * 1051 | * @deprecated no longer used since 2.4 1052 | */ 1053 | public static function register_publish_hooks() { 1054 | /* Available post types */ 1055 | $post_types = get_post_types( 1056 | array( 1057 | 'public' => true, 1058 | ) 1059 | ); 1060 | 1061 | /* Empty data? */ 1062 | if ( empty( $post_types ) ) { 1063 | return; 1064 | } 1065 | 1066 | /* Loop the post types */ 1067 | foreach ( $post_types as $post_type ) { 1068 | add_action( 'publish_' . $post_type, array( __CLASS__, 'publish_post_types' ), 10, 2 ); 1069 | add_action( 'publish_future_' . $post_type, array( __CLASS__, 'flush_total_cache' ) ); 1070 | } 1071 | } 1072 | 1073 | /** 1074 | * Removes the post type cache on post updates 1075 | * 1076 | * @param int $post_id Post ID. 1077 | * @param object $post Post object. 1078 | * 1079 | * @since 2.0.3 1080 | * 1081 | * @deprecated no longer used since 2.4 1082 | */ 1083 | public static function publish_post_types( $post_id, $post ) { 1084 | /* No post_id? */ 1085 | if ( empty( $post_id ) || empty( $post ) ) { 1086 | return; 1087 | } 1088 | 1089 | /* Post status check */ 1090 | if ( ! in_array( $post->post_status, array( 'publish', 'future' ), true ) ) { 1091 | return; 1092 | } 1093 | 1094 | /* Check user role */ 1095 | if ( ! current_user_can( 'publish_posts' ) ) { 1096 | return; 1097 | } 1098 | 1099 | /* Remove cache OR flush */ 1100 | if ( 1 !== self::$options['reset_on_post'] ) { 1101 | self::remove_page_cache_by_post_id( $post_id ); 1102 | } else { 1103 | self::flush_total_cache(); 1104 | } 1105 | } 1106 | 1107 | /** 1108 | * Removes the post type cache if saved or updated 1109 | * 1110 | * @param int $id Post ID. 1111 | * @param WP_Post $post_after Post object following the update. 1112 | * @param WP_Post $post_before Post object before the update. 1113 | * 1114 | * @since 2.0.3 1115 | * @since 2.1.7 Make the function public. 1116 | * @since 2.4.0 Renamed to save_update_trash_post and introduced parameters. 1117 | */ 1118 | public static function save_update_trash_post( $id, $post_after, $post_before ) { 1119 | $status = get_post_status( $post_before ); 1120 | 1121 | /* Post type published? */ 1122 | if ( 'publish' === $status ) { 1123 | self::flush_cache_for_posts( $id ); 1124 | } 1125 | } 1126 | 1127 | /** 1128 | * Removes the post type cache before an existing post type is updated in the db 1129 | * 1130 | * @param int $id Post ID. 1131 | * @param array $data Post data. 1132 | * 1133 | * @since 2.0.3 1134 | * @since 2.3.0 1135 | * @since 2.4.0 Renamed to post_update. 1136 | */ 1137 | public static function post_update( $id, $data ) { 1138 | $new_status = $data['post_status']; 1139 | $old_status = get_post_status( $id ); 1140 | 1141 | /* Was it published and is it not trashed now? */ 1142 | if ( 'trash' !== $new_status && 'publish' === $old_status ) { 1143 | self::flush_cache_for_posts( $id ); 1144 | } 1145 | } 1146 | 1147 | /** 1148 | * Clear cache when any post type has been created or updated 1149 | * 1150 | * @param int|WP_Post $post Post ID or object. 1151 | * 1152 | * @since 2.4.0 1153 | */ 1154 | public static function flush_cache_for_posts( $post ) { 1155 | if ( is_int( $post ) ) { 1156 | $post_id = $post; 1157 | $data = get_post( $post_id ); 1158 | 1159 | if ( ! is_object( $data ) ) { 1160 | return; 1161 | } 1162 | } elseif ( is_object( $post ) ) { 1163 | $post_id = $post->ID; 1164 | } else { 1165 | return; 1166 | } 1167 | 1168 | /* Remove cache OR flush */ 1169 | if ( 1 !== self::$options['reset_on_post'] ) { 1170 | self::remove_page_cache_by_post_id( $post_id ); 1171 | } else { 1172 | self::flush_total_cache(); 1173 | } 1174 | } 1175 | 1176 | /** 1177 | * Flush post cache on WooCommerce stock changes. 1178 | * 1179 | * @param int|WC_Product $product Product ID or object. 1180 | * 1181 | * @since 2.4.0 1182 | */ 1183 | public static function flush_woocommerce( $product ) { 1184 | if ( is_int( $product ) ) { 1185 | $id = $product; 1186 | } else { 1187 | $id = $product->get_id(); 1188 | } 1189 | 1190 | self::flush_cache_for_posts( $id ); 1191 | } 1192 | 1193 | /** 1194 | * Removes a page (id) from cache 1195 | * 1196 | * @param int $post_id Post ID. 1197 | * 1198 | * @since 2.0.3 1199 | */ 1200 | public static function remove_page_cache_by_post_id( $post_id ) { 1201 | $post_id = (int) $post_id; 1202 | if ( ! $post_id ) { 1203 | return; 1204 | } 1205 | 1206 | self::remove_page_cache_by_url( get_permalink( $post_id ) ); 1207 | } 1208 | 1209 | /** 1210 | * Removes a page url from cache 1211 | * 1212 | * @param string $url Page URL. 1213 | * 1214 | * @since 0.1 1215 | */ 1216 | public static function remove_page_cache_by_url( $url ) { 1217 | $url = (string) $url; 1218 | if ( ! $url ) { 1219 | return; 1220 | } 1221 | 1222 | $hash = self::_cache_hash( $url ); 1223 | call_user_func( array( self::$method, 'delete_item' ), $hash, $url ); 1224 | 1225 | /** 1226 | * Call hook for further actions after cache has been flushed for a single page. 1227 | * 1228 | * @since 2.4.0 1229 | * 1230 | * @param string $url Page URL. 1231 | * @param string $hash Cache hash for given URL. 1232 | */ 1233 | do_action( 'cachify_removed_cache_by_url', $url, $hash ); 1234 | } 1235 | 1236 | /** 1237 | * Get cache validity 1238 | * 1239 | * @return int Validity period in seconds. 1240 | * 1241 | * @since 2.0.0 1242 | */ 1243 | private static function _cache_expires() { 1244 | return HOUR_IN_SECONDS * self::$options['cache_expires']; 1245 | } 1246 | 1247 | /** 1248 | * Determine if cache details should be printed in signature 1249 | * 1250 | * @return bool Show details in signature. 1251 | * 1252 | * @since 2.3.0 1253 | */ 1254 | private static function _signature_details() { 1255 | return 1 === self::$options['sig_detail']; 1256 | } 1257 | 1258 | /** 1259 | * Get hash value for caching 1260 | * 1261 | * @param string $url URL to hash [optional]. 1262 | * 1263 | * @return string Cachify hash value. 1264 | * 1265 | * @since 0.1 1266 | * @since 2.0 1267 | */ 1268 | private static function _cache_hash( $url = '' ) { 1269 | $prefix = is_ssl() ? 'https-' : ''; 1270 | 1271 | if ( empty( $url ) ) { 1272 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated 1273 | $url = '//' . wp_unslash( $_SERVER['HTTP_HOST'] ) . wp_unslash( $_SERVER['REQUEST_URI'] ); 1274 | } 1275 | 1276 | $url_parts = wp_parse_url( $url ); 1277 | $hash_key = $prefix . $url_parts['host'] . $url_parts['path']; 1278 | 1279 | return md5( $hash_key ) . '.cachify'; 1280 | } 1281 | 1282 | /** 1283 | * Split by comma 1284 | * 1285 | * @param string $input String to split. 1286 | * 1287 | * @return array Splitted values. 1288 | * 1289 | * @since 0.9.1 1290 | * @since 1.0 1291 | */ 1292 | private static function _preg_split( $input ) { 1293 | return (array) preg_split( '/,/', $input, -1, PREG_SPLIT_NO_EMPTY ); 1294 | } 1295 | 1296 | /** 1297 | * Check for index page 1298 | * 1299 | * @return bool TRUE if index 1300 | * 1301 | * @since 0.6 1302 | */ 1303 | private static function _is_index() { 1304 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated 1305 | return basename( wp_unslash( $_SERVER['SCRIPT_NAME'] ) ) === 'index.php'; 1306 | } 1307 | 1308 | /** 1309 | * Check for mobile devices 1310 | * 1311 | * @return bool TRUE if mobile 1312 | * 1313 | * @since 0.9.1 1314 | */ 1315 | private static function _is_mobile() { 1316 | $templatedir = get_template_directory(); 1317 | return ( strpos( $templatedir, 'wptouch' ) || strpos( $templatedir, 'carrington' ) || strpos( $templatedir, 'jetpack' ) || strpos( $templatedir, 'handheld' ) ); 1318 | } 1319 | 1320 | /** 1321 | * Check if user is logged in or marked 1322 | * 1323 | * @return bool TRUE on "marked" users 1324 | * 1325 | * @since 2.0.0 1326 | */ 1327 | private static function _is_logged_in() { 1328 | /* Logged in */ 1329 | if ( is_user_logged_in() ) { 1330 | return true; 1331 | } 1332 | 1333 | /* Cookie? */ 1334 | if ( empty( $_COOKIE ) ) { 1335 | return false; 1336 | } 1337 | 1338 | /* Loop */ 1339 | foreach ( $_COOKIE as $k => $v ) { 1340 | if ( preg_match( '/^(wp-postpass|wordpress_logged_in|comment_author)_/', $k ) ) { 1341 | return true; 1342 | } 1343 | } 1344 | 1345 | return false; 1346 | } 1347 | 1348 | /** 1349 | * Register all hooks to flush the total cache 1350 | * 1351 | * @since 2.4.0 1352 | */ 1353 | public static function register_flush_cache_hooks() { 1354 | /* Define all default flush cache hooks */ 1355 | $flush_cache_hooks = array( 1356 | 'cachify_flush_cache' => 10, 1357 | '_core_updated_successfully' => 10, 1358 | 'switch_theme' => 10, 1359 | 'before_delete_post' => 10, 1360 | 'wp_trash_post' => 10, 1361 | 'create_term' => 10, 1362 | 'delete_term' => 10, 1363 | 'edit_terms' => 10, 1364 | 'user_register' => 10, 1365 | 'edit_user_profile_update' => 10, 1366 | 'delete_user' => 10, 1367 | /* third party */ 1368 | 'autoptimize_action_cachepurged' => 10, 1369 | ); 1370 | 1371 | $flush_cache_hooks = apply_filters( 'cachify_flush_cache_hooks', $flush_cache_hooks ); 1372 | 1373 | /* Loop all hooks and register actions */ 1374 | foreach ( $flush_cache_hooks as $hook => $priority ) { 1375 | add_action( $hook, array( 'Cachify', 'flush_total_cache' ), $priority, 0 ); 1376 | } 1377 | } 1378 | 1379 | /** 1380 | * Define exclusions for caching 1381 | * 1382 | * @hook bool cachify_skip_cache 1383 | * 1384 | * @return bool TRUE on exclusion 1385 | * 1386 | * @since 0.2 1387 | */ 1388 | private static function _skip_cache() { 1389 | 1390 | /* Plugin options */ 1391 | $options = self::$options; 1392 | 1393 | /* Skip for all request methods except GET */ 1394 | if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'GET' !== $_SERVER['REQUEST_METHOD'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing 1395 | return true; 1396 | } 1397 | if ( ! empty( $_GET ) && get_option( 'permalink_structure' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing 1398 | return true; 1399 | } 1400 | 1401 | /* Only cache requests routed through main index.php (skip AJAX, WP-Cron, WP-CLI etc.) */ 1402 | if ( ! self::_is_index() ) { 1403 | return true; 1404 | } 1405 | 1406 | /* Logged in */ 1407 | if ( $options['only_guests'] && self::_is_logged_in() ) { 1408 | return true; 1409 | } 1410 | 1411 | /* No cache hook */ 1412 | if ( apply_filters( 'cachify_skip_cache', false ) ) { 1413 | return true; 1414 | } 1415 | 1416 | /* Conditional Tags */ 1417 | if ( is_search() || is_404() || is_feed() || is_trackback() || is_robots() || is_preview() || post_password_required() ) { 1418 | return true; 1419 | } 1420 | 1421 | /* WooCommerce usage */ 1422 | if ( defined( 'DONOTCACHEPAGE' ) && DONOTCACHEPAGE ) { 1423 | return true; 1424 | } 1425 | 1426 | /* Mobile request */ 1427 | if ( self::_is_mobile() ) { 1428 | return true; 1429 | } 1430 | 1431 | /* Post IDs */ 1432 | if ( $options['without_ids'] && is_singular() ) { 1433 | $without_ids = array_map( 'intval', self::_preg_split( $options['without_ids'] ) ); 1434 | if ( in_array( $GLOBALS['wp_query']->get_queried_object_id(), $without_ids, true ) ) { 1435 | return true; 1436 | } 1437 | } 1438 | 1439 | /* User Agents */ 1440 | if ( $options['without_agents'] && isset( $_SERVER['HTTP_USER_AGENT'] ) ) { 1441 | $user_agent_strings = self::_preg_split( $options['without_agents'] ); 1442 | foreach ( $user_agent_strings as $user_agent_string ) { 1443 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 1444 | if ( strpos( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ), $user_agent_string ) !== false ) { 1445 | return true; 1446 | } 1447 | } 1448 | } 1449 | 1450 | // Sitemap feature added in WP 5.5. 1451 | if ( get_query_var( 'sitemap' ) || get_query_var( 'sitemap-subtype' ) || get_query_var( 'sitemap-stylesheet' ) ) { 1452 | return true; 1453 | } 1454 | 1455 | /* Content Negotiation */ 1456 | 1457 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 1458 | if ( isset( $_SERVER['HTTP_ACCEPT'] ) && false === strpos( $_SERVER['HTTP_ACCEPT'], 'text/html' ) ) { 1459 | return true; 1460 | } 1461 | 1462 | return false; 1463 | } 1464 | 1465 | /** 1466 | * Minify HTML code 1467 | * 1468 | * @hook array cachify_minify_ignore_tags 1469 | * 1470 | * @param string $data Original HTML code. 1471 | * 1472 | * @return string Minified code 1473 | * 1474 | * @since 0.9.2 1475 | */ 1476 | private static function _minify_cache( $data ) { 1477 | /* Disabled? */ 1478 | if ( ! self::$options['compress_html'] ) { 1479 | return $data; 1480 | } 1481 | 1482 | /* Avoid slow rendering */ 1483 | if ( strlen( $data ) > 700000 ) { 1484 | return $data; 1485 | } 1486 | 1487 | /* Ignore this html tags */ 1488 | $ignore_tags = (array) apply_filters( 1489 | 'cachify_minify_ignore_tags', 1490 | array( 1491 | 'textarea', 1492 | 'pre', 1493 | ) 1494 | ); 1495 | 1496 | /* Add the script tag */ 1497 | if ( self::MINIFY_HTML_JS !== self::$options['compress_html'] ) { 1498 | $ignore_tags[] = 'script'; 1499 | } 1500 | 1501 | /* Empty blacklist? | TODO: Make it better */ 1502 | if ( ! $ignore_tags ) { 1503 | return $data; 1504 | } 1505 | 1506 | /* Convert to string */ 1507 | $ignore_regex = implode( '|', $ignore_tags ); 1508 | 1509 | /* Minify */ 1510 | $cleaned = preg_replace( 1511 | array( 1512 | '//s', 1513 | '#(?ix)(?>[^\S ]\s*|\s{2,})(?=(?:(?:[^<]++|<(?!/?(?:' . $ignore_regex . ')\b))*+)(?:<(?>' . $ignore_regex . ')\b|\z))#', 1514 | ), 1515 | array( 1516 | '', 1517 | ' ', 1518 | ), 1519 | $data 1520 | ); 1521 | 1522 | /* Fault */ 1523 | if ( strlen( $cleaned ) <= 1 ) { 1524 | return $data; 1525 | } 1526 | 1527 | return $cleaned; 1528 | } 1529 | 1530 | /** 1531 | * Flush total cache 1532 | * 1533 | * @param bool $clear_all_methods Flush all caching methods (default: FALSE). 1534 | * 1535 | * @since 0.1 1536 | * @since 2.0 1537 | */ 1538 | public static function flush_total_cache( $clear_all_methods = false ) { 1539 | // We do not need to flush the cache for saved post revisions. 1540 | if ( did_action( 'save_post_revision' ) ) { 1541 | return; 1542 | } 1543 | 1544 | if ( $clear_all_methods ) { 1545 | /* DB */ 1546 | Cachify_DB::clear_cache(); 1547 | 1548 | /* HDD */ 1549 | Cachify_HDD::clear_cache(); 1550 | 1551 | /* REDIS */ 1552 | Cachify_REDIS::clear_cache(); 1553 | 1554 | /* MEMCACHED */ 1555 | Cachify_MEMCACHED::clear_cache(); 1556 | } else { 1557 | call_user_func( array( self::$method, 'clear_cache' ) ); 1558 | } 1559 | 1560 | /** 1561 | * Call hook for further actions after total cache has been flushed. 1562 | * 1563 | * @since 2.4.0 1564 | * 1565 | * @param bool $clear_all_methods All available caching backends have been flushed. 1566 | */ 1567 | do_action( 'cachify_flushed_total_cache', $clear_all_methods ); 1568 | 1569 | /* Transient */ 1570 | delete_transient( 'cachify_cache_size' ); 1571 | } 1572 | 1573 | /** 1574 | * Assign the cache 1575 | * 1576 | * @param string $data Content of the page. 1577 | * 1578 | * @return string Content of the page. 1579 | * 1580 | * @since 0.1 1581 | * @since 2.0 1582 | */ 1583 | public static function set_cache( $data ) { 1584 | /* Empty? */ 1585 | if ( empty( $data ) ) { 1586 | return ''; 1587 | } 1588 | 1589 | /** 1590 | * Filters whether the buffered data should actually be cached 1591 | * 1592 | * @param bool $should_cache Whether the data should be cached. 1593 | * @param string $data The actual data. 1594 | * @param object $method Instance of the selected caching method. 1595 | * @param string $cache_hash The cache hash. 1596 | * @param int $cache_expires Cache validity period. 1597 | * 1598 | * @since 2.3.0 1599 | */ 1600 | $should_cache = apply_filters( 1601 | 'cachify_store_item', 1602 | 200 === http_response_code(), 1603 | $data, 1604 | self::$method, 1605 | self::_cache_hash(), 1606 | self::_cache_expires() 1607 | ); 1608 | 1609 | /* Save? */ 1610 | if ( $should_cache ) { 1611 | /** 1612 | * Filters the buffered data itself 1613 | * 1614 | * @param string $data The actual data. 1615 | * @param object $method Instance of the selected caching method. 1616 | * @param string $cache_hash The cache hash. 1617 | * @param int $cache_expires Cache validity period. 1618 | * 1619 | * @since 2.4.0 1620 | */ 1621 | $data = apply_filters( 'cachify_modify_output', $data, self::$method, self::_cache_hash(), self::_cache_expires() ); 1622 | 1623 | call_user_func( 1624 | array( 1625 | self::$method, 1626 | 'store_item', 1627 | ), 1628 | self::_cache_hash(), 1629 | self::_minify_cache( $data ), 1630 | self::_cache_expires(), 1631 | self::_signature_details() 1632 | ); 1633 | } 1634 | 1635 | return $data; 1636 | } 1637 | 1638 | /** 1639 | * Manage the cache. 1640 | * 1641 | * @since 0.1 1642 | */ 1643 | public static function manage_cache() { 1644 | /* No caching? */ 1645 | if ( self::_skip_cache() ) { 1646 | return; 1647 | } 1648 | 1649 | /* Data present in cache */ 1650 | $cache = call_user_func( 1651 | array( 1652 | self::$method, 1653 | 'get_item', 1654 | ), 1655 | self::_cache_hash() 1656 | ); 1657 | 1658 | /* No cache? */ 1659 | if ( empty( $cache ) ) { 1660 | ob_start( 'Cachify::set_cache' ); 1661 | return; 1662 | } 1663 | 1664 | /* Process cache */ 1665 | call_user_func( 1666 | array( 1667 | self::$method, 1668 | 'print_cache', 1669 | ), 1670 | self::_signature_details(), 1671 | $cache 1672 | ); 1673 | } 1674 | 1675 | /** 1676 | * Register CSS 1677 | * 1678 | * @param string $hook Current hook. 1679 | * 1680 | * @since 1.0 1681 | */ 1682 | public static function add_admin_resources( $hook ) { 1683 | /* Hooks check */ 1684 | if ( 'index.php' !== $hook && 'settings_page_cachify' !== $hook ) { 1685 | return; 1686 | } 1687 | 1688 | /* Register css */ 1689 | switch ( $hook ) { 1690 | case 'index.php': 1691 | wp_enqueue_style( 'cachify-dashboard' ); 1692 | break; 1693 | 1694 | default: 1695 | break; 1696 | } 1697 | } 1698 | 1699 | /** 1700 | * Fixing some admin dashboard styles 1701 | * 1702 | * @since 2.3.0 1703 | */ 1704 | public static function admin_dashboard_styles() { 1705 | $wp_version = get_bloginfo( 'version' ); 1706 | 1707 | if ( version_compare( $wp_version, '5.3', '<' ) ) { 1708 | wp_add_inline_style( 'cachify-dashboard', '#dashboard_right_now .cachify-icon use { fill: #82878c; }' ); 1709 | } 1710 | } 1711 | 1712 | /** 1713 | * Fixing some admin dashboard styles 1714 | * 1715 | * @since 2.3.0 1716 | * 1717 | * @deprecated included in dashboard.css since 2.4 1718 | */ 1719 | public static function admin_dashboard_dark_mode_styles() { 1720 | wp_add_inline_style( 'cachify-dashboard', '#dashboard_right_now .cachify-icon use { fill: #bbc8d4; }' ); 1721 | } 1722 | 1723 | /** 1724 | * Add options page 1725 | * 1726 | * @since 1.0 1727 | */ 1728 | public static function add_page() { 1729 | add_options_page( 1730 | __( 'Cachify', 'cachify' ), 1731 | __( 'Cachify', 'cachify' ), 1732 | 'manage_options', 1733 | 'cachify', 1734 | array( 1735 | __CLASS__, 1736 | 'options_page', 1737 | ) 1738 | ); 1739 | } 1740 | 1741 | /** 1742 | * Register settings 1743 | * 1744 | * @since 1.0 1745 | */ 1746 | public static function register_settings() { 1747 | register_setting( 1748 | 'cachify', 1749 | 'cachify', 1750 | array( 1751 | __CLASS__, 1752 | 'validate_options', 1753 | ) 1754 | ); 1755 | } 1756 | 1757 | /** 1758 | * Validate options 1759 | * 1760 | * @param array $data Array of form values. 1761 | * 1762 | * @return array Array of validated values. 1763 | * 1764 | * @since 1.0 1765 | * @since 2.1.3 1766 | */ 1767 | public static function validate_options( $data ) { 1768 | /* Empty data? */ 1769 | if ( empty( $data ) ) { 1770 | return array(); 1771 | } 1772 | 1773 | /* Flush cache */ 1774 | self::flush_total_cache( true ); 1775 | 1776 | /* Notification */ 1777 | if ( self::$options['use_apc'] !== $data['use_apc'] && $data['use_apc'] >= self::METHOD_HDD && self::METHOD_REDIS != $data['use_apc'] ) { 1778 | add_settings_error( 1779 | 'cachify_method_tip', 1780 | 'cachify_method_tip', 1781 | esc_html__( 'The server configuration file (e.g. .htaccess) needs to be adjusted. Please have a look at the setup tab.', 'cachify' ), 1782 | 'notice-warning' 1783 | ); 1784 | } 1785 | 1786 | /* Return */ 1787 | return array( 1788 | 'only_guests' => (int) ( ! empty( $data['only_guests'] ) ), 1789 | 'compress_html' => (int) $data['compress_html'], 1790 | 'cache_expires' => (int) ( isset( $data['cache_expires'] ) ? $data['cache_expires'] : self::$options['cache_expires'] ), 1791 | 'without_ids' => (string) isset( $data['without_ids'] ) ? sanitize_text_field( $data['without_ids'] ) : '', 1792 | 'without_agents' => (string) isset( $data['without_agents'] ) ? sanitize_text_field( $data['without_agents'] ) : '', 1793 | 'use_apc' => (int) $data['use_apc'], 1794 | 'reset_on_post' => (int) ( ! empty( $data['reset_on_post'] ) ), 1795 | 'reset_on_comment' => (int) ( ! empty( $data['reset_on_comment'] ) ), 1796 | 'sig_detail' => (int) ( ! empty( $data['sig_detail'] ) ), 1797 | 'change_robots_txt' => (int) ( ! empty( $data['change_robots_txt'] ) ), 1798 | ); 1799 | } 1800 | 1801 | /** 1802 | * Display options page 1803 | * 1804 | * @since 1.0 1805 | */ 1806 | public static function options_page() { 1807 | $options = self::_get_options(); 1808 | $cachify_tabs = self::_get_tabs( $options ); 1809 | $current_tab = isset( $_GET['cachify_tab'] ) && isset( $cachify_tabs[ $_GET['cachify_tab'] ] ) 1810 | ? sanitize_text_field( wp_unslash( $_GET['cachify_tab'] ) ) 1811 | : 'settings'; 1812 | ?> 1813 | 1814 |
1815 |

Cachify

1816 | 1817 | 1 ) { 1820 | echo ''; 1838 | } 1839 | 1840 | /* Include current tab */ 1841 | include $cachify_tabs[ $current_tab ]['page']; 1842 | 1843 | /* Include common footer */ 1844 | include 'cachify.settings-footer.php'; 1845 | ?> 1846 |
1847 | array( 1863 | 'name' => __( 'Settings', 'cachify' ), 1864 | 'page' => 'cachify.settings.php', 1865 | ), 1866 | ); 1867 | 1868 | if ( self::METHOD_HDD === $options['use_apc'] ) { 1869 | /* Setup tab for HDD Cache */ 1870 | $tabs['setup'] = array( 1871 | 'name' => __( 'Setup', 'cachify' ), 1872 | 'page' => 'setup/cachify.hdd.' . ( self::$is_nginx ? 'nginx' : 'htaccess' ) . '.php', 1873 | ); 1874 | } elseif ( self::METHOD_MMC === $options['use_apc'] && self::$is_nginx ) { 1875 | /* Setup tab for Memcached */ 1876 | $tabs['setup'] = array( 1877 | 'name' => __( 'Setup', 'cachify' ), 1878 | 'page' => 'setup/cachify.memcached.nginx.php', 1879 | ); 1880 | } 1881 | 1882 | return $tabs; 1883 | } 1884 | } 1885 | -------------------------------------------------------------------------------- /inc/setup/cachify.hdd.htaccess.php: -------------------------------------------------------------------------------- 1 | 13 | RewriteEngine on 14 | 15 | # set hostname directory 16 | RewriteCond %{HTTPS} on 17 | RewriteRule .* - [E=CACHIFY_HOST:https-%{HTTP_HOST}] 18 | RewriteCond %{HTTPS} off 19 | RewriteRule .* - [E=CACHIFY_HOST:%{HTTP_HOST}] 20 | 21 | # set subdirectory 22 | RewriteCond %{REQUEST_URI} /$ 23 | RewriteRule .* - [E=CACHIFY_DIR:%{REQUEST_URI}] 24 | RewriteCond %{REQUEST_URI} ^$ 25 | RewriteRule .* - [E=CACHIFY_DIR:/] 26 | '; 27 | 28 | if ( Cachify_HDD::is_gzip_enabled() ) { 29 | $htaccess .= ' 30 | # gzip 31 | RewriteRule .* - [E=CACHIFY_SUFFIX:] 32 | 33 | RewriteCond %{HTTP:Accept-Encoding} gzip 34 | RewriteRule .* - [E=CACHIFY_SUFFIX:.gz] 35 | AddType text/html .gz 36 | AddEncoding gzip .gz 37 | 38 | '; 39 | } 40 | 41 | $htaccess .= ' 42 | # Main Rules 43 | RewriteCond %{HTTP_ACCEPT} .*text/html.* 44 | RewriteCond %{REQUEST_METHOD} GET 45 | RewriteCond %{QUERY_STRING} ^$ 46 | RewriteCond %{REQUEST_URI} !^/(wp-admin|wp-content/cache)/.* 47 | RewriteCond %{HTTP_COOKIE} !(wp-postpass|wordpress_logged_in|comment_author)_ 48 | RewriteCond ' . WP_CONTENT_DIR . '/cache/cachify/%{ENV:CACHIFY_HOST}%{ENV:CACHIFY_DIR}index.html%{ENV:CACHIFY_SUFFIX} -f 49 | RewriteRule ^(.*) ' . wp_make_link_relative( content_url() ) . '/cache/cachify/%{ENV:CACHIFY_HOST}%{ENV:CACHIFY_DIR}index.html%{ENV:CACHIFY_SUFFIX} [L] 50 | 51 | # END CACHIFY'; 52 | 53 | // phpcs:disable Squiz.PHP.EmbeddedPhp 54 | ?> 55 | 56 |

57 |

58 | 59 | 62 | 63 |

64 |
    65 |
  1. 66 | 67 |
  2. 68 |
  3. 69 | 70 |
  4. 71 |
  5. 72 |
    73 |
    <IfModule mod_cache.c>
    74 |   CacheDisable /
    75 | </IfModule>
    76 |
  6. 77 |
  7. 78 |
    79 |
    AddDefaultCharset UTF-8
    80 |
  8. 81 |
82 | -------------------------------------------------------------------------------- /inc/setup/cachify.hdd.nginx.php: -------------------------------------------------------------------------------- 1 | 11 | 12 |

13 |

14 | 15 | 57 | 58 | () 59 | 60 |

61 |
    62 |
  1. 63 | ${http_host}', 68 | '${host}' 69 | ); 70 | ?> 71 |
  2. 72 |
73 | -------------------------------------------------------------------------------- /inc/setup/cachify.memcached.nginx.php: -------------------------------------------------------------------------------- 1 | 11 | 12 |

13 |

14 | 15 | 52 | 53 | () 54 | 55 |

56 |
    57 |
  1. 58 | ${http_host}', 63 | '${host}' 64 | ); 65 | ?> 66 |
  2. 67 |
  3. 68 | memcached_pass localhost:11211;', 73 | 'memcached_pass 127.0.0.1:11211;' 74 | ); 75 | ?> 76 |
  4. 77 |
78 | -------------------------------------------------------------------------------- /js/admin-bar-flush.js: -------------------------------------------------------------------------------- 1 | /* global cachify_admin_bar_flush_ajax_object */ 2 | ( function() { 3 | var is_flushing = false, 4 | admin_bar_cachify_list_item = document.getElementById( 'wp-admin-bar-cachify' ), 5 | flush_link = admin_bar_cachify_list_item.querySelector( 'a.ab-item' ), 6 | fallback_url = flush_link.getAttribute( 'href' ), 7 | aria_live_area = document.querySelector( '.ab-aria-live-area' ); 8 | 9 | // Replacing flush link with button because with AJAX action, it is semantically not a link anymore. 10 | var button = document.createRange().createContextualFragment( '' ); 11 | flush_link.parentNode.replaceChild( button, flush_link ); 12 | 13 | var admin_bar_icon = admin_bar_cachify_list_item.querySelector( '#wp-admin-bar-cachify .ab-icon' ); 14 | 15 | document.querySelector( '#wp-admin-bar-cachify .ab-item' ).addEventListener( 'click', flush ); 16 | 17 | admin_bar_icon.addEventListener( 'animationend', function() { 18 | admin_bar_icon.classList.remove( 'animate-fade' ); 19 | } ); 20 | 21 | function flush_icon_remove_classes() { 22 | var classes = [ 23 | 'animate-fade', 24 | 'animate-pulse', 25 | 'dashicons-trash', 26 | 'dashicons-yes', 27 | 'dashicons-yes-alt', 28 | 'dashicons-dismiss', 29 | ]; 30 | 31 | for ( var i = 0; i < classes.length; i++ ) { 32 | admin_bar_icon.classList.remove( classes[i] ); 33 | } 34 | } 35 | 36 | function start_flush_icon_reset_timeout() { 37 | setTimeout( function() { 38 | flush_icon_remove_classes(); 39 | admin_bar_icon.classList.add( 'animate-fade' ); 40 | admin_bar_icon.classList.add( 'dashicons-trash' ); 41 | is_flushing = false; 42 | aria_live_area.textContent = ''; 43 | }, 2000 ); 44 | } 45 | 46 | function flush( event ) { 47 | event.preventDefault(); 48 | 49 | if ( is_flushing ) { 50 | return; 51 | } 52 | is_flushing = true; 53 | aria_live_area.textContent = cachify_admin_bar_flush_ajax_object.flushing; 54 | 55 | if ( admin_bar_icon !== null ) { 56 | flush_icon_remove_classes(); 57 | admin_bar_icon.classList.add( 'animate-pulse' ); 58 | admin_bar_icon.classList.add( 'dashicons-trash' ); 59 | } 60 | 61 | var request = new XMLHttpRequest(); 62 | request.addEventListener( 'load', function() { 63 | if ( this.status === 200 ) { 64 | start_flush_icon_reset_timeout(); 65 | flush_icon_remove_classes(); 66 | admin_bar_icon.classList.add( 'animate-fade' ); 67 | admin_bar_icon.classList.add( cachify_admin_bar_flush_ajax_object.dashicon_success ); 68 | aria_live_area.textContent = cachify_admin_bar_flush_ajax_object.flushed; 69 | return; 70 | } 71 | 72 | window.location = fallback_url; 73 | } ); 74 | 75 | request.addEventListener( 'error', function() { 76 | window.location = fallback_url; 77 | } ); 78 | 79 | request.open( 'DELETE', cachify_admin_bar_flush_ajax_object.url ); 80 | request.setRequestHeader( 'X-WP-Nonce', cachify_admin_bar_flush_ajax_object.nonce ); 81 | request.send(); 82 | } 83 | }() ); 84 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | cachify.php 11 | inc 12 | 13 | 14 | inc/setup/ 15 | inc/cachify.settings.php 16 | inc/cachify.settings-footer.php 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ./tests/ 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | # Cachify # 2 | * Contributors: pluginkollektiv 3 | * Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=TD4AMD2D8EMZW 4 | * Tags: cache, caching, performance, optimize, speed 5 | * Requires at least: 4.7 6 | * Tested up to: 6.7 7 | * Requires PHP: 5.6 8 | * Stable tag: 2.4.1 9 | * License: GPLv2 or later 10 | * License URI: https://www.gnu.org/licenses/gpl-2.0.html 11 | 12 | Smart, efficient cache solution for WordPress. Use DB, HDD, Redis or Memcached for storing your blog pages. Make WordPress faster! 13 | 14 | ## Description ## 15 | *Cachify* optimizes your page loads by caching posts, pages and custom post types as static content. You can choose between caching via database, on the web server’s hard drive (HDD), Memcached (only on Nginx) or Redis. Whenever a page or post is loaded, it can be pulled directly from the cache. The amount of database queries and PHP requests will dramatically decrease towards zero, depending on the caching method you chose. 16 | 17 | ### Features ### 18 | * Works with custom post types. 19 | * Caching methods: DB, HDD, Redis and Memcached. 20 | * “Flush Cache” button in the WordPress toolbar. 21 | * Ready for WordPress Multisite. 22 | * Optional compression of HTML markup. 23 | * White lists for posts and user agents. 24 | * Manual and automatic cache reset. 25 | * Automatic cache management. 26 | * Dashboard widget for cached objects. 27 | * Settings for Apache and Nginx servers. 28 | * Extendability via hooks/filters. 29 | 30 | ### Support ### 31 | * Community support via the [support forums on wordpress.org](https://wordpress.org/support/plugin/cachify/) 32 | * We don’t handle support via e-mail, Twitter, GitHub issues etc. 33 | 34 | ### Contribute ### 35 | * Active development of this plugin is handled [on GitHub](https://github.com/pluginkollektiv/cachify). 36 | * Pull requests for documented bugs are highly appreciated. 37 | * If you think you’ve found a bug (e.g. you’re experiencing unexpected behavior), please post at the [support forums](https://wordpress.org/support/plugin/cachify/) first. 38 | * If you want to help us translate this plugin you can do so [on WordPress Translate](https://translate.wordpress.org/projects/wp-plugins/cachify/). 39 | 40 | ### Credits ### 41 | * Author: [Sergej Müller](https://sergejmueller.github.io) 42 | * Maintainers: [pluginkollektiv](https://pluginkollektiv.org) 43 | 44 | 45 | ## Installation ## 46 | * If you don’t know how to install a plugin for WordPress, [here’s how](https://wordpress.org/support/article/managing-plugins/#installing-plugins). 47 | 48 | ### Requirements ### 49 | * PHP 5.6 or greater 50 | * WordPress 4.7 or greater 51 | * Memcached in Nginx (optional) 52 | * Redis (optional, via the phpredis module) 53 | 54 | 55 | ## Frequently Asked Questions ## 56 | 57 | ### No cache expiration option while using HDD cache? ### 58 | The cache expiration can not be considered due to technical reasons. If the cache stock has to be emptied at certain time intervals, then it is recommended to call a prepared PHP file by a cronjob. 59 | 60 | ### PHP Fatal error: Cannot use output buffering in output buffering display handlers in Unknown on line 0 ### 61 | This error message may occur after commissioning the caching plugin. The hint appears because there are no cache files on the HDD for output. This is probably due to the fact that Cachify could not store files in the cache folder. Please check the write-permissions for the cache folder (found in the WordPress directory *wp-content*) and set them if necessary. 62 | 63 | ### My Website looks in some parts broken after activating Cachify! ### 64 | Please make sure there is no issue that caused by the Cache minify feature. Just deactivate it or use HTML only. If the issue still exist please feel free to report it at the [support forums](https://wordpress.org/support/plugin/cachify/). With this feature any unnecessary characters such as breaks and HTML comments are removed from the source code. 65 | 66 | ### Cachify HDD: Character encoding does not work correctly ### 67 | If you use Cachify to store the cache on HDD there is no PHP to run. In the case of misconfigured servers, this can lead to incorrect display of the special characters on web pages. The error can be corrected by an extension of the system file .htaccess: *AddDefaultCharset UTF-8* 68 | 69 | ### Cachify with CDN support? ### 70 | Currently the caching plugin for WordPress has no connection to a CDN provider. Although the Buzzword CDN (Content Delivery Network) is praised as a performance factor, CDN makes little sense for WordPress websites with a national audience. In this case, a home host could provide the requested files faster than a worldwide CDN service provider because the next node could be far away. 71 | 72 | 73 | ### When does Cachify automaticaly flush its cache? ### 74 | * After publishing new posts 75 | * After publishing new pages 76 | * After publishing new custom post types 77 | * After publishing new sheduled posts (only Cachify DB) 78 | * After updating WordPress 79 | * If you confirm the trash button on the adminbar 80 | * After saving Cachify and wpSEO settings 81 | 82 | ### Which parts of the website are not cached by default? ### 83 | * Password protected pages 84 | * Feeds 85 | * Trackbacks 86 | * Robots 87 | * Previews 88 | * Mobile-themes (WP-Touch, Carrington, Jetpack Mobile) 89 | * Search 90 | * Error pages 91 | 92 | ### The cache folder is indexed by search engines! ### 93 | To ensure that Google and other search engines do not index the static contents of the cache folder (otherwise there could be duplicate content), the robots.txt file which is located in the main directory of a WordPress installation should be expanded by disabling the path to the cache file (disallow). This issue should only happen if you use a *static robots.txt* or you changed the *wp-content* location. And so might look a robots.txt: 94 | 95 | `User-agent: * 96 | Disallow: */cache/cachify/ 97 | Allow: /` 98 | 99 | A complete documentation is available in the [online handbook](https://cachify.pluginkollektiv.org/documentation/). 100 | 101 | ## Changelog ## 102 | 103 | ### 2.4.1 ### 104 | 105 | * Fix: validation of Redis context parameters array in server hook no longer fails (#315) (#317) 106 | * Enhance: move some settings related code out of main class (#321) 107 | * Maintenance: Tested up to WordPress 6.7 108 | 109 | ### 2.4.0 ### 110 | 111 | Requires PHP 5.6 and WordPress 4.7 or above 112 | 113 | * New: introduce Redis (also KeyDB and Valkey) support using the _phpredis_ module (#253), (#252, props @newtovaux) 114 | * New: add `cachify_modify_output` filter 115 | * New: add `cachify_create_gzip_files` to disable creation of static GZip files (#262, props @angcl) 116 | * New: add hooks `cachify_removed_cache_by_url` and `cachify_flushed_total_cache` for additional actions after clearing (#294, props @ouun) 117 | * Removed: APC support (#304) 118 | * Enhance: adjust styling for setup instructions (#215, props @timse201) 119 | * Enhance: update hooks for Multisite initialization in WordPress 5.1 and above (#246, props @ouun) 120 | * Enhance: rework flush hooks and add some third-party triggers for Autoptimize and WooCommerce (#225, props @timse201) 121 | * Enhance: clean up some internal error suppressions (#256) 122 | * Enhance: inform user on cache clear in admin bar (#257, props @angcl) 123 | * Enhance: do not flush the cache for post revisions (#261, props @angcl) 124 | * Enhance: prevent unnecessary cache clearing in some cases (#223) (#224, props @timse201) 125 | * Enhance: remove empty directories when clearing the HDD cache (#289) 126 | * Enhance: introduce common interface for caching backends (#298, props @lloc) 127 | * Enhance: enhance examples for .htaccess and nginx configuration (#302) 128 | * Enhance: show admin notice instead of silent fallback to DB cache, if selected backend is unavailable (#305) 129 | * Enhance: disable gzip creation of required PHP extension is missing (#308) 130 | * Enhance: various internal code clean ups 131 | * Fix: invalidate cache when permalink changes (#285, #286, props @raffaelj) 132 | * Fix: remove empty directories when pruning the HDD cache (#289) 133 | * Fix: correctly add user-agent to robots.txt (#282) (#283) 134 | * Fix: exclude _sitemap.xml_ from caching (#242) (#254) 135 | * Fix: prevent cache generation of non-GET requests (#200) (#258) 136 | * Fix: prevent cache generation of requests with status different from 200 OK (#266) (#267, props @karlkowald) 137 | * Fix: prevent cache generation of non-HTML responses when using content negotiation (#265) (#273, props @Ancocodet) 138 | * Fix: fix styling for various dark mode plugins (#264) (#278) 139 | * Fix: fix SVG markup for icons in dashboard widget (#269, props @Latz) 140 | * Fix: added missing .gz suffix in htaccess (#287) (#291, props @raffaelj) 141 | * Fix: fix some brand names and unify spelling (#297, props @pedro-mendonca) 142 | * Maintenance: Tested up to WordPress 6.6 143 | 144 | ### 2.3.2 ### 145 | * Fix: enforce WordPress environment for caching modules (#221, props timse201) 146 | * Fix: Remove unnecessary build artifacts from plugin deployment (#226) 147 | * Fix: Fix input sanitization for APC proxy (#240) (#241) 148 | * Maintenance: Remove unused language folder (#214, props timse201) 149 | * Maintenance: Update documentation links (#211, #212, props timse201) 150 | * Maintenance: Update documentation links (#213, props timse201) 151 | * Maintenance: More precise tags in README file (#216, props timse201) 152 | * Maintenance: Tested up to WordPress 5.8 153 | 154 | ### 2.3.1 ### 155 | * Fix: clean up unused parameter evaluation after publishing a post to prevent PHP notice (#187) (#188) 156 | * Fix: correct minor spelling mistakes (#193, props timse201) 157 | * Fix: update support links (#194, props timse201) 158 | 159 | ### 2.3.0 ### 160 | * New: WP-CLI integration (#165, props derweili) 161 | * New: `cachify_flush_cache_hooks` filter added to modify all hooks that flush the cache 162 | * New: Flush cache when a user is created / updated / deleted 163 | * New: Flush cache when a term is created / updated / deleted (#169, props derweili) 164 | * New: Cache behavior after post modification is now configurable in plugin settings (#176) 165 | * Enhance: Cache exceptions/User-Agents translation (#52, props timse201) 166 | * Enhance: Readme FAQ (#51, props timse201) 167 | * Enhance: sizeable exclusion boxes + placeholder (#53, props timse201) 168 | * Enhance: FAQ and Support links (#55, props timse201) 169 | * Enhance: Add text caption to "flush cache" button 170 | * Enhance: Icon font converted to SVG (#64) 171 | * Enhance: Improved HDD cache invalidation for hierarchical post types (#71, props Syberspace) 172 | * Enhance: Unified and shortened HTML signature across all caching methods (#108) (#109) 173 | * Security: Tabnabbing prevention (#55, props timse201) 174 | * Maintenance: Tested up to WordPress 5.4 175 | 176 | ### 2.2.4 ### 177 | * Fixes caching for mixed HTTPS and HTTP setups 178 | * Fixes an issue with the icon styling in the admin toolbar 179 | * Ensures compatibility with the latest WordPress version 180 | 181 | ### 2.2.3 ### 182 | * New: Generated a POT file 183 | * New: Added German formal translation 184 | * Updated, translated + formatted README.md 185 | * Updated expired link URLs in plugin and languages files 186 | * Updated [plugin authors](https://gist.github.com/glueckpress/f058c0ab973d45a72720) 187 | 188 | ### 2.2.2 ### 189 | * Fix: parameter return by filter `dashboard_glance_items` 190 | * Generous use of the filter `esc_html` 191 | 192 | ### 2.2.1 ### 193 | * Fix for the PHP notice "Call to undefined function is_plugin_active_for_network" on WordPress Multisite 194 | 195 | ### 2.2.0 ### 196 | * Toolbar: Display of the "Flush the Cachify cache" button on the frontend 197 | * Toolbar: Controlling the display of the "Flush the Cachify cache" button via hook 198 | 199 | For the complete changelog, check out our [GitHub repository](https://github.com/pluginkollektiv/cachify). 200 | 201 | ## Upgrade Notice ## 202 | 203 | ### 2.3.2 ### 204 | This is a minor maintenance release. It is recommended for all users. 205 | 206 | ### 2.3.1 ### 207 | This is a minor bug fix release that prevents PHP warnings introduced in 2.3.0. It is recommended for all users. 208 | 209 | ### 2.3.0 ### 210 | To improve Cachify and make use of new core functions, we decided to drop support for WordPress 4.3 and older. Please make sure your WordPress is always up to date. 211 | 212 | ## Screenshots ## 213 | 1. Cachify Dashboard Widget 214 | 2. Cachify settings 215 | 3. Flush Cache button in admin bar 216 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | Test Me

Test Content.

', 37 | 3600, 38 | false 39 | ); 40 | 41 | $cached = Cachify_DB::get_item( '965b4abf2414e45036ab90c9d3f8dbc7' ); 42 | self::assertIsArray( $cached, 'item was not stored' ); 43 | self::assertEquals( 44 | 'Test Me

Test Content.

', 45 | $cached['data'], 46 | 'unexpected data in cache' 47 | ); 48 | self::assertIsInt( $cached['meta']['queries'], 'number of queries not filled' ); 49 | self::assertIsString( $cached['meta']['timer'], 'timing not filled' ); 50 | self::assertIsString( $cached['meta']['memory'], 'memory not filled' ); 51 | self::assertIsInt( $cached['meta']['time'], 'time not filled' ); 52 | 53 | // Another item. 54 | Cachify_DB::store_item( 55 | 'ef7e4a0540f6cde19e6eb658c69b0064', 56 | 'Test 2

Test Content #2.

', 57 | 3600, 58 | false 59 | ); 60 | self::assertIsArray( Cachify_DB::get_item( 'ef7e4a0540f6cde19e6eb658c69b0064' ), 'second item was not stored' ); 61 | 62 | // Delete the first item. 63 | Cachify_DB::delete_item( '965b4abf2414e45036ab90c9d3f8dbc7' ); 64 | self::assertFalse( Cachify_DB::get_item( '965b4abf2414e45036ab90c9d3f8dbc7' ), 'first item was not deleted' ); 65 | self::assertIsArray( Cachify_DB::get_item( 'ef7e4a0540f6cde19e6eb658c69b0064' ), 'second item should still be present' ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/test-cachify-hdd.php: -------------------------------------------------------------------------------- 1 | Test Me

Test Content.

', 70 | 3600, // Ignored. 71 | false 72 | ); 73 | self::assertTrue( Cachify_HDD::get_item( '965b4abf2414e45036ab90c9d3f8dbc7' ) ); 74 | self::assertTrue( is_file( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/testme/index.html' ) ); 75 | $cached = file_get_contents( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/testme/index.html' ); 76 | self::assertStringStartsWith( 77 | 'Test Me

Test Content.

78 | 79 | ', $cached ); 84 | 85 | // A subpage 86 | self::go_to( '/testme/sub' ); 87 | Cachify_HDD::store_item( 88 | '965b4abf2414e45036ab90c9d3f8dbc7', // Ignored. 89 | 'Test Me

This is a subpage.

', 90 | 3600, // Ignored. 91 | false 92 | ); 93 | self::assertTrue( Cachify_HDD::get_item( '965b4abf2414e45036ab90c9d3f8dbc7' ) ); 94 | self::assertTrue( is_file( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/testme/sub/index.html' ) ); 95 | 96 | // Another item. 97 | self::go_to( '/test2/' ); 98 | Cachify_HDD::store_item( 99 | 'ef7e4a0540f6cde19e6eb658c69b0064', // Ignored. 100 | 'Test 2

Test Content #2.

', 101 | 3600, // Ignored. 102 | true 103 | ); 104 | self::assertTrue( is_file( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/test2/index.html' ) ); 105 | $cached = file_get_contents( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/test2/index.html' ); 106 | self::assertStringStartsWith( 107 | 'Test 2

Test Content #2.

108 | 109 | ', $cached ); 114 | 115 | // Delete the first item. 116 | Cachify_HDD::delete_item( '965b4abf2414e45036ab90c9d3f8dbc7', 'http://example.org/testme/' ); 117 | self::assertFalse( is_file( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/testme/index.html' ), 'first item was not deleted' ); 118 | self::assertTrue( is_file( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/test2/index.html' ), 'second item should still be present' ); 119 | self::assertTrue( is_file( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/testme/sub/index.html' ), 'subpage should now have been deleted' ); 120 | 121 | // Delete the subpage. 122 | Cachify_HDD::delete_item( '965b4abf2414e45036ab90c9d3f8dbc7', 'http://example.org/testme/sub' ); 123 | self::assertFalse( is_dir( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/testme/sub/index.html' ), 'subpage item was not deleted' ); 124 | self::assertFalse( is_dir( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/testme/sub' ), 'empty directory was not deleted' ); 125 | 126 | // Clear the cache. 127 | Cachify_HDD::clear_cache(); 128 | self::assertFalse( is_dir( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/testme' ), 'empty directory was not deleted' ); 129 | self::assertFalse( is_dir( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/test2' ), 'second test page was not deleted' ); 130 | self::assertFalse( Cachify_HDD::get_item( '965b4abf2414e45036ab90c9d3f8dbc7' ) ); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /tests/test-cachify-memcached.php: -------------------------------------------------------------------------------- 1 | unavailable_method, 'unexpected name of unavailable method' ); 32 | $noop = new Cachify_NOOP(); 33 | self::assertSame( '', $noop->unavailable_method, 'unexpected default name of unavailable method' ); 34 | } 35 | 36 | /** 37 | * Test the actual caching. 38 | */ 39 | public function test_caching() { 40 | self::go_to( '/testme/' ); 41 | Cachify_NOOP::store_item( 42 | '965b4abf2414e45036ab90c9d3f8dbc7', 43 | 'Test Me

Test Content.

', 44 | 3600, 45 | false 46 | ); 47 | self::assertFalse( 48 | Cachify_NOOP::get_item('965b4abf2414e45036ab90c9d3f8dbc7'), 49 | "item should not have been stored" 50 | ); 51 | 52 | Cachify_NOOP::delete_item( '965b4abf2414e45036ab90c9d3f8dbc7' ); 53 | self::assertFalse( 54 | Cachify_NOOP::get_item('965b4abf2414e45036ab90c9d3f8dbc7'), 55 | "item present after deletion" 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/test-cachify-redis.php: -------------------------------------------------------------------------------- 1 | 10, 28 | 'test_2' => 20, 29 | ); 30 | } 31 | ); 32 | 33 | // Call flush registration. 34 | Cachify::register_flush_cache_hooks(); 35 | 36 | // Verify that the filter has been called. 37 | self::assertNotNull( $original_capture, 'Filter not called' ); 38 | self::assertEquals( 12, count( $original_capture ), 'Unexpected number of default hooks' ); 39 | self::assertEmpty( 40 | array_filter( 41 | $original_capture, 42 | function( $v ) { 43 | return 10 !== $v; 44 | } 45 | ), 46 | 'All default filters should have priority 10' 47 | ); 48 | 49 | // Verify that the action has been hooked with given priority. 50 | self::assertEquals( 51 | 10, 52 | has_action( 'test_1', array( Cachify::class, 'flush_total_cache' ) ), 53 | 'Flush action not hooked as expected' 54 | ); 55 | self::assertEquals( 56 | 20, 57 | has_action( 'test_2', array( Cachify::class, 'flush_total_cache' ) ), 58 | 'Flush action not hooked as expected' 59 | ); 60 | } 61 | 62 | /** 63 | * Test registration of scripts. 64 | */ 65 | public function test_register_scripts() { 66 | Cachify::register_scripts(); 67 | self::assertTrue( wp_script_is( 'cachify-admin-bar-flush', 'registered' ) ); 68 | $script = wp_scripts()->registered['cachify-admin-bar-flush']; 69 | self::assertStringEndsWith( 70 | '/js/admin-bar-flush.min.js', 71 | $script->src, 72 | 'unexpected script source' 73 | ); 74 | } 75 | 76 | /** 77 | * Test registration of styles. 78 | */ 79 | public function test_register_styles() { 80 | Cachify::register_styles(); 81 | self::assertTrue( wp_style_is( 'cachify-dashboard', 'registered' ) ); 82 | self::assertTrue( wp_style_is( 'cachify-admin-bar-flush', 'registered' ) ); 83 | 84 | $style = wp_styles()->registered['cachify-dashboard']; 85 | self::assertStringEndsWith( 86 | '/css/dashboard.min.css', 87 | $style->src, 88 | 'unexpected dashboard style source' 89 | ); 90 | 91 | $style = wp_styles()->registered['cachify-admin-bar-flush']; 92 | self::assertStringEndsWith( 93 | '/css/admin-bar-flush.min.css', 94 | $style->src, 95 | 'unexpected admin bar style source' 96 | ); 97 | } 98 | 99 | /** 100 | * Test single site plugin activation. 101 | */ 102 | public function test_on_activation() { 103 | self::assertFalse( get_option( 'cachify' ), 'Cachify option should not be initialized initially' ); 104 | Cachify::on_activation(); 105 | self::assertEquals( array() , get_option( 'cachify' ), 'Cachify option not initialized' ); 106 | } 107 | 108 | 109 | /** 110 | * Test hook for robots.txt customization. 111 | */ 112 | public function test_robots_txt() { 113 | // Initial robots.txt content. 114 | $robots_txt = "User-agent: *\nDisallow: /wordpress/wp-admin/\nAllow: /wordpress/wp-admin/admin-ajax.php\n"; 115 | 116 | // DB cache enabled. 117 | update_option( 118 | 'cachify', 119 | array( 120 | 'use_apc' => Cachify::METHOD_DB, 121 | 'change_robots_txt' => 1, 122 | ) 123 | ); 124 | new Cachify(); 125 | 126 | self::assertEquals( 127 | $robots_txt, 128 | Cachify::robots_txt( $robots_txt ), 129 | 'robots.txt should not be modified using DB cache' 130 | ); 131 | 132 | // HDD cache enabled. 133 | update_option( 134 | 'cachify', 135 | array( 136 | 'use_apc' => Cachify::METHOD_HDD, 137 | 'change_robots_txt' => 1, 138 | ) 139 | ); 140 | new Cachify(); 141 | 142 | self::assertEquals( 143 | $robots_txt . "\nUser-agent: *\nDisallow: */cache/cachify/\n", 144 | Cachify::robots_txt( $robots_txt ), 145 | 'robots.txt should have been modified using HDD cache' 146 | ); 147 | 148 | // Disable robots.txt modification. 149 | update_option( 150 | 'cachify', 151 | array( 152 | 'use_apc' => Cachify::METHOD_HDD, 153 | 'change_robots_txt' => 0, 154 | ) 155 | ); 156 | 157 | self::assertEquals( 158 | $robots_txt . "\nUser-agent: *\nDisallow: */cache/cachify/\n", 159 | Cachify::robots_txt( $robots_txt ), 160 | 'robots.txt should have been modified using HDD cache' 161 | ); 162 | } 163 | 164 | /** 165 | * Test call of hooks after flushing the cache. 166 | */ 167 | public function test_flushed_total_hook() { 168 | $flushed_total = array(); 169 | add_action( 170 | 'cachify_flushed_total_cache', 171 | function( $arg ) use ( &$flushed_total ) { 172 | $flushed_total[] = $arg; 173 | } 174 | ); 175 | 176 | Cachify::flush_total_cache( ); 177 | Cachify::flush_total_cache( false ); 178 | Cachify::flush_total_cache( true ); 179 | 180 | self::assertEquals( 181 | array( false, false, true ), 182 | $flushed_total, 183 | 'unexpected hook calls after flushing the cache' 184 | ); 185 | } 186 | 187 | /** 188 | * Test call of hooks after remove cache by URL. 189 | */ 190 | public function test_removed_by_url_hook() { 191 | $flushed_single = array(); 192 | add_action( 193 | 'cachify_removed_cache_by_url', 194 | function( $url, $hash ) use ( &$flushed_single ) { 195 | $flushed_single[] = array( $url, $hash ); 196 | }, 197 | 10, 198 | 2 199 | ); 200 | 201 | Cachify::remove_page_cache_by_url( 'https://example.com/foo' ); 202 | Cachify::remove_page_cache_by_url( 'https://example.com/bar' ); 203 | 204 | self::assertEquals( 205 | array( 206 | array( 'https://example.com/foo', '45cc6f45ed67ff733a550ceb93ac2694.cachify' ), 207 | array( 'https://example.com/bar', '7b579c8fd2d8d8685f9680e7f5fedadc.cachify' ), 208 | ), 209 | $flushed_single, 210 | 'unexpected hook calls after removing by url' 211 | ); 212 | 213 | } 214 | } 215 | --------------------------------------------------------------------------------