diff --git a/wp-content/plugins/pantheon-advanced-page-cache/LICENSE b/wp-content/plugins/pantheon-advanced-page-cache/LICENSE new file mode 100644 index 000000000..d159169d1 --- /dev/null +++ b/wp-content/plugins/pantheon-advanced-page-cache/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/wp-content/plugins/pantheon-advanced-page-cache/assets/css/styles.css b/wp-content/plugins/pantheon-advanced-page-cache/assets/css/styles.css new file mode 100644 index 000000000..2f2bbac70 --- /dev/null +++ b/wp-content/plugins/pantheon-advanced-page-cache/assets/css/styles.css @@ -0,0 +1,50 @@ +.pantheon-page-cache hr { + display: none; +} +.pantheon-page-cache tr.cache-default-max-age { + display: block; + padding: 10px; + background: #ffffff; + color: #1e1e1e; + box-shadow: 0 0 10px 5px rgba(0, 0, 0, 0.1); +} +.pantheon-page-cache tr.cache-default-max-age td { + padding: 0; +} +.pantheon-page-cache tr.cache-default-max-age .pantheon-cache-default-max-age { + padding: 0 10px 10px 0; +} +.pantheon-page-cache tr.cache-default-max-age .pantheon-cache-default-max-age h3 { + margin-top: 0.5em; +} +.pantheon-page-cache tr.cache-default-max-age .pantheon-cache-default-max-age i { + margin-right: 10px; + line-height: 1em; +} +.pantheon-page-cache tr.cache-default-max-age .pantheon-cache-default-max-age-info-bar { + background: #1d7afc; + color: #ffffff; + padding: 10px 0 10px 10px; + text-align: left; + display: block; + width: 100%; + margin-bottom: 1em; +} +.pantheon-page-cache tr.cache-default-max-age .pantheon-cache-default-max-age-info-bar a { + color: #ffffff; + text-decoration: underline; +} +.pantheon-page-cache tr.cache-default-max-age .pantheon-cache-default-max-age-info-bar a:hover, .pantheon-page-cache tr.cache-default-max-age .pantheon-cache-default-max-age-info-bar a:active { + color: #ffffff; + text-decoration: none; +} +.pantheon-page-cache tr.cache-default-max-age .pantheon-cache-default-max-age select { + background-color: #ffffff; + color: #1e1e1e; + border: 1px solid #757575; + border-radius: 0; +} +.pantheon-page-cache tr.cache-default-max-age .pantheon-cache-default-max-age-description { + color: #757575; + margin: 10px 0 0 0; +} \ No newline at end of file diff --git a/wp-content/plugins/pantheon-advanced-page-cache/assets/css/styles.min.css b/wp-content/plugins/pantheon-advanced-page-cache/assets/css/styles.min.css new file mode 100644 index 000000000..88f03fdb8 --- /dev/null +++ b/wp-content/plugins/pantheon-advanced-page-cache/assets/css/styles.min.css @@ -0,0 +1 @@ +.pantheon-page-cache hr{display:none}.pantheon-page-cache tr.cache-default-max-age{display:block;padding:10px;background:#fff;color:#1e1e1e;box-shadow:0 0 10px 5px rgba(0,0,0,.1)}.pantheon-page-cache tr.cache-default-max-age td{padding:0}.pantheon-page-cache tr.cache-default-max-age .pantheon-cache-default-max-age{padding:0 10px 10px 0}.pantheon-page-cache tr.cache-default-max-age .pantheon-cache-default-max-age h3{margin-top:.5em}.pantheon-page-cache tr.cache-default-max-age .pantheon-cache-default-max-age i{margin-right:10px;line-height:1em}.pantheon-page-cache tr.cache-default-max-age .pantheon-cache-default-max-age-info-bar{background:#1d7afc;color:#fff;padding:10px 0 10px 10px;text-align:left;display:block;width:100%;margin-bottom:1em}.pantheon-page-cache tr.cache-default-max-age .pantheon-cache-default-max-age-info-bar a{color:#fff;text-decoration:underline}.pantheon-page-cache tr.cache-default-max-age .pantheon-cache-default-max-age-info-bar a:active,.pantheon-page-cache tr.cache-default-max-age .pantheon-cache-default-max-age-info-bar a:hover{color:#fff;text-decoration:none}.pantheon-page-cache tr.cache-default-max-age .pantheon-cache-default-max-age select{background-color:#fff;color:#1e1e1e;border:1px solid #757575;border-radius:0}.pantheon-page-cache tr.cache-default-max-age .pantheon-cache-default-max-age-description{color:#757575;margin:10px 0 0} \ No newline at end of file diff --git a/wp-content/plugins/pantheon-advanced-page-cache/assets/sass/main.scss b/wp-content/plugins/pantheon-advanced-page-cache/assets/sass/main.scss new file mode 100644 index 000000000..5cadc4479 --- /dev/null +++ b/wp-content/plugins/pantheon-advanced-page-cache/assets/sass/main.scss @@ -0,0 +1,62 @@ +.pantheon-page-cache { + hr { + display: none; + } + + tr.cache-default-max-age { + display: block; + padding: $padding; + background: $white; + color: $charcoal; + box-shadow: $shadow; + + td { + padding: 0; + } + + .pantheon-cache-default-max-age { + padding: 0 $padding $padding 0; + + h3 { + margin-top: 0.5em; + } + + i { + margin-right: $padding; + line-height: 1.0em; + } + + &-info-bar { + background: $blue; + color: $white; + padding: $padding 0 $padding $padding; + text-align: left; + display: block; + width: 100%; + margin-bottom: 1em; + + a { + color: $white; + text-decoration: underline; + } + + a:hover, a:active { + color: $white; + text-decoration: none; + } + } + + select { + background-color: $white; + color: $charcoal; + border: 1px solid $gray; + border-radius: 0; + } + + &-description { + color: $gray; + margin: $padding 0 0 0; + } + } + } +} diff --git a/wp-content/plugins/pantheon-advanced-page-cache/assets/sass/mixins.scss b/wp-content/plugins/pantheon-advanced-page-cache/assets/sass/mixins.scss new file mode 100644 index 000000000..cfe7507e2 --- /dev/null +++ b/wp-content/plugins/pantheon-advanced-page-cache/assets/sass/mixins.scss @@ -0,0 +1,9 @@ +// Colors +$blue: #1d7afc; +$white: #ffffff; +$gray: #757575; +$charcoal: #1e1e1e; + +$shadow: 0 0 10px 5px rgba(0, 0, 0, 0.1); + +$padding: 10px; diff --git a/wp-content/plugins/pantheon-advanced-page-cache/assets/sass/styles.scss b/wp-content/plugins/pantheon-advanced-page-cache/assets/sass/styles.scss new file mode 100644 index 000000000..af07f6dd8 --- /dev/null +++ b/wp-content/plugins/pantheon-advanced-page-cache/assets/sass/styles.scss @@ -0,0 +1,2 @@ +@import 'mixins'; +@import 'main'; diff --git a/wp-content/plugins/pantheon-advanced-page-cache/inc/admin-interface.php b/wp-content/plugins/pantheon-advanced-page-cache/inc/admin-interface.php new file mode 100644 index 000000000..8cd0b24c7 --- /dev/null +++ b/wp-content/plugins/pantheon-advanced-page-cache/inc/admin-interface.php @@ -0,0 +1,610 @@ + 1.4.0. + if ( version_compare( PANTHEON_MU_PLUGIN_VERSION, '1.4.0', '>' ) ) { + add_action( 'admin_notices', __NAMESPACE__ . '\\admin_notice_maybe_recommend_higher_max_age' ); + } else { + add_action( 'admin_notices', __NAMESPACE__ . '\\admin_notice_old_mu_plugin' ); + } + } else { + add_action( 'admin_notices', __NAMESPACE__ . '\\admin_notice_no_mu_plugin' ); + } + + add_filter( 'site_status_tests', __NAMESPACE__ . '\\default_cache_max_age_test' ); + add_action( 'update_option_pantheon-cache', __NAMESPACE__ . '\\clear_max_age_compare_cache' ); + add_action( 'admin_init', __NAMESPACE__ . '\\set_max_age_to_default' ); + add_action( 'admin_notices', __NAMESPACE__ . '\\max_age_updated_admin_notice' ); + add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\\enqueue_admin_assets' ); + // Not implementing the info bar for now. + add_filter( 'pantheon_apc_max_age_header_enabled', '__return_false' ); + add_filter( 'pantheon_cache_max_age_field_before_html', __NAMESPACE__ . '\\add_max_age_setting_header' ); + add_filter( 'pantheon_cache_max_age_field_after_html', __NAMESPACE__ . '\\add_max_age_setting_description' ); + add_filter( 'pantheon_cache_max_age_input', __NAMESPACE__ . '\\update_default_ttl_input' ); + add_filter( 'pantheon_cache_max_age_input_allowed_html', __NAMESPACE__ . '\\max_age_input_allowed_html' ); + add_filter( 'nonce_life', __NAMESPACE__ . '\\filter_nonce_cache_lifetime' ); +} + +/** + * Enqueue admin assets. + * + * @since 2.0.0 + * @return void + */ +function enqueue_admin_assets() { + $screen = get_current_screen(); + + if ( 'settings_page_pantheon-cache' !== $screen->id ) { + return; + } + + // If WP_DEBUG is true, append a timestamp to the end of the path so we get a fresh copy of the css. + $debug = defined( 'WP_DEBUG' ) && WP_DEBUG ? '-' . time() : ''; + // Use minified css unless SCRIPT_DEBUG is true. + $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; + wp_enqueue_style( 'papc-admin', plugin_dir_url( __DIR__ ) . "assets/css/styles$min.css", [], '2.0.0' . $debug ); +} + +/** + * Add a header to the max-age setting field. + * + * @since 2.0.0 + * @return string + */ +function add_max_age_setting_header() { + ob_start(); + ?> +
+ + + above the recommended value.', 'pantheon-advanced-page-cache' ); + $below_recommended_message = __( 'Your cache maximum age is currently below the recommended value.', 'pantheon-advanced-page-cache' ); + $recommended_message = __( 'Your cache maximum age is currently set to the recommended value.', 'pantheon-advanced-page-cache' ); + $recommendation_message = get_current_max_age() > WEEK_IN_SECONDS ? $above_recommended_message : ( get_current_max_age() < WEEK_IN_SECONDS ? $below_recommended_message : $recommended_message ); + $filtered_message = $is_filtered ? sprintf( + // translators: %s is the humanized max-age. + __( 'This value has been hardcoded to %s via a filter.', 'pantheon-advanced-page-cache' ), + '' . humanized_max_age() . '' + ) : ''; + $pantheon_cache = get_option( 'pantheon-cache', [] ); + $has_custom_ttl = isset( $pantheon_cache['default_ttl'] ) && ! array_key_exists( $pantheon_cache['default_ttl'], max_age_options() ); + $filtered_message .= $has_custom_ttl && ! $is_filtered ? '
' . __( 'Warning:The cache max age is not one of the recommended values. If this is not intentional, you should remove this custom value and save the settings, then select one of the options from the dropdown.', 'pantheon-advanced-page-cache' ) : ''; + + ob_start(); + ?> +

+ +

+
+ ' . __( 'Custom: ', 'pantheon-advanced-page-cache' ) . '' . $default_input . '

'; + return $output; + } + + $input_field = ''; + $output .= $input_field; + return $output; +} + +/** + * Filter the allowed HTML for the max-age input field. + * + * @since 2.0.0 + * @param array $allowed_html The allowed HTML. + * @return array + */ +function max_age_input_allowed_html( $allowed_html ) { + $allowed_html['p'] = []; + $allowed_html['strong'] = []; + return $allowed_html; +} + +/** + * Get the default max age options. Default values are 1 week, 1 month, 1 year. + * + * @since 2.0.0 + * @return array + */ +function max_age_options() { + $options = [ + WEEK_IN_SECONDS => esc_html__( 'Recommended (1 week)', 'pantheon-cache' ), + MONTH_IN_SECONDS => esc_html__( 'Extended (1 month)', 'pantheon-cache' ), + YEAR_IN_SECONDS => esc_html__( 'Perpetual (1 year)', 'pantheon-cache' ), + ]; + + /** + * Allow the default TTL options to be filtered. + * + * @param array $options The default TTL options (time value => display text). + * @return array + */ + return apply_filters( 'pantheon_apc_max_age_options', $options ); +} + +/** + * Display an admin notice if the Pantheon MU plugin was not found. + * + * @since 2.0.0 + * @return void + */ +function admin_notice_no_mu_plugin() { + /** + * Allow disabling the admin notice. + * + * @param bool $disable_admin_notices Whether to disable the admin notice. + * @param string $callback The name of the current callback function. + */ + if ( apply_filters( 'pantheon_apc_disable_admin_notices', false, __FUNCTION__ ) ) { + return; + } + + wp_admin_notice( + // translators: %s is a link to the Pantheon MU plugin. + sprintf( __( 'Pantheon Advanced Page Cache works best on the Pantheon platform. If you are working inside a Pantheon environment, ensure your site is using the Pantheon MU plugin.', 'pantheon-advanced-page-cache' ), 'https://github.com/pantheon-systems/pantheon-mu-plugin' ), + [ + 'type' => 'error', + 'dismissible' => true, + ] + ); +} + +/** + * Display an admin notice if the Pantheon MU plugin is out of date. + * + * @since 2.0.0 + * @return void + */ +function admin_notice_old_mu_plugin() { + $current_screen = get_current_screen(); + + if ( apply_filters( 'pantheon_apc_disable_admin_notices', false, __FUNCTION__ ) || 'settings_page_pantheon-cache' !== $current_screen->id ) { + return; + } + + $mu_plugin_version = PANTHEON_MU_PLUGIN_VERSION; + $message = sprintf( + // translators: %1$s is a link to the Pantheon MU plugin, %2$s is the version of the MU plugin. + __( 'You appear to have an old version of the Pantheon MU plugin. 1.4.0 or above expected but %2$s found.', 'pantheon-advanced-page-cache' ), + 'https://github.com/pantheon-systems/pantheon-mu-plugin', + $mu_plugin_version + ); + + // Check if there's a composer.json file in the root of the site. + if ( file_exists( ABSPATH . 'composer.json' ) ) { + $message .= ' ' . __( 'If you are using Composer, you can update the MU plugin by running composer update.', 'pantheon-advanced-page-cache' ); + } else { + $message .= ' ' . __( 'You should Apply Updates from the Pantheon Dashboard to get the latest version of WordPress and the Pantheon MU plugin.', 'pantheon-advanced-page-cache' ); + } + + wp_admin_notice( + // translators: %s is a link to the Pantheon MU plugin. + $message, + [ + 'type' => 'warning', + 'dismissible' => true, + ] + ); +} + +/** + * Display an admin notice if the max-age is less than a week but not equal to 600 seconds. + * + * @since 2.0.0 + * @return void + */ +function admin_notice_maybe_recommend_higher_max_age() { + $current_screen = get_current_screen(); + $global_warning_shown = current_user_can( 'manage_options' ) ? get_user_meta( get_current_user_id(), 'pantheon_max_age_global_warning_notice', true ) : true; + + if ( apply_filters( 'pantheon_apc_disable_admin_notices', false, __FUNCTION__ ) ) { + return; + } + + $message = ''; + $dismissable = true; + $max_age_rank = max_age_compare(); + $current_max_age = get_current_max_age(); + + // Check if the max-age rank is acceptable or if current max-age is 600 seconds and we haven't yet reset it to the default. + if ( + $max_age_rank === 0 || + ( $current_max_age === 600 && ! get_option( 'pantheon_max_age_updated', false ) ) + ) { + return; + } + + if ( isset( $current_screen->id ) && 'settings_page_pantheon-cache' === $current_screen->id ) { + // If the current max-age value has a rank of 3 or more (10 is the highest), we'll note that it's very low. + $very_low = $max_age_rank > 3 ? __( 'This is a very low value and may not be optimal for your site.', 'pantheon-advanced-page-cache' ) : ''; + $message = sprintf( + // translators: %1$s is the current max-age, %2$d is the current max-age in seconds, %3$s is a message that displays if the value is very low, %44d is the recommended max age in seconds, %5$s is the humanized recommended max age, %6$s is debug information that is written to the HTML DOM but not displayed. + __( 'The cache max age is currently set to %1$s. %2$s Consider increasing the cache max age to at least %3$s.%4$s', 'pantheon-advanced-page-cache' ), + humanized_max_age(), + $very_low, + humanized_max_age( true ), + sprintf( '', $max_age_rank ) + ); + } + + // Global notice on all pages _except_ the Pantheon cache settings page. + if ( ! $global_warning_shown && ( ! isset( $current_screen->id ) || 'settings_page_pantheon-cache' !== $current_screen->id ) ) { + $message = sprintf( + // translators: %s is a link to the Pantheon GCDN configuration page. + __( 'Your site\'s cache max age is set below the recommendation (1 week). Visit the Pantheon GCDN configuration page to update the setting.%2$s' ), + admin_url( 'options-general.php?page=pantheon-cache' ), + sprintf( '', $max_age_rank ) + ); + $dismissable = false; + update_user_meta( get_current_user_id(), 'pantheon_max_age_global_warning_notice', true ); + } + + if ( ! empty( $message ) ) { + // Escalating notice types based on the max-age rank. + $notice_type = ( $max_age_rank === 1 ? 'info' : $max_age_rank > 3 ) ? 'error' : 'warning'; + + wp_admin_notice( + $message, + [ + 'type' => $notice_type, + 'dismissible' => $dismissable, + ] + ); + } +} + +/** + * Get the current max-age value. + * + * This comes from the Pantheon mu-plugin and only exists if settings were actually saved. + * + * If the site existed prior to 1.4.0 of the mu-plugin, the default value is 600 seconds. Otherwise, the default value is 1 week. + * + * @since 2.0.0 + * @return int + */ +function get_current_max_age() { + $options = get_option( 'pantheon-cache', [] ); + + // If the default_ttl option is not set, we're using the default, which is 1 week. + if ( ! isset( $options['default_ttl'] ) ) { + return get_default_max_age(); + } + + return apply_filters( 'pantheon_cache_default_max_age', $options['default_ttl'] ); +} + +/** + * Add a test to the Site Health page to check the cache max-age. + * + * @param array $tests The Site Health tests. + * + * @since 2.0.0 + * @return array + */ +function default_cache_max_age_test( $tests ) { + $tests['direct']['pantheon_edge_cache'] = [ + 'label' => __( 'Pantheon Edge Cache', 'pantheon-advanced-page-cache' ), + 'test' => __NAMESPACE__ . '\\test_cache_max_age', + ]; + + return $tests; +} + +/** + * Get the humanized max-age. + * + * @param bool $recommended Whether to get the recommended max-age. + * + * @since 2.0.0 + * @return string + */ +function humanized_max_age( $recommended = false ) { + $time = time(); + $current_max_age = $recommended ? get_default_max_age() : get_current_max_age(); + $humanized_time = human_time_diff( $time, $time + $current_max_age ); + + return $humanized_time; +} + +/** + * Get the default max-age. + * + * @since 2.0.0 + * @return int + */ +function get_default_max_age() { + return apply_filters( 'pantheon_cache_default_max_age', WEEK_IN_SECONDS ); +} + +/** + * Compare the current max-age to the default max-age. + * + * @since 2.0.0 + * @return int A ranked value from 0 to 10 where 0 is optimal (equal to or greater than the recommended max age) and 10 is very bad. + */ +function max_age_compare() { + $cached_rank = get_transient( 'papc_max_age_compare' ); + + if ( false !== $cached_rank ) { + return $cached_rank; + } + + $current_max_age = get_current_max_age(); + $default_max_age = get_default_max_age(); + $diff = $current_max_age - $default_max_age; + + if ( $diff >= 0 ) { + return 0; + } + + // Rank the difference on a scale of 0 ($current_max_age >= $default_max_age) to 10 and return the rank int. + $rank = round( abs( $diff ) / $default_max_age * 10 ); + + $cached_rank = min( max( $rank, 1 ), 10 ); + set_transient( 'papc_max_age_compare', $cached_rank, WEEK_IN_SECONDS ); + return $cached_rank; +} + +/** + * The GCDN cache max-age Site Health test. + * + * @since 2.0.0 + * @return array + */ +function test_cache_max_age() { + $default_max_age = get_default_max_age(); + $current_max_age = get_current_max_age(); + $humanized_time = humanized_max_age(); + $humanized_reccomended_time = humanized_max_age( true ); + $recommend_color = max_age_compare() > 3 ? 'red' : 'orange'; + + if ( $current_max_age < $default_max_age ) { + $result = [ + 'label' => __( 'Pantheon GCDN Cache Max Age', 'pantheon-advanced-page-cache' ), + 'status' => 'recommended', + 'badge' => [ + 'label' => __( 'Performance', 'pantheon-advanced-page-cache' ), + 'color' => $recommend_color, + ], + 'description' => sprintf( + // translators: %1$s is the current humanized max-age, %2$s is the recommended max-age, %3$d is the admin URL to change the setting. + __( 'The Pantheon GCDN cache max age is currently set to %1$s. We recommend increasing to %2$s. You can increase the cache max age in the Pantheon Page Cache settings.', 'pantheon-advanced-page-cache' ), + $humanized_time, + $humanized_reccomended_time, + admin_url( 'options-general.php?page=pantheon-cache' ) + ), + 'test' => 'pantheon_edge_cache', + ]; + + return $result; + } + + $result = [ + 'label' => sprintf( + // translators: %s is the humanized time. + __( 'Pantheon GCDN Cache Max Age set to %1$s', 'pantheon-advanced-page-cache' ), + $humanized_time, + $humanized_reccomended_time + ), + 'status' => 'good', + 'badge' => [ + 'label' => __( 'Performance', 'pantheon-advanced-page-cache' ), + 'color' => 'blue', + ], + 'description' => sprintf( + '%1$s
%2$s', + sprintf( + // translators: %1$s is the current max-age, %2$s is the recommended max-age. + __( 'The Pantheon cache max age is currently set to %1$s. Our recommendation is %2$s or more.', 'pantheon-advanced-page-cache' ), + $humanized_time, + $humanized_reccomended_time + ), + sprintf( + // translators: %s is a link to the cache configuration guide. + __( 'View our cache configuration guide for more information.', 'pantheon-advanced-page-cache' ), + 'https://docs.pantheon.io/guides/wordpress-configurations/wordpress-cache-plugin#pantheon-page-cache-plugin-configuration' + ) + ), + 'test' => 'pantheon_edge_cache', + ]; + + return $result; +} + +/** + * Clear the max-age compare cache when the max-age is updated. + * + * @since 2.0.0 + * @return void + */ +function clear_max_age_compare_cache() { + delete_transient( 'papc_max_age_compare' ); +} + +/** + * Set the default_ttl from the mu-plugin to WEEK_IN_SECONDS if it was saved as 600 seconds. + * + * @since 2.0.0 + * @return bool + */ +function set_max_age_to_default() { + $pantheon_cache = get_option( 'pantheon-cache', [] ); + $pantheon_max_age_updated = get_option( 'pantheon_max_age_updated', false ); + + // If we've already done this, bail. + if ( $pantheon_max_age_updated ) { + return; + } + + // If nothing is saved, bail. The default is used automatically. + if ( ! isset( $pantheon_cache['default_ttl'] ) ) { + return; + } + + // Everything beyond this point assumes the max age has been set manually or will be set to the default. + update_option( 'pantheon_max_age_updated', true ); + + // If the default_ttl is not 600, bail. + if ( 600 !== $pantheon_cache['default_ttl'] ) { + return; + } + + // Set the max age. At this point, we should only be here if it was set to 600. We're using the filter here in case someone has overridden the default. + $pantheon_cache['default_ttl'] = apply_filters( 'pantheon_cache_default_max_age', WEEK_IN_SECONDS ); + + // Update the option and set the max_age_updated flag and show the admin notice. + update_option( 'pantheon-cache', $pantheon_cache ); +} + +/** + * Display an admin notice if the max-age was updated. + * + * @since 2.0.0 + * @return void + */ +function max_age_updated_admin_notice() { + // Check if notices should be disabled. This includes if the user is using a version of WordPress that does not support wp_admin_notice. + if ( apply_filters( 'pantheon_apc_disable_admin_notices', false, __FUNCTION__ ) ) { + return; + } + + $pantheon_cache = get_option( 'pantheon-cache', [] ); + $current_user_id = get_current_user_id(); + + // Can the user manage options? If not, don't show the notice. + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + + // Check if the max-age was updated. + $max_age_updated = get_option( 'pantheon_max_age_updated', false ); + if ( ! $max_age_updated ) { + return; + } + + // Check user meta to see if this user has seen this notice before. + $dismissed = get_user_meta( $current_user_id, 'pantheon_max_age_updated_notice', true ); + if ( $dismissed ) { + return; + } + + if ( ! isset( $pantheon_cache['default_ttl'] ) || $pantheon_cache['default_ttl'] !== WEEK_IN_SECONDS ) { + return; + } + + // If we got here, this is the _first time_ this user has seen this notice since the option was updated. Show the notice and update the user meta. + wp_admin_notice( + sprintf( + // translators: %1$s is the humanized max-age, %2$d is a link to the Pantheon documentation. + __( 'The Pantheon GCDN cache max age has been updated. The previous value was 10 minutes. The new value is %1$s. For more information, refer to the Pantheon documentation.', 'pantheon-advanced-page-cache' ), + humanized_max_age(), + 'https://docs.pantheon.io/guides/wordpress-configurations/wordpress-cache-plugin' + ), + [ + 'type' => 'info', + 'dismissible' => false, + ] + ); + + if ( ! $max_age_updated ) { + return; + } + + // Update the user meta to prevent this notice from showing again after they've seen it once. + update_user_meta( $current_user_id, 'pantheon_max_age_updated_notice', true ); +} + +/** + * Filter the nonce cache lifetime. + * + * @param int $lifetime The lifetime of the nonce. + * + * @since 2.0.0 + * @return int + */ +function filter_nonce_cache_lifetime( $lifetime ) { + // Bail early if we're in the admin. + if ( is_admin() ) { + return $lifetime; + } + + // Filter the cache default max age to less than the nonce lifetime when creating nonces on the front-end. This prevents the cache from keeping the nonce around longer than it should. + add_filter( 'pantheon_cache_default_max_age', function () use ( $lifetime ) { + return $lifetime - HOUR_IN_SECONDS; + } ); + + return $lifetime; +} diff --git a/wp-content/plugins/pantheon-advanced-page-cache/inc/class-cli.php b/wp-content/plugins/pantheon-advanced-page-cache/inc/class-cli.php new file mode 100644 index 000000000..64c49e07f --- /dev/null +++ b/wp-content/plugins/pantheon-advanced-page-cache/inc/class-cli.php @@ -0,0 +1,104 @@ +... + * : One or more surrogate keys. + * + * ## EXAMPLES + * + * # Purge the 'post-1' surrogate key from cache. + * $ wp pantheon cache purge-key post-1 + * Success: Purged key. + * + * @subcommand purge-key + */ + public function purge_key( $args ) { + $ret = pantheon_wp_clear_edge_keys( $args ); + if ( is_wp_error( $ret ) ) { + WP_CLI::error( $ret ); + } else { + $message = count( $args ) > 1 ? 'Purged keys.' : 'Purged key.'; + WP_CLI::success( $message ); + } + } + + /** + * Purge one or more paths from cache. + * + * ## OPTIONS + * + * ... + * : One or more paths. + * + * ## EXAMPLES + * + * # Purge the homepage from cache. + * $ wp pantheon cache purge-path '/' + * Success: Purged path. + * + * @subcommand purge-path + */ + public function purge_path( $args ) { + $ret = pantheon_wp_clear_edge_paths( $args ); + if ( is_wp_error( $ret ) ) { + WP_CLI::error( $ret ); + } else { + $message = count( $args ) > 1 ? 'Purged paths.' : 'Purged path.'; + WP_CLI::success( $message ); + } + } + + /** + * Purge the entire page cache. + * + * WARNING! Purging the entire page cache can have a severe performance + * impact on a high-traffic site. We encourage you to explore other options + * first. + * + * ## OPTIONS + * + * [--yes] + * : Answer yes to the confirmation message. + * + * ## EXAMPLES + * + * # Purging the entire page cache will display a confirmation prompt. + * $ wp pantheon cache purge-all + * Are you sure you want to purge the entire page cache? [y/n] y + * Success: Purged page cache. + * + * @subcommand purge-all + */ + public function purge_all( $_, $assoc_args ) { + WP_CLI::confirm( 'Are you sure you want to purge the entire page cache?', $assoc_args ); + $ret = pantheon_wp_clear_edge_all(); + if ( is_wp_error( $ret ) ) { + WP_CLI::error( $ret ); + } else { + WP_CLI::success( 'Purged page cache.' ); + } + } +} diff --git a/wp-content/plugins/pantheon-advanced-page-cache/inc/class-emitter.php b/wp-content/plugins/pantheon-advanced-page-cache/inc/class-emitter.php new file mode 100644 index 000000000..5b1aa7cc7 --- /dev/null +++ b/wp-content/plugins/pantheon-advanced-page-cache/inc/class-emitter.php @@ -0,0 +1,462 @@ + true ], 'objects' ) as $post_type ) { + add_filter( "rest_prepare_{$post_type->name}", [ __CLASS__, 'filter_rest_prepare_post' ], 10, 3 ); + $base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name; + self::get_instance()->rest_api_collection_endpoints[ '/wp/v2/' . $base ] = $post_type->name; + } + foreach ( get_taxonomies( [ 'show_in_rest' => true ], 'objects' ) as $taxonomy ) { + add_filter( "rest_prepare_{$taxonomy->name}", [ __CLASS__, 'filter_rest_prepare_term' ], 10, 3 ); + $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; + self::get_instance()->rest_api_collection_endpoints[ '/wp/v2/' . $base ] = $taxonomy->name; + } + add_filter( 'rest_prepare_comment', [ __CLASS__, 'filter_rest_prepare_comment' ], 10, 3 ); + self::get_instance()->rest_api_collection_endpoints['/wp/v2/comments'] = 'comment'; + add_filter( 'rest_prepare_user', [ __CLASS__, 'filter_rest_prepare_user' ], 10, 3 ); + add_filter( 'rest_pre_get_setting', [ __CLASS__, 'filter_rest_pre_get_setting' ], 10, 2 ); + self::get_instance()->rest_api_collection_endpoints['/wp/v2/users'] = 'user'; + } + + /** + * Reset surrogate keys before a REST API response is generated. + * + * @param mixed $result Response to replace the requested version with. + * @param WP_REST_Server $server Server instance. + * @param WP_REST_Request $request Request used to generate the response. + */ + public static function filter_rest_pre_dispatch( $result, $server, $request ) { + if ( isset( self::get_instance()->rest_api_collection_endpoints[ $request->get_route() ] ) ) { + self::get_instance()->rest_api_surrogate_keys[] = 'rest-' . self::get_instance()->rest_api_collection_endpoints[ $request->get_route() ] . '-collection'; + } + return $result; + } + + /** + * Render surrogate keys after a REST API response is prepared + * + * @param WP_HTTP_Response $result Result to send to the client. Usually a WP_REST_Response. + * @param WP_REST_Server $server Server instance. + */ + public static function filter_rest_post_dispatch( $result, $server ) { + $keys = self::get_rest_api_surrogate_keys(); + if ( ! empty( $keys ) && $result instanceof \WP_REST_Response ) { + $result->header( self::HEADER_KEY, implode( ' ', $keys ) ); + } + return $result; + } + + /** + * Determine which posts are present in a REST API response. + * + * @param WP_REST_Response $response The response object. + * @param WP_Post $post Post object. + * @param WP_REST_Request $request Request object. + */ + public static function filter_rest_prepare_post( $response, $post, $request ) { + self::get_instance()->rest_api_surrogate_keys[] = 'rest-post-' . $post->ID; + return $response; + } + + /** + * Determine which terms are present in a REST API response. + * + * @param WP_REST_Response $response The response object. + * @param WP_Post $term Term object. + * @param WP_REST_Request $request Request object. + */ + public static function filter_rest_prepare_term( $response, $term, $request ) { + self::get_instance()->rest_api_surrogate_keys[] = 'rest-term-' . $term->term_id; + return $response; + } + + /** + * Determine which comments are present in a REST API response. + * + * @param WP_REST_Response $response The response object. + * @param WP_Comment $comment The original comment object. + * @param WP_REST_Request $request Request used to generate the response. + */ + public static function filter_rest_prepare_comment( $response, $comment, $request ) { + self::get_instance()->rest_api_surrogate_keys[] = 'rest-comment-' . $comment->comment_ID; + self::get_instance()->rest_api_surrogate_keys[] = 'rest-comment-post-' . $comment->comment_post_ID; + return $response; + } + + /** + * Determine which users are present in a REST API response. + * + * @param WP_REST_Response $response The response object. + * @param WP_Post $user User object. + * @param WP_REST_Request $request Request object. + */ + public static function filter_rest_prepare_user( $response, $user, $request ) { + self::get_instance()->rest_api_surrogate_keys[] = 'rest-user-' . $user->ID; + return $response; + } + + /** + * Determine which settings are present in a REST API request + * + * @param mixed $result Value to use for the requested setting. Can be a scalar + * matching the registered schema for the setting, or null to + * follow the default get_option() behavior. + * @param string $name Setting name (as shown in REST API responses). + */ + public static function filter_rest_pre_get_setting( $result, $name ) { + self::get_instance()->rest_api_surrogate_keys[] = 'rest-setting-' . $name; + return $result; + } + + /** + * Get the surrogate keys to be included in this view. + * + * Surrogate keys are generated based on the main WP_Query. + * + * @return array + */ + public static function get_main_query_surrogate_keys() { + global $wp_query; + + $keys = []; + if ( is_front_page() ) { + $keys[] = 'front'; + } + if ( is_home() ) { + $keys[] = 'home'; + } + if ( is_404() ) { + $keys[] = '404'; + } + if ( is_feed() ) { + $keys[] = 'feed'; + } + if ( is_date() ) { + $keys[] = 'date'; + } + if ( is_paged() ) { + $keys[] = 'paged'; + } + if ( is_search() ) { + $keys[] = 'search'; + if ( $wp_query->found_posts ) { + $keys[] = 'search-results'; + } else { + $keys[] = 'search-no-results'; + } + } + + if ( ! empty( $wp_query->posts ) ) { + foreach ( $wp_query->posts as $p ) { + $keys[] = 'post-' . $p->ID; + if ( $wp_query->is_singular() ) { + if ( post_type_supports( $p->post_type, 'author' ) ) { + $keys[] = 'post-user-' . $p->post_author; + } + + /** + * Filter pantheon_should_add_terms + * Gives the option to skip taxonomy terms for a given post + * + * @param $add_terms whether or not to create surrogate keys for a given post's taxonomy terms. + * @param $wp_query the full WP_Query object. + * @return bool + * usage: add_filter( 'pantheon_should_add_terms',"__return_false", 10, 2); + */ + $add_terms = apply_filters( 'pantheon_should_add_terms', true, $wp_query ); + if ( ! $add_terms ) { + continue; + } + + foreach ( get_object_taxonomies( $p ) as $tax ) { + $terms = get_the_terms( $p->ID, $tax ); + if ( $terms && ! is_wp_error( $terms ) ) { + foreach ( $terms as $t ) { + $keys[] = 'post-term-' . $t->term_id; + } + } + } + } + } + } + + if ( is_singular() ) { + $keys[] = 'single'; + if ( is_attachment() ) { + $keys[] = 'attachment'; + } + } elseif ( is_archive() ) { + $keys[] = 'archive'; + if ( is_post_type_archive() ) { + $keys[] = 'post-type-archive'; + $post_types = get_query_var( 'post_type' ); + // If multiple post types are queried, create a surrogate key for each. + if ( is_array( $post_types ) ) { + foreach ( $post_types as $post_type ) { + $keys[] = "$post_type-archive"; + } + } else { + $keys[] = "$post_types-archive"; + } + } elseif ( is_author() ) { + $user_id = get_queried_object_id(); + if ( $user_id ) { + $keys[] = 'user-' . $user_id; + } + } elseif ( is_category() || is_tag() || is_tax() ) { + $term_id = get_queried_object_id(); + if ( $term_id ) { + $keys[] = 'term-' . $term_id; + } + } + } + + // Don't emit surrogate keys in the admin, unless defined by the filter. + if ( is_admin() ) { + $keys = []; + } + + /** + * Customize surrogate keys sent in the header. + * + * @param array $keys Existing surrogate keys generated by the plugin. + */ + $keys = pantheon_wp_prefix_surrogate_keys_with_blog_id( $keys ); + $keys = apply_filters( 'pantheon_wp_main_query_surrogate_keys', $keys ); + $keys = array_unique( $keys ); + $keys = self::filter_huge_surrogate_keys_list( $keys ); + return $keys; + } + + /** + * Get the surrogate keys to be included in this view. + * + * Surrogate keys are generated based on filters added to REST API controllers. + * + * @return array + */ + public static function get_rest_api_surrogate_keys() { + + /** + * Customize surrogate keys sent in the REST API header. + * + * @param array $keys Existing surrogate keys generated by the plugin. + */ + $keys = self::get_instance()->rest_api_surrogate_keys; + $keys = pantheon_wp_prefix_surrogate_keys_with_blog_id( $keys ); + $keys = apply_filters( 'pantheon_wp_rest_api_surrogate_keys', $keys ); + $keys = array_unique( $keys ); + $keys = self::filter_huge_surrogate_keys_list( $keys ); + return $keys; + } + + /** + * Reset surrogate keys stored on the instance. + */ + public static function reset_rest_api_surrogate_keys() { + self::get_instance()->rest_api_surrogate_keys = []; + } + + /** + * Filter the surrogate keys to ensure that the length doesn't exceed what nginx can handle. + * + * @param array $keys Existing surrogate keys generated by the plugin. + * + * @return array + */ + public static function filter_huge_surrogate_keys_list( $keys ) { + $output = implode( ' ', $keys ); + if ( strlen( $output ) <= self::HEADER_MAX_LENGTH ) { + return $keys; + } + + $keycats = []; + foreach ( $keys as $k ) { + $p = strrpos( $k, '-' ); + if ( false === $p ) { + $keycats[ $k ][] = $k; + continue; + } + $cat = substr( $k, 0, $p + 1 ); + $keycats[ $cat ][] = $k; + } + + // Sort by the output length of the key category. + uasort( + $keycats, + function ( $a, $b ) { + $ca = strlen( implode( ' ', $a ) ); + $cb = strlen( implode( ' ', $b ) ); + if ( $ca === $cb ) { + return 0; + } + return $ca > $cb ? -1 : 1; + } + ); + + $cats = array_keys( $keycats ); + foreach ( $cats as $c ) { + $keycats[ $c ] = [ $c . 'huge' ]; + $keyout = []; + foreach ( $keycats as $v ) { + $keyout = array_merge( $keyout, $v ); + } + $output = implode( ' ', $keyout ); + if ( strlen( $output ) <= self::HEADER_MAX_LENGTH ) { + return $keyout; + } + } + + return $keyout; + } + + /** + * Inspect the model and get the right surrogate keys. + * + * @param WPGraphQL\Model\Model|mixed $model Model object, array, etc. + */ + public static function filter_graphql_dataloader_get_model( $model ) { + if ( ! $model instanceof \WPGraphQL\Model\Model ) { + return $model; + } + + $reflect = new \ReflectionClass( $model ); + $class_short_name = $reflect->getShortName(); + $surrogate_key_prefix = strtolower( $class_short_name ); + if ( isset( $model->id ) ) { + if ( ! empty( $model->databaseId ) ) { + self::get_instance()->graphql_surrogate_keys[] = $surrogate_key_prefix . '-' . $model->databaseId; + } + } + return $model; + } + + /** + * Get the surrogate keys to be included in this view. + * + * Surrogate keys are generated based on filters added to GraphQL controllers. + * + * @return array + */ + public static function get_graphql_surrogate_keys() { + + /** + * Customize surrogate keys sent in the GraphQL header. + * + * @param array $keys Existing surrogate keys generated by the plugin. + */ + $keys = self::get_instance()->graphql_surrogate_keys; + $keys[] = 'graphql-collection'; + $keys = pantheon_wp_prefix_surrogate_keys_with_blog_id( $keys ); + $keys = apply_filters( 'pantheon_wp_graphql_surrogate_keys', $keys ); + $keys = array_unique( $keys ); + $keys = self::filter_huge_surrogate_keys_list( $keys ); + return $keys; + } + + /** + * Send additional headers to graphql response. + * + * @param array $headers Existing headers as set by graphql plugin. + */ + public static function filter_graphql_response_headers_to_send( $headers ) { + $keys = self::get_graphql_surrogate_keys(); + if ( ! empty( $keys ) ) { + $headers[ self::HEADER_KEY ] = implode( ' ', $keys ); + } + return $headers; + } +} diff --git a/wp-content/plugins/pantheon-advanced-page-cache/inc/class-purger.php b/wp-content/plugins/pantheon-advanced-page-cache/inc/class-purger.php new file mode 100644 index 000000000..28e595b19 --- /dev/null +++ b/wp-content/plugins/pantheon-advanced-page-cache/inc/class-purger.php @@ -0,0 +1,383 @@ +post_status ) { + return; + } + self::purge_post_with_related( $post ); + } + + /** + * Purge surrogate keys associated with a post being published or unpublished. + * + * @param string $new_status New status for the post. + * @param string $old_status Old status for the post. + * @param WP_Post $post Post object. + */ + public static function action_transition_post_status( $new_status, $old_status, $post ) { + if ( 'publish' !== $new_status && 'publish' !== $old_status ) { + return; + } + self::purge_post_with_related( $post ); + } + + /** + * Purge surrogate keys associated with a post being deleted. + * + * @param integer $post_id ID for the post to be deleted. + */ + public static function action_before_delete_post( $post_id ) { + $post = get_post( $post_id ); + self::purge_post_with_related( $post ); + } + + /** + * Purge surrogate keys associated with an attachment being deleted. + * + * @param integer $post_id ID for the modified attachment. + */ + public static function action_delete_attachment( $post_id ) { + $post = get_post( $post_id ); + self::purge_post_with_related( $post ); + } + + /** + * Purge the post's surrogate key when the post cache is cleared. + * + * @param integer $post_id ID for the modified post. + */ + public static function action_clean_post_cache( $post_id ) { + $type = get_post_type( $post_id ); + + /** + * Allow specific post types to ignore the purge process. + * + * @param array $ignored_post_types Post types to ignore. + * @return array + * @since 1.5.0-dev + */ + $ignored_post_types = apply_filters( 'pantheon_purge_post_type_ignored', [ 'revision' ] ); + + if ( $type && in_array( $type, $ignored_post_types, true ) ) { + return; + } + + $keys = [ + 'post-' . $post_id, + 'rest-post-' . $post_id, + 'post-huge', + 'rest-post-huge', + ]; + + $keys = pantheon_wp_prefix_surrogate_keys_with_blog_id( $keys ); + /** + * Surrogate keys purged when clearing post cache. + * + * @param array $keys Surrogate keys. + * @param array $post_id ID for purged post. + */ + $keys = apply_filters( 'pantheon_purge_clean_post_cache', $keys, $post_id ); + pantheon_wp_clear_edge_keys( $keys ); + } + + /** + * Purge surrogate keys associated with a term being created. + * + * @param integer $term_id ID for the created term. + * @param int $tt_id Term taxonomy ID. + * @param string $taxonomy Taxonomy slug. + */ + public static function action_created_term( $term_id, $tt_id, $taxonomy ) { + self::purge_term( $term_id ); + $keys = [ 'rest-' . $taxonomy . '-collection' ]; + $keys = pantheon_wp_prefix_surrogate_keys_with_blog_id( $keys ); + /** + * Surrogate keys purged when creating a new term. + * + * @param array $keys Surrogate keys. + * @param array $term_id ID for new term. + * @param array $tt_id Term taxonomy ID for new term. + * @param string $taxonomy Taxonomy for the new term. + */ + $keys = apply_filters( 'pantheon_purge_create_term', $keys, $term_id, $tt_id, $taxonomy ); + pantheon_wp_clear_edge_keys( $keys ); + } + + /** + * Purge surrogate keys associated with a term being edited. + * + * @param integer $term_id ID for the edited term. + */ + public static function action_edited_term( $term_id ) { + self::purge_term( $term_id ); + } + + /** + * Purge surrogate keys associated with a term being deleted. + * + * @param integer $term_id ID for the deleted term. + */ + public static function action_delete_term( $term_id ) { + self::purge_term( $term_id ); + } + + /** + * Purge the term's archive surrogate key when the term is modified. + * + * @param integer $term_ids One or more IDs of modified terms. + */ + public static function action_clean_term_cache( $term_ids ) { + $keys = []; + $term_ids = is_array( $term_ids ) ? $term_ids : [ $term_ids ]; + foreach ( $term_ids as $term_id ) { + $keys[] = 'term-' . $term_id; + $keys[] = 'rest-term-' . $term_id; + } + $keys[] = 'term-huge'; + $keys[] = 'rest-term-huge'; + $keys = pantheon_wp_prefix_surrogate_keys_with_blog_id( $keys ); + /** + * Surrogate keys purged when clearing term cache. + * + * @param array $keys Surrogate keys. + * @param array $term_ids IDs for purged terms. + */ + $keys = apply_filters( 'pantheon_purge_clean_term_cache', $keys, $term_ids ); + pantheon_wp_clear_edge_keys( $keys ); + } + + /** + * Purge surrogate keys when an approved comment is updated. + * + * @param integer $id The comment ID. + * @param WP_Comment $comment Comment object. + */ + public static function action_wp_insert_comment( $id, $comment ) { + if ( 1 !== (int) $comment->comment_approved ) { + return; + } + $keys = [ + 'rest-comment-' . $comment->comment_ID, + 'rest-comment-collection', + 'rest-comment-huge', + ]; + $keys = pantheon_wp_prefix_surrogate_keys_with_blog_id( $keys ); + /** + * Surrogate keys purged when inserting a new comment. + * + * @param array $keys Surrogate keys. + * @param integer $id Comment ID. + * @param WP_Comment $comment Comment to be inserted. + */ + $keys = apply_filters( 'pantheon_purge_insert_comment', $keys, $id, $comment ); + pantheon_wp_clear_edge_keys( $keys ); + } + + /** + * Purge surrogate keys when a comment is approved or unapproved. + * + * @param int|string $new_status The new comment status. + * @param int|string $old_status The old comment status. + * @param object $comment The comment data. + */ + public static function action_transition_comment_status( $new_status, $old_status, $comment ) { + $keys = [ + 'rest-comment-' . $comment->comment_ID, + 'rest-comment-collection', + 'rest-comment-huge', + ]; + $keys = pantheon_wp_prefix_surrogate_keys_with_blog_id( $keys ); + /** + * Surrogate keys purged when transitioning a comment status. + * + * @param array $keys Surrogate keys. + * @param string $new_status New comment status. + * @param string $old_status Old comment status. + * @param WP_Comment $comment Comment being transitioned. + */ + $keys = apply_filters( 'pantheon_purge_transition_comment_status', $keys, $new_status, $old_status, $comment ); + pantheon_wp_clear_edge_keys( $keys ); + } + + /** + * Purge the comment's surrogate key when the comment is modified. + * + * @param integer $comment_id Modified comment id. + */ + public static function action_clean_comment_cache( $comment_id ) { + $keys = [ + 'rest-comment-' . $comment_id, + 'rest-comment-huge', + ]; + $keys = pantheon_wp_prefix_surrogate_keys_with_blog_id( $keys ); + /** + * Surrogate keys purged when cleaning comment cache. + * + * @param array $keys Surrogate keys. + * @param integer $id Comment ID. + */ + $keys = apply_filters( 'pantheon_purge_clean_comment_cach', $keys, $comment_id ); + pantheon_wp_clear_edge_keys( $keys ); + } + + /** + * Purge the surrogate keys associated with a post being modified. + * + * @param object $post Object representing the modified post. + */ + private static function purge_post_with_related( $post ) { + /** + * Allow specific post types to ignore the purge process. + * + * @param array $ignored_post_types Post types to ignore. + * @return array + * @since 1.5.0-dev + */ + $ignored_post_types = apply_filters( 'pantheon_purge_post_type_ignored', [ 'revision' ] ); + + if ( in_array( $post->post_type, $ignored_post_types, true ) ) { + return; + } + + $keys = [ + 'post-' . $post->ID, + $post->post_type . '-archive', + 'rest-' . $post->post_type . '-collection', + 'home', + 'front', + '404', + 'feed', + 'post-huge', + ]; + + if ( post_type_supports( $post->post_type, 'author' ) ) { + $keys[] = 'user-' . $post->post_author; + $keys[] = 'user-huge'; + } + + if ( post_type_supports( $post->post_type, 'comments' ) ) { + $keys[] = 'rest-comment-post-' . $post->ID; + $keys[] = 'rest-comment-post-huge'; + } + + $taxonomies = wp_list_filter( + get_object_taxonomies( $post->post_type, 'objects' ), + [ 'public' => true ] + ); + + foreach ( $taxonomies as $taxonomy ) { + $terms = get_the_terms( $post, $taxonomy->name ); + if ( $terms ) { + foreach ( $terms as $term ) { + $keys[] = 'term-' . $term->term_id; + } + $keys[] = 'term-huge'; + } + } + + $keys = pantheon_wp_prefix_surrogate_keys_with_blog_id( $keys ); + /** + * Related surrogate keys purged when purging a post. + * + * @param array $keys Surrogate keys. + * @param WP_Post $post Post object. + */ + $keys = apply_filters( 'pantheon_purge_post_with_related', $keys, $post ); + pantheon_wp_clear_edge_keys( $keys ); + } + + /** + * Purge the surrogate keys associated with a term being modified. + * + * @param integer $term_id ID for the modified term. + */ + private static function purge_term( $term_id ) { + $keys = [ + 'term-' . $term_id, + 'rest-term-' . $term_id, + 'post-term-' . $term_id, + 'term-huge', + 'rest-term-huge', + 'post-term-huge', + ]; + $keys = pantheon_wp_prefix_surrogate_keys_with_blog_id( $keys ); + /** + * Surrogate keys purged when purging a term. + * + * @param array $keys Surrogate keys. + * @param integer $term_id Term ID. + */ + $keys = apply_filters( 'pantheon_purge_term', $keys, $term_id ); + pantheon_wp_clear_edge_keys( $keys ); + } + + + /** + * Purge a variety of surrogate keys when a user is modified. + * + * @param integer $user_id ID for the modified user. + */ + public static function action_clean_user_cache( $user_id ) { + $keys = [ + 'user-' . $user_id, + 'rest-user-' . $user_id, + 'user-huge', + 'rest-user-huge', + ]; + $keys = pantheon_wp_prefix_surrogate_keys_with_blog_id( $keys ); + /** + * Surrogate keys purged when clearing user cache. + * + * @param array $keys Surrogate keys. + * @param array $user_id ID for purged user. + */ + $keys = apply_filters( 'pantheon_purge_clean_user_cache', $keys, $user_id ); + pantheon_wp_clear_edge_keys( $keys ); + } + + /** + * Purge a variety of surrogate keys when an option is modified. + * + * @param string $option Name of the updated option. + */ + public static function action_updated_option( $option ) { + if ( ! function_exists( 'get_registered_settings' ) ) { + return; + } + $settings = get_registered_settings(); + if ( empty( $settings[ $option ] ) || empty( $settings[ $option ]['show_in_rest'] ) ) { + return; + } + $rest_name = ! empty( $settings[ $option ]['show_in_rest']['name'] ) ? $settings[ $option ]['show_in_rest']['name'] : $option; + $keys = [ + 'rest-setting-' . $rest_name, + 'rest-setting-huge', + ]; + $keys = pantheon_wp_prefix_surrogate_keys_with_blog_id( $keys ); + /** + * Surrogate keys purged when updating an option cache. + * + * @param array $keys Surrogate keys. + * @param string $option Option name. + */ + $keys = apply_filters( 'pantheon_purge_updated_option', $keys, $option ); + pantheon_wp_clear_edge_keys( $keys ); + } +} diff --git a/wp-content/plugins/pantheon-advanced-page-cache/inc/class-user-interface.php b/wp-content/plugins/pantheon-advanced-page-cache/inc/class-user-interface.php new file mode 100644 index 000000000..e22a60cd6 --- /dev/null +++ b/wp-content/plugins/pantheon-advanced-page-cache/inc/class-user-interface.php @@ -0,0 +1,62 @@ +add_menu( [ + 'parent' => '', + 'id' => 'clear-page-cache', + 'title' => $title, + 'meta' => [ + 'title' => __( 'Delete cache of the current URL.', 'pantheon-advanced-page-cache' ), + ], + 'href' => wp_nonce_url( admin_url( 'admin-ajax.php?action=pantheon_clear_url_cache&path=' . rawurlencode( preg_replace( '/[ <>\'\"\r\n\t\(\)]/', '', $request_uri ) ) ), 'clear-url-cache' ), + ] ); + } + + /** + * Handle an admin-ajax request to clear the URL cache. + */ + public static function handle_ajax_clear_url_cache() { + $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( $_GET['_wpnonce'] ) : ''; + if ( empty( $nonce ) + || ! wp_verify_nonce( $nonce, 'clear-url-cache' ) + || ! current_user_can( 'delete_others_posts' ) ) { + wp_die( esc_html__( "You shouldn't be doing this.", 'pantheon-advanced-page-cache' ) ); + } + + $path = isset( $_GET['path'] ) ? sanitize_text_field( $_GET['path'] ) : ''; + $ret = pantheon_wp_clear_edge_paths( [ $path ] ); + if ( is_wp_error( $ret ) ) { + wp_die( wp_kses_post( $ret->get_error_message() ) ); + } + wp_safe_redirect( add_query_arg( 'message', 'pantheon-cleared-url-cache', preg_replace( '/[ <>\'\"\r\n\t\(\)]/', '', $path ) ) ); + exit; + } +} diff --git a/wp-content/plugins/pantheon-advanced-page-cache/pantheon-advanced-page-cache.php b/wp-content/plugins/pantheon-advanced-page-cache/pantheon-advanced-page-cache.php new file mode 100644 index 000000000..06b130ffc --- /dev/null +++ b/wp-content/plugins/pantheon-advanced-page-cache/pantheon-advanced-page-cache.php @@ -0,0 +1,196 @@ +getMessage() ); + } + return true; +} + +/** + * Purge the cache for specific paths. + * + * @param array $paths URI paths to purge. + */ +function pantheon_wp_clear_edge_paths( $paths ) { + /** + * Fires when purging specific URI paths. + * + * @param array $paths URI paths to purge. + */ + do_action( 'pantheon_wp_clear_edge_paths', $paths ); + + try { + if ( function_exists( 'pantheon_clear_edge_paths' ) ) { + pantheon_clear_edge_paths( $paths ); + } + } catch ( Exception $e ) { + return new WP_Error( 'pantheon_clear_edge_paths', $e->getMessage() ); + } + return true; +} + +/** + * Purge the entire cache. + */ +function pantheon_wp_clear_edge_all() { + /** + * Fires when purging the entire cache. + */ + do_action( 'pantheon_wp_clear_edge_all' ); + + try { + if ( function_exists( 'pantheon_clear_edge_all' ) ) { + pantheon_clear_edge_all(); + } + } catch ( Exception $e ) { + return new WP_Error( 'pantheon_clear_edge_all', $e->getMessage() ); + } + return true; +} + +/** + * Prefix surrogate keys with the blog ID to provide compatibility with WPMS. See https://github.com/pantheon-systems/pantheon-advanced-page-cache/issues/196. + * + * @param array $keys Keys to be prefixed. + */ +function pantheon_wp_prefix_surrogate_keys_with_blog_id( $keys ) { + // Do not prefix keys if this is not a multisite install. + if ( ! is_multisite() ) { + return $keys; + } + + // Array that will hold the new keys. + $prefixed_keys = []; + + $prefix = 'blog-' . get_current_blog_id() . '-'; + $prefix = apply_filters( 'pantheon_wp_surrogate_key_prepend', $prefix ); + foreach ( $keys as $key ) { + $prefixed_keys[] = $prefix . $key; + } + + return $prefixed_keys; +} + +/** + * Bootstrapper for namespaced files that aren't classes. + * + * Expects that a bootstrap() function exists in the namespaced file. + * + * @since 2.0.0 + * @return void + */ +function pantheon_bootstrap_namespaces() { + $namespaced_files = [ + '\\Pantheon_Advanced_Page_Cache\\Admin_Interface' => __DIR__ . '/inc/admin-interface.php', + ]; + + foreach ( $namespaced_files as $namespace => $file ) { + if ( file_exists( $file ) ) { + require $file; + call_user_func( $namespace . '\\bootstrap' ); + } else { + wp_die( esc_html( "Could not find $file" ), 'Pantheon Advanced Page Cache error' ); + + } + } +} + +/** + * Registers the class autoloader. + */ +spl_autoload_register( + function ( $class_autoloader ) { + $class_autoloader = ltrim( $class_autoloader, '\\' ); + if ( 0 !== stripos( $class_autoloader, 'Pantheon_Advanced_Page_Cache\\' ) ) { + return; + } + + $parts = explode( '\\', $class_autoloader ); + array_shift( $parts ); // Don't need "Pantheon_Advanced_Page_Cache". + $last = array_pop( $parts ); // File should be 'class-[...].php'. + $last = 'class-' . $last . '.php'; + $parts[] = $last; + $file = __DIR__ . '/inc/' . str_replace( '_', '-', strtolower( implode( '/', $parts ) ) ); + if ( file_exists( $file ) ) { + require $file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable + } + } +); + +/** + * Init namespaced files. + */ +add_action( 'plugins_loaded', 'pantheon_bootstrap_namespaces' ); + +/** + * Registers relevant UI + */ +add_action( 'admin_bar_menu', [ 'Pantheon_Advanced_Page_Cache\User_Interface', 'action_admin_bar_menu' ], 99 ); // End of the stack. +add_action( 'wp_ajax_pantheon_clear_url_cache', [ 'Pantheon_Advanced_Page_Cache\User_Interface', 'handle_ajax_clear_url_cache' ] ); + +/** + * Emits the appropriate surrogate tags per view. + */ +add_filter( 'wp', [ 'Pantheon_Advanced_Page_Cache\Emitter', 'action_wp' ] ); +add_action( 'rest_api_init', [ 'Pantheon_Advanced_Page_Cache\Emitter', 'action_rest_api_init' ] ); +add_filter( 'rest_pre_dispatch', [ 'Pantheon_Advanced_Page_Cache\Emitter', 'filter_rest_pre_dispatch' ], 10, 3 ); +add_filter( 'rest_post_dispatch', [ 'Pantheon_Advanced_Page_Cache\Emitter', 'filter_rest_post_dispatch' ], 10, 2 ); + +add_filter( 'graphql_dataloader_get_model', [ 'Pantheon_Advanced_Page_Cache\Emitter', 'filter_graphql_dataloader_get_model' ] ); +add_filter( 'graphql_response_headers_to_send', [ 'Pantheon_Advanced_Page_Cache\Emitter', 'filter_graphql_response_headers_to_send' ] ); + +/** + * Clears surrogate tags when various modification behaviors are performed. + */ +add_action( 'wp_insert_post', [ 'Pantheon_Advanced_Page_Cache\Purger', 'action_wp_insert_post' ], 10, 2 ); +add_action( 'transition_post_status', [ 'Pantheon_Advanced_Page_Cache\Purger', 'action_transition_post_status' ], 10, 3 ); +add_action( 'before_delete_post', [ 'Pantheon_Advanced_Page_Cache\Purger', 'action_before_delete_post' ] ); +add_action( 'delete_attachment', [ 'Pantheon_Advanced_Page_Cache\Purger', 'action_delete_attachment' ] ); +add_action( 'clean_post_cache', [ 'Pantheon_Advanced_Page_Cache\Purger', 'action_clean_post_cache' ] ); +add_action( 'created_term', [ 'Pantheon_Advanced_Page_Cache\Purger', 'action_created_term' ], 10, 3 ); +add_action( 'edited_term', [ 'Pantheon_Advanced_Page_Cache\Purger', 'action_edited_term' ] ); +add_action( 'delete_term', [ 'Pantheon_Advanced_Page_Cache\Purger', 'action_delete_term' ] ); +add_action( 'clean_term_cache', [ 'Pantheon_Advanced_Page_Cache\Purger', 'action_clean_term_cache' ] ); +add_action( 'wp_insert_comment', [ 'Pantheon_Advanced_Page_Cache\Purger', 'action_wp_insert_comment' ], 10, 2 ); +add_action( 'transition_comment_status', [ 'Pantheon_Advanced_Page_Cache\Purger', 'action_transition_comment_status' ], 10, 3 ); +add_action( 'clean_comment_cache', [ 'Pantheon_Advanced_Page_Cache\Purger', 'action_clean_comment_cache' ] ); +add_action( 'clean_user_cache', [ 'Pantheon_Advanced_Page_Cache\Purger', 'action_clean_user_cache' ] ); +add_action( 'updated_option', [ 'Pantheon_Advanced_Page_Cache\Purger', 'action_updated_option' ] ); + +/** + * Registers the WP-CLI commands. + */ +if ( defined( 'WP_CLI' ) && WP_CLI ) { + WP_CLI::add_command( 'pantheon cache', 'Pantheon_Advanced_Page_Cache\CLI' ); +} diff --git a/wp-content/plugins/pantheon-advanced-page-cache/readme.txt b/wp-content/plugins/pantheon-advanced-page-cache/readme.txt new file mode 100644 index 000000000..bdc77808b --- /dev/null +++ b/wp-content/plugins/pantheon-advanced-page-cache/readme.txt @@ -0,0 +1,481 @@ +=== Pantheon Advanced Page Cache === +Contributors: getpantheon, danielbachhuber, kporras07, jspellman, jazzs3quence, ryanshoover, rwagner00, pwtyler +Tags: pantheon, cdn, cache +Requires at least: 6.4 +Tested up to: 6.5.3 +Stable tag: 2.0.0 +License: GPLv2 or later +License URI: http://www.gnu.org/licenses/gpl-2.0.html + +Automatically clear related pages from Pantheon's Edge when you update content. High TTL. Fresh content. Visitors never wait. + +== Description == + +[![CircleCI](https://circleci.com/gh/pantheon-systems/pantheon-advanced-page-cache.svg?style=svg)](https://circleci.com/gh/pantheon-systems/pantheon-advanced-page-cache) + +For sites wanting fine-grained control over how their responses are represented in their edge cache, Pantheon Advanced Page Cache is the golden ticket. Here's a high-level overview of how the plugin works: + +1. When a response is generated, the plugin uses surrogate keys based on WordPress' main `WP_Query` object to "tag" the response with identifers for the data used in the response. See the "Adding Custom Keys" section for including your own surrogate keys. +2. When WordPress data is modified, the plugin triggers a purge request for the data's corresponding surrogate keys. + +Because of its surrogate key technology, Pantheon Advanced Page Cache empowers WordPress sites with a significantly more accurate cache purge mechanism, and generally higher cache hit rate. It even works with the WordPress REST API. + +Go forth and make awesome! And, once you've built something great, [send us feature requests (or bug reports)](https://github.com/pantheon-systems/pantheon-advanced-page-cache/issues). + +== Installation == + +To install Pantheon Advanced Page Cache, follow these steps: + +1. Install the plugin from WordPress.org using the WordPress dashboard. +2. Activate the plugin. + +To install Pantheon Advanced Page Cache in one line with WP-CLI: + + wp plugin install pantheon-advanced-page-cache --activate + +== How It Works == + +Pantheon Advanced Page Cache makes heavy use of surrogate keys, which enable responses to be "tagged" with identifiers that can then later be used in purge requests. For instance, a home page response might include the `Surrogate-Key` header with these keys: + + Surrogate-Key: front home post-43 user-4 post-41 post-9 post-7 post-1 user-1 + +Similarly, a `GET` requests to `/wp-json/wp/v2/posts` might include the `Surrogate-Key` header with these keys: + + Surrogate-Key: rest-post-collection rest-post-43 rest-post-43 rest-post-9 rest-post-7 rest-post-1 + +Because cached responses include metadata describing the data therein, surrogate keys enable more flexible purging behavior like: + +* When a post is updated, clear the cache for the post's URL, the homepage, any index view the post appears on, and any REST API endpoints the post is present in. +* When an author changes their name, clear the cache for the author's archive and any post they've authored. + +There is a limit to the number of surrogate keys in a response, so we've optimized them based on a user's expectation of a normal WordPress site. See the "Emitted Keys" section for full details on which keys are included, and the "Adding Custom Keys" section following for information on how to add your own. + += Adding Custom Keys = + +By default, Pantheon Advanced Page Cache generates surrogate keys based on an interpretation of the main `WP_Query` query object. Because WordPress sends headers before the page is rendered, you need to use the `pantheon_wp_main_query_surrogate_keys` filter to include additional surrogate keys for any data present on the page. + +For example, to include surrogate keys for a sidebar rendered on the homepage, you can filter the keys using the `is_home()` template tag: + + /** + * Add surrogate key for the featured content sidebar rendered on the homepage. + */ + add_filter( 'pantheon_wp_main_query_surrogate_keys', function( $keys ){ + if ( is_home() ) { + $keys[] = 'sidebar-home-featured'; + } + return $keys; + }); + +Then, when sidebars are updated, you can use the `pantheon_wp_clear_edge_keys()` helper function to emit a purge event specific to the surrogate key: + + /** + * Trigger a purge event for the featured content sidebar when widgets are updated. + */ + add_action( 'update_option_sidebars_widgets', function() { + pantheon_wp_clear_edge_keys( array( 'sidebar-home-featured' ) ); + }); + +Similarly, to include surrogate keys for posts queried on the homepage, you can pre-fetch the posts before the page is rendered: + + /** + * An example of pre-fetching a WP_Query to tag the + * response with queried data. You'd use `papcx_wp_query()` + * a second time within your template to use the data. + */ + add_filter( 'pantheon_wp_main_query_surrogate_keys', function( $keys ) { + if ( is_home() ) { + $query = papcx_wp_query( array( + 'post_type' => 'page', + ) ); + foreach( $query->posts as $post ) { + $keys[] = 'post-' . $post->ID; + } + } + return $keys; + }); + + /** + * Register a 'papc-non-persistent' cache group to cache data + * in a non-persistent manner. We only want data in this group + * to be cached within the page request. + */ + add_action( 'init', function(){ + wp_cache_add_non_persistent_groups( array( 'papc-non-persistent' ) ); + }); + + /** + * Helper function to instantiate a WP_Query object only + * once per page request. + * + * @param array $args Arguments to pass to WP_Query. + * @return WP_Query + */ + function papcx_wp_query( $args = array() ) { + $cache_key = md5( serialize( $args ) ); + // WP_Query object will be in cache the second time we use the function. + $cache_value = wp_cache_get( $cache_key, 'papc-non-persistent' ); + if ( false !== $cache_value ) { + return $cache_value; + } + $query = new WP_Query( $args ); + wp_cache_set( $cache_key, $query, 'papc-non-persistent' ); + return $query; + } + +Because Pantheon Advanced Page Cache already handles WordPress post purge events, there's no additional call to `pantheon_wp_clear_edge_keys()`. + +Lastly, the `pantheon_wp_rest_api_surrogate_keys` filter lets you filter surrogate keys present in a REST API response. + +Need a bit more power? In addition to `pantheon_wp_clear_edge_keys()`, there are two additional helper functions you can use: + +* `pantheon_wp_clear_edge_paths( $paths = array() )` - Purge cache for one or more paths. +* `pantheon_wp_clear_edge_all()` - Warning! With great power comes great responsibility. Purge the entire cache, but do so wisely. + += Ignoring Specific Post Types = + +By default, Pantheon Advanced Page Cache is pretty aggressive in how it clears its surrogate keys. Specifically, any time `wp_insert_post` is called (which can include any time a post of any type is added or updated, even private post types), it will purge a variety of keys including `home`, `front`, `404` and `feed`. To bypass or override this behavior, since 1.5.0-dev we have a filter allowing an array of post types to ignore to be passed before those caches are purged. By default, the `revision` post type is ignored, but others can be added: + + /** + * Add a custom post type to the ignored post types. + * + * @param array $ignored_post_types The array of ignored post types. + * @return array + */ + function filter_ignored_posts( $ignored_post_types ) { + $ignored_post_types[] = 'my-post-type'; // Ignore my-post-type from cache purges. + return $ignored_post_types; + } + + add_filter( 'pantheon_purge_post_type_ignored', 'filter_ignored_posts' ); + +This will prevent the cache from being purged if the given post type is updated. + += Setting the Cache Max Age with a filter = + +The cache max age setting is controlled by the [Pantheon Page Cache](https://docs.pantheon.io/guides/wordpress-configurations/wordpress-cache-plugin) admin page. As of 2.0.0, there are three cache age options by default — 1 week, 1 month, 1 year. Pantheon Advanced Page Cache automatically purges the cache of updated and related posts and pages, but you might want to override the cache max age value and set it programmatically. In this case, you can use the `pantheon_cache_default_max_age` filter added in [Pantheon MU plugin 1.4.0+](https://docs.pantheon.io/guides/wordpress-configurations/wordpress-cache-plugin#override-the-default-max-age). For example: + + add_filter( 'pantheon_cache_default_max_age', function() { + return 10 * DAY_IN_SECONDS; + } ); + +When the cache max age is filtered in this way, the admin option is disabled and a notice is displayed. + += Setting the Cache Max Age with a filter = + +The cache max age setting is controlled by the [Pantheon Page Cache](https://docs.pantheon.io/guides/wordpress-configurations/wordpress-cache-plugin) admin page. As of 2.0.0, there are three cache age options by default — 1 week, 1 month, 1 year. Pantheon Advanced Page Cache automatically purges the cache of updated and related posts and pages, but you might want to override the cache max age value and set it programmatically. In this case, you can use the `pantheon_cache_default_max_age` filter added in [Pantheon MU plugin 1.4.0+](https://docs.pantheon.io/guides/wordpress-configurations/wordpress-cache-plugin#override-the-default-max-age). For example: + + add_filter( 'pantheon_cache_default_max_age', function() { + return 10 * DAY_IN_SECONDS; + } ); + +When the cache max age is filtered in this way, the admin option is disabled and a notice is displayed. + +== WP-CLI Commands == + +This plugin implements a variety of [WP-CLI](https://wp-cli.org) commands. All commands are grouped into the `wp pantheon cache` namespace. + + $ wp help pantheon cache + + NAME + + wp pantheon cache + + DESCRIPTION + + Manage the Pantheon Advanced Page Cache. + + SYNOPSIS + + wp pantheon cache + + SUBCOMMANDS + + purge-all Purge the entire page cache. + purge-key Purge one or more surrogate keys from cache. + purge-path Purge one or more paths from cache. + +Use `wp help pantheon cache ` to learn more about each command. + +== Debugging == + +By default, Pantheon's infrastructure strips out the `Surrogate-Key` response header before responses are served to clients. The contents of this header can be viewed as `Surrogate-Key-Raw` by adding on a debugging header to the request. + +A direct way of inspecting headers is with `curl -I`. This command will make a request and show just the response headers. Adding `-H "Pantheon-Debug:1"` will result in `Surrogate-Key-Raw` being included in the response headers. The complete command looks like this: + + curl -IH "Pantheon-Debug:1" https://scalewp.io/ + +Piping to `grep` will filter the output down to just the `Surrogate-Key-Raw` header: + + curl -IH "Pantheon-Debug:1" https://scalewp.io/ | grep -i Surrogate-Key-Raw + +Tada! + +== Emitted Keys and Purge Events = + += Emitted Keys on Traditional Views = + +**Home `/`** + +* Emits surrogate keys: `home`, `front`, `post-` (all posts in main query) + +**Single post `/2016/10/14/surrogate-keys/`** + +* Emits surrogate keys: `single`, `post-`, `post-user-`, `post-term-` (all terms assigned to post) + +**Author archive `/author/pantheon/`** + +* Emits surrogate keys: `archive`, `user-`, `post-` (all posts in main query) + +**Term archive `/tag/cdn/`** + +* Emits surrogate keys: `archive`, `term-`, `post-` (all posts in main query) + +**Day archive `/2016/10/14/`** + +* Emits surrogate keys: `archive`, `date`, `post-` (all posts in main query) + +**Month archive `/2016/10/`** + +* Emits surrogate keys: `archive`, `date`, `post-` (all posts in main query) + +**Year archive `/2016/`** + +* Emits surrogate keys: `archive`, `date`, `post-` (all posts in main query) + +**Search `/?s=`** + +* Emits surrogate keys: `search`, either `search-results` or `search-no-results`, `post-` (all posts in main query) + +**Not found (404)** + +* Emits surrogate keys: `404` + += Emitted Keys on REST API Endpoints = + +**Posts** + +* `/wp-json/wp/v2/posts` emits surrogate keys: `rest-post-collection`, `rest-post-` +* `/wp-json/wp/v2/posts/` emits surrogate keys: `rest-post-` + +**Pages** + +* `/wp-json/wp/v2/pages` emits surrogate keys: `rest-page-collection`, `rest-post-` +* `/wp-json/wp/v2/pages/` emits surrogate keys: `rest-post-` + +**Categories** + +* `/wp-json/wp/v2/categories` emits surrogate keys: `rest-category-collection`, `rest-term-` +* `/wp-json/wp/v2/categories/` emits surrogate keys: `rest-term-` + +**Tags** + +* `/wp-json/wp/v2/tags` emits surrogate keys: `rest-post_tag-collection`, `rest-term-` +* `/wp-json/wp/v2/tags/` emits surrogate keys: `rest-term-` + +**Comments** + +* `/wp-json/wp/v2/comments` emits surrogate keys: `rest-comment-collection`, `rest-comment-post-`, `rest-comment-` +* `/wp-json/wp/v2/comments/` emits surrogate keys: `rest-comment-post-`, `rest-comment-` + +**Users** + +* `/wp-json/wp/v2/users` emits surrogate keys: `rest-user-collection`, `rest-user-` +* `/wp-json/wp/v2/users/` emits surrogate keys: `rest-user-` + +**Settings** + +* `/wp-json/wp/v2/settings` emits surrogate keys: `rest-setting-` + += Purge Events = + +Different WordPress actions cause different surrogate keys to be purged, documented here. + +**wp_insert_post / transition_post_status / before_delete_post / delete_attachment** + +* Purges surrogate keys: `home`, `front`, `404`, `post-`, `user-`, `term-`, `rest--collection`, `rest-comment-post-` +* Affected views: homepage, single post, any page with 404 header, any archive where post displays, author archive, term archive, REST API collection and resource endpoints + +**clean_post_cache** + +* Purges surrogate keys: `post-`, `rest-post-` +* Affected views: single post, REST API resource endpoint + +**created_term / edited_term / delete_term** + +* Purges surrogate keys: `term-`, `post-term-`, `rest--collection` +* Affected views: term archive, any post where the term is assigned, REST API collection and resource endpoints + +**clean_term_cache** + +* Purges surrogate keys: `term-`, `rest-term-` +* Affected views: term archive, REST API resource endpoint + +**wp_insert_comment / transition_comment_status** + +* Purges surrogate keys: `rest-comment-collection`, `rest-comment-` +* Affected views: REST API collection and resource endpoints + +**clean_comment_cache** + +* Purges surrogate keys: `rest-comment-` +* Affected views: REST API resource endpoint + +**clean_user_cache** + +* Purges surrogate keys: `user-`, `rest-user-` +* Affected views: author archive, any post where the user is the author + +**updated_option** + +* Purges surrogate keys: `rest-setting-` +* Affected views: REST API resource endpoint + +== Surrogate Keys for taxonomy terms == +Setting surrogate keys for posts with large numbers of taxonomies (such as WooCommerce products with a large number of global attributes) can suffer from slower queries. Surrogate keys can be skipped for 'product' post types' taxonomy terms (or any other criteria you see fit) with the following filter: + + function custom_should_add_terms($should_add_terms, $wp_query) { + if ( $wp_query->is_singular( 'product' ) ) { + return false; + } + return $should_add_terms; + } + add_filter('pantheon_should_add_terms', 'custom_should_add_terms', 10, 2); + +== Other Filters == + += pantheon_apc_disable_admin_notices = +Since 2.0.0, Pantheon Advanced Page Cache displays a number of admin notices about your current cache max age value. You can disable these notices with the `pantheon_apc_disable_admin_notices` filter. + + add_filter( 'pantheon_apc_disable_admin_notices', '__return_true' ); + +Alternately, the function callback is passed into the `pantheon_apc_disable_admin_notices` filter, allowing you to specify precisely _which_ notice to disable, for example: + + add_filter( 'pantheon_apc_disable_admin_notices', function( $disable_notices, $callback ) { + if ( $callback === '\\Pantheon_Advanced_Page_Cache\\Admin_Interface\\admin_notice_maybe_recommend_higher_max_age' ) { + return true; + } + return $disable_notices; + }, 10, 2 ); + +The above example would disable _only_ the admin notice recommending a higher cache max age. + +== Other Filters == + += pantheon_apc_disable_admin_notices = +Since 2.0.0, Pantheon Advanced Page Cache displays a number of admin notices about your current cache max age value. You can disable these notices with the `pantheon_apc_disable_admin_notices` filter. + + add_filter( 'pantheon_apc_disable_admin_notices', '__return_true' ); + +Alternately, the function callback is passed into the `pantheon_apc_disable_admin_notices` filter, allowing you to specify precisely _which_ notice to disable, for example: + + add_filter( 'pantheon_apc_disable_admin_notices', function( $disable_notices, $callback ) { + if ( $callback === '\\Pantheon_Advanced_Page_Cache\\Admin_Interface\\admin_notice_maybe_recommend_higher_max_age' ) { + return true; + } + return $disable_notices; + }, 10, 2 ); + +The above example would disable _only_ the admin notice recommending a higher cache max age. + +== Plugin Integrations == + +Pantheon Advanced Page Cache integrates with WordPress plugins, including: + +* [WPGraphQL](https://wordpress.org/plugins/wp-graphql/) + +== Contributing == + +See [CONTRIBUTING.md](https://github.com/pantheon-systems/wp-saml-auth/blob/master/CONTRIBUTING.md) for information on contributing. + +== Changelog == += 2.0.0 (28 May 2024) = +* Adds new admin alerts and Site Health tests about default cache max age settings and recommendations [[#268](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/268), [#271](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/271)]. The default Pantheon GCDN cache max age value has been updated to 1 week in the [Pantheon MU plugin](https://github.com/pantheon-systems/pantheon-mu-plugin). For more information, see the [release note](https://docs.pantheon.io/release-notes/2024/04/pantheon-mu-plugin-1-4-0-update). +* Updated UI in Pantheon Page Cache admin page when used in a Pantheon environment (with the Pantheon MU plugin). [[#272](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/272)] This UI change takes effect when [Pantheon MU plugin version 1.4.3](https://docs.pantheon.io/release-notes/2024/05/pantheon-mu-plugin-1-4-3-update) is available on your site. +* Automatically updates the cache max age to the recommended value (1 week) if it was saved at the old default value (600 seconds). [[#269](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/269)] +* Adds a hook into the `nonce_life` filter when nonces are created on the front-end to set the `pantheon_cache_default_max_age` to less than the nonce lifetime to avoid nonces expiring before the cache does. [[#282](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/282)] props [@ryanshoover](https://profiles.wordpress.org/ryanshoover/) + += 1.5.0 (11 March 2024) = +* Adds filter `pantheon_purge_post_type_ignored` to allow an array of post types to ignore before purging cache [[#258](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/258)] +* Adds [wpunit-helpers](https://github.com/pantheon-systems/wpunit-helpers) for running/setting up WP Unit tests + += 1.4.2 (October 16, 2023) = +* Updates Pantheon WP Coding Standards to 2.0 [[#249](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/249)] +* Fixes an issue where a PHP warning was thrown when surrogate keys were emitted from archive pages with multiple post types. [[#252](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/252)] + += 1.4.1 (August 8, 2023) = +* Send the REST API response header to the result and not the REST server [[#237](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/237)]. Props [@srtfisher](https://github.com/srtfisher) & [@felixarntz](https://github.com/felixarntz). + += 1.4.0 (August 1, 2023) = +* Bumped Dependencies [[236](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/236)] +* Add filter `pantheon_should_add_terms` to allow disabling surrogate keys for posts' taxonomy terms [[239](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/239)] + += 1.3.0 (April 19, 2023) = +* Adds support for WordPress Multisite which resolves issue where editing a Post on one subsite clears the home page cache of other sites in the Multisite install if it has a Post containing the same ID [[#228](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/228)]. + += 1.2.4 (April 13, 2023) = +* Adds surrogate key to post-type archive pages (e.g. "portfolio") that's specific to that archive(e.g. "portfolio-archive"), and clears that archive where appropriate [[#225](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/225)]. + += 1.2.3 (April 5, 2023) = +* Bump tested up to version to 6.2 + += 1.2.2 (March 14, 2023) = +* Adds PHP 8.2 compatibility [[#218](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/218)]. +* Bump dependencies [[#204](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/204)]. + += 1.2.1 (February 23, 2023) = +* Handle models that are not instances of the `WPGraphQL\Model\Model` class [[#212](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/212)]. +* Make dependabot target develop branch [[#209](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/209)]. +* Bump dependencies [[#210](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/210)] [[#214](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/214)]. + += 1.2.0 (November 29, 2022) = +* Adds Github Actions for building tag and deploying to wp.org. Add CONTRIBUTING.md. [[#203](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/203)] + += 1.1.0 (November 1, 2022) = +* Hook into WPGraphQL to emit surrogate keys [[#199](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/199)]. +* Add Plugin Integrations section to README + += 1.0.0 (March 2, 2020) = +* Plugin is stable. + += 0.3.1 (October 27th, 2019) = +* Fixes reversed argument order with use of `implode()` [[#139](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/139)]. +* Various PHPCS cleanup [[#127](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/127)]. + += 0.3.0 (November 27th, 2017) = +* Emits '404' surrogate key on 404s; purges when purging the homepage [[#107](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/107)]. +* Adds more specific filters for modifying surrogate keys in different contexts [[#109](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/109)]. +* Cleans up codebase according to WordPress Coding Standards [[#110](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/110), [#116](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/116)]. + += 0.2.1 (October 25th, 2017) = +* Ensures use of `?_embed` emits correct surrogate keys [[#103](https://github.com/pantheon-systems/pantheon-advanced-page-cache/pull/103)]. + += 0.2.0 (August 10th, 2017) = +* Automatically trims large lists of surrogate keys that break Nginx and Varnish limits for header size. + += 0.1.5 (May 24th, 2017) = +* Disables emitting surrogate keys for the admin, unless explicitly added by filter. + += 0.1.4 (March 7th, 2017) = +* Emits `feed` surrogate key for RSS feeds, and purges when posts are created, modified, or deleted. + += 0.1.3 (March 1st, 2017) = +* Prevents error notices by only accessing `$rest_base` property of post types and taxonomies when set. + += 0.1.2 (December 6th, 2016) = +* Permits admins to flush cache for a specific page if the `delete_others_posts` capability has been deleted. + += 0.1.1 (November 30th, 2016) = +* Drops settings UI in favor of including it in Pantheon's WordPress upstream. + += 0.1.0 (November 23rd, 2016) = +* Initial release. + +== Upgrade Notice == += 2.0.0 (28 May 2024) = +This release requires a minimum WordPress version of 6.4.0. It uses Site Health checks and the `wp_admin_notices` function to alert users to the new cache max-age default settings and recommendations. The plugin will still function with earlier versions, but you will not get the benefit of the alerts and Site Health checks. + +This version also automatically updates the cache max age (set in the [Pantheon Page Cache settings](https://docs.pantheon.io/guides/wordpress-configurations/wordpress-cache-plugin)) to the recommended value (1 week) if it was saved at the old default value (600 seconds). If the cache max age was set to any other value (or not set at all), it will not be changed. A one-time notice will be displayed in the admin interface to inform administrators of this change. + += 1.3.0 = += 1.3.0 = +Note that the Pantheon Advanced Page Cache 1.3.0 release now prefixes keys on a WordPress Multisite (WPMS) with the blog ID. For users who already have this plugin installed on a WPMS, they will need to click the Clear Cache button on the settings page to generate the prefixed keys.