feat: caching, optimization, legal pages & footer

- WP Super Cache enabled (PHP mode, gzip, Nginx compatible)
- Autoptimize: CSS/HTML minification + deferred JS + Google Fonts optimization
- Cookie Notice: GDPR/LOPD banner styled with brand colors (navy/burgundy/gold)
- Legal pages: Aviso Legal, Política de Privacidad, Política de Cookies (ES)
- MU-plugin: custom footer with legal links + Cloud Host credit
- Footer: copyright, legal nav, Hosted & Maintained by Cloud Host (cloudhost.es)
- Security: X-Frame-Options, X-Content-Type, Referrer-Policy headers
- Security: XML-RPC disabled, REST user enumeration blocked
- Performance: emoji scripts removed, post revisions limited to 3
This commit is contained in:
Malin
2026-05-19 19:58:11 +02:00
parent 67241f537f
commit 6daabcab65
277 changed files with 96841 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
<?php
/**
* Autoptimize's magic 404 handler.
*
* Configure your webserver to have requests for files that are no longer in
* /wp-content/cache/autoptimize/ to redirect to this file. AO's .htaccess file
* will have a "Errordocument:" directive to automatically do this.
*
* This file has simple logic to redirect to the "fallback" files that are
* created automatically by AO to avoid visitors seeing broken pages or
* Googlebot getting utterly confused.
*
* Error logging is off by default (don't want to flood your php errorlog, but
* can be enabled by this code snippet:
*
* add_filter( 'autoptimize_filter_cache_fallback_log_errors', '__return_true' );
*
* Warning: the fallback files might not apply to all pages, so this is a just
* a temporary solution, you really should clear any page cache to avoid requests
* to files that don't exist in AO's cache.
*/
$original_request = strtok( $_SERVER['REQUEST_URI'], '?' );
if ( strpos( $original_request, 'uucss/uucss-' ) !== false ) {
$original_request = preg_replace( '/uucss\/uucss-[a-z0-9]{32}-/', 'css/', $original_request );
}
$fallback_target = preg_replace( '/(.*)_(?:[a-z0-9]{32})\.(js|css)$/', '${1}_fallback.${2}', $original_request );
$ao_cache_dir = '/www/wwwroot/acrib.es/wp-content/cache/autoptimize/';
$js_or_css = pathinfo( $original_request, PATHINFO_EXTENSION );
// add multisite logic.
$multisite = false;
if ( true === $multisite ) {
preg_match( '#\/([0-9]{1,5})\/(?:js|css)\/[a-z0-9]*_fallback\.(?:js|css)$#', $fallback_target, $child_site_id );
$ao_root_cache_dir = preg_replace( '#[0-9]*\/$#', '', $ao_cache_dir );
$ao_cache_dir = $ao_root_cache_dir . $child_site_id[1] . '/';
}
$fallback_path = $ao_cache_dir . $js_or_css . '/autoptimize_fallback.' . $js_or_css;
if ( $original_request !== $fallback_target && file_exists( $fallback_path ) ) {
// error_log( 'Autoptimize file ' . $original_request . ' not found, using fallback instead.' );
header( 'HTTP/1.1 302 Found' );
header( 'Location: ' . $fallback_target );
} else {
// error_log( 'Autoptimize file ' . $original_request . ' not found, sending 410 gone response.' );
header( 'HTTP/1.1 410 Gone' );
}
exit();

View File

@@ -0,0 +1,68 @@
<?php
/**
* ACRIB Core MU-Plugin
* Always-on: custom footer, security hardening, performance tweaks.
* @version 1.0
*/
// --- Footer: bottom bar with legal links + Cloud Host credit ---
add_action('wp_footer', function () {
$year = date('Y');
$legal_url = home_url('/aviso-legal/');
$pp_url = home_url('/politica-privacidad/');
$cookie_url = home_url('/politica-cookies/');
?>
<div class="acrib-footer-bottom">
<div class="acrib-footer-bottom-inner">
<p class="acrib-footer-copyright">
&copy; <?php echo $year; ?> <strong>ACRIB</strong> &mdash; Asociaci&oacute;n Casa Rom&acirc;neasc&#259; de las Islas Baleares
</p>
<ul class="acrib-footer-legal-links">
<li><a href="<?php echo esc_url($legal_url); ?>">Aviso Legal</a></li>
<li><a href="<?php echo esc_url($pp_url); ?>">Privacidad</a></li>
<li><a href="<?php echo esc_url($cookie_url); ?>">Cookies</a></li>
</ul>
<p class="acrib-footer-hosted">
Hosted &amp; Maintained by <a href="https://cloudhost.es" target="_blank" rel="noopener noreferrer">Cloud Host</a>
</p>
</div>
</div>
<?php
}, 100);
// --- Security headers ---
add_action('send_headers', function () {
if (!is_admin()) {
header('X-Frame-Options: SAMEORIGIN');
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: strict-origin-when-cross-origin');
header('Permissions-Policy: camera=(), microphone=(), geolocation=()');
}
});
// --- Disable XML-RPC ---
add_filter('xmlrpc_enabled', '__return_false');
remove_action('wp_head', 'rsd_link');
remove_action('wp_head', 'wlwmanifest_link');
remove_action('wp_head', 'wp_generator');
remove_action('wp_head', 'wp_shortlink_wp_head');
// --- Disable REST API user enumeration ---
add_filter('rest_endpoints', function ($endpoints) {
unset($endpoints['/wp/v2/users'], $endpoints['/wp/v2/users/(?P<id>[\d]+)']);
return $endpoints;
});
// --- Disable emoji (reduces ~20KB page weight) ---
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('wp_print_styles', 'print_emoji_styles');
remove_action('admin_print_scripts', 'print_emoji_detection_script');
remove_action('admin_print_styles', 'print_emoji_styles');
remove_filter('the_content_feed', 'wp_staticize_emoji');
remove_filter('comment_text_rss', 'wp_staticize_emoji');
remove_filter('wp_mail', 'wp_staticize_emoji_for_email');
// --- Limit post revisions ---
if (!defined('WP_POST_REVISIONS')) {
define('WP_POST_REVISIONS', 3);
}

View File

@@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
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.
{description}
Copyright (C) {year} {fullname}
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.
{signature of Ty Coon}, 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.

View File

@@ -0,0 +1,102 @@
<?php
/**
* Plugin Name: Autoptimize
* Plugin URI: https://autoptimize.com/pro/
* Description: Makes your site faster by optimizing CSS, JS, Images, Google fonts and more.
* Version: 3.1.15.1
* Author: Frank Goossens (futtta)
* Author URI: https://autoptimize.com/pro/
* Text Domain: autoptimize
* License: GPLv2
* Released under the GNU General Public License (GPL)
* https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
*/
/**
* Autoptimize main plugin file.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
define( 'AUTOPTIMIZE_PLUGIN_VERSION', '3.1.15.1' );
// plugin_dir_path() returns the trailing slash!
define( 'AUTOPTIMIZE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'AUTOPTIMIZE_PLUGIN_FILE', __FILE__ );
// Bail early if attempting to run on non-supported php versions.
if ( version_compare( PHP_VERSION, '5.6', '<' ) ) {
function autoptimize_incompatible_admin_notice() {
echo '<div class="error"><p>' . esc_html__( 'Autoptimize requires PHP 5.6 (or higher) to function properly. Please upgrade PHP. The Plugin has been auto-deactivated.', 'autoptimize' ) . '</p></div>';
if ( isset( $_GET['activate'] ) ) {
unset( $_GET['activate'] );
}
}
function autoptimize_deactivate_self() {
deactivate_plugins( plugin_basename( AUTOPTIMIZE_PLUGIN_FILE ) );
}
add_action( 'admin_notices', 'autoptimize_incompatible_admin_notice' );
add_action( 'admin_init', 'autoptimize_deactivate_self' );
return;
}
function autoptimize_autoload( $class_name ) {
if ( in_array( $class_name, array( 'AO_Minify_HTML', 'JSMin' ) ) ) {
$file = strtolower( $class_name );
$file = str_replace( '_', '-', $file );
$path = dirname( __FILE__ ) . '/classes/external/php/';
$filepath = $path . $file . '.php';
} elseif ( false !== strpos( $class_name, 'Autoptimize\\tubalmartin\\CssMin' ) ) {
$file = str_replace( 'Autoptimize\\tubalmartin\\CssMin\\', '', $class_name );
$path = dirname( __FILE__ ) . '/classes/external/php/yui-php-cssmin-bundled/';
$filepath = $path . $file . '.php';
} elseif ( 'autoptimize' === substr( $class_name, 0, 11 ) ) {
// One of our "old" classes.
$file = $class_name;
$path = dirname( __FILE__ ) . '/classes/';
$filepath = $path . $file . '.php';
} elseif ( 'PAnD' === $class_name ) {
$file = 'persist-admin-notices-dismissal';
$path = dirname( __FILE__ ) . '/classes/external/php/persist-admin-notices-dismissal/';
$filepath = $path . $file . '.php';
}
// If we didn't match one of our rules, bail!
if ( ! isset( $filepath ) || ! is_readable( $filepath ) ) {
return;
}
require $filepath;
}
spl_autoload_register( 'autoptimize_autoload' );
// Load WP CLI command(s) on demand.
if ( defined( 'WP_CLI' ) && WP_CLI ) {
require AUTOPTIMIZE_PLUGIN_DIR . 'classes/autoptimizeCLI.php';
}
// filter to disable AO both on front- and backend.
if ( apply_filters( 'autoptimize_filter_disable_plugin', false ) ) {
return;
}
/**
* Retrieve the instance of the main plugin class.
*
* @return autoptimizeMain
*/
function autoptimize() {
static $plugin = null;
if ( null === $plugin ) {
$plugin = new autoptimizeMain( AUTOPTIMIZE_PLUGIN_VERSION, AUTOPTIMIZE_PLUGIN_FILE );
}
return $plugin;
}
autoptimize()->run();

View File

@@ -0,0 +1,143 @@
<?php
/*
Plugin Name: Autoptimize Helper
Plugin URI: http://blog.futtta.be/autoptimize
Description: Autoptimize Helper contains some helper functions to make Autoptimize even more flexible
Author: Frank Goossens (futtta)
Version: 0.2
Author URI: http://blog.futtta.be/
*/
/* autoptimize_filter_css_datauri_maxsize: change the threshold at which background images are turned into data uri's;
@param $urisize: default size
@return: your own preferred size */
// add_filter('autoptimize_filter_css_datauri_maxsize','my_ao_override_dataursize',10,1);
function my_ao_override_dataursize($urisizeIn) {
return 100000;
}
/* autoptimize_filter_css_datauri_exclude: exclude background images from being turned into data uri's;
@param $imageexcl: default images excluded (empty)
@return: comma-seperated list of images to exclude */
// add_filter('autoptimize_filter_css_datauri_exclude','my_ao_exclude_image',10,1);
function my_ao_exclude_image($imageexcl) {
return $imageexcl . ", adfreebutton.jpg, otherimage.png";
}
/* autoptimize_filter_js_defer: change flag added to javascript
@param $defer: default value, "" when forced in head, "defer " when not forced in head
@return: new value */
// add_filter('autoptimize_filter_js_defer','my_ao_override_defer',10,1);
function my_ao_override_defer($defer) {
return $defer."async ";
}
/* autoptimize_filter_noptimize: stop autoptimize from optimizing, e.g. based on URL as in example
@return: boolean, true or false */
// add_filter('autoptimize_filter_noptimize','my_ao_noptimize',10,1);
function my_ao_noptimize( $flag_in ) {
if (strpos($_SERVER['REQUEST_URI'],'no-autoptimize-now')!==false) {
return true;
} else {
return $flag_in;
}
}
/* autoptimize_filter_js_exclude; JS optimization exclude strings, as configured in admin page
@param $exclude: comma-seperated list of exclude strings
@return: comma-seperated list of exclude strings */
// add_filter('autoptimize_filter_js_exclude','my_ao_override_jsexclude',10,1);
function my_ao_override_jsexclude($exclude) {
return $exclude.", customize-support";
}
/* autoptimize_filter_css_exclude: CSS optimization exclude strings, as configured in admin page
@param $exclude: comma-seperated list of exclude strings
@return: comma-seperated list of exclude strings */
// add_filter('autoptimize_filter_css_exclude','my_ao_override_cssexclude',10,1);
function my_ao_override_cssexclude($exclude) {
return $exclude.", recentcomments";
}
/* autoptimize_filter_js_movelast: internal array of what script can be moved to the bottom of the HTML
@param array $movelast
@return: updated array */
// add_filter('autoptimize_filter_js_movelast','my_ao_override_movelast',10,1);
function my_ao_override_movelast($movelast) {
$movelast[]="console.log";
return $movelast;
}
/* autoptimize_filter_css_replacetag: where in the HTML is optimized CSS injected
@param array $replacetag, containing the html-tag and the method (inject "before", "after" or "replace")
@return array with updated values */
// add_filter('autoptimize_filter_css_replacetag','my_ao_override_css_replacetag',10,1);
function my_ao_override_css_replacetag($replacetag) {
return array("<head>","after");
}
/* autoptimize_filter_js_replacetag: where in the HTML is optimized JS injected
@param array $replacetag, containing the html-tag and the method (inject "before", "after" or "replace")
@return array with updated values */
// add_filter('autoptimize_filter_js_replacetag','my_ao_override_js_replacetag',10,1);
function my_ao_override_js_replacetag($replacetag) {
return array("<injectjs />","replace");
}
/* autoptimize_js_do_minify: do we want to minify? if set to false autoptimize effectively only aggregates, but does not minify
@return: boolean true or false */
// add_filter('autoptimize_js_do_minify','my_ao_js_minify',10,1);
function my_ao_js_minify() {
return false;
}
/* autoptimize_css_do_minify: do we want to minify? if set to false autoptimize effectively only aggregates, but does not minify
@return: boolean true or false */
// add_filter('autoptimize_css_do_minify','my_ao_css_minify',10,1);
function my_ao_css_minify() {
return false;
}
/* autoptimize_js_include_inline: do we want AO to also aggregate inline JS?
@return: boolean true or false */
// add_filter('autoptimize_js_include_inline','my_ao_js_include_inline',10,1);
function my_ao_js_include_inline() {
return false;
}
/* autoptimize_css_include_inline: do we want AO to also aggregate inline CSS?
@return: boolean true or false */
// add_filter('autoptimize_css_include_inline','my_ao_css_include_inline',10,1);
function my_ao_css_include_inline() {
return false;
}
/* autoptimize_filter_css_defer_inline: what CSS to inline when "defer and inline" is activated
@param $inlined: string with above the fold CSS as configured in admin
@return: updated string with above the fold CSS */
// add_filter('autoptimize_filter_css_defer_inline','my_ao_css_defer_inline',10,1);
function my_ao_css_defer_inline($inlined) {
return $inlined."h2,h1{color:red !important;}";
}
/* autoptimize_filter_css_fonts_cdn: do we want to move fonts to the CDN-url as well
@return: false (default) or true */
// add_filter('autoptimize_filter_css_fonts_cdn','my_css_cdnfont',10,0);
function my_css_cdnfont(){
return true;
}

View File

@@ -0,0 +1,711 @@
<?php
/**
* Base class other (more-specific) classes inherit from.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
abstract class autoptimizeBase
{
/**
* Holds content being processed (html, scripts, styles)
*
* @var string
*/
protected $content = '';
/**
* Controls debug logging.
*
* @var bool
*/
public $debug_log = false;
/**
* Initiated $cdn_url.
*
* @var string
*/
public $cdn_url = '';
public function __construct( $content )
{
$this->content = $content;
}
/**
* Reads the page and collects tags.
*
* @param array $options Options.
*
* @return bool
*/
abstract public function read( $options );
/**
* Joins and optimizes collected things.
*
* @return bool
*/
abstract public function minify();
/**
* Caches the things.
*
* @return void
*/
abstract public function cache();
/**
* Returns the content
*
* @return string
*/
abstract public function getcontent();
/**
* Tranfsorms a given URL to a full local filepath if possible.
* Returns local filepath or false.
*
* @param string $url URL to transform.
*
* @return bool|string
*/
public function getpath( $url )
{
$url = apply_filters( 'autoptimize_filter_cssjs_alter_url', $url );
if ( is_null( $url ) ) {
return false;
}
if ( false !== strpos( $url, '%' ) ) {
$url = urldecode( $url );
}
$site_host = parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST );
$content_host = parse_url( AUTOPTIMIZE_WP_ROOT_URL, PHP_URL_HOST );
// Normalizing attempts...
$double_slash_position = strpos( $url, '//' );
if ( 0 === $double_slash_position ) {
if ( is_ssl() ) {
$url = 'https:' . $url;
} else {
$url = 'http:' . $url;
}
} elseif ( ( false === $double_slash_position ) && ( false === strpos( $url, $site_host ) ) ) {
if ( AUTOPTIMIZE_WP_SITE_URL === $site_host ) {
$url = AUTOPTIMIZE_WP_SITE_URL . $url;
} elseif ( 0 === strpos( $url, '/' ) ) {
$url = '//' . $site_host . autoptimizeUtils::path_canonicalize( $url );
} else {
$url = AUTOPTIMIZE_WP_SITE_URL . autoptimizeUtils::path_canonicalize( $url );
}
}
if ( $site_host !== $content_host ) {
$url = str_replace( AUTOPTIMIZE_WP_CONTENT_URL, AUTOPTIMIZE_WP_SITE_URL . AUTOPTIMIZE_WP_CONTENT_NAME, $url );
}
// First check; hostname wp site should be hostname of url!
$url_host = @parse_url( $url, PHP_URL_HOST ); // @codingStandardsIgnoreLine
if ( $url_host !== $site_host ) {
/**
* First try to get all domains from WPML (if available)
* then explicitely declare $this->cdn_url as OK as well
* then apply own filter autoptimize_filter_cssjs_multidomain takes an array of hostnames
* each item in that array will be considered part of the same WP multisite installation
*/
$multidomains = array();
$multidomains_wpml = apply_filters( 'wpml_setting', array(), 'language_domains' );
if ( ! empty( $multidomains_wpml ) ) {
$multidomains = array_map( array( $this, 'get_url_hostname' ), $multidomains_wpml );
}
if ( ! empty( $this->cdn_url ) ) {
$multidomains[] = parse_url( $this->cdn_url, PHP_URL_HOST );
}
$multidomains = apply_filters( 'autoptimize_filter_cssjs_multidomain', $multidomains );
if ( ! empty( $multidomains ) ) {
if ( in_array( $url_host, $multidomains ) ) {
$url = str_replace( $url_host, $site_host, $url );
} else {
return false;
}
} else {
return false;
}
}
// Try to remove "wp root url" from url while not minding http<>https.
$tmp_ao_root = preg_replace( '/https?:/', '', AUTOPTIMIZE_WP_ROOT_URL );
if ( $site_host !== $content_host ) {
// As we replaced the content-domain with the site-domain, we should match against that.
$tmp_ao_root = preg_replace( '/https?:/', '', AUTOPTIMIZE_WP_SITE_URL );
}
if ( is_multisite() && ! is_main_site() && ! empty( $this->cdn_url ) && apply_filters( 'autoptimize_filter_base_getpage_multisite_cdn_juggling', true ) ) {
// multisite child sites with CDN need the network_site_url as tmp_ao_root but only if directory-based multisite.
$_network_site_url = network_site_url();
if ( strpos( AUTOPTIMIZE_WP_SITE_URL, $_network_site_url ) !== false ) {
$tmp_ao_root = preg_replace( '/https?:/', '', $_network_site_url );
}
}
$tmp_url = preg_replace( '/https?:/', '', $url );
$path = str_replace( $tmp_ao_root, '', $tmp_url );
// If path starts with :// or //, this is not a URL in the WP context and
// we have to assume we can't aggregate.
if ( preg_match( '#^:?//#', $path ) ) {
// External script/css (adsense, etc).
return false;
}
// Prepend with WP_ROOT_DIR to have full path to file.
$path = str_replace( '//', '/', trailingslashit( WP_ROOT_DIR ) . $path );
// Allow path to be altered, e.g. in the case of bedrock-like setups where
// core, theme & plugins might be in different locations on the filesystem.
$path = apply_filters( 'autoptimize_filter_base_getpath_path', $path, $url );
// Final check: does file exist and is it readable?
if ( file_exists( $path ) && is_file( $path ) && is_readable( $path ) ) {
return $path;
} else {
return false;
}
}
/**
* Returns the hostname part of a given $url if we're able to parse it.
* If not, it returns the original url (prefixed with http:// scheme in case
* it was missing).
* Used as callback for WPML multidomains filter.
*
* @param string $url URL.
*
* @return string
*/
protected function get_url_hostname( $url )
{
// Checking that the url starts with something vaguely resembling a protocol.
if ( ( 0 !== strpos( $url, 'http' ) ) && ( 0 !== strpos( $url, '//' ) ) ) {
$url = 'http://' . $url;
}
// Grab the hostname.
$hostname = parse_url( $url, PHP_URL_HOST );
// Fallback when parse_url() fails.
if ( empty( $hostname ) ) {
$hostname = $url;
}
return $hostname;
}
/**
* Hides everything between noptimize-comment tags.
*
* @param string $markup Markup to process.
*
* @return string
*/
protected function hide_noptimize( $markup )
{
return $this->replace_contents_with_marker_if_exists(
'NOPTIMIZE',
'/<!--\s?noptimize\s?-->/',
'#<!--\s?noptimize\s?-->.*?<!--\s?/\s?noptimize\s?-->#is',
$markup
);
}
/**
* Unhide noptimize-tags.
*
* @param string $markup Markup to process.
*
* @return string
*/
protected function restore_noptimize( $markup )
{
return $this->restore_marked_content( 'NOPTIMIZE', $markup );
}
/**
* Hides "iehacks" content.
*
* @param string $markup Markup to process.
*
* @return string
*/
protected function hide_iehacks( $markup )
{
return $this->replace_contents_with_marker_if_exists(
'IEHACK', // Marker name...
'<!--[if', // Invalid regex, will fallback to search using strpos()...
'#<!--\[if.*?\[endif\]-->#is', // Replacement regex...
$markup
);
}
/**
* Restores "hidden" iehacks content.
*
* @param string $markup Markup to process.
*
* @return string
*/
protected function restore_iehacks( $markup )
{
return $this->restore_marked_content( 'IEHACK', $markup );
}
/**
* "Hides" content within HTML comments using a regex-based replacement
* if HTML comment markers are found.
* `<!--example-->` becomes `%%COMMENTS%%ZXhhbXBsZQ==%%COMMENTS%%`
*
* @param string $markup Markup to process.
*
* @return string
*/
protected function hide_comments( $markup )
{
return $this->replace_contents_with_marker_if_exists(
'COMMENTS',
'<!--',
'#<!--.*?-->#is',
$markup
);
}
/**
* Restores original HTML comment markers inside a string whose HTML
* comments have been "hidden" by using `hide_comments()`.
*
* @param string $markup Markup to process.
*
* @return string
*/
protected function restore_comments( $markup )
{
return $this->restore_marked_content( 'COMMENTS', $markup );
}
/**
* Replaces the given URL with the CDN-version of it when CDN replacement
* is supposed to be done.
*
* @param string $url URL to process.
*
* @return string
*/
public function url_replace_cdn( $url )
{
// For 2.3 back-compat in which cdn-ing appeared to be automatically
// including WP subfolder/subdirectory into account as part of cdn-ing,
// even though it might've caused serious troubles in certain edge-cases.
$cdn_url = autoptimizeUtils::tweak_cdn_url_if_needed( $this->cdn_url );
// Allows API/filter to further tweak the cdn url...
$cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', $cdn_url );
if ( ! empty( $cdn_url ) && false === strpos( $url, $cdn_url ) && false !== apply_filters( 'autoptimize_filter_base_apply_cdn', true, $url ) ) {
// Simple str_replace-based approach fails when $url is protocol-or-host-relative.
$is_protocol_relative = autoptimizeUtils::is_protocol_relative( $url );
$is_host_relative = ( ! $is_protocol_relative && ( '/' === $url[0] ) );
$cdn_url = esc_url( rtrim( $cdn_url, '/' ) );
if ( $is_host_relative ) {
// Prepending host-relative urls with the cdn url.
$url = $cdn_url . $url;
} else {
// Either a protocol-relative or "regular" url, replacing it either way.
if ( $is_protocol_relative ) {
// Massage $site_url so that simple str_replace() still "works" by
// searching for the protocol-relative version of AUTOPTIMIZE_WP_SITE_URL.
$site_url = str_replace( array( 'http:', 'https:' ), '', AUTOPTIMIZE_WP_SITE_URL );
} else {
$site_url = AUTOPTIMIZE_WP_SITE_URL;
}
$url = str_replace( $site_url, $cdn_url, $url );
}
}
// Allow API filter to take further care of CDN replacement.
$url = apply_filters( 'autoptimize_filter_base_replace_cdn', $url );
return $url;
}
/**
* Injects/replaces the given payload markup into `$this->content`
* at the specified location.
* If the specified tag cannot be found, the payload is appended into
* $this->content along with a warning wrapped inside <!--noptimize--> tags.
*
* @param string $payload Markup to inject.
* @param array $where Array specifying the tag name and method of injection.
* Index 0 is the tag name (i.e., `</body>`).
* Index 1 specifies ˛'before', 'after' or 'replace'. Defaults to 'before'.
*
* @return void
*/
protected function inject_in_html( $payload, $where )
{
$warned = false;
$position = autoptimizeUtils::strpos( $this->content, $where[0] );
if ( false !== $position ) {
// Found the tag, setup content/injection as specified.
if ( 'after' === $where[1] ) {
$content = $where[0] . $payload;
} elseif ( 'replace' === $where[1] ) {
$content = $payload;
} else {
$content = $payload . $where[0];
}
// Place where specified.
$this->content = autoptimizeUtils::substr_replace(
$this->content,
$content,
$position,
// Using plain strlen() should be safe here for now, since
// we're not searching for multibyte chars here still...
strlen( $where[0] )
);
} else {
// Couldn't find what was specified, just append and add a warning.
$this->content .= $payload;
if ( ! $warned ) {
$tag_display = str_replace( array( '<', '>' ), '', $where[0] );
$this->content .= '<!--noptimize--><!-- Autoptimize found a problem with the HTML in your Theme, tag `' . $tag_display . '` missing --><!--/noptimize-->';
$warned = true;
}
}
}
/**
* Returns true if given `$tag` is found in the list of `$removables`.
*
* @param string $tag Tag to search for.
* @param array $removables List of things considered completely removable.
*
* @return bool
*/
protected function isremovable( $tag, $removables )
{
foreach ( $removables as $match ) {
if ( false !== strpos( $tag, $match ) ) {
return true;
}
}
return false;
}
/**
* Callback used in `self::inject_minified()`.
*
* @param array $matches Regex matches.
*
* @return string
*/
public function inject_minified_callback( $matches )
{
static $conf = null;
if ( null === $conf ) {
$conf = autoptimizeConfig::instance();
}
/**
* $matches[1] holds the whole match caught by regex in self::inject_minified(),
* so we take that and split the string on `|`.
* First element is the filepath, second is the md5 hash of contents
* the filepath had when it was being processed.
* If we don't have those, we'll bail out early.
*/
$filepath = null;
$filehash = null;
// Grab the parts we need.
$parts = explode( '|', $matches[1] );
if ( ! empty( $parts ) ) {
$filepath = isset( $parts[0] ) ? base64_decode( $parts[0] ) : null;
$filehash = isset( $parts[1] ) ? $parts[1] : null;
}
// Bail early if something's not right...
if ( ! $filepath || ! $filehash ) {
return "\n";
}
$filecontent = file_get_contents( $filepath );
// Some things are differently handled for css/js...
$is_js_file = ( '.js' === substr( $filepath, -3, 3 ) );
$is_css_file = false;
if ( ! $is_js_file ) {
$is_css_file = ( '.css' === substr( $filepath, -4, 4 ) );
}
// BOMs being nuked here unconditionally (regardless of where they are)!
$filecontent = preg_replace( "#\x{EF}\x{BB}\x{BF}#", '', $filecontent );
// Remove comments and blank lines.
if ( $is_js_file ) {
$filecontent = preg_replace( '#^\s*\/\/.*$#Um', '', $filecontent );
}
// Nuke un-important comments.
$filecontent = preg_replace( '#^\s*\/\*[^!].*\*\/\s?#Um', '', $filecontent );
// Normalize newlines.
$filecontent = preg_replace( '#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#', "\n", $filecontent );
// JS specifics.
if ( $is_js_file ) {
// Append a semicolon at the end of js files if it's missing.
$last_char = substr( $filecontent, -1, 1 );
if ( ';' !== $last_char && '}' !== $last_char ) {
$filecontent .= ';';
}
// Check if try/catch should be used.
$opt_js_try_catch = $conf->get( 'autoptimize_js_trycatch' );
if ( 'on' === $opt_js_try_catch ) {
// It should, wrap in try/catch.
$filecontent = 'try{' . $filecontent . '}catch(e){}';
}
} elseif ( $is_css_file ) {
$filecontent = autoptimizeStyles::fixurls( $filepath, $filecontent );
} else {
$filecontent = '';
}
// Return modified (or empty!) code/content.
return "\n" . $filecontent;
}
/**
* Inject already minified code in optimized JS/CSS.
*
* @param string $in Markup.
*
* @return string
*/
protected function inject_minified( $in )
{
$out = $in;
if ( false !== strpos( $in, '%%INJECTLATER%%' ) ) {
$out = preg_replace_callback(
'#\/\*\!%%INJECTLATER' . AUTOPTIMIZE_HASH . '%%(.*?)%%INJECTLATER%%\*\/#is',
array( $this, 'inject_minified_callback' ),
$in
);
}
return $out;
}
/**
* Specialized method to create the INJECTLATER marker.
* These are somewhat "special", in the sense that they're additionally wrapped
* within an "exclamation mark style" comment, so that they're not stripped
* out by minifiers.
* They also currently contain the hash of the file's contents too (unlike other markers).
*
* @param string $filepath Filepath.
* @param string $hash Hash.
*
* @return string
*/
public static function build_injectlater_marker( $filepath, $hash )
{
$contents = '/*!' . self::build_marker( 'INJECTLATER', $filepath, $hash ) . '*/';
return $contents;
}
/**
* Creates and returns a `%%`-style named marker which holds
* the base64 encoded `$data`.
* If `$hash` is provided, it's appended to the base64 encoded string
* using `|` as the separator (in order to support building the
* somewhat special/different INJECTLATER marker).
*
* @param string $name Marker name.
* @param string $data Marker data which will be base64-encoded.
* @param string|null $hash Optional.
*
* @return string
*/
public static function build_marker( $name, $data, $hash = null )
{
// Start the marker, add the data.
$marker = '%%' . $name . AUTOPTIMIZE_HASH . '%%' . base64_encode( $data );
// Add the hash if provided.
if ( null !== $hash ) {
$marker .= '|' . $hash;
}
// Close the marker.
$marker .= '%%' . $name . '%%';
return $marker;
}
/**
* Searches for `$search` in `$content` (using either `preg_match()`
* or `strpos()`, depending on whether `$search` is a valid regex pattern or not).
* If something is found, it replaces `$content` using `$re_replace_pattern`,
* effectively creating our named markers (`%%{$marker}%%`.
* These are then at some point replaced back to their actual/original/modified
* contents using `autoptimizeBase::restore_marked_content()`.
*
* @param string $marker Marker name (without percent characters).
* @param string $search A string or full blown regex pattern to search for in $content. Uses `strpos()` or `preg_match()`.
* @param string $re_replace_pattern Regex pattern to use when replacing contents.
* @param string $content Content to work on.
*
* @return string
*/
public static function replace_contents_with_marker_if_exists( $marker, $search, $re_replace_pattern, $content )
{
$found = false;
$is_regex = autoptimizeUtils::str_is_valid_regex( $search );
if ( $is_regex ) {
$found = preg_match( $search, $content );
} else {
$found = ( false !== strpos( $content, $search ) );
}
if ( $found ) {
$content = preg_replace_callback(
$re_replace_pattern,
function( $matches ) use ( $marker ) {
return autoptimizeBase::build_marker( $marker, $matches[0] );
},
$content
);
// Check for error (for example, an error can occur if $content is very large).
if ( null === $content ) {
$error_message = 'Autoptimize: preg_replace_callback() failed';
if ( function_exists( 'preg_last_error_msg' ) ) {
$error_message .= ': ' . preg_last_error_msg();
}
error_log( $error_message );
}
}
return $content;
}
/**
* Complements `autoptimizeBase::replace_contents_with_marker_if_exists()`.
*
* @param string $marker Marker.
* @param string $content Markup.
*
* @return string
*/
public static function restore_marked_content( $marker, $content )
{
if ( false !== strpos( $content, $marker ) ) {
$content = preg_replace_callback(
'#%%' . $marker . AUTOPTIMIZE_HASH . '%%(.*?)%%' . $marker . '%%#is',
function ( $matches ) {
return base64_decode( $matches[1] );
},
$content
);
}
return $content;
}
/**
* Logs given `$data` for debugging purposes (when debug logging is on).
*
* @param mixed $data Data to log.
*
* @return void
*/
protected function debug_log( $data )
{
if ( ! isset( $this->debug_log ) || ! $this->debug_log ) {
return;
}
if ( ! is_string( $data ) && ! is_resource( $data ) ) {
$data = var_export( $data, true );
}
error_log( $data );
}
/**
* Checks if a single local css/js file can be minified and returns source if so.
*
* @param string $filepath Filepath.
*
* @return bool|string to be minified code or false.
*/
protected function prepare_minify_single( $filepath )
{
// Decide what we're dealing with, return false if we don't know.
if ( autoptimizeUtils::str_ends_in( $filepath, '.js' ) ) {
$type = 'js';
} elseif ( autoptimizeUtils::str_ends_in( $filepath, '.css' ) ) {
$type = 'css';
} else {
return false;
}
// Bail if it looks like its already minifed (by having -min or .min in filename).
$minified_variants = array(
'-min.' . $type,
'.min.' . $type,
);
foreach ( $minified_variants as $ending ) {
if ( autoptimizeUtils::str_ends_in( $filepath, $ending ) && true === apply_filters( 'autoptimize_filter_base_prepare_exclude_minified', true ) ) {
return false;
}
}
// Get file contents, bail if empty.
$contents = file_get_contents( $filepath );
return $contents;
}
/**
* Given an autoptimizeCache instance returns the (maybe cdn-ed) url of
* the cached file.
*
* @param autoptimizeCache $cache autoptimizeCache instance.
*
* @return string
*/
protected function build_minify_single_url( autoptimizeCache $cache )
{
$url = AUTOPTIMIZE_CACHE_URL . $cache->getname();
// CDN-replace the resulting URL if needed...
$url = $this->url_replace_cdn( $url );
return $url;
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* WP-CLI commands for Autoptimize.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// This is a WP-CLI command, so bail if it's not available.
if ( ! defined( 'WP_CLI' ) ) {
return;
}
class autoptimizeCLI extends \WP_CLI_Command
{
/**
* Clears the cache.
*
* @subcommand clear
*
* @param array $args Args.
* @param array $args_assoc Associative args.
*
* @return void
*/
public function clear( $args, $args_assoc ) {
WP_CLI::line( esc_html__( 'Flushing the cache...', 'autoptimize' ) );
autoptimizeCache::clearall();
WP_CLI::success( esc_html__( 'Cache flushed.', 'autoptimize' ) );
}
}
WP_CLI::add_command( 'autoptimize', 'autoptimizeCLI' );

View File

@@ -0,0 +1,69 @@
<?php
/**
* Thin wrapper around css minifiers to avoid rewriting a bunch of existing code.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeCSSmin
{
/**
* Minifier instance.
*
* @var Autoptimize\tubalmartin\CssMin\Minifier|null
*/
protected $minifier = null;
/**
* Construtor.
*
* @param bool $raise_limits Whether to raise memory limits or not. Default true.
*/
public function __construct( $raise_limits = true )
{
$this->minifier = new Autoptimize\tubalmartin\CssMin\Minifier( $raise_limits );
}
/**
* Runs the minifier on given string of $css.
* Returns the minified css.
*
* @param string $css CSS to minify.
*
* @return string
*/
public function run( $css )
{
// hide calc() if filter says yes (as YUI CSS compressor PHP port has problems retaining spaces around + and - operators).
// regex see tests at https://regex101.com/r/ofGQG9/1
if ( apply_filters( 'autoptimize_filter_css_hide_calc', true ) ) {
$css = autoptimizeBase::replace_contents_with_marker_if_exists( 'CALC', 'calc(', '#(calc|min|max|clamp)\([^;}]*\)#m', $css );
}
// minify.
$result = $this->minifier->run( $css );
// restore calc() if filter says yes.
if ( apply_filters( 'autoptimize_filter_css_hide_calc', true ) ) {
$result = autoptimizeBase::restore_marked_content( 'CALC', $result );
}
return $result;
}
/**
* Static helper.
*
* @param string $css CSS to minify.
*
* @return string
*/
public static function minify( $css )
{
$minifier = new self();
return $minifier->run( $css );
}
}

View File

@@ -0,0 +1,855 @@
<?php
/**
* Handles disk-cache-related operations.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeCache
{
/**
* Cache filename.
*
* @var string
*/
private $filename;
/**
* Cache directory path (with a trailing slash).
*
* @var string
*/
private $cachedir;
/**
* Whether gzipping is done by the web server or us.
* True => we don't gzip, the web server does it.
* False => we do it ourselves.
*
* @var bool
*/
private $nogzip;
/**
* Ctor.
*
* @param string $md5 Hash.
* @param string $ext Extension.
*/
public function __construct( $md5, $ext = 'php' )
{
$_min_ext = '';
if ( apply_filters( 'autoptimize_filter_cache_url_add_min_ext', false ) ) {
$_min_ext = '.min';
}
$this->cachedir = AUTOPTIMIZE_CACHE_DIR;
$this->nogzip = AUTOPTIMIZE_CACHE_NOGZIP;
if ( ! $this->nogzip ) {
$this->filename = AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . $_min_ext . '.php';
} else {
if ( in_array( $ext, array( 'js', 'css' ) ) ) {
$this->filename = $ext . '/' . AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . $_min_ext . '.' . $ext;
} else {
$this->filename = AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . $_min_ext . '.' . $ext;
}
}
}
/**
* Returns true if the cached file exists on disk.
*
* @return bool
*/
public function check()
{
return file_exists( $this->cachedir . $this->filename );
}
/**
* Returns cache contents if they exist, false otherwise.
*
* @return string|false
*/
public function retrieve()
{
if ( $this->check() ) {
if ( false == $this->nogzip ) {
return file_get_contents( $this->cachedir . $this->filename . '.none' );
} else {
return file_get_contents( $this->cachedir . $this->filename );
}
}
return false;
}
/**
* Stores given $data in cache.
*
* @param string $data Data to cache.
* @param string $mime Mimetype.
*
* @return void|bool
*/
public function cache( $data, $mime )
{
// readonly FS explicitly OK'ed by developer, so just pretend all is OK.
if ( defined( 'AUTOPTIMIZE_CACHE_READONLY' ) ) {
return true;
}
// off by default; check if cachedirs exist every time before caching
//
// to be activated for users that experience these ugly errors;
// PHP Warning: file_put_contents failed to open stream: No such file or directory.
if ( apply_filters( 'autoptimize_filter_cache_checkdirs_on_write', false ) ) {
$this->check_and_create_dirs();
}
if ( false === $this->nogzip ) {
// We handle gzipping ourselves.
$file = 'default.php';
$phpcode = file_get_contents( AUTOPTIMIZE_PLUGIN_DIR . 'config/' . $file );
$phpcode = str_replace( array( '%%CONTENT%%', 'exit;' ), array( $mime, '' ), $phpcode );
file_put_contents( $this->cachedir . $this->filename, $phpcode );
file_put_contents( $this->cachedir . $this->filename . '.none', $data );
} else {
// Write code to cache without doing anything else.
file_put_contents( $this->cachedir . $this->filename, $data );
// save fallback .js or .css file if filter true (to be false by default) but not if snippet or single.
if ( self::do_fallback() && strpos( $this->filename, '_snippet_' ) === false && strpos( $this->filename, '_single_' ) === false ) {
$_extension = pathinfo( $this->filename, PATHINFO_EXTENSION );
$_fallback_file = AUTOPTIMIZE_CACHEFILE_PREFIX . 'fallback.' . $_extension;
if ( ( 'css' === $_extension || 'js' === $_extension ) && ! file_exists( $this->cachedir . $_extension . '/' . $_fallback_file ) ) {
file_put_contents( $this->cachedir . $_extension . '/' . $_fallback_file, $data );
}
}
if ( apply_filters( 'autoptimize_filter_cache_create_static_gzip', false ) ) {
// Create an additional cached gzip file.
file_put_contents( $this->cachedir . $this->filename . '.gz', gzencode( $data, 9, FORCE_GZIP ) );
// If PHP Brotli extension is installed, create an additional cached Brotli file.
if ( function_exists( 'brotli_compress' ) ) {
file_put_contents( $this->cachedir . $this->filename . '.br', brotli_compress( $data, 11, BROTLI_GENERIC ) );
}
}
}
// Provide 3rd party action hook for every cache file that is created.
// This hook can for example be used to inject a copy of the created cache file to a other domain.
do_action( 'autoptimize_action_cache_file_created', $this->cachedir . $this->filename );
}
/**
* Get cache filename.
*
* @return string
*/
public function getname()
{
// NOTE: This could've maybe been a do_action() instead, however,
// that ship has sailed.
// The original idea here was to provide 3rd party code a hook so that
// it can "listen" to all the complete autoptimized-urls that the page
// will emit... Or something to that effect I think?
apply_filters( 'autoptimize_filter_cache_getname', AUTOPTIMIZE_CACHE_URL . $this->filename );
return $this->filename;
}
/**
* Returns true if given `$file` is considered a valid Autoptimize cache file,
* false otherwise.
*
* @param string $dir Directory name (with a trailing slash).
* @param string $file Filename.
* @return bool
*/
protected static function is_valid_cache_file( $dir, $file )
{
if ( '.' !== $file && '..' !== $file &&
false !== strpos( $file, AUTOPTIMIZE_CACHEFILE_PREFIX ) &&
is_file( $dir . $file ) ) {
// It's a valid file!
return true;
}
// Everything else is considered invalid!
return false;
}
/**
* Clears contents of AUTOPTIMIZE_CACHE_DIR.
*
* @return void
*/
protected static function clear_cache_classic()
{
$contents = self::get_cache_contents();
foreach ( $contents as $name => $files ) {
$dir = rtrim( AUTOPTIMIZE_CACHE_DIR . $name, '/' ) . '/';
foreach ( $files as $file ) {
if ( self::is_valid_cache_file( $dir, $file ) ) {
@unlink( $dir . $file ); // @codingStandardsIgnoreLine
}
}
}
@unlink( AUTOPTIMIZE_CACHE_DIR . '/.htaccess' ); // @codingStandardsIgnoreLine
}
/**
* Recursively deletes the specified pathname (file/directory) if possible.
* Returns true on success, false otherwise.
*
* @param string $pathname Pathname to remove.
*
* @return bool
*/
protected static function rmdir( $pathname )
{
$files = self::get_dir_contents( $pathname );
foreach ( $files as $file ) {
$path = $pathname . '/' . $file;
if ( is_dir( $path ) ) {
self::rmdir( $path );
} else {
unlink( $path );
}
}
return rmdir( $pathname );
}
/**
* Clears contents of AUTOPTIMIZE_CACHE_DIR by renaming the current
* cache directory into a new one with a unique name and then
* re-creating the default (empty) cache directory.
*
* Important/ Fixme: this does not take multisite into account, so
* if advanced_cache_clear_enabled is true (it is not by default)
* then the content for all subsites is zapped!
*
* @return bool Returns true when everything is done successfully, false otherwise.
*/
protected static function clear_cache_via_rename()
{
$ok = false;
$dir = self::get_pathname_base();
$new_name = self::get_unique_name();
// Makes sure the new pathname is on the same level...
$new_pathname = dirname( $dir ) . '/' . $new_name;
$renamed = @rename( $dir, $new_pathname ); // @codingStandardsIgnoreLine
// When renamed, re-create the default cache directory back so it's
// available again...
if ( $renamed ) {
$ok = self::cacheavail();
}
return $ok;
}
/**
* Returns true when advanced cache clearing is enabled.
*
* @return bool
*/
public static function advanced_cache_clear_enabled()
{
return apply_filters( 'autoptimize_filter_cache_clear_advanced', false );
}
/**
* Returns a (hopefully) unique new cache folder name for renaming purposes.
*
* @return string
*/
protected static function get_unique_name()
{
$prefix = self::get_advanced_cache_clear_prefix();
$new_name = uniqid( $prefix, true );
return $new_name;
}
/**
* Get cache prefix name used in advanced cache clearing mode.
*
* @return string
*/
protected static function get_advanced_cache_clear_prefix()
{
$pathname = self::get_pathname_base();
$basename = basename( $pathname );
$prefix = $basename . '-artifact-';
return $prefix;
}
/**
* Returns an array of file and directory names found within
* the given $pathname without '.' and '..' elements.
*
* @param string $pathname Pathname.
*
* @return array
*/
protected static function get_dir_contents( $pathname )
{
return array_slice( scandir( $pathname ), 2 );
}
/**
* Wipes directories which were created as part of the fast cache clearing
* routine (which renames the current cache directory into a new one with
* a custom-prefixed unique name).
*
* @return bool
*/
public static function delete_advanced_cache_clear_artifacts()
{
// Don't go through these motions (called from the cachechecker) if advanced cache clear isn't even active.
if ( ! self::advanced_cache_clear_enabled() ) {
return false;
}
$dir = self::get_pathname_base();
$prefix = self::get_advanced_cache_clear_prefix();
$parent = dirname( $dir );
$ok = false;
// Returns the list of files without '.' and '..' elements.
$files = self::get_dir_contents( $parent );
if ( is_array( $files ) && ! empty( $files ) ) {
foreach ( $files as $file ) {
$path = $parent . '/' . $file;
$prefixed = ( false !== strpos( $path, $prefix ) );
// Removing only our own (prefixed) directories...
if ( is_dir( $path ) && $prefixed ) {
$ok = self::rmdir( $path );
}
}
}
return $ok;
}
/**
* Returns the cache directory pathname used.
* Done as a function so we canSlightly different
* if multisite is used and `autoptimize_separate_blog_caches` filter
* is used.
*
* @return string
*/
public static function get_pathname()
{
$pathname = self::get_pathname_base();
if ( is_multisite() && apply_filters( 'autoptimize_separate_blog_caches', true ) ) {
$blog_id = get_current_blog_id();
$pathname .= $blog_id . '/';
}
return $pathname;
}
/**
* Returns the base path of our cache directory.
*
* @return string
*/
protected static function get_pathname_base()
{
$pathname = WP_CONTENT_DIR . AUTOPTIMIZE_CACHE_CHILD_DIR;
return $pathname;
}
/**
* Deletes everything from the cache directories.
*
* @param bool $propagate Whether to trigger additional actions when cache is purged.
*
* @return bool
*/
public static function clearall( $propagate = true )
{
if ( defined( 'ET_CORE_VERSION' ) && 'Divi' === get_template() ) {
// see https://blog.futtta.be/2018/11/17/warning-divi-purging-autoptimizes-cache/ .
$dbt = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2 );
$caller = isset( $dbt[1]['function'] ) ? $dbt[1]['function'] : null;
if ( 'et_core_clear_wp_cache' === $caller ) {
if ( apply_filters( 'autoptimize_filter_cache_divi_wrong_complain', true ) ) {
_doing_it_wrong( 'autoptimizeCache::clearall', 'Divi devs: please don\'t clear Autoptimize\'s cache, it is unneeded and can break sites. You can contact me at futtta@gmail.com to discuss.', 'Autoptimize 2.9.6' );
}
return false;
}
}
if ( ! self::cacheavail() || true === apply_filters( 'autoptimize_filter_cache_clearall_disabled', false ) ) {
return false;
}
// TODO/FIXME: If cache is big, switch to advanced/new cache clearing automatically?
if ( self::advanced_cache_clear_enabled() ) {
self::clear_cache_via_rename();
} else {
self::clear_cache_classic();
}
// Remove 404 handler if required.
if ( self::do_fallback() ) {
$_fallback_php = trailingslashit( WP_CONTENT_DIR ) . 'autoptimize_404_handler.php';
@unlink( $_fallback_php ); // @codingStandardsIgnoreLine
}
// Remove the transient so it gets regenerated...
delete_transient( 'autoptimize_stats' );
// Cache was just purged, clear page cache and allow others to hook into our purging...
if ( true === $propagate ) {
if ( ! function_exists( 'autoptimize_do_cachepurged_action' ) ) {
function autoptimize_do_cachepurged_action() {
do_action( 'autoptimize_action_cachepurged' );
}
}
add_action( 'shutdown', 'autoptimize_do_cachepurged_action', 11 );
add_action( 'autoptimize_action_cachepurged', array( 'autoptimizeCache', 'flushPageCache' ), 10, 0 );
}
// Warm cache (part of speedupper)!
if ( apply_filters( 'autoptimize_filter_speedupper', true ) && false == get_transient( 'autoptimize_cache_warmer_protector' ) ) {
set_transient( 'autoptimize_cache_warmer_protector', 'I shall not warm cache for another 10 minutes.', 60 * 10 );
$url = site_url() . '/?ao_speedup_cachebuster=' . rand( 1, 100000 );
$url = apply_filters( 'autoptimize_filter_cache_warmer_url', $url );
$cache = @wp_remote_get( $url ); // @codingStandardsIgnoreLine
unset( $cache );
}
return true;
}
/**
* Wrapper for clearall but with false param
* to ensure the event is not propagated to others
* through our own hooks (to avoid infinite loops).
*
* @return bool
*/
public static function clearall_actionless()
{
return self::clearall( false );
}
/**
* Returns the contents of our cache dirs.
*
* @return array
*/
protected static function get_cache_contents()
{
$contents = array();
foreach ( array( '', 'js', 'css' ) as $dir ) {
$contents[ $dir ] = scandir( AUTOPTIMIZE_CACHE_DIR . $dir );
}
return $contents;
}
/**
* Returns stats about cached contents.
*
* @return array
*/
public static function stats()
{
$stats = get_transient( 'autoptimize_stats' );
// If no transient, do the actual scan!
if ( ! is_array( $stats ) ) {
if ( ! self::cacheavail() ) {
return 0;
}
$stats = self::stats_scan();
$count = $stats[0];
if ( $count > 100 ) {
// Store results in transient.
set_transient(
'autoptimize_stats',
$stats,
apply_filters( 'autoptimize_filter_cache_statsexpiry', HOUR_IN_SECONDS )
);
}
}
return $stats;
}
/**
* Performs a scan of cache directory contents and returns an array
* with 3 values: count, size, timestamp.
* count = total number of found files
* size = total filesize (in bytes) of found files
* timestamp = unix timestamp when the scan was last performed/finished.
*
* @return array
*/
protected static function stats_scan()
{
$count = 0;
$size = 0;
// Scan everything in our cache directories.
foreach ( self::get_cache_contents() as $name => $files ) {
$dir = rtrim( AUTOPTIMIZE_CACHE_DIR . $name, '/' ) . '/';
foreach ( $files as $file ) {
if ( self::is_valid_cache_file( $dir, $file ) ) {
if ( AUTOPTIMIZE_CACHE_NOGZIP &&
(
false !== strpos( $file, '.js' ) ||
false !== strpos( $file, '.css' ) ||
false !== strpos( $file, '.img' ) ||
false !== strpos( $file, '.txt' )
)
) {
// Web server is gzipping, we count .js|.css|.img|.txt files.
$count++;
} elseif ( ! AUTOPTIMIZE_CACHE_NOGZIP && false !== strpos( $file, '.none' ) ) {
// We are gzipping ourselves via php, counting only .none files.
$count++;
}
$size += filesize( $dir . $file );
}
}
}
$stats = array( $count, $size, time() );
return $stats;
}
/**
* Ensures the cache directory exists, is writeable and contains the
* required .htaccess files.
* Returns false in case it fails to ensure any of those things.
*
* @return bool
*/
public static function cacheavail()
{
// readonly FS explicitly OK'ed by dev, let's assume the cache dirs are there!
if ( defined( 'AUTOPTIMIZE_CACHE_READONLY' ) ) {
return true;
}
if ( false === autoptimizeCache::check_and_create_dirs() ) {
return false;
}
// Using .htaccess inside our cache folder to overrule wp-super-cache.
$htaccess = AUTOPTIMIZE_CACHE_DIR . '/.htaccess';
if ( ! is_file( $htaccess ) ) {
/**
* Create `wp-content/AO_htaccess_tmpl` file with
* whatever htaccess rules you might need
* if you want to override default AO htaccess
*/
$htaccess_tmpl = WP_CONTENT_DIR . '/AO_htaccess_tmpl';
if ( is_file( $htaccess_tmpl ) ) {
$content = file_get_contents( $htaccess_tmpl );
} elseif ( is_multisite() || ! AUTOPTIMIZE_CACHE_NOGZIP ) {
$content = '<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css A30672000
ExpiresByType text/javascript A30672000
ExpiresByType application/javascript A30672000
</IfModule>
<IfModule mod_headers.c>
Header append Cache-Control "public, immutable"
</IfModule>
<IfModule mod_deflate.c>
<FilesMatch "\.(js|css)$">
SetOutputFilter DEFLATE
</FilesMatch>
</IfModule>
<IfModule mod_authz_core.c>
<Files *.php>
Require all granted
</Files>
</IfModule>
<IfModule !mod_authz_core.c>
<Files *.php>
Order allow,deny
Allow from all
</Files>
</IfModule>';
} else {
$content = '<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css A30672000
ExpiresByType text/javascript A30672000
ExpiresByType application/javascript A30672000
</IfModule>
<IfModule mod_headers.c>
Header append Cache-Control "public, immutable"
</IfModule>
<IfModule mod_deflate.c>
<FilesMatch "\.(js|css)$">
SetOutputFilter DEFLATE
</FilesMatch>
</IfModule>
<IfModule mod_authz_core.c>
<Files *.php>
Require all denied
</Files>
</IfModule>
<IfModule !mod_authz_core.c>
<Files *.php>
Order deny,allow
Deny from all
</Files>
</IfModule>';
}
if ( self::do_fallback() === true ) {
$content .= "\nErrorDocument 404 " . trailingslashit( parse_url( content_url(), PHP_URL_PATH ) ) . 'autoptimize_404_handler.php';
}
@file_put_contents( $htaccess, $content ); // @codingStandardsIgnoreLine
}
if ( self::do_fallback() ) {
self::check_fallback_php();
}
// All OK!
return true;
}
/**
* Checks if fallback-php file exists and create it if not.
*
* Return bool
*/
public static function check_fallback_php() {
$_fallback_filename = 'autoptimize_404_handler.php';
$_fallback_php = trailingslashit( WP_CONTENT_DIR ) . $_fallback_filename;
$_fallback_status = true;
if ( ! file_exists( $_fallback_php ) && is_writable( WP_CONTENT_DIR ) ) {
$_fallback_php_contents = file_get_contents( AUTOPTIMIZE_PLUGIN_DIR . 'config/' . $_fallback_filename );
$_fallback_php_contents = str_replace( '<?php exit;', '<?php', $_fallback_php_contents );
$_fallback_php_contents = str_replace( '<!--ao-cache-dir-->', AUTOPTIMIZE_CACHE_DIR, $_fallback_php_contents );
$_fallback_php_contents = str_replace( '<!--ao-cachefile-prefix-->', AUTOPTIMIZE_CACHEFILE_PREFIX, $_fallback_php_contents );
if ( is_multisite() ) {
$_fallback_php_contents = str_replace( '$multisite = false;', '$multisite = true;', $_fallback_php_contents );
}
if ( apply_filters( 'autoptimize_filter_cache_fallback_log_errors', false ) ) {
$_fallback_php_contents = str_replace( '// error_log', 'error_log', $_fallback_php_contents );
}
$_fallback_status = file_put_contents( $_fallback_php, $_fallback_php_contents );
}
return $_fallback_status;
}
/**
* Tells if AO should try to avoid 404's by creating fallback filesize
* and create a php 404 handler and tell .htaccess to redirect to said handler
* and hook into WordPress to redirect 404 to said handler as well. NGINX users
* are smart enough to get this working, no? ;-)
*
* Return bool
*/
public static function do_fallback() {
static $_do_fallback = null;
if ( null === $_do_fallback ) {
$_do_fallback = (bool) apply_filters( 'autoptimize_filter_cache_do_fallback', autoptimizeOptionWrapper::get_option( 'autoptimize_cache_fallback', '1' ) );
}
return $_do_fallback;
}
/**
* Hooks into template_redirect, will act on 404-ing requests for
* Autoptimized files and redirects to the fallback CSS/ JS if available
* and 410'ing ("Gone") if fallback not available.
*/
public static function wordpress_notfound_fallback() {
$original_request = strtok( $_SERVER['REQUEST_URI'], '?' );
if ( strpos( $original_request, wp_basename( WP_CONTENT_DIR ) . AUTOPTIMIZE_CACHE_CHILD_DIR ) !== false && is_404() ) {
// make sure this is not considered a 404.
global $wp_query;
$wp_query->is_404 = false;
// set fallback path.
$js_or_css = pathinfo( $original_request, PATHINFO_EXTENSION );
$fallback_path = AUTOPTIMIZE_CACHE_DIR . $js_or_css . '/autoptimize_fallback.' . $js_or_css;
// prepare for Shakeeb's Unused CSS files to be 404-handled as well.
if ( strpos( $original_request, 'uucss/uucss-' ) !== false ) {
$original_request = preg_replace( '/uucss\/uucss-[a-z0-9]{32}-/', 'css/', $original_request );
}
// set fallback URL.
$fallback_target = preg_replace( '/(.*)_(?:[a-z0-9]{32})\.(js|css)$/', '${1}_fallback.${2}', $original_request );
// redirect to fallback if possible.
if ( $original_request !== $fallback_target && file_exists( $fallback_path ) ) {
// redirect to fallback.
wp_redirect( $fallback_target, 302 );
} else {
// return HTTP 410 (gone) reponse.
status_header( 410 );
}
}
}
/**
* Checks if cache dirs exist and create if not.
* Returns false if not succesful.
*
* @return bool
*/
public static function check_and_create_dirs() {
if ( ! defined( 'AUTOPTIMIZE_CACHE_DIR' ) ) {
// We didn't set a cache.
return false;
}
foreach ( array( '', 'js', 'css' ) as $dir ) {
if ( ! self::check_cache_dir( AUTOPTIMIZE_CACHE_DIR . $dir ) ) {
return false;
}
}
return true;
}
/**
* Ensures the specified `$dir` exists and is writeable.
* Returns false if that's not the case.
*
* @param string $dir Directory to check/create.
*
* @return bool
*/
protected static function check_cache_dir( $dir )
{
// Try creating the dir if it doesn't exist.
if ( ! file_exists( $dir ) ) {
@mkdir( $dir, 0775, true ); // @codingStandardsIgnoreLine
if ( ! file_exists( $dir ) ) {
return false;
}
}
// If we still cannot write, bail.
if ( ! is_writable( $dir ) ) {
return false;
}
// Create an index.html in there to avoid prying eyes!
$idx_file = rtrim( $dir, '/\\' ) . '/index.html';
if ( ! is_file( $idx_file ) ) {
@file_put_contents( $idx_file, '<html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>' ); // @codingStandardsIgnoreLine
}
return true;
}
/**
* Flushes as many page cache plugin's caches as possible.
*
* @return void
*/
// @codingStandardsIgnoreStart
public static function flushPageCache()
{
if ( function_exists( 'wp_cache_clear_cache' ) ) {
if ( is_multisite() ) {
$blog_id = get_current_blog_id();
wp_cache_clear_cache( $blog_id );
} else {
wp_cache_clear_cache();
}
} elseif ( has_action( 'cachify_flush_cache' ) ) {
do_action( 'cachify_flush_cache' );
} elseif ( function_exists( 'w3tc_pgcache_flush' ) ) {
w3tc_pgcache_flush();
} elseif ( function_exists( 'wp_fast_cache_bulk_delete_all' ) ) {
wp_fast_cache_bulk_delete_all();
} elseif ( function_exists( 'rapidcache_clear_cache' ) ) {
rapidcache_clear_cache();
} elseif ( class_exists( 'Swift_Performance_Cache' ) ) {
Swift_Performance_Cache::clear_all_cache();
} elseif ( class_exists( 'WpFastestCache' ) ) {
$wpfc = new WpFastestCache();
$wpfc->deleteCache();
} elseif ( class_exists( 'c_ws_plugin__qcache_purging_routines' ) ) {
c_ws_plugin__qcache_purging_routines::purge_cache_dir(); // quick cache
} elseif ( class_exists( 'zencache' ) ) {
zencache::clear();
} elseif ( class_exists( 'comet_cache' ) ) {
comet_cache::clear();
} elseif ( class_exists( 'WpeCommon' ) ) {
// WPEngine cache purge/flush methods to call by default
$wpe_methods = array(
'purge_varnish_cache',
);
// More agressive clear/flush/purge behind a filter
if ( apply_filters( 'autoptimize_flush_wpengine_aggressive', false ) ) {
$wpe_methods = array_merge( $wpe_methods, array( 'purge_memcached', 'clear_maxcdn_cache' ) );
}
// Filtering the entire list of WpeCommon methods to be called (for advanced usage + easier testing)
$wpe_methods = apply_filters( 'autoptimize_flush_wpengine_methods', $wpe_methods );
foreach ( $wpe_methods as $wpe_method ) {
if ( method_exists( 'WpeCommon', $wpe_method ) ) {
WpeCommon::$wpe_method();
}
}
} elseif ( function_exists( 'sg_cachepress_purge_cache' ) ) {
sg_cachepress_purge_cache();
} elseif ( array_key_exists( 'KINSTA_CACHE_ZONE', $_SERVER ) ) {
$_kinsta_clear_cache_url = 'https://localhost/kinsta-clear-cache-all';
$_kinsta_response = wp_remote_get(
$_kinsta_clear_cache_url,
array(
'sslverify' => false,
'timeout' => 5,
)
);
} elseif ( class_exists( 'RaidboxesNginxCacheFunctions' ) ) {
$rb_cache_helper = new RaidboxesNginxCacheFunctions();
$rb_cache_helper->purge_cache();
} elseif ( defined('NGINX_HELPER_BASENAME') ) {
do_action( 'rt_nginx_helper_purge_all' );
} elseif ( file_exists( WP_CONTENT_DIR . '/wp-cache-config.php' ) && function_exists( 'prune_super_cache' ) ) {
// fallback for WP-Super-Cache
global $cache_path;
if ( is_multisite() ) {
$blog_id = get_current_blog_id();
prune_super_cache( get_supercache_dir( $blog_id ), true );
prune_super_cache( $cache_path . 'blogs/', true );
} else {
prune_super_cache( $cache_path . 'supercache/', true );
prune_super_cache( $cache_path, true );
}
} elseif ( class_exists( 'NginxCache' ) ) {
$nginx_cache = new NginxCache();
$nginx_cache->purge_zone_once();
}
}
// @codingStandardsIgnoreEnd
}

View File

@@ -0,0 +1,115 @@
<?php
/**
* CacheChecker - new in AO 2.0
*
* Daily cronned job (filter to change freq. + filter to disable).
* Checks if cachesize is > 0.5GB (size is filterable), if so, an option is set which controls showing an admin notice.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeCacheChecker
{
const SCHEDULE_HOOK = 'ao_cachechecker';
public function __construct()
{
}
public function run()
{
$this->add_hooks();
}
public function add_hooks()
{
if ( is_admin() ) {
add_action( 'plugins_loaded', array( $this, 'setup' ) );
}
add_action( self::SCHEDULE_HOOK, array( $this, 'cronjob' ) );
add_action( 'admin_notices', array( $this, 'show_admin_notice' ) );
}
public function setup()
{
$do_cache_check = (bool) apply_filters( 'autoptimize_filter_cachecheck_do', true );
$schedule = wp_get_schedule( self::SCHEDULE_HOOK );
$frequency = apply_filters( 'autoptimize_filter_cachecheck_frequency', 'twicedaily' );
if ( ! in_array( $frequency, array( 'hourly', 'twicedaily', 'daily', 'weekly', 'monthly' ) ) ) {
$frequency = 'twicedaily';
}
if ( $do_cache_check && ( ! $schedule || $schedule !== $frequency ) ) {
if ( $schedule ) {
wp_clear_scheduled_hook( self::SCHEDULE_HOOK );
}
wp_schedule_event( time(), $frequency, self::SCHEDULE_HOOK );
} elseif ( $schedule && ! $do_cache_check ) {
wp_clear_scheduled_hook( self::SCHEDULE_HOOK );
}
}
public function cronjob()
{
// Check cachesize and act accordingly.
$max_size = (int) apply_filters( 'autoptimize_filter_cachecheck_maxsize', 536870912 );
$do_cache_check = (bool) apply_filters( 'autoptimize_filter_cachecheck_do', true );
$stat_array = autoptimizeCache::stats();
$cache_size = round( $stat_array[1] );
if ( ( $cache_size > $max_size ) && ( $do_cache_check ) ) {
autoptimizeOptionWrapper::update_option( 'autoptimize_cachesize_notice', true );
if ( apply_filters( 'autoptimize_filter_cachecheck_sendmail', true ) ) {
$home_url = esc_url( home_url() );
$ao_mailto = apply_filters( 'autoptimize_filter_cachecheck_mailto', autoptimizeOptionWrapper::get_option( 'admin_email', '' ) );
$ao_mailsubject = esc_html__( 'Autoptimize cache size warning', 'autoptimize' ) . ' (' . $home_url . ')';
$ao_mailbody = esc_html__( 'Autoptimize\'s cache size is getting big, consider purging the cache. Have a look at https://wordpress.org/plugins/autoptimize/faq/ to see how you can keep the cache size under control.', 'autoptimize' ) . ' (site: ' . $home_url . ')';
if ( ! empty( $ao_mailto ) ) {
$ao_mailresult = wp_mail( $ao_mailto, $ao_mailsubject, $ao_mailbody );
if ( ! $ao_mailresult ) {
error_log( 'Autoptimize could not send cache size warning mail.' );
}
}
}
}
// Check if 3rd party services (e.g. image proxy) are up.
autoptimizeUtils::check_service_availability();
// Nukes advanced cache clearing artifacts if they exists...
autoptimizeCache::delete_advanced_cache_clear_artifacts();
// Check image optimization stats.
autoptimizeImages::instance()->query_img_provider_stats();
}
public function show_admin_notice()
{
if ( (bool) autoptimizeOptionWrapper::get_option( 'autoptimize_cachesize_notice', false ) && current_user_can( 'manage_options' ) ) {
echo '<div class="notice notice-warning"><p>';
// Translators: first two variables are strong tags, 3rd is link to the AO FAQ and the 4th closes that tag.
printf( esc_html__( '%1$sAutoptimize\'s cache size is getting big%2$s, consider purging the cache. Have a look at %3$sthe Autoptimize FAQ%4$s to see how you can keep the cache size under control.', 'autoptimize' ), '<strong>', '</strong>', '<a href="https://wordpress.org/plugins/autoptimize/faq/" target="_blank" rel="noopener noreferrer">', '</a>' );
echo '</p></div>';
autoptimizeOptionWrapper::update_option( 'autoptimize_cachesize_notice', false );
}
// Notice for image proxy usage.
$_imgopt_notice = autoptimizeImages::instance()->get_imgopt_status_notice_wrapper();
if ( current_user_can( 'manage_options' ) && is_array( $_imgopt_notice ) && array_key_exists( 'status', $_imgopt_notice ) && in_array( $_imgopt_notice['status'], array( 1, -1, -2, -3 ) ) ) {
$_dismissible = 'ao-img-opt-notice-';
$_hide_notice = '7';
if ( -1 == $_imgopt_notice['status'] || -2 == $_imgopt_notice['status'] || -3 == $_imgopt_notice['status'] ) {
$_hide_notice = '1';
}
$_imgopt_notice_dismissible = apply_filters( 'autoptimize_filter_imgopt_notice_dismissable', $_dismissible . $_hide_notice );
if ( $_imgopt_notice && PAnD::is_admin_notice_active( $_imgopt_notice_dismissible ) ) {
echo '<div class="notice notice-warning is-dismissible" data-dismissible="' . $_imgopt_notice_dismissible . '"><p><strong>' . esc_html__( 'Autoptimize', 'autoptimize' ) . '</strong>: ' . $_imgopt_notice['notice'] . '</p></div>';
}
}
}
}

View File

@@ -0,0 +1,146 @@
<?php
/**
* Multiple compatibility snippets to ensure important/ stubborn plugins work out of the box.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeCompatibility
{
/**
* Options.
*
* @var array
*/
protected $conf = array();
/**
* Constructor.
*/
public function __construct()
{
if ( ! is_admin() && ! defined( 'DOING_CRON' ) ) {
$this->conf = autoptimizeConfig::instance();
$this->run();
}
}
/**
* Runs multiple compatibility snippets to ensure important plugins work out of the box.
*/
public function run()
{
// Edit with Elementor in frontend admin menu (so for editors/ administrators) needs JS opt. disabled to appear & function.
if ( defined( 'ELEMENTOR_VERSION' ) && is_user_logged_in() && current_user_can( 'edit_posts' ) && apply_filters( 'autoptimize_filter_compatibility_editelementor_active', true ) ) {
add_filter( 'autoptimize_filter_js_noptimize', '__return_true' );
}
// Revslider; jQuery should not be deferred + exclude all revslider JS.
if ( defined( 'RS_REVISION' ) && $this->conf->get( 'autoptimize_js' ) && true == $this->inline_js_config_checker() && apply_filters( 'autoptimize_filter_compatibility_revslider_active', true ) ) {
add_filter(
'autoptimize_filter_js_exclude',
function( $js_excl = '', $html = '' ) {
$revslider_excl = 'revslider, setREVStartSize, window.RSIW, window.RS_MODULES, jquery.min.js';
if ( ! empty( $html ) && false !== strpos( $html, '<rs-slides' ) ) {
if ( is_array( $js_excl ) ) {
$js_excl = implode( ',', $js_excl );
}
$js_excl .= ',' . $revslider_excl;
}
return $js_excl;
},
11,
2
);
}
// Revslider; remove revslider JS if no slides in HTML for non-logged in users.
if ( defined( 'RS_REVISION' ) && $this->conf->get( 'autoptimize_js' ) && false === is_user_logged_in() && apply_filters( 'autoptimize_filter_compatibility_revslider_remover_active', true ) ) {
add_filter(
'autoptimize_filter_js_removables',
function( $to_remove = '', $html = '' ) {
if ( ! empty( $html ) && false === strpos( $html, '<rs-slides' ) ) {
$to_remove .= 'plugins/revslider, setREVStartSize, window.RSIW, window.RS_MODULES';
}
return $to_remove;
},
11,
2
);
}
// Exclude jQuery if inline JS is found that requires jQuery.
if ( $this->inline_js_config_checker() && false === strpos( $this->conf->get( 'autoptimize_js_exclude' ), 'jquery.min.js' ) && apply_filters( 'autoptimize_filter_compatibility_inline_jquery', true ) ) {
add_filter(
'autoptimize_filter_js_exclude',
function( $js_excl = '', $html = '' ) {
if ( ! empty( $html ) && preg_match( '/<script[^>]*>[^<]*(jQuery|\$)\([^<]*<\/script>/Usm', $html ) ) {
if ( is_array( $js_excl ) ) {
$js_excl = implode( ',', $js_excl );
}
if ( false === strpos( $js_excl, 'jquery.min.js' ) ) {
$js_excl .= ', jquery.min.js';
}
// also exclude jquery.js if for whatever reason that is still used.
if ( false === strpos( $js_excl, 'jquery.js' ) ) {
$js_excl .= ', jquery.js';
}
}
return $js_excl;
},
12,
2
);
}
// Make JS-based Gutenberg blocks work OOTB.
if ( $this->inline_js_config_checker() && apply_filters( 'autoptimize_filter_compatibility_gutenberg_js', true ) ) {
add_filter(
'autoptimize_filter_js_exclude',
function( $js_excl = '', $html = '' ) {
if ( ! empty( $html ) && false !== strpos( $html, 'wp.i18n' ) || false !== strpos( $html, 'wp.apiFetch' ) || false !== strpos( $html, 'window.lodash' ) ) {
if ( is_array( $js_excl ) ) {
$js_excl = implode( ',', $js_excl );
}
if ( false === strpos( $js_excl, 'jquery.min.js' ) ) {
$js_excl .= ', jquery.min.js';
}
if ( false === strpos( $js_excl, 'wp-includes/js/dist' ) ) {
$js_excl .= ', wp-includes/js/dist';
}
}
return $js_excl;
},
13,
2
);
}
}
public function inline_js_config_checker() {
static $inline_js_flagged = null;
if ( null === $inline_js_flagged ) {
if ( ( $this->conf->get( 'autoptimize_js_aggregate' ) || apply_filters( 'autoptimize_filter_js_dontaggregate', false ) ) && apply_filters( 'autoptimize_js_include_inline', $this->conf->get( 'autoptimize_js_include_inline' ) ) ) {
// if all files and also inline JS are aggregated we don't have to worry about inline JS.
$inline_js_flagged = false;
} else if ( apply_filters( 'autoptimize_filter_js_defer_not_aggregate', $this->conf->get( 'autoptimize_js_defer_not_aggregate' ) ) && apply_filters( 'autoptimize_js_filter_defer_inline', $this->conf->get( 'autoptimize_js_defer_inline' ) ) ) {
// and when not aggregating but deferring all including inline JS, then all is OK too.
$inline_js_flagged = false;
}
// in all other cases we need to pay attention to inline JS requiring src'ed JS to be available.
$inline_js_flagged = true;
}
return $inline_js_flagged;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,433 @@
<?php
/**
* Critical CSS base file (initializes all ccss files).
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeCriticalCSSBase {
/**
* Main plugin filepath.
* Used for activation/deactivation/uninstall hooks.
*
* @var string
*/
protected $filepath = null;
/**
* Critical CSS options
*
* @var array
*/
protected $_options = null;
/**
* Core object
*
* @var object
*/
protected $_core = null;
/**
* Cron object
*
* @var object
*/
protected $_cron = null;
/**
* Settings object
*
* @var object
*/
protected $_settings = null;
/**
* Enqueue object
*
* @var object
*/
protected $_enqueue = null;
public function __construct()
{
// define constant, but only once.
if ( ! defined( 'AO_CCSS_DIR' ) ) {
// Define a constant with the directory to store critical CSS in.
if ( is_multisite() ) {
$blog_id = get_current_blog_id();
define( 'AO_CCSS_DIR', WP_CONTENT_DIR . '/uploads/ao_ccss/' . $blog_id . '/' );
} else {
define( 'AO_CCSS_DIR', WP_CONTENT_DIR . '/uploads/ao_ccss/' );
}
}
if ( ! defined( 'AO_CCSS_VER' ) ) {
// Define plugin version.
define( 'AO_CCSS_VER', 'AO_' . AUTOPTIMIZE_PLUGIN_VERSION );
// Define constants for criticalcss.com base path and API endpoints.
// fixme: AO_CCSS_URL should be read from the autoptimize availability json stored as option.
define( 'AO_CCSS_URL', 'https://criticalcss.com' );
define( 'AO_CCSS_API', apply_filters( 'autoptimize_filter_ccss_service_url', AO_CCSS_URL . '/api/premium/' ) );
define( 'AO_CCSS_SLEEP', 10 );
}
// Define support files locations, in case they are not already defined.
if ( ! defined( 'AO_CCSS_LOCK' ) ) {
define( 'AO_CCSS_LOCK', AO_CCSS_DIR . 'queue.lock' );
}
if ( ! defined( 'AO_CCSS_LOG' ) ) {
define( 'AO_CCSS_LOG', AO_CCSS_DIR . 'queuelog.html' );
}
if ( ! defined( 'AO_CCSS_DEBUG' ) ) {
define( 'AO_CCSS_DEBUG', AO_CCSS_DIR . 'queue.json' );
}
$this->filepath = __FILE__;
// Add keychecker action for scheduled use.
add_action( 'ao_ccss_keychecker', array( $this, 'ao_ccss_check_key' ) );
}
public function setup() {
// check if we need to upgrade.
$this->check_upgrade();
// add/ remove scheduled jobs.
if ( $this->is_api_active() ) {
// make sure the 10 minutes cron schedule is added.
add_filter( 'cron_schedules', array( $this, 'ao_ccss_interval' ) );
if ( ! wp_next_scheduled( 'ao_ccss_queue' ) ) {
// make sure ao_ccss_queue is scheduled OK if an API key is active.
wp_schedule_event( time(), apply_filters( 'ao_ccss_queue_schedule', 'ao_ccss' ), 'ao_ccss_queue' );
}
if ( ! wp_next_scheduled( 'ao_ccss_maintenance' ) ) {
// and schedule maintenance job.
wp_schedule_event( time(), 'twicedaily', 'ao_ccss_maintenance' );
}
if ( wp_next_scheduled( 'ao_ccss_keychecker' ) ) {
// api is active now, no need to check key as it is checked by using the API.
wp_clear_scheduled_hook( 'ao_ccss_keychecker' );
}
} else {
if ( wp_next_scheduled( 'ao_ccss_queue' ) ) {
wp_clear_scheduled_hook( 'ao_ccss_queue' );
}
if ( wp_next_scheduled( 'ao_ccss_maintenance' ) ) {
wp_clear_scheduled_hook( 'ao_ccss_maintenance' );
}
// add keychecker logic if api is not active but we have a key so maybe this is a temporary issue, check if key is OK daily.
$ao_ccss_key = $this->get_option( 'key' );
if ( ! empty( $ao_ccss_key ) && ! wp_next_scheduled( 'ao_ccss_keychecker' ) ) {
wp_schedule_event( time(), 'twicedaily', 'ao_ccss_keychecker' );
} else if ( empty( $ao_ccss_key ) && wp_next_scheduled( 'ao_ccss_keychecker' ) ) {
// edge case: we had a inactive key that was checked daily, but it is now removed, so remove keychecker from schedule.
wp_clear_scheduled_hook( 'ao_ccss_keychecker' );
}
}
// check/ create AO_CCSS_DIR.
if ( ! file_exists( AO_CCSS_DIR ) ) {
$this->create_ao_ccss_dir();
}
}
public function load_requires() {
// Required libs, core is always needed.
$this->_core = new autoptimizeCriticalCSSCore();
if ( ( defined( 'WP_CLI' ) || defined( 'DOING_CRON' ) || is_admin() ) && $this->is_api_active() ) {
// cron only initiated when doing cron (or wp_cli or is_amdin) and when we have an active API key.
$this->_cron = new autoptimizeCriticalCSSCron();
}
if ( is_admin() ) {
$this->_settings = new autoptimizeCriticalCSSSettings();
} else if ( $this->is_api_active() ) {
// enqueuing only done when not wp-admin and when API is active.
$this->_enqueue = new autoptimizeCriticalCSSEnqueue();
}
}
/**
* Log a message via CCSS Core object
*
* @param string $msg Message to log.
* @param int $lvl Loglevel.
*
* @return empty
*/
public function log( $msg, $lvl ) {
return $this->_core->ao_ccss_log( $msg, $lvl );
}
/**
* Get viewport from CCSS Core object
*
* @return array
*/
public function viewport() {
return $this->_core->ao_ccss_viewport();
}
/**
* Check CCSS contents from Core object
*
* @param string $ccss Critical CSS to be checked.
*
* @return bool
*/
public function check_contents( $ccss ) {
return $this->_core->ao_ccss_check_contents( $ccss );
}
/**
* Get key status from Core object
*
* @param bool $render Indicates if key status is to be rendered.
*
* @return array
*/
public function key_status( $render ) {
return $this->_core->ao_ccss_key_status( $render );
}
/**
* Return valid types from core object
*
* @return array
*/
public function get_types() {
return $this->_core->get_types();
}
/**
* Run enqueue in CCSS Enqueue object
*
* @param string $hash Hash (default empty).
* @param string $path Path (default empty).
* @param string $type (default is_page).
*/
public function enqueue( $hash = '', $path = '', $type = 'is_page' ) {
// Enqueue is sometimes required on wp-admin requests, load it just-in-time.
if ( is_null( $this->_enqueue ) && $this->is_api_active() ) {
$this->_enqueue = new autoptimizeCriticalCSSEnqueue();
}
return $this->_enqueue->ao_ccss_enqueue( $hash, $path, $type );
}
/**
* Check auto-rules in CCSS Settings object
*/
public function has_autorules() {
return $this->_settings->ao_ccss_has_autorules();
}
/**
* Get a Critical CSS option
*
* @param string $name The option name.
*
* @return mixed
*/
public function get_option( $name ) {
if ( is_null( $this->_options ) ) {
$this->fetch_options();
}
if ( isset( $this->_options[ $name ] ) ) {
return $this->_options[ $name ];
}
return null;
}
public function flush_options() {
$this->_options = null;
}
protected function fetch_options() {
if ( ! is_null( $this->_options ) ) {
return $this->_options;
}
$this->_options = array(
'css_defer' => autoptimizeOptionWrapper::get_option( 'autoptimize_css_defer' ),
'css_defer_inline' => autoptimizeOptionWrapper::get_option( 'autoptimize_css_defer_inline' ),
'rules_raw' => get_option( 'autoptimize_ccss_rules', false ),
'additional' => get_option( 'autoptimize_ccss_additional' ),
'queue_raw' => get_option( 'autoptimize_ccss_queue', false ),
'viewport' => get_option( 'autoptimize_ccss_viewport', false ),
'finclude' => get_option( 'autoptimize_ccss_finclude', false ),
'rtimelimit' => get_option( 'autoptimize_ccss_rtimelimit', '30' ),
'noptimize' => get_option( 'autoptimize_ccss_noptimize', false ),
'debug' => get_option( 'autoptimize_ccss_debug', false ),
'key' => apply_filters( 'autoptimize_filter_ccss_key', get_option( 'autoptimize_ccss_key' ) ),
'keyst' => get_option( 'autoptimize_ccss_keyst' ),
'loggedin' => get_option( 'autoptimize_ccss_loggedin', '1' ),
'forcepath' => get_option( 'autoptimize_ccss_forcepath', '1' ),
'servicestatus' => get_option( 'autoptimize_service_availablity' ),
'deferjquery' => get_option( 'autoptimize_ccss_deferjquery', false ),
'domain' => get_option( 'autoptimize_ccss_domain' ),
'unloadccss' => get_option( 'autoptimize_ccss_unloadccss', false ),
);
if ( ( strpos( $this->_options['domain'], 'http' ) === false && strpos( $this->_options['domain'], 'uggc' ) === 0 ) || 'abar' === $this->_options['domain'] ) {
$this->_options['domain'] = str_rot13( $this->_options['domain'] );
} elseif ( strpos( $this->_options['domain'], 'http' ) !== false ) {
// not rot13'ed yet, do so now (goal; avoid migration plugins change the bound domain).
update_option( 'autoptimize_ccss_domain', str_rot13( $this->_options['domain'] ) );
}
// Setup the rules array.
if ( empty( $this->_options['rules_raw'] ) ) {
$this->_options['rules'] = array(
'paths' => array(),
'types' => array(),
);
} else {
$this->_options['rules'] = json_decode( $this->_options['rules_raw'], true );
}
// Setup the queue array.
if ( empty( $this->_options['queue_raw'] ) ) {
$this->_options['queue'] = array();
} else {
$this->_options['queue'] = json_decode( $this->_options['queue_raw'], true );
}
// Override API key if constant is defined.
if ( defined( 'AUTOPTIMIZE_CRITICALCSS_API_KEY' ) ) {
$this->_options['key'] = AUTOPTIMIZE_CRITICALCSS_API_KEY;
}
return $this->_options;
}
public function on_upgrade() {
$key = $this->get_option( 'key' );
// Create the cache directory if it doesn't exist already.
if ( ! file_exists( AO_CCSS_DIR ) ) {
$this->create_ao_ccss_dir();
}
// Create a scheduled event for the queue.
if ( $this->is_api_active() && ! wp_next_scheduled( 'ao_ccss_queue' ) ) {
wp_schedule_event( time(), apply_filters( 'ao_ccss_queue_schedule', 'ao_ccss' ), 'ao_ccss_queue' );
}
// Create a scheduled event for log maintenance.
if ( $this->is_api_active() && ! wp_next_scheduled( 'ao_ccss_maintenance' ) ) {
wp_schedule_event( time(), 'twicedaily', 'ao_ccss_maintenance' );
}
}
public function check_upgrade() {
$db_version = get_option( 'autoptimize_ccss_version', '' );
if ( AO_CCSS_VER !== $db_version ) {
// check schedules & re-schedule if needed.
$this->on_upgrade();
// and update db_version.
update_option( 'autoptimize_ccss_version', AO_CCSS_VER );
}
}
public function ao_ccss_interval( $schedules ) {
// Let interval be configurable.
if ( ! defined( 'AO_CCSS_DEBUG_INTERVAL' ) ) {
$intsec = 600;
$inttxt = '10 minutes';
} else {
$intsec = AO_CCSS_DEBUG_INTERVAL;
if ( $intsec >= 120 ) {
$inttxt = $intsec / 60 . ' minutes';
} else {
$inttxt = $intsec . ' second(s)';
}
$this->log( 'Using custom WP-Cron interval of ' . $inttxt, 3 );
}
// Attach interval to schedule.
$schedules['ao_ccss'] = array(
'interval' => $intsec,
// translators: the variable contains a string describing the insterval.
'display' => sprintf( esc_html__( 'Every %s (Autoptimize Crit. CSS)', 'autoptimize' ), $inttxt ),
);
return $schedules;
}
public function create_ao_ccss_dir() {
// Make sure dir to write ao_ccss exists and is writable.
if ( ! is_dir( AO_CCSS_DIR ) ) {
// TODO: use wp_mkdir_p() ?
$mkdirresp = @mkdir( AO_CCSS_DIR, 0775, true ); // @codingStandardsIgnoreLine
} else {
$mkdirresp = true;
}
// Make sure our index.html is there.
if ( ! is_file( AO_CCSS_DIR . 'index.html' ) ) {
$fileresp = file_put_contents( AO_CCSS_DIR . 'index.html', '<html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>' );
} else {
$fileresp = true;
}
if ( true === $fileresp && true === $mkdirresp ) {
return true;
} else {
return false;
}
}
/**
* Helper function to determine if there is an active API key.
*
* @return bool
*/
public function is_api_active() {
// using options instead of more complex $this->key_status (which gave some dependancy issues ... ;-) ).
$ao_ccss_key = $this->get_option( 'key' );
$ao_ccss_keyst = $this->get_option( 'keyst' );
if ( ! empty( $ao_ccss_key ) && $ao_ccss_keyst && 2 == $ao_ccss_keyst ) {
return true;
}
return false;
}
/**
* Helper function to determine if a rule is MANUAL.
*
* @param array $rule Rule to check.
*
* @return bool
*/
public function is_rule_manual( $rule ) {
if ( is_array( $rule ) && false == $rule['hash'] && false != $rule['file'] ) {
return true;
}
return false;
}
/**
* Scheduled action to check an inactive key. Not part of autoptimizeCriticalCSSCron.php
* to allow us to only load the main cron logic if we have an active key to begin with.
*/
public function ao_ccss_check_key() {
$ao_ccss_key = $this->get_option( 'key' );
$_result = $this->_core->ao_ccss_key_validation( $ao_ccss_key );
$_resmsg = ( true === $_result ) ? 'ok' : 'nok';
$this->log( 'Inactive key checked, result was ' . $_resmsg, 3 );
}
}

View File

@@ -0,0 +1,664 @@
<?php
/**
* Critical CSS Core logic:
* gets called by AO core, checks the rules and if a matching rule is found returns the associated CCSS.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeCriticalCSSCore {
/**
* Critical CSS page types.
*
* @var array
*/
protected $_types = null;
/**
* Critical CSS object.
*
* @var object
*/
protected $criticalcss;
public function __construct() {
$this->criticalcss = autoptimize()->criticalcss();
$this->run();
}
public function run() {
$css_defer = $this->criticalcss->get_option( 'css_defer' );
$deferjquery = $this->criticalcss->get_option( 'deferjquery' );
$unloadccss = $this->criticalcss->get_option( 'unloadccss' );
if ( ! $css_defer ) {
return;
}
// add all filters to do CCSS
// Set AO behavior: disable minification to avoid double minifying and caching.
add_filter( 'autoptimize_filter_css_critcss_minify', '__return_false' );
add_filter( 'autoptimize_filter_css_defer_inline', array( $this, 'ao_ccss_frontend' ), 10, 1 );
// Add the action to enqueue jobs for CriticalCSS cron.
if ( $this->criticalcss->is_api_active() ) {
add_action( 'autoptimize_action_css_hash', array( $this->criticalcss, 'enqueue' ), 10, 1 );
}
// conditionally add the filter to defer jquery and others but only if not done so in autoptimizeScripts.
$_native_defer = false;
if ( 'on' === autoptimizeOptionWrapper::get_option( 'autoptimize_js_defer_not_aggregate' ) && 'on' === autoptimizeOptionWrapper::get_option( 'autoptimize_js_defer_inline' ) ) {
$_native_defer = true;
}
if ( $deferjquery && ! $_native_defer ) {
add_filter( 'autoptimize_html_after_minify', array( $this, 'ao_ccss_defer_jquery' ), 11, 1 );
}
// conditionally add filter to unload the CCSS.
if ( $unloadccss ) {
add_filter( 'autoptimize_html_after_minify', array( $this, 'ao_ccss_unloadccss' ), 12, 1 );
}
// Order paths by length, as longest ones have greater priority in the rules.
$rules = $this->criticalcss->get_option( 'rules' );
if ( ! empty( $rules['paths'] ) ) {
$keys = array_map( 'strlen', array_keys( $rules['paths'] ) );
array_multisort( $keys, SORT_DESC, $rules['paths'] );
// TODO: Not sure what we're doing here. Sorted the $keys,
// but they don't seem to be used anywhere.
}
// Add an array with default WordPress's conditional tags
// NOTE: these tags are sorted.
$this->_types = $this->get_ao_ccss_core_types();
// Extend conditional tags on plugin initalization.
add_action( apply_filters( 'autoptimize_filter_ccss_extend_types_hook', 'init' ), array( $this, 'ao_ccss_extend_types' ) );
// When autoptimize cache is cleared, also clear transient cache for page templates.
add_action( 'autoptimize_action_cachepurged', array( $this, 'ao_ccss_clear_page_tpl_cache' ), 10, 0 );
}
public function ao_ccss_frontend( $inlined ) {
// Apply CriticalCSS to frontend pages
// Attach types and settings arrays.
$rules = $this->criticalcss->get_option( 'rules' );
$additional = $this->criticalcss->get_option( 'additional' );
$loggedin = $this->criticalcss->get_option( 'loggedin' );
$debug = $this->criticalcss->get_option( 'debug' );
$no_ccss = '';
$additional = autoptimizeStyles::sanitize_css( $additional );
// Only if keystatus is OK and option to add CCSS for logged on users is on or user is not logged in.
if ( $loggedin || ! is_user_logged_in() ) {
// Check for a valid CriticalCSS based on path to return its contents.
$req_path = strtok( $_SERVER['REQUEST_URI'], '?' );
if ( ! empty( $rules['paths'] ) ) {
foreach ( $rules['paths'] as $path => $rule ) {
// explicit match OR partial match if MANUAL rule.
if ( ( $this->criticalcss->is_api_active() || $this->criticalcss->is_rule_manual( $rule ) ) && ( $req_path == $path || urldecode( $req_path ) == $path || ( apply_filters( 'autoptimize_filter_ccss_core_path_partial_match', true ) && false == $rule['hash'] && false != $rule['file'] && strpos( $req_path, str_replace( site_url(), '', $path ) ) !== false ) ) ) {
if ( file_exists( AO_CCSS_DIR . $rule['file'] ) ) {
$_ccss_contents = file_get_contents( AO_CCSS_DIR . $rule['file'] );
if ( 'none' != $_ccss_contents ) {
if ( $debug ) {
$_ccss_contents = '/* PATH: ' . $path . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
}
return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $additional );
} else {
if ( $debug ) {
$this->criticalcss->log( 'Path based rule with value "none" found.', 3 );
}
$no_ccss = 'none';
}
}
}
}
}
// Check for a valid CriticalCSS based on conditional tags to return its contents.
if ( ! empty( $rules['types'] ) && 'none' !== $no_ccss ) {
// order types-rules by the order of the original $ao_ccss_types array so as not to depend on the order in which rules were added.
$rules['types'] = array_replace( array_intersect_key( array_flip( $this->_types ), $rules['types'] ), $rules['types'] );
$is_front_page = is_front_page();
foreach ( $rules['types'] as $type => $rule ) {
if ( ( $this->criticalcss->is_api_active() || $this->criticalcss->is_rule_manual( $rule ) ) && in_array( $type, $this->_types ) && file_exists( AO_CCSS_DIR . $rule['file'] ) ) {
$_ccss_contents = file_get_contents( AO_CCSS_DIR . $rule['file'] );
if ( $is_front_page && 'is_front_page' == $type ) {
if ( 'none' != $_ccss_contents ) {
if ( $debug ) {
$_ccss_contents = '/* TYPES: ' . $type . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
}
return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $additional );
} else {
if ( $debug ) {
$this->criticalcss->log( 'Conditional rule for is_front_page with value "none" found.', 3 );
}
$no_ccss = 'none';
}
} elseif ( ( $this->criticalcss->is_api_active() || $this->criticalcss->is_rule_manual( $rule ) ) && strpos( $type, 'custom_post_' ) === 0 && ! $is_front_page ) {
if ( get_post_type( get_the_ID() ) === substr( $type, 12 ) ) {
if ( 'none' != $_ccss_contents ) {
if ( $debug ) {
$_ccss_contents = '/* TYPES: ' . $type . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
}
return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $additional );
} else {
if ( $debug ) {
$this->criticalcss->log( 'Conditional rule custom_post with value "none" found.', 3 );
}
$no_ccss = 'none';
}
}
} elseif ( ( $this->criticalcss->is_api_active() || $this->criticalcss->is_rule_manual( $rule ) ) && 0 === strpos( $type, 'template_' ) && ! $is_front_page ) {
if ( is_page_template( substr( $type, 9 ) ) ) {
if ( 'none' != $_ccss_contents ) {
if ( $debug ) {
$_ccss_contents = '/* TYPES: ' . $type . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
}
return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $additional );
} else {
if ( $debug ) {
$this->criticalcss->log( 'Conditional rule for template with value "none" found.', 3 );
}
$no_ccss = 'none';
}
}
} elseif ( ( $this->criticalcss->is_api_active() || $this->criticalcss->is_rule_manual( $rule ) ) && ! $is_front_page ) {
// all "normal" conditional tags, core + woo + buddypress + edd + bbpress
// but we have to remove the prefix for the non-core ones for them to function.
$type = str_replace( array( 'woo_', 'bp_', 'bbp_', 'edd_' ), '', $type );
if ( function_exists( $type ) && call_user_func( $type ) ) {
if ( 'none' != $_ccss_contents ) {
if ( $debug ) {
$_ccss_contents = '/* TYPES: ' . $type . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
}
return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $additional );
} else {
if ( $debug ) {
$this->criticalcss->log( 'Conditional rule for ' . $type . ' with value "none" found.', 3 );
}
$no_ccss = 'none';
}
}
}
}
}
}
}
// Finally, inline the default CriticalCSS if any or else the entire CSS for the page
// This also applies to logged in users if the option to add CCSS for logged in users has been disabled.
if ( ! empty( $inlined ) && 'none' !== $no_ccss ) {
if ( $debug ) {
$this->criticalcss->log( 'Using default "above the fold" CSS.', 3 );
}
return apply_filters( 'autoptimize_filter_ccss_core_ccss', $inlined . $additional );
} else {
if ( $debug ) {
$this->criticalcss->log( 'No matching CCSS found, switching to inlining full CSS.', 3 );
}
add_filter( 'autoptimize_filter_css_inline', '__return_true' );
return;
}
}
public function ao_ccss_defer_jquery( $in ) {
$loggedin = $this->criticalcss->get_option( 'loggedin' );
// defer all linked and inline JS.
if ( ( ! is_user_logged_in() || $loggedin ) && preg_match_all( '#<script.*>(.*)</script>#Usmi', $in, $matches, PREG_SET_ORDER ) ) {
foreach ( $matches as $match ) {
if ( str_replace( apply_filters( 'autoptimize_filter_ccss_core_defer_exclude', array( 'data-noptimize="1"', 'data-cfasync="false"', 'data-pagespeed-no-defer' ) ), '', $match[0] ) !== $match[0] ) {
// do not touch JS with noptimize/ cfasync/ pagespeed-no-defer flags.
continue;
} elseif ( '' !== $match[1] && ( ! preg_match( '/<script.* type\s?=.*>/', $match[0] ) || preg_match( '/type\s*=\s*[\'"]?(?:text|application)\/(?:javascript|ecmascript)[\'"]?/i', $match[0] ) ) ) {
// base64-encode and defer all inline JS.
$base64_js = '<script defer src="data:text/javascript;base64,' . base64_encode( $match[1] ) . '"></script>';
$in = str_replace( $match[0], $base64_js, $in );
} elseif ( str_replace( array( ' defer', ' async' ), '', $match[0] ) === $match[0] ) {
// and defer linked JS unless already deferred or asynced.
$new_match = str_replace( '<script ', '<script defer ', $match[0] );
$in = str_replace( $match[0], $new_match, $in );
}
}
}
return $in;
}
public function ao_ccss_unloadccss( $html_in ) {
// set media attrib of inline CCSS to none at onLoad to avoid it impacting full CSS (rarely needed).
$_unloadccss_js = apply_filters( 'autoptimize_filter_ccss_core_unloadccss_js', '<script>window.addEventListener("load", function(event) {var el = document.getElementById("aoatfcss"); if(el) el.media = "none";})</script>' );
if ( false !== strpos( $html_in, $_unloadccss_js . '</body>' ) ) {
return $html_in;
}
return str_replace( '</body>', $_unloadccss_js . '</body>', $html_in );
}
/**
* Get the types array.
*
* @return array|null
*/
public function get_types() {
return $this->_types;
}
public function ao_ccss_extend_types() {
// Extend contidional tags
// Attach the conditional tags array.
// in some cases $ao_ccss_types is empty and/or not an array, this should work around that problem.
if ( empty( $this->_types ) || ! is_array( $this->_types ) ) {
$this->_types = $this->get_ao_ccss_core_types();
$this->ao_ccss_log( 'Empty types array in extend, refetching array with core conditionals.', 3 );
}
// Custom Post Types.
$cpts = get_post_types(
array(
'public' => true,
'_builtin' => false,
),
'names',
'and'
);
foreach ( $cpts as $cpt ) {
array_unshift( $this->_types, 'custom_post_' . $cpt );
}
// Templates.
// Transient cache to avoid frequent disk reads.
$templates = get_transient( 'autoptimize_ccss_page_templates' );
if ( ! $templates ) {
$templates = wp_get_theme()->get_page_templates();
set_transient( 'autoptimize_ccss_page_templates', $templates, HOUR_IN_SECONDS );
}
foreach ( $templates as $tplfile => $tplname ) {
array_unshift( $this->_types, 'template_' . $tplfile );
}
// bbPress tags.
if ( function_exists( 'is_bbpress' ) ) {
$this->_types = array_merge(
array(
'bbp_is_bbpress',
'bbp_is_favorites',
'bbp_is_forum_archive',
'bbp_is_replies_created',
'bbp_is_reply_edit',
'bbp_is_reply_move',
'bbp_is_search',
'bbp_is_search_results',
'bbp_is_single_forum',
'bbp_is_single_reply',
'bbp_is_single_topic',
'bbp_is_single_user',
'bbp_is_single_user_edit',
'bbp_is_single_view',
'bbp_is_subscriptions',
'bbp_is_topic_archive',
'bbp_is_topic_edit',
'bbp_is_topic_merge',
'bbp_is_topic_split',
'bbp_is_topic_tag',
'bbp_is_topic_tag_edit',
'bbp_is_topics_created',
'bbp_is_user_home',
'bbp_is_user_home_edit',
),
$this->_types
);
}
// BuddyPress tags.
if ( function_exists( 'is_buddypress' ) ) {
$this->_types = array_merge(
array(
'bp_is_activation_page',
'bp_is_activity',
'bp_is_blogs',
'bp_is_buddypress',
'bp_is_change_avatar',
'bp_is_create_blog',
'bp_is_friend_requests',
'bp_is_friends',
'bp_is_friends_activity',
'bp_is_friends_screen',
'bp_is_group_admin_page',
'bp_is_group_create',
'bp_is_group_forum',
'bp_is_group_forum_topic',
'bp_is_group_home',
'bp_is_group_invites',
'bp_is_group_leave',
'bp_is_group_members',
'bp_is_group_single',
'bp_is_groups',
'bp_is_messages',
'bp_is_messages_compose_screen',
'bp_is_messages_conversation',
'bp_is_messages_inbox',
'bp_is_messages_sentbox',
'bp_is_my_activity',
'bp_is_my_blogs',
'bp_is_notices',
'bp_is_profile_edit',
'bp_is_register_page',
'bp_is_settings_component',
'bp_is_user',
'bp_is_user_profile',
'bp_is_wire',
),
$this->_types
);
}
// Easy Digital Downloads (EDD) tags.
if ( function_exists( 'edd_is_checkout' ) ) {
$this->_types = array_merge(
array(
'edd_is_checkout',
'edd_is_failed_transaction_page',
'edd_is_purchase_history_page',
'edd_is_success_page',
),
$this->_types
);
}
// WooCommerce tags.
if ( class_exists( 'WooCommerce' ) ) {
$this->_types = array_merge(
array(
'woo_is_account_page',
'woo_is_cart',
'woo_is_checkout',
'woo_is_product',
'woo_is_product_category',
'woo_is_product_tag',
'woo_is_shop',
'woo_is_wc_endpoint_url',
'woo_is_woocommerce',
),
$this->_types
);
}
}
public function get_ao_ccss_core_types() {
return array(
'is_404',
'is_front_page',
'is_home',
'is_page',
'is_single',
'is_category',
'is_author',
'is_archive',
'is_search',
'is_attachment',
'is_sticky',
'is_paged',
);
}
public function ao_ccss_key_status( $render ) {
// Provide key status
// Get key and key status.
$key = $this->criticalcss->get_option( 'key' );
$key_status = $this->criticalcss->get_option( 'keyst' );
// Prepare returned variables.
$key_return = array();
$status = false;
if ( $key && 2 == $key_status ) {
// Key exists and its status is valid.
// Set valid key status.
$status = 'valid';
$status_msg = esc_html__( 'Valid' );
$color = '#46b450'; // Green.
$message = null;
} elseif ( $key && 1 == $key_status ) {
// Key exists but its validation has failed.
// Set invalid key status.
$status = 'invalid';
$status_msg = esc_html__( 'Invalid' );
$color = '#dc3232'; // Red.
// Translators: link to criticalcss.com page.
$message = sprintf( esc_html__( 'Your API key is invalid. Please enter a valid %1$scriticalcss.com%2$s key.', 'autoptimize' ), '<a href="https://criticalcss.com/?aff=1" target="_blank">', '</a>' );
} elseif ( $key && ! $key_status ) {
// Key exists but it has no valid status yet
// Perform key validation.
$key_check = $this->ao_ccss_key_validation( $key );
// Key is valid, set valid status.
if ( $key_check ) {
$status = 'valid';
$status_msg = esc_html__( 'Valid' );
$color = '#46b450'; // Green.
$message = null;
} else {
// Key is invalid, set invalid status.
$status = 'invalid';
$status_msg = esc_html__( 'Invalid' );
$color = '#dc3232'; // Red.
if ( get_option( 'autoptimize_ccss_keyst' ) == 1 ) {
// Translators: link to criticalcss.com page.
$message = sprintf( esc_html__( 'Your API key is invalid. Please enter a valid %1$scriticalcss.com%2$s key.', 'autoptimize' ), '<a href="https://criticalcss.com/?aff=1" target="_blank">', '</a>' );
} else {
$message = esc_html__( 'Something went wrong when checking your API key, make sure you server can communicate with https://criticalcss.com and/ or try again later.', 'autoptimize' );
}
}
} else {
// No key nor status
// Set no key status.
$status = 'nokey';
$status_msg = esc_html__( 'None' );
$color = '#ffb900'; // Yellow.
// Translators: link to criticalcss.com page.
$message = sprintf( esc_html__( 'Please enter a valid %1$scriticalcss.com%2$s API key to start.', 'autoptimize' ), '<a href="https://criticalcss.com/?aff=1" target="_blank">', '</a>' );
}
// Fill returned values.
$key_return['status'] = $status;
// Provide rendering information if required.
if ( $render ) {
$key_return['stmsg'] = $status_msg;
$key_return['color'] = $color;
$key_return['msg'] = $message;
}
// Return key status.
return $key_return;
}
public function ao_ccss_key_validation( $key ) {
$noptimize = $this->criticalcss->get_option( 'noptimize' );
// POST a dummy job to criticalcss.com to check for key validation
// Prepare home URL for the request.
$src_url = get_home_url();
// Avoid AO optimizations if required by config or avoid lazyload if lazyload is active in AO.
if ( ! empty( $noptimize ) ) {
$src_url .= '/?ao_noptirocket=1';
} elseif ( class_exists( 'autoptimizeImages', false ) && autoptimizeImages::should_lazyload_wrapper() ) {
$src_url .= '/?ao_nolazy=1';
}
$src_url = apply_filters( 'autoptimize_filter_ccss_cron_srcurl', $src_url );
if ( true !== autoptimizeUtils::is_local_server( parse_url( $src_url, PHP_URL_HOST ) ) ) {
// Prepare the request.
$url = esc_url_raw( AO_CCSS_API . 'generate' );
$args = array(
'headers' => apply_filters(
'autoptimize_ccss_cron_api_generate_headers',
array(
'User-Agent' => 'Autoptimize v' . AO_CCSS_VER,
'Content-type' => 'application/json; charset=utf-8',
'Authorization' => 'JWT ' . $key,
'Connection' => 'close',
)
),
// Body must be JSON.
'body' => json_encode(
apply_filters(
'autoptimize_ccss_cron_api_generate_body',
array(
'url' => $src_url,
'aff' => 1,
'aocssv' => AO_CCSS_VER,
)
),
JSON_UNESCAPED_SLASHES
),
);
// Dispatch the request and store its response code.
$req = wp_safe_remote_post( $url, $args );
$code = wp_remote_retrieve_response_code( $req );
$body = json_decode( wp_remote_retrieve_body( $req ), true );
if ( 200 == $code ) {
// Response is OK.
// Set key status as valid and log key check.
update_option( 'autoptimize_ccss_keyst', 2 );
$this->ao_ccss_log( 'criticalcss.com: API key is valid, updating key status', 3 );
// extract job-id from $body and put it in the queue as a P job
// but only if no jobs and no rules!
$queue = $this->criticalcss->get_option( 'queue' );
$rules = $this->criticalcss->get_option( 'rules' );
if ( ( empty( $queue ) || 0 == count( $queue ) ) && ( empty( $rules ) || ( is_array( $rules ) && 0 == count( $rules['types'] ) && 0 == count( $rules['paths'] ) ) ) ) {
if ( 'JOB_QUEUED' == $body['job']['status'] || 'JOB_ONGOING' == $body['job']['status'] ) {
$jprops['ljid'] = 'firstrun';
$jprops['rtarget'] = 'types|is_front_page';
$jprops['ptype'] = 'is_front_page';
$jprops['hashes'][] = 'dummyhash';
$jprops['hash'] = 'dummyhash';
$jprops['file'] = null;
$jprops['jid'] = $body['job']['id'];
$jprops['jqstat'] = $body['job']['status'];
$jprops['jrstat'] = null;
$jprops['jvstat'] = null;
$jprops['jctime'] = microtime( true );
$jprops['jftime'] = null;
$queue['/'] = $jprops;
$queue_raw = json_encode( $queue );
update_option( 'autoptimize_ccss_queue', $queue_raw, false );
$this->ao_ccss_log( 'Created P job for is_front_page based on API key check response.', 3 );
}
}
return true;
} elseif ( 401 == $code ) {
// Response is unauthorized
// Set key status as invalid and log key check.
update_option( 'autoptimize_ccss_keyst', 1 );
$this->ao_ccss_log( 'criticalcss.com: API key is invalid, updating key status', 3 );
return false;
} else {
// Response unkown
// Log key check attempt.
$this->ao_ccss_log( 'criticalcss.com: could not check API key status, this is a service error, body follows if any...', 2 );
if ( ! empty( $body ) ) {
$this->ao_ccss_log( print_r( $body, true ), 2 );
}
if ( is_wp_error( $req ) ) {
$this->ao_ccss_log( $req->get_error_message(), 2 );
}
return false;
}
} else {
// localhost/ private network server, no API check possible.
return false;
}
}
public function ao_ccss_viewport() {
// Get viewport size
// Attach viewport option.
$viewport = $this->criticalcss->get_option( 'viewport' );
return array(
'w' => ! empty( $viewport['w'] ) ? $viewport['w'] : '',
'h' => ! empty( $viewport['h'] ) ? $viewport['h'] : '',
);
}
public function ao_ccss_check_contents( $ccss ) {
// Perform basic exploit avoidance and CSS validation.
if ( ! empty( $ccss ) ) {
// Try to avoid code injection.
$blocklist = array( '#!/', 'function(', '<script', '<?php', '</style', ' onload=', ' onerror=', ' onmouse', ' onscroll=', ' onclick=' );
foreach ( $blocklist as $blocklisted ) {
if ( stripos( $ccss, $blocklisted ) !== false ) {
$this->ao_ccss_log( 'Critical CSS received contained blocklisted content.', 2 );
return false;
}
}
// Check for most basics CSS structures.
$needlist = array( '{', '}', ':' );
foreach ( $needlist as $needed ) {
if ( false === strpos( $ccss, $needed ) && 'none' !== $ccss ) {
$this->ao_ccss_log( 'Critical CSS received did not seem to contain real CSS.', 2 );
return false;
}
}
}
// Return true if file critical CSS is sane.
return true;
}
public function ao_ccss_log( $msg, $lvl ) {
// Commom logging facility
// Attach debug option.
$debug = $this->criticalcss->get_option( 'debug' );
// Prepare log levels, where accepted $lvl are:
// 1: II (for info)
// 2: EE (for error)
// 3: DD (for debug)
// Default: UU (for unkown).
$level = false;
if ( $debug ) {
switch ( $lvl ) {
case 1:
$level = 'II';
break;
case 2:
$level = 'EE';
break;
case 3:
$level = 'DD';
break;
default:
$level = 'UU';
}
}
// Prepare and write a log message if there's a valid level.
if ( $level ) {
// Prepare message.
$message = date( 'c' ) . ' - [' . $level . '] ' . htmlentities( $msg ) . '<br>'; // @codingStandardsIgnoreLine
// Write message to log file.
error_log( $message, 3, AO_CCSS_LOG );
}
}
public function ao_ccss_clear_page_tpl_cache() {
// Clears transient cache for page templates.
delete_transient( 'autoptimize_ccss_page_templates' );
}
}

View File

@@ -0,0 +1,881 @@
<?php
/**
* Critical CSS Cron logic:
* processes the queue, submitting jobs to criticalcss.com and retrieving generated CSS from criticalcss.com and saving rules.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeCriticalCSSCron {
/**
* Critical CSS object.
*
* @var object
*/
protected $criticalcss;
public function __construct() {
$this->criticalcss = autoptimize()->criticalcss();
// Add queue control to a registered event.
add_action( 'ao_ccss_queue', array( $this, 'ao_ccss_queue_control' ) );
// Add cleaning job to a registered event.
add_action( 'ao_ccss_maintenance', array( $this, 'ao_ccss_cleaning' ) );
}
public function ao_ccss_queue_control() {
// The queue execution backend.
$key = $this->criticalcss->get_option( 'key' );
if ( empty( $key ) ) {
// no key set, not processing the queue!
$this->criticalcss->log( 'No key set, so not processing queue.', 3 );
return;
}
/**
* Provide a debug facility for the queue
* This debug facility provides a way to easily force some queue behaviors useful for development and testing.
* To enable this feature, create the file AO_CCSS_DIR . 'queue.json' with a JSON object like the one bellow:
*
* {"enable":bool,"htcode":int,"status":0|"str","resultStatus ":0|"str","validationStatus":0|"str"}
*
* Where values are:
* - enable : 0 or 1, enable or disable this debug facility straight from the file
* - htcode : 0 or any HTTP reponse code (e.g. 2xx, 4xx, 5xx) to force API responses
* - status : 0 or a valid value for 'status' (see 'Generating critical css - Job Status Types' in spec docs)
* - resultStatus : 0 or a valid value for 'resultStatus' (see 'Appendix - Result status types' in the spec docs)
* - validationStatus: 0 or a valid value for 'validationStatus' (see 'Appendix - Validation status types' in the spec docs)
*
* When properly set, queue will always finish a job with the declared settings above regardless of the real API responses.
*/
$queue_debug = false;
if ( file_exists( AO_CCSS_DEBUG ) ) {
$qdobj_raw = file_get_contents( AO_CCSS_DEBUG );
$qdobj = json_decode( $qdobj_raw, true );
if ( $qdobj ) {
if ( 1 === $qdobj['enable'] ) {
$queue_debug = true;
$this->criticalcss->log( 'Queue operating in debug mode with the following settings: <' . $qdobj_raw . '>', 3 );
}
}
}
// Set some default values for $qdobj to avoid function call warnings.
if ( ! $queue_debug ) {
$qdobj['htcode'] = false;
}
// Check if queue is already running.
$queue_lock = false;
if ( file_exists( AO_CCSS_LOCK ) ) {
$queue_lock = true;
}
// Proceed with the queue if it's not already running.
if ( ! $queue_lock ) {
// Log queue start and create the lock file.
$this->criticalcss->log( 'Queue control started', 3 );
if ( touch( AO_CCSS_LOCK ) ) {
$this->criticalcss->log( 'Queue control locked', 3 );
}
// Attach required variables.
$queue = $this->criticalcss->get_option( 'queue' );
$rtimelimit = $this->criticalcss->get_option( 'rtimelimit' );
// make sure we have the queue and bail if not.
if ( empty( $queue ) || ! is_array( $queue ) ) {
$this->criticalcss->log( 'Job processing cannot work on an empty queue, aborting.', 3 );
unlink( AO_CCSS_LOCK );
return;
}
// Initialize counters.
if ( empty( $rtimelimit ) || 0 == $rtimelimit ) {
// no time limit set, let's go with 1000 seconds.
$rtimelimit = 1000;
}
$mt = time() + (int) $rtimelimit; // maxtime queue processing can run.
$jc = 1; // job count number.
$jr = 1; // jobs requests number.
$jt = count( $queue ); // number of jobs in queue.
// Sort queue by ascending job status (e.g. ERROR, JOB_ONGOING, JOB_QUEUED, NEW...).
array_multisort( array_column( $queue, 'jqstat' ), $queue ); // @codingStandardsIgnoreLine
// Iterates over the entire queue.
foreach ( $queue as $path => $jprops ) {
// Prepare flags and target rule.
$update = false;
$deljob = false;
$rule_update = false;
$oldccssfile = false;
$trule = explode( '|', $jprops['rtarget'] );
// Log job count.
$this->criticalcss->log( 'Processing job ' . $jc . ' of ' . $jt . ' with id <' . $jprops['ljid'] . '> and status <' . $jprops['jqstat'] . '>', 3 );
// Process NEW jobs.
if ( 'NEW' == $jprops['jqstat'] ) {
// Log the new job.
$this->criticalcss->log( 'Found NEW job with local ID <' . $jprops['ljid'] . '>, starting its queue processing', 3 );
// Compare job and rule hashes (if any).
$hash = $this->ao_ccss_diff_hashes( $jprops['ljid'], $jprops['hash'], $jprops['hashes'], $jprops['rtarget'] );
// If job hash is new or different of a previous one.
if ( $hash ) {
if ( $jr > 2 ) {
// we already posted 2 jobs to criticalcss.com, don't post more this run
// but we can keep on processing the queue to keep it tidy.
$this->criticalcss->log( 'Holding off on generating request for job with local ID <' . $jprops['ljid'] . '>, maximum number of POSTS reached.', 3 );
continue;
}
// Set job hash.
$jprops['hash'] = $hash;
// Dispatch the job generate request and increment request count.
$apireq = $this->ao_ccss_api_generate( $path, $queue_debug, $qdobj['htcode'] );
$jr++;
// NOTE: All the following conditions maps to the ones in admin_settings_queue.js.php.
if ( empty( $apireq ) ) {
// ERROR: no response
// Update job properties.
$jprops['jqstat'] = 'NO_RESPONSE';
$jprops['jrstat'] = 'NONE';
$jprops['jvstat'] = 'NONE';
$jprops['jftime'] = microtime( true );
$this->criticalcss->log( 'Job id <' . $jprops['ljid'] . '> request has no response, status now is <' . $jprops['jqstat'] . '>', 3 );
} elseif ( array_key_exists( 'errorCode', $apireq ) && 'INVALID_JWT_TOKEN' == $apireq['errorCode'] ) {
// ERROR: key validation
// Update job properties.
$jprops['jqstat'] = $apireq['errorCode'];
$jprops['jrstat'] = $apireq['error'];
$jprops['jvstat'] = 'NONE';
$jprops['jftime'] = microtime( true );
$this->criticalcss->log( 'API key validation error when processing job id <' . $jprops['ljid'] . '>, job status is now <' . $jprops['jqstat'] . '>', 3 );
} elseif ( array_key_exists( 'job', $apireq ) && array_key_exists( 'status', $apireq['job'] ) && ( 'JOB_QUEUED' == $apireq['job']['status'] || 'JOB_ONGOING' == $apireq['job']['status'] ) ) {
// SUCCESS: request has a valid result.
// Update job properties.
$jprops['jid'] = $apireq['job']['id'];
$jprops['jqstat'] = $apireq['job']['status'];
$this->criticalcss->log( 'Job id <' . $jprops['ljid'] . '> generate request successful, remote id <' . $jprops['jid'] . '>, status now is <' . $jprops['jqstat'] . '>', 3 );
} elseif ( array_key_exists( 'job', $apireq ) && array_key_exists( 'status', $apireq['job'] ) && 'STATUS_JOB_BAD' == $apireq['job']['status'] ) {
// ERROR: concurrent requests
// Update job properties.
$jprops['jid'] = $apireq['job']['id'];
$jprops['jqstat'] = $apireq['job']['status'];
if ( $apireq['job']['error'] ) {
$jprops['jrstat'] = $apireq['job']['error'];
} else {
$jprops['jrstat'] = 'Baby did a bad bad thing';
}
$jprops['jvstat'] = 'NONE';
$jprops['jftime'] = microtime( true );
$this->criticalcss->log( 'Concurrent requests when processing job id <' . $jprops['ljid'] . '>, job status is now <' . $jprops['jqstat'] . '>', 3 );
} else {
// UNKNOWN: unhandled generate exception
// Update job properties.
$jprops['jqstat'] = 'JOB_UNKNOWN';
$jprops['jrstat'] = 'NONE';
$jprops['jvstat'] = 'NONE';
$jprops['jftime'] = microtime( true );
$this->criticalcss->log( 'Job id <' . $jprops['ljid'] . '> generate request has an UNKNOWN condition, status now is <' . $jprops['jqstat'] . '>, check log messages above for more information', 2 );
$this->criticalcss->log( 'Job response was: ' . json_encode( $apireq ), 3 );
}
} else {
// SUCCESS: Job hash is equal to a previous one, so it's done
// Update job status and finish time.
$jprops['jqstat'] = 'JOB_DONE';
$jprops['jftime'] = microtime( true );
$this->criticalcss->log( 'Job id <' . $jprops['ljid'] . '> requires no further processing, status now is <' . $jprops['jqstat'] . '>', 3 );
}
// Set queue update flag.
$update = true;
} elseif ( 'JOB_QUEUED' == $jprops['jqstat'] || 'JOB_ONGOING' == $jprops['jqstat'] ) {
// Process QUEUED and ONGOING jobs
// Log the pending job.
$this->criticalcss->log( 'Found PENDING job with local ID <' . $jprops['ljid'] . '>, continuing its queue processing', 3 );
// Dispatch the job result request and increment request count.
$apireq = $this->ao_ccss_api_results( $jprops['jid'], $queue_debug, $qdobj['htcode'] );
// NOTE: All the following condigitons maps to the ones in admin_settings_queue.js.php
// Replace API response values if queue debugging is enabled and some value is set.
if ( $queue_debug ) {
if ( $qdobj['status'] ) {
$apireq['status'] = $qdobj['status'];
}
if ( $qdobj['resultStatus'] ) {
$apireq['resultStatus'] = $qdobj['resultStatus'];
}
if ( $qdobj['validationStatus'] ) {
$apireq['validationStatus'] = $qdobj['validationStatus'];
}
}
if ( empty( $apireq ) || ! is_array( $apireq ) ) {
// ERROR: no response
// Update job properties.
$jprops['jqstat'] = 'NO_RESPONSE';
$jprops['jrstat'] = 'NONE';
$jprops['jvstat'] = 'NONE';
$jprops['jftime'] = microtime( true );
$this->criticalcss->log( 'Job id <' . $jprops['ljid'] . '> request has no response, status now is <' . $jprops['jqstat'] . '>', 3 );
} elseif ( array_key_exists( 'status', $apireq ) && ( 'JOB_QUEUED' == $apireq['status'] || 'JOB_ONGOING' == $apireq['status'] ) ) {
// SUCCESS: request has a valid result
// Process a PENDING job
// Update job properties.
$jprops['jqstat'] = $apireq['status'];
$this->criticalcss->log( 'Job id <' . $jprops['ljid'] . '> result request successful, remote id <' . $jprops['jid'] . '>, status <' . $jprops['jqstat'] . '> unchanged', 3 );
} elseif ( array_key_exists( 'status', $apireq ) && 'JOB_DONE' == $apireq['status'] ) {
// Process a DONE job
// New resultStatus from ccss.com "HTML_404", consider as "GOOD" for now.
if ( 'HTML_404' == $apireq['resultStatus'] ) {
$apireq['resultStatus'] = 'GOOD';
}
if ( 'GOOD' == $apireq['resultStatus'] && ( 'GOOD' == $apireq['validationStatus'] || 'WARN' == $apireq['validationStatus'] ) ) {
// SUCCESS: GOOD job with GOOD or WARN validation
// Update job properties.
$jprops['file'] = $this->ao_ccss_save_file( $apireq['css'], $trule, false );
$jprops['jqstat'] = $apireq['status'];
$jprops['jrstat'] = $apireq['resultStatus'];
$jprops['jvstat'] = $apireq['validationStatus'];
$jprops['jftime'] = microtime( true );
$rule_update = true;
do_action( 'autoptimize_action_ccss_cron_rule_saved', $jprops['rtarget'], $jprops['file'] );
$this->criticalcss->log( 'Job id <' . $jprops['ljid'] . '> result request successful, remote id <' . $jprops['jid'] . '>, status <' . $jprops['jqstat'] . '>, file saved <' . $jprops['file'] . '>', 3 );
} elseif ( 'GOOD' == $apireq['resultStatus'] && ( 'BAD' == $apireq['validationStatus'] || 'SCREENSHOT_WARN_BLANK' == $apireq['validationStatus'] ) ) {
// SUCCESS: GOOD job with BAD or SCREENSHOT_WARN_BLANK validation
// Update job properties.
$jprops['jqstat'] = $apireq['status'];
$jprops['jrstat'] = $apireq['resultStatus'];
$jprops['jvstat'] = $apireq['validationStatus'];
$jprops['jftime'] = microtime( true );
if ( apply_filters( 'autoptimize_filter_ccss_save_review_rules', true ) ) {
$jprops['file'] = $this->ao_ccss_save_file( $apireq['css'], $trule, true );
$rule_update = true;
do_action( 'autoptimize_action_ccss_cron_rule_saved', $jprops['rtarget'], $jprops['file'] );
$this->criticalcss->log( 'Job id <' . $jprops['ljid'] . '> result request successful, remote id <' . $jprops['jid'] . '>, status <' . $jprops['jqstat'] . ', file saved <' . $jprops['file'] . '> but requires REVIEW', 3 );
} else {
$this->criticalcss->log( 'Job id <' . $jprops['ljid'] . '> result request successful, remote id <' . $jprops['jid'] . '>, status <' . $jprops['jqstat'] . ', file not saved because it required REVIEW.', 3 );
}
} elseif ( 'GOOD' != $apireq['resultStatus'] && ( 'GOOD' != $apireq['validationStatus'] || 'WARN' != $apireq['validationStatus'] || 'BAD' != $apireq['validationStatus'] || 'SCREENSHOT_WARN_BLANK' != $apireq['validationStatus'] ) ) {
// ERROR: no GOOD, WARN or BAD results
// Update job properties.
$jprops['jqstat'] = $apireq['status'];
$jprops['jrstat'] = $apireq['resultStatus'];
$jprops['jvstat'] = $apireq['validationStatus'];
$jprops['jftime'] = microtime( true );
$this->criticalcss->log( 'Job id <' . $jprops['ljid'] . '> result request successful but job FAILED, status now is <' . $jprops['jqstat'] . '>', 3 );
$apireq['css'] = '/* critical css removed for DEBUG logging purposes */';
$this->criticalcss->log( 'Job response was: ' . json_encode( $apireq ), 3 );
} else {
// UNKNOWN: unhandled JOB_DONE exception
// Update job properties.
$jprops['jqstat'] = 'JOB_UNKNOWN';
$jprops['jrstat'] = $apireq['resultStatus'];
$jprops['jvstat'] = $apireq['validationStatus'];
$jprops['jftime'] = microtime( true );
$this->criticalcss->log( 'Job id <' . $jprops['ljid'] . '> result request successful but job is UNKNOWN, status now is <' . $jprops['jqstat'] . '>', 2 );
$apireq['css'] = '/* critical css removed for DEBUG logging purposes */';
$this->criticalcss->log( 'Job response was: ' . json_encode( $apireq ), 3 );
}
} elseif ( array_key_exists( 'job', $apireq ) && is_array( $apireq['job'] ) && array_key_exists( 'status', $apireq['job'] ) && ( 'JOB_FAILED' == $apireq['job']['status'] || 'STATUS_JOB_BAD' == $apireq['job']['status'] ) ) {
// ERROR: failed job
// Update job properties.
$jprops['jqstat'] = $apireq['job']['status'];
if ( $apireq['job']['error'] ) {
$jprops['jrstat'] = $apireq['job']['error'];
} else {
$jprops['jrstat'] = 'Baby did a bad bad thing';
}
$jprops['jvstat'] = 'NONE';
$jprops['jftime'] = microtime( true );
$this->criticalcss->log( 'Job id <' . $jprops['ljid'] . '> result request successful but job FAILED, status now is <' . $jprops['jqstat'] . '>', 3 );
} elseif ( array_key_exists( 'error', $apireq ) && 'This css no longer exists. Please re-generate it.' == $apireq['error'] ) {
// ERROR: CSS doesn't exist
// Update job properties.
$jprops['jqstat'] = 'NO_CSS';
$jprops['jrstat'] = $apireq['error'];
$jprops['jvstat'] = 'NONE';
$jprops['jftime'] = microtime( true );
$this->criticalcss->log( 'Job id <' . $jprops['ljid'] . '> result request successful but job FAILED, status now is <' . $jprops['jqstat'] . '>', 3 );
} else {
// UNKNOWN: unhandled results exception
// Update job properties.
$jprops['jqstat'] = 'JOB_UNKNOWN';
$jprops['jrstat'] = 'NONE';
$jprops['jvstat'] = 'NONE';
$jprops['jftime'] = microtime( true );
$this->criticalcss->log( 'Job id <' . $jprops['ljid'] . '> result request has an UNKNOWN condition, status now is <' . $jprops['jqstat'] . '>, check log messages above for more information', 2 );
}
// Set queue update flag.
$update = true;
}
// Mark DONE jobs for removal.
if ( 'JOB_DONE' == $jprops['jqstat'] ) {
$update = true;
$deljob = true;
}
// Persist updated queue object.
if ( $update ) {
if ( ! $deljob ) {
// Update properties of a NEW or PENDING job...
$queue[ $path ] = $jprops;
} else {
// ...or remove the DONE job.
unset( $queue[ $path ] );
$this->criticalcss->log( 'Job id <' . $jprops['ljid'] . '> is DONE and was removed from the queue', 3 );
}
// Update queue object.
$queue_raw = json_encode( $queue );
update_option( 'autoptimize_ccss_queue', $queue_raw, false );
$this->criticalcss->log( 'Queue updated by job id <' . $jprops['ljid'] . '>', 3 );
// Update target rule.
if ( $rule_update ) {
$this->ao_ccss_rule_update( $jprops['ljid'], $jprops['rtarget'], $jprops['file'], $jprops['hash'] );
$this->criticalcss->log( 'Job id <' . $jprops['ljid'] . '> updated the target rule <' . $jprops['rtarget'] . '>', 3 );
}
} else {
// Or log no queue action.
$this->criticalcss->log( 'Nothing to do on this job', 3 );
}
// Break the loop if request time limit is (almost exceeded).
if ( time() > $mt ) {
$this->criticalcss->log( 'The time limit of ' . $rtimelimit . ' seconds was exceeded, queue control must finish now', 3 );
break;
}
// Increment job counter.
$jc++;
}
// Remove the lock file and log the queue end.
if ( file_exists( AO_CCSS_LOCK ) ) {
unlink( AO_CCSS_LOCK );
$this->criticalcss->log( 'Queue control unlocked', 3 );
}
$this->criticalcss->log( 'Queue control finished', 3 );
// Log that queue is locked.
} else {
$this->criticalcss->log( 'Queue is already running, skipping the attempt to run it again', 3 );
}
}
public function ao_ccss_diff_hashes( $ljid, $hash, $hashes, $rule ) {
// Compare job hashes
// STEP 1: update job hashes.
if ( 1 == count( $hashes ) ) {
// Job with a single hash
// Set job hash.
$hash = $hashes[0];
$this->criticalcss->log( 'Job id <' . $ljid . '> updated with SINGLE hash <' . $hash . '>', 3 );
} else {
// Job with multiple hashes
// Loop through hashes to concatenate them.
$nhash = '';
foreach ( $hashes as $shash ) {
$nhash .= $shash;
}
// Set job hash.
$hash = md5( $nhash );
$this->criticalcss->log( 'Job id <' . $ljid . '> updated with a COMPOSITE hash <' . $hash . '>', 3 );
}
// STEP 2: compare job to existing jobs to prevent double submission for same type+hash.
$queue = $this->criticalcss->get_option( 'queue' );
foreach ( $queue as $queue_item ) {
$this->criticalcss->log( 'Comparing <' . $rule . $hash . '> with <' . $queue_item['rtarget'] . $queue_item['hash'] . '>', 3 );
if ( $queue_item['hash'] == $hash && $queue_item['rtarget'] == $rule && in_array( $queue_item['jqstat'], array( 'JOB_QUEUED', 'JOB_ONGOING', 'JOB_DONE' ) ) ) {
$this->criticalcss->log( 'Job id <' . $ljid . '> matches the already pending job <' . $queue_item['ljid'] . '>', 3 );
return false;
}
}
// STEP 3: compare job and existing rule (if any) hashes
// Attach required arrays.
$rules = $this->criticalcss->get_option( 'rules' );
// Prepare rule variables.
$trule = explode( '|', $rule );
if ( is_array( $trule ) && ! empty( $trule ) && array_key_exists( $trule[1], $rules[ $trule[0] ] ) ) {
$srule = $rules[ $trule[0] ][ $trule[1] ];
} else {
$srule = '';
}
// If hash is empty, set it to now for a "forced job".
if ( empty( $hash ) ) {
$hash = 'new';
$this->criticalcss->log( 'Job id <' . $ljid . '> had no hash, assuming forced job so setting hash to new', 3 );
}
// Check if a MANUAL rule exist and return false.
if ( ! empty( $srule ) && ( 0 == $srule['hash'] && 0 != $srule['file'] ) ) {
$this->criticalcss->log( 'Job id <' . $ljid . '> matches the MANUAL rule <' . $trule[0] . '|' . $trule[1] . '>', 3 );
return false;
} elseif ( ! empty( $srule ) ) {
// Check if an AUTO rule exist.
if ( $hash === $srule['hash'] && is_file( AO_CCSS_DIR . $srule['file'] ) && 0 != filesize( AO_CCSS_DIR . $srule['file'] ) ) {
// Check if job hash matches rule, if the CCSS file exists said file is not empty and return FALSE is so.
$this->criticalcss->log( 'Job id <' . $ljid . '> with hash <' . $hash . '> MATCH the one in rule <' . $trule[0] . '|' . $trule[1] . '>', 3 );
return false;
} else {
// Or return the new hash if they differ.
$this->criticalcss->log( 'Job id <' . $ljid . '> with hash <' . $hash . '> DOES NOT MATCH the one in rule <' . $trule[0] . '|' . $trule[1] . '> or rule\'s CCSS file was invalid.', 3 );
return $hash;
}
} else {
// Return the hash for a job that has no rule yet.
$this->criticalcss->log( 'Job id <' . $ljid . '> with hash <' . $hash . '> has no rule yet', 3 );
return $hash;
}
}
public function ao_ccss_api_generate( $path, $debug, $dcode ) {
// POST jobs to criticalcss.com and return responses
// Get key and key status.
$key = $this->criticalcss->get_option( 'key' );
$key_status = $this->criticalcss->get_option( 'keyst' );
$noptimize = $this->criticalcss->get_option( 'noptimize' );
// Prepare full URL to request.
$site_host = get_site_url();
$site_path = parse_url( $site_host, PHP_URL_PATH );
if ( ! empty( $site_path ) ) {
$site_host = str_replace( $site_path, '', $site_host );
}
// Logic to bind to one domain to avoid site clones of sites would
// automatically begin spawning requests to criticalcss.com which has
// a per domain cost.
$domain = $this->criticalcss->get_option( 'domain' );
if ( empty( $domain ) ) {
// first request being done, update option to allow future requests are only allowed if from same domain.
update_option( 'autoptimize_ccss_domain', str_rot13( $site_host ) );
} elseif ( trim( $domain, '\'"' ) !== 'none' && parse_url( $site_host, PHP_URL_HOST ) !== parse_url( $domain, PHP_URL_HOST ) && apply_filters( 'autoptimize_filter_ccss_bind_domain', true ) ) {
// not the same domain, log as error and return without posting to criticalcss.com.
$this->criticalcss->log( 'Request for domain ' . $site_host . ' does not match bound domain ' . $domain . ' so not proceeding.', 2 );
return false;
}
$src_url = $site_host . $path;
// Avoid AO optimizations if required by config or avoid lazyload if lazyload is active in AO.
if ( ! empty( $noptimize ) ) {
$src_url .= '?ao_noptirocket=1';
} elseif ( ( class_exists( 'autoptimizeImages', false ) && autoptimizeImages::should_lazyload_wrapper() ) || apply_filters( 'autoptimize_filter_ccss_enforce_nolazy', false ) ) {
$src_url .= '?ao_nolazy=1';
}
$src_url = apply_filters( 'autoptimize_filter_ccss_cron_srcurl', $src_url );
if ( true !== autoptimizeUtils::is_local_server( parse_url( $src_url, PHP_URL_HOST ) ) ) {
// Initialize request body.
$body = array();
$body['url'] = $src_url;
$body['aff'] = 1;
$body['aocssv'] = AO_CCSS_VER;
// Prepare and add viewport size to the body if available.
$viewport = $this->criticalcss->viewport();
if ( ! empty( $viewport['w'] ) && ! empty( $viewport['h'] ) ) {
$body['width'] = $viewport['w'];
$body['height'] = $viewport['h'];
}
// Prepare and add forceInclude to the body if available.
$finclude = $this->criticalcss->get_option( 'finclude' );
$finclude = $this->ao_ccss_finclude( $finclude );
if ( ! empty( $finclude ) ) {
$body['forceInclude'] = $finclude;
}
// Add filter to allow the body array to be altered (e.g. to add customPageHeaders).
$body = apply_filters( 'autoptimize_ccss_cron_api_generate_body', $body );
// Body must be json and log it.
$body = json_encode( $body, JSON_UNESCAPED_SLASHES );
$this->criticalcss->log( 'criticalcss.com: POST generate request body is ' . $body, 3 );
// Prepare the request.
$url = esc_url_raw( AO_CCSS_API . 'generate?aover=' . AO_CCSS_VER );
$args = array(
'headers' => apply_filters(
'autoptimize_ccss_cron_api_generate_headers',
array(
'User-Agent' => 'Autoptimize v' . AO_CCSS_VER,
'Content-type' => 'application/json; charset=utf-8',
'Authorization' => 'JWT ' . $key,
'Connection' => 'close',
)
),
'body' => $body,
);
// Dispatch the request and store its response code.
$req = wp_safe_remote_post( $url, $args );
$code = wp_remote_retrieve_response_code( $req );
$body = json_decode( wp_remote_retrieve_body( $req ), true );
if ( $debug && $dcode ) {
// If queue debug is active, change response code.
$code = $dcode;
}
if ( 200 == $code ) {
// Response code is OK.
// Workaround criticalcss.com non-RESTful reponses.
if ( 'JOB_QUEUED' == $body['job']['status'] || 'JOB_ONGOING' == $body['job']['status'] || 'STATUS_JOB_BAD' == $body['job']['status'] ) {
// Log successful and return encoded request body.
$this->criticalcss->log( 'criticalcss.com: POST generate request for path <' . $src_url . '> replied successfully', 3 );
// This code also means the key is valid, so cache key status for 24h if not already cached.
if ( ( ! $key_status || 2 != $key_status ) && $key ) {
update_option( 'autoptimize_ccss_keyst', 2 );
$this->criticalcss->log( 'criticalcss.com: API key is valid, updating key status', 3 );
}
// Return the request body.
return $body;
} else {
// Log successful requests with invalid reponses.
$this->criticalcss->log( 'criticalcss.com: POST generate request for path <' . $src_url . '> replied with code <' . $code . '> and an UNKNOWN error condition, body follows...', 2 );
$this->criticalcss->log( print_r( $body, true ), 2 );
return $body;
}
} else {
// Response code is anything else.
// Log failed request with a valid response code and return body.
if ( $code ) {
$this->criticalcss->log( 'criticalcss.com: POST generate request for path <' . $src_url . '> replied with error code <' . $code . '>, body follows...', 2 );
$this->criticalcss->log( print_r( $body, true ), 2 );
if ( 401 == $code ) {
// If request is unauthorized, also clear key status.
update_option( 'autoptimize_ccss_keyst', 1 );
$this->criticalcss->log( 'criticalcss.com: API key is invalid, updating key status', 3 );
}
// Return the request body.
return $body;
} else {
// Log failed request with no response and return false.
$this->criticalcss->log( 'criticalcss.com: POST generate request for path <' . $src_url . '> has no response, this could be a service timeout', 2 );
if ( is_wp_error( $req ) ) {
$this->criticalcss->log( $req->get_error_message(), 2 );
}
return false;
}
}
} else {
// localhost/ private network server, no CCSS possible.
$this->criticalcss->log( 'ccss cron: job not created at ccss.com as for local server', 3 );
return false;
}
}
public function ao_ccss_api_results( $jobid, $debug, $dcode ) {
// GET jobs from criticalcss.com and return responses
// Get key.
$key = $this->criticalcss->get_option( 'key' );
// Prepare the request.
$url = AO_CCSS_API . 'results?resultId=' . $jobid;
$args = array(
'headers' => apply_filters(
'autoptimize_ccss_cron_api_generate_headers',
array(
'User-Agent' => 'Autoptimize CriticalCSS Power-Up v' . AO_CCSS_VER,
'Authorization' => 'JWT ' . $key,
'Connection' => 'close',
)
),
);
// Dispatch the request and store its response code.
$req = wp_safe_remote_get( $url, $args );
$code = wp_remote_retrieve_response_code( $req );
$body = json_decode( wp_remote_retrieve_body( $req ), true );
if ( $debug && $dcode ) {
// If queue debug is active, change response code.
$code = $dcode;
}
if ( 200 == $code ) {
// Response code is OK.
if ( is_array( $body ) && ( array_key_exists( 'status', $body ) || array_key_exists( 'job', $body ) ) && ( 'JOB_QUEUED' == $body['status'] || 'JOB_ONGOING' == $body['status'] || 'JOB_DONE' == $body['status'] || 'JOB_FAILED' == $body['status'] || 'JOB_UNKNOWN' == $body['status'] || 'STATUS_JOB_BAD' == $body['job']['status'] ) ) {
// Workaround criticalcss.com non-RESTful reponses
// Log successful and return encoded request body.
$this->criticalcss->log( 'criticalcss.com: GET results request for remote job id <' . $jobid . '> replied successfully', 3 );
return $body;
} elseif ( is_array( $body ) && ( array_key_exists( 'error', $body ) && 'This css no longer exists. Please re-generate it.' == $body['error'] ) ) {
// Handle no CSS reply
// Log no CSS error and return encoded request body.
$this->criticalcss->log( 'criticalcss.com: GET results request for remote job id <' . $jobid . '> replied successfully but the CSS for it does not exist anymore', 3 );
return $body;
} else {
// Log failed request and return false.
$this->criticalcss->log( 'criticalcss.com: GET results request for remote job id <' . $jobid . '> replied with code <' . $code . '> and an UNKNOWN error condition, body follows...', 2 );
$this->criticalcss->log( print_r( $body, true ), 2 );
return false;
}
} else {
// Response code is anything else
// Log failed request with a valid response code and return body.
if ( $code ) {
$this->criticalcss->log( 'criticalcss.com: GET results request for remote job id <' . $jobid . '> replied with error code <' . $code . '>, body follows...', 2 );
$this->criticalcss->log( print_r( $body, true ), 2 );
if ( 401 == $code ) {
// If request is unauthorized, also clear key status.
update_option( 'autoptimize_ccss_keyst', 1 );
$this->criticalcss->log( 'criticalcss.com: API key is invalid, updating key status', 3 );
}
// Return the request body.
return $body;
} else {
// Log failed request with no response and return false.
$this->criticalcss->log( 'criticalcss.com: GET results request for remote job id <' . $jobid . '> has no response, this could be a service timeout', 2 );
return false;
}
}
}
public function ao_ccss_save_file( $ccss, $target, $review ) {
// Save critical CSS into the filesystem and return its filename
// Prepare review mark.
if ( $review ) {
$rmark = '_R';
} else {
$rmark = '';
}
// Prepare target rule, filename and content.
$filename = false;
$content = $ccss;
if ( $this->criticalcss->check_contents( $content ) ) {
// Sanitize content, set filename and try to save file.
$file = AO_CCSS_DIR . 'ccss_' . md5( $ccss . $target[1] ) . $rmark . '.css';
$status = file_put_contents( $file, $content, LOCK_EX );
$filename = pathinfo( $file, PATHINFO_BASENAME );
$this->criticalcss->log( 'Critical CSS file for the rule <' . $target[0] . '|' . $target[1] . '> was saved as <' . $filename . '>, size in bytes is <' . $status . '>', 3 );
if ( ! $status ) {
// If file has not been saved, reset filename.
$this->criticalcss->log( 'Critical CSS file <' . $filename . '> could not be not saved', 2 );
$filename = false;
return $filename;
}
} else {
$this->criticalcss->log( 'Critical CSS received did not pass content check', 2 );
return $filename;
}
// Remove old critical CSS if a previous one existed in the rule and if that file exists in filesystem
// Attach required arrays.
$rules = $this->criticalcss->get_option( 'rules' );
// Only proceed if the rule already existed.
if ( array_key_exists( $target[1], $rules[ $target[0] ] ) ) {
$srule = $rules[ $target[0] ][ $target[1] ];
$oldfile = $srule['file'];
if ( $oldfile && $oldfile !== $filename ) {
$delfile = AO_CCSS_DIR . $oldfile;
if ( file_exists( $delfile ) ) {
$unlinkst = unlink( $delfile );
if ( $unlinkst ) {
$this->criticalcss->log( 'A previous critical CSS file <' . $oldfile . '> was removed for the rule <' . $target[0] . '|' . $target[1] . '>', 3 );
}
}
}
}
// Return filename or false.
return $filename;
}
public function ao_ccss_rule_update( $ljid, $srule, $file, $hash ) {
// Update or create a rule
// Attach required arrays.
$rules = $this->criticalcss->get_option( 'rules' );
// Prepare rule variables.
$trule = explode( '|', $srule );
if ( array_key_exists( $trule[1], $rules[ $trule[0] ] ) ) {
$rule = $rules[ $trule[0] ][ $trule[1] ];
} else {
$rule = array();
}
$action = false;
$rtype = '';
if ( is_array( $rule ) && array_key_exists( 'hash', $rule ) && 0 === $rule['hash'] && array_key_exists( 'file', $rule ) && 0 !== $rule['file'] ) {
// manual rule, don't ever overwrite.
$action = 'NOT UPDATED';
$rtype = 'MANUAL';
} elseif ( is_array( $rule ) && array_key_exists( 'hash', $rule ) && 0 === $rule['hash'] && array_key_exists( 'file', $rule ) && 0 === $rule['file'] ) {
// If this is an user created AUTO rule with no hash and file yet, update its hash and filename
// Set rule hash, file and action flag.
$rule['hash'] = $hash;
$rule['file'] = $file;
$action = 'UPDATED';
$rtype = 'AUTO';
} elseif ( is_array( $rule ) && array_key_exists( 'hash', $rule ) && 0 !== $rule['hash'] && ctype_alnum( $rule['hash'] ) ) {
// If this is an genuine AUTO rule, update its hash and filename
// Set rule hash, file and action flag.
$rule['hash'] = $hash;
$rule['file'] = $file;
$action = 'UPDATED';
$rtype = 'AUTO';
} else {
// If rule doesn't exist, create an AUTO rule
// AUTO rules were only for types, but will now also work for paths.
if ( ( 'types' == $trule[0] || 'paths' == $trule[0] ) && ! empty( $trule[1] ) ) {
// Set rule hash and file and action flag.
$rule['hash'] = $hash;
$rule['file'] = $file;
$action = 'CREATED';
$rtype = 'AUTO';
} else {
// Log that no rule was created.
$this->criticalcss->log( 'Exception, no AUTO rule created', 3 );
}
}
if ( $action ) {
// If a rule creation/update is required, persist updated rules object.
$rules[ $trule[0] ][ $trule[1] ] = $rule;
$rules_raw = json_encode( $rules );
update_option( 'autoptimize_ccss_rules', $rules_raw );
$this->criticalcss->flush_options();
$this->criticalcss->log( 'Target rule <' . $srule . '> of type <' . $rtype . '> was ' . $action . ' for job id <' . $ljid . '>', 3 );
// and trigger action for whoever needs to be aware.
do_action( 'autoptimize_action_ccss_cron_rule_updated', $srule, $file, '' );
} else {
$this->criticalcss->log( 'No rule action required', 3 );
}
}
function ao_ccss_finclude( $finclude_raw ) {
// Prepare forceInclude object.
if ( ! empty( $finclude_raw ) ) {
// If there are any content
// Convert raw string into arra and initialize the returning object.
$fincludes = explode( ',', $finclude_raw );
$finclude = array();
// Interacts over every rule.
$i = 0;
foreach ( $fincludes as $include ) {
// Trim leading and trailing whitespaces.
$include = trim( $include );
if ( substr( $include, 0, 2 ) === '//' ) {
// Regex rule
// Format value as required.
$include = str_replace( '//', '/', $include );
$include = $include . '/i';
// Store regex object.
$finclude[ $i ]['type'] = 'RegExp';
$finclude[ $i ]['value'] = $include;
} else {
// Simple value rule.
$finclude[ $i ]['value'] = $include;
}
$i++;
}
// Return forceInclude object.
return $finclude;
} else {
// Or just return false if empty.
return false;
}
}
public function ao_ccss_cleaning() {
// Perform plugin maintenance
// Truncate log file >= 1MB .
if ( file_exists( AO_CCSS_LOG ) ) {
if ( filesize( AO_CCSS_LOG ) >= 1048576 ) {
$logfile = fopen( AO_CCSS_LOG, 'w' );
fclose( $logfile );
}
}
// Remove lock file.
if ( file_exists( AO_CCSS_LOCK ) ) {
unlink( AO_CCSS_LOCK );
}
// Make sure queue processing is scheduled, recreate if not.
if ( ! wp_next_scheduled( 'ao_ccss_queue' ) ) {
wp_schedule_event( time(), apply_filters( 'ao_ccss_queue_schedule', 'ao_ccss' ), 'ao_ccss_queue' );
}
// Queue cleaning.
$queue = $this->criticalcss->get_option( 'queue' );
if ( isset( $queue ) && is_array( $queue ) ) {
$queue_purge_threshold = 100;
$queue_purge_age = 24 * 60 * 60;
$queue_length = count( $queue );
$timestamp_yesterday = microtime( true ) - $queue_purge_age;
$remove_old_new = false;
$queue_altered = false;
if ( $queue_length > $queue_purge_threshold ) {
$remove_old_new = true;
}
foreach ( $queue as $path => $job ) {
if ( ( $remove_old_new && 'NEW' == $job['jqstat'] && $job['jctime'] < $timestamp_yesterday ) || in_array( $job['jqstat'], array( 'JOB_FAILED', 'STATUS_JOB_BAD', 'NO_CSS', 'NO_RESPONSE' ) ) ) {
unset( $queue[ $path ] );
$queue_altered = true;
}
}
// save queue to options!
if ( $queue_altered ) {
$queue_raw = json_encode( $queue );
update_option( 'autoptimize_ccss_queue', $queue_raw, false );
$this->criticalcss->log( 'Queue cleaning done.', 3 );
}
}
// re-check key if invalid.
$keyst = $this->criticalcss->get_option( 'keyst' );
if ( 1 == $keyst ) {
$this->ao_ccss_api_generate( '', '', '' );
}
}
}

View File

@@ -0,0 +1,335 @@
<?php
/**
* Critical CSS job enqueue logic.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeCriticalCSSEnqueue {
/**
* Critical CSS object.
*
* @var object
*/
protected $criticalcss;
public function __construct() {
$this->criticalcss = autoptimize()->criticalcss();
}
public function ao_ccss_enqueue( $hash = '', $path = '', $type = 'is_page' ) {
// Get key status.
$key = $this->criticalcss->key_status( false );
// Queue is available to anyone...
$enqueue = true;
// ... which are not the ones below.
if ( true === autoptimizeUtils::is_local_server() ) {
$enqueue = false;
$this->criticalcss->log( 'cant enqueue as local/ private', 3 );
} elseif ( 'nokey' == $key['status'] || 'invalid' == $key['status'] ) {
$enqueue = false;
$this->criticalcss->log( 'Job queuing is not available: no valid API key found.', 3 );
} elseif ( ! empty( $hash ) && ( is_user_logged_in() || is_feed() || is_404() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) || $this->ao_ccss_ua() || false === apply_filters( 'autoptimize_filter_ccss_enqueue_should_enqueue', true ) ) ) {
$enqueue = false;
$this->criticalcss->log( 'Job queuing is not available for WordPress\'s logged in users, feeds, error pages, ajax calls or calls from criticalcss.com itself.', 3 );
} elseif ( empty( $hash ) && empty( $path ) || ( ( 'is_single' !== $type ) && ( 'is_page' !== $type ) ) ) {
$enqueue = false;
$this->criticalcss->log( 'Forced job queuing failed, no path or not right type', 3 );
}
if ( ! $enqueue ) {
return;
}
// Continue if queue is available
// Attach required arrays/ vars.
$rules = $this->criticalcss->get_option( 'rules' );
$queue_raw = $this->criticalcss->get_option( 'queue_raw' );
$queue = $this->criticalcss->get_option( 'queue' );
$forcepath = $this->criticalcss->get_option( 'forcepath' );
// Get request path and page type, and initialize the queue update flag.
if ( ! empty( $hash ) ) {
$req_orig = $_SERVER['REQUEST_URI'];
$req_type = $this->ao_ccss_get_type();
} elseif ( ! empty( $path ) ) {
$req_orig = $path;
if ( '/' === $path ) {
$req_type = 'is_front_page';
} else {
$req_type = $type;
}
}
$req_path = strtok( $req_orig, '?' );
// now that we really have the path, check if there's no garbage in there (due to some themes serving a non 404 page even if the resource does not exist resulting in all sorts of nonsense rules).
if ( true === apply_filters( 'autoptimize_filter_ccss_enqueue_block_garbage' , true ) && str_ireplace( apply_filters( 'autoptimize_filter_ccss_enqueue_blocklist', array( '.php', 'data:text/javascript;base64', '/.', '/null', '.jpeg', '.jpg', '.png' ) ), '', $req_path ) !== $req_path ) {
$this->criticalcss->log( 'Job not enqueued looks like the path is just garbage; ' . $req_path, 3 );
return;
}
// Check if we have a lang param. we need to keep as WPML can switch languages based on that
// and that includes RTL -> LTR so diff. structure, so rules would be RTL vs LTR
// but this needs changes in the structure of the rule object so off by default for now
// as now this will simply result in conditional rules being overwritten.
if ( apply_filters( 'autoptimize_filter_ccss_coreenqueue_honor_lang', false ) && strpos( $req_orig, 'lang=' ) !== false ) {
$req_params = strtok( '?' );
parse_str( $req_params, $req_params_arr );
if ( array_key_exists( 'lang', $req_params_arr ) && ! empty( $req_params_arr['lang'] ) ) {
$req_path .= '?lang=' . $req_params_arr['lang'];
}
}
$job_qualify = false;
$target_rule = false;
$rule_properties = false;
$queue_update = false;
// Match for paths in rules.
foreach ( $rules['paths'] as $path => $props ) {
// Prepare rule target and log.
$target_rule = 'paths|' . $path;
$this->criticalcss->log( 'Qualifying path <' . $req_path . '> for job submission by rule <' . $target_rule . '>', 3 );
// Path match
// -> exact match needed for AUTO rules
// -> partial match OK for MANUAL rules (which have empty hash and a file with CCSS).
if ( $path === $req_path || ( false == $props['hash'] && false != $props['file'] && preg_match( '|' . $path . '|', $req_path ) ) ) {
// There's a path match in the rule, so job QUALIFIES with a path rule match.
$job_qualify = true;
$rule_properties = $props;
$this->criticalcss->log( 'Path <' . $req_path . '> QUALIFIED for job submission by rule <' . $target_rule . '>', 3 );
// Stop processing other path rules.
break;
}
}
// Match for types in rules if no path rule matches and if we're not enforcing paths.
if ( '' !== $hash && ! $job_qualify && ( ! $forcepath || ! in_array( $req_type, apply_filters( 'autoptimize_filter_ccss_coreenqueue_forcepathfortype', array( 'is_page' ) ) ) || ! apply_filters( 'autoptimize_filter_ccss_coreenqueue_ignorealltypes', false ) ) ) {
foreach ( $rules['types'] as $type => $props ) {
// Prepare rule target and log.
$target_rule = 'types|' . $type;
$this->criticalcss->log( 'Qualifying page type <' . $req_type . '> on path <' . $req_path . '> for job submission by rule <' . $target_rule . '>', 3 );
if ( $req_type == $type ) {
// Type match.
// There's a type match in the rule, so job QUALIFIES with a type rule match.
$job_qualify = true;
$rule_properties = $props;
$this->criticalcss->log( 'Page type <' . $req_type . '> on path <' . $req_path . '> QUALIFIED for job submission by rule <' . $target_rule . '>', 3 );
// Stop processing other type rules.
break;
}
}
}
if ( $job_qualify && ( ( false == $rule_properties['hash'] && false != $rule_properties['file'] ) || strpos( $req_type, 'template_' ) !== false ) ) {
// If job qualifies but rule hash is false and file isn't false (MANUAL rule) or if template, job does not qualify despite what previous evaluations says.
$job_qualify = false;
$this->criticalcss->log( 'Job submission DISQUALIFIED by MANUAL rule <' . $target_rule . '> with hash <' . $rule_properties['hash'] . '> and file <' . $rule_properties['file'] . '>', 3 );
} elseif ( ! $job_qualify && empty( $rule_properties ) ) {
// But if job does not qualify and rule properties are set, job qualifies as there is no matching rule for it yet
// Fill-in the new target rule.
$job_qualify = true;
// Should we switch to path-base AUTO-rules? Conditions:
// 1. forcepath option has to be enabled (off by default)
// 2. request type should be (by default, but filterable) one of is_page (removed for now: woo_is_product or woo_is_product_category).
if ( ( $forcepath && in_array( $req_type, apply_filters( 'autoptimize_filter_ccss_coreenqueue_forcepathfortype', array( 'is_page' ) ) ) ) || apply_filters( 'autoptimize_filter_ccss_coreenqueue_ignorealltypes', false ) || empty( $hash ) ) {
if ( '/' !== $req_path ) {
$target_rule = 'paths|' . $req_path;
} else {
// Exception; we don't want a path-based rule for "/" as that messes things up, hard-switch this to a type-based is_front_page rule.
$target_rule = 'types|' . 'is_front_page';
}
} else {
$target_rule = 'types|' . $req_type;
}
$this->criticalcss->log( 'Job submission QUALIFIED by MISSING rule for page type <' . $req_type . '> on path <' . $req_path . '>, new rule target is <' . $target_rule . '>', 3 );
} else {
// Or just log a job qualified by a matching rule.
$this->criticalcss->log( 'Job submission QUALIFIED by AUTO rule <' . $target_rule . '> with hash <' . $rule_properties['hash'] . '> and file <' . $rule_properties['file'] . '>', 3 );
}
// Submit job.
if ( $job_qualify ) {
if ( ! array_key_exists( $req_path, $queue ) ) {
// This is a NEW job
// Merge job into the queue.
$queue[ $req_path ] = $this->ao_ccss_define_job(
$req_path,
$target_rule,
$req_type,
$hash,
null,
null,
null,
null,
true
);
// Set update flag.
$queue_update = true;
} else {
// This is an existing job
// The job is still NEW, most likely this is extra CSS file for the same page that needs a hash.
if ( 'NEW' == $queue[ $req_path ]['jqstat'] ) {
// Add hash if it's not already in the job.
if ( ! in_array( $hash, $queue[ $req_path ]['hashes'] ) ) {
// Push new hash to its array and update flag.
$queue_update = array_push( $queue[ $req_path ]['hashes'], $hash );
// Log job update.
$this->criticalcss->log( 'Hashes UPDATED on local job id <' . $queue[ $req_path ]['ljid'] . '>, job status NEW, target rule <' . $queue[ $req_path ]['rtarget'] . '>, hash added: ' . $hash, 3 );
// Return from here as the hash array is already updated.
return true;
}
} elseif ( 'NEW' != $queue[ $req_path ]['jqstat'] && 'JOB_QUEUED' != $queue[ $req_path ]['jqstat'] && 'JOB_ONGOING' != $queue[ $req_path ]['jqstat'] ) {
// Allow requeuing jobs that are not NEW, JOB_QUEUED or JOB_ONGOING
// Merge new job keeping some previous job values.
$queue[ $req_path ] = $this->ao_ccss_define_job(
$req_path,
$target_rule,
$req_type,
$hash,
$queue[ $req_path ]['file'],
$queue[ $req_path ]['jid'],
$queue[ $req_path ]['jrstat'],
$queue[ $req_path ]['jvstat'],
false
);
// Set update flag.
$queue_update = true;
}
}
if ( $queue_update ) {
// Persist the job to the queue and return.
$queue_raw = json_encode( $queue );
update_option( 'autoptimize_ccss_queue', $queue_raw, false );
$this->criticalcss->flush_options();
return true;
} else {
// Or just return false if no job was added.
$this->criticalcss->log( 'A job for path <' . $req_path . '> already exist with NEW or PENDING status, skipping job creation', 3 );
return false;
}
}
}
public function ao_ccss_get_type() {
// Get the type of a page
// Attach the conditional tags array.
$types = $this->criticalcss->get_types();
$forcepath = $this->criticalcss->get_option( 'forcepath' );
// By default, a page type is false.
$page_type = false;
// Iterates over the array to match a type.
foreach ( $types as $type ) {
if ( is_404() ) {
$page_type = 'is_404';
break;
} elseif ( is_front_page() ) {
// identify frontpage immediately to avoid it also matching a CPT or template.
$page_type = 'is_front_page';
break;
} elseif ( strpos( $type, 'custom_post_' ) !== false && ( ! $forcepath || ! is_page() ) && is_singular() ) {
// Match custom post types and not page or page not forced to path-based.
if ( get_post_type( get_the_ID() ) === substr( $type, 12 ) ) {
$page_type = $type;
break;
}
} elseif ( strpos( $type, 'template_' ) !== false && ( ! $forcepath || ! is_page() ) ) {
// Match templates if not page or if page is not forced to path-based.
if ( is_page_template( substr( $type, 9 ) ) ) {
$page_type = $type;
break;
}
} else {
// Match all other existing types
// but remove prefix to be able to check if the function exists & returns true.
$_type = str_replace( array( 'woo_', 'bp_', 'bbp_', 'edd_' ), '', $type );
if ( function_exists( $_type ) && call_user_func( $_type ) ) {
// Make sure we only return for one page, not for the "paged pages" (/page/2 ..).
if ( ! is_page() || ! is_paged() ) {
$page_type = $type;
break;
}
}
}
}
// Return the page type.
return $page_type;
}
public function ao_ccss_define_job( $path, $target, $type, $hash, $file, $jid, $jrstat, $jvstat, $create ) {
// Define a job entry to be created or updated
// Define commom job properties.
$path = array();
$path['ljid'] = $this->ao_ccss_job_id();
$path['rtarget'] = $target;
$path['ptype'] = $type;
$path['hashes'] = array( $hash );
$path['hash'] = $hash;
$path['file'] = $file;
$path['jid'] = $jid;
$path['jqstat'] = 'NEW';
$path['jrstat'] = $jrstat;
$path['jvstat'] = $jvstat;
$path['jctime'] = microtime( true );
$path['jftime'] = null;
// Set operation requested.
if ( $create ) {
$operation = 'CREATED';
} else {
$operation = 'UPDATED';
}
// Log job creation.
$this->criticalcss->log( 'Job ' . $operation . ' with local job id <' . $path['ljid'] . '> for target rule <' . $target . '>', 3 );
return $path;
}
public function ao_ccss_job_id( $length = 6 ) {
// Generate random strings for the local job ID
// Based on https://stackoverflow.com/a/4356295 .
$characters = '0123456789abcdefghijklmnopqrstuvwxyz';
$characters_length = strlen( $characters );
$random_string = 'j-';
for ( $i = 0; $i < $length; $i++ ) {
$random_string .= $characters[ rand( 0, $characters_length - 1 ) ];
}
return $random_string;
}
public function ao_ccss_ua() {
// Check for criticalcss.com user agent.
$agent = '';
if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
$agent = $_SERVER['HTTP_USER_AGENT'];
}
// Check for UA and return TRUE when criticalcss.com is the detected UA, false when not.
$rtn = strpos( $agent, AO_CCSS_URL );
if ( 0 === $rtn ) {
$rtn = true;
} else {
$rtn = false;
}
return ( $rtn );
}
}

View File

@@ -0,0 +1,433 @@
<?php
/**
* Critical CSS Options page.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeCriticalCSSSettings {
/**
* Options.
*
* @var bool
*/
private $settings_screen_do_remote_http = true;
/**
* Critical CSS object.
*
* @var object
*/
protected $criticalcss;
public function __construct() {
$this->criticalcss = autoptimize()->criticalcss();
$this->settings_screen_do_remote_http = apply_filters( 'autoptimize_settingsscreen_remotehttp', $this->settings_screen_do_remote_http );
$this->run();
}
protected function enabled()
{
return apply_filters( 'autoptimize_filter_show_criticalcss_tabs', true );
}
public function run()
{
if ( $this->enabled() ) {
add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_critcss_tabs' ), 10, 1 );
add_action( 'admin_enqueue_scripts', array( $this, 'admin_assets' ) );
if ( $this->is_multisite_network_admin() && autoptimizeOptionWrapper::is_ao_active_for_network() ) {
add_action( 'network_admin_menu', array( $this, 'add_critcss_admin_menu' ) );
} else {
add_action( 'admin_menu', array( $this, 'add_critcss_admin_menu' ) );
}
$criticalcss_ajax = new autoptimizeCriticalCSSSettingsAjax();
// if debug logging is off but the file is present, then remove the debug log file.
if ( empty( $this->criticalcss->get_option( 'debug' ) ) && file_exists( AO_CCSS_LOG ) ) {
unlink( AO_CCSS_LOG );
}
}
}
public function add_critcss_tabs( $in )
{
$in = array_merge( $in, array( 'ao_critcss' => apply_filters( 'autoptimize_filter_ccss_tab_text', '⚡ ' . esc_html__( 'Critical CSS', 'autoptimize' ) ) ) );
return $in;
}
public function add_critcss_admin_menu()
{
// Register settings.
register_setting( 'ao_ccss_options_group', 'autoptimize_css_defer_inline' );
register_setting( 'ao_ccss_options_group', 'autoptimize_ccss_rules' );
register_setting( 'ao_ccss_options_group', 'autoptimize_ccss_additional' );
register_setting( 'ao_ccss_options_group', 'autoptimize_ccss_queue' );
register_setting( 'ao_ccss_options_group', 'autoptimize_ccss_viewport' );
register_setting( 'ao_ccss_options_group', 'autoptimize_ccss_finclude' );
register_setting( 'ao_ccss_options_group', 'autoptimize_ccss_rtimelimit' );
register_setting( 'ao_ccss_options_group', 'autoptimize_ccss_noptimize' );
register_setting( 'ao_ccss_options_group', 'autoptimize_ccss_debug' );
register_setting( 'ao_ccss_options_group', 'autoptimize_ccss_key' );
register_setting( 'ao_ccss_options_group', 'autoptimize_ccss_keyst' );
register_setting( 'ao_ccss_options_group', 'autoptimize_ccss_loggedin' );
register_setting( 'ao_ccss_options_group', 'autoptimize_ccss_forcepath' );
register_setting( 'ao_ccss_options_group', 'autoptimize_ccss_deferjquery' );
register_setting( 'ao_ccss_options_group', 'autoptimize_ccss_domain' );
register_setting( 'ao_ccss_options_group', 'autoptimize_ccss_unloadccss' );
// And add submenu-page.
add_submenu_page( '', 'Critical CSS', 'Critical CSS', 'manage_options', 'ao_critcss', array( $this, 'ao_criticalcsssettings_page' ) );
}
public function admin_assets( $hook ) {
// Return if plugin is not hooked.
if ( 'settings_page_ao_critcss' != $hook && 'admin_page_ao_critcss' != $hook ) {
return;
}
// Stylesheets to add.
wp_enqueue_style( 'wp-jquery-ui-dialog' );
wp_enqueue_style( 'ao-tablesorter', plugins_url( 'critcss-inc/css/ao-tablesorter/style.css', __FILE__ ), null, AUTOPTIMIZE_PLUGIN_VERSION );
wp_enqueue_style( 'ao-ccss-admin-css', plugins_url( 'critcss-inc/css/admin_styles.css', __FILE__ ), null, AUTOPTIMIZE_PLUGIN_VERSION );
// Scripts to add.
wp_enqueue_script( 'jquery-ui-dialog', '', array( 'jquery' ), null, true );
wp_enqueue_script( 'md5', plugins_url( 'critcss-inc/js/md5.min.js', __FILE__ ), null, AUTOPTIMIZE_PLUGIN_VERSION, true );
wp_enqueue_script( 'tablesorter', plugins_url( 'critcss-inc/js/jquery.tablesorter.min.js', __FILE__ ), array( 'jquery' ), AUTOPTIMIZE_PLUGIN_VERSION, true );
wp_enqueue_script( 'ao-ccss-admin-license', plugins_url( 'critcss-inc/js/admin_settings.js', __FILE__ ), array( 'jquery' ), AUTOPTIMIZE_PLUGIN_VERSION, true );
}
public function ao_criticalcsssettings_page()
{
// these are not OO yet, simply require for now.
require_once( 'critcss-inc/admin_settings_rules.php' );
require_once( 'critcss-inc/admin_settings_queue.php' );
require_once( 'critcss-inc/admin_settings_key.php' );
require_once( 'critcss-inc/admin_settings_adv.php' );
require_once( 'critcss-inc/admin_settings_explain.php' );
$ao_ccss_key = $this->criticalcss->get_option( 'key' );
$ao_ccss_keyst = $this->criticalcss->get_option( 'keyst' );
$ao_css_defer = $this->criticalcss->get_option( 'css_defer' );
$ao_ccss_deferjquery = $this->criticalcss->get_option( 'deferjquery' );
$ao_ccss_queue = $this->criticalcss->get_option( 'queue' );
$ao_ccss_rules = $this->criticalcss->get_option( 'rules' );
$ao_ccss_servicestatus = $this->criticalcss->get_option( 'servicestatus' );
$ao_ccss_finclude = $this->criticalcss->get_option( 'finclude' );
$ao_ccss_rtimelimit = $this->criticalcss->get_option( 'rtimelimit' );
$ao_ccss_debug = $this->criticalcss->get_option( 'debug' );
$ao_ccss_noptimize = $this->criticalcss->get_option( 'noptimize' );
$ao_css_defer_inline = $this->criticalcss->get_option( 'css_defer_inline' );
$ao_ccss_loggedin = $this->criticalcss->get_option( 'loggedin' );
$ao_ccss_forcepath = $this->criticalcss->get_option( 'forcepath' );
$ao_ccss_domain = $this->criticalcss->get_option( 'domain' );
?>
<script>document.title = "Autoptimize: <?php esc_html_e( 'Critical CSS', 'autoptimize' ); ?> " + document.title;</script>
<div class="wrap">
<div id="autoptimize_main">
<div id="ao_title_and_button">
<h1><?php apply_filters( 'autoptimize_filter_settings_is_pro', false ) ? esc_html_e( 'Autoptimize Pro Settings', 'autoptimize' ) : esc_html_e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
</div>
<?php
// Print AO settings tabs.
echo autoptimizeConfig::ao_admin_tabs();
if ( autoptimizeUtils::is_local_server() && isset( $ao_ccss_key ) ) { ?>
<div class="notice-warning notice"><p>
<?php
echo esc_html__( 'The Critical CSS service does not work on locally hosted sites or when the server is on a private network.', 'autoptimize' );
?>
</p></div>
<?php }
$mkdirresult = $this->criticalcss->create_ao_ccss_dir();
// Warn if we could not create those files.
if ( ( true !== $mkdirresult ) ) {
?>
<div class="notice-error notice"><p>
<?php
esc_html_e( 'Could not create the required directory. Make sure the webserver can write to the wp-content/uploads directory.', 'autoptimize' );
?>
</p></div>
<?php
}
// Check if CSS optimization is on.
if ( 'on' !== autoptimizeOptionWrapper::get_option( 'autoptimize_css' ) || 'on' !== autoptimizeOptionWrapper::get_option( 'autoptimize_css_defer' ) ) {
?>
<div class="notice-info notice"><p>
<?php
esc_html_e( 'To be able to use Critical CSS you will have to enable CSS optimization and make sure "eliminate render-blocking CSS" is active on the main Autoptimize settings page.', 'autoptimize' );
?>
</p></div>
<?php
}
// Check for "inline & defer CSS" being active in Autoptimize.
if ( ! empty( $ao_ccss_key ) && ! $ao_css_defer && empty( $ao_ccss_keyst ) ) {
// no keystate so likely in activation-process of CCSS, let's enable "inline & defer CSS" immediately to make things easier!
autoptimizeOptionWrapper::update_option( 'autoptimize_css_defer', 'on' );
?>
<div class="notice-info notice"><p>
<?php
esc_html_e( 'The "Eliminate render-blocking CSS" option was activated to allow critical CSS to be used.', 'autoptimize' );
?>
</p></div>
<?php
}
// check if defer jQuery is active and warn if so.
if ( 1 == $ao_ccss_deferjquery && PAnD::is_admin_notice_active( 'i-know-about-defer-inline-forever' ) ) {
?>
<div data-dismissible="i-know-about-defer-inline-forever" class="notice-warning notice is-dismissible"><p>
<?php
esc_html_e( 'You have "defer jQuery and other non-aggregated JS-files" active (under Advanced Settings), but that functionality is deprecated and will be removed in the next major version of Autoptimize. Consider using the new "Do not aggregate but defer" and "Also defer inline JS" options on the main settings page instead.', 'autoptimize' );
?>
</p></div>
<?php
}
// warn if it looks as though the queue processing job looks isn't running
// but store result in transient as to not to have to go through 2 arrays each and every time.
$_warn_cron = get_transient( 'ao_ccss_cronwarning' );
if ( ! empty( $ao_ccss_key ) && false === $_warn_cron ) {
$_jobs_all_new = true;
$_oldest_job_timestamp = microtime( true ); // now.
$_jobs_too_old = true;
// go over queue array.
if ( empty( $ao_ccss_queue ) ) {
// no jobs, then no warning.
$_jobs_all_new = false;
} else {
foreach ( $ao_ccss_queue as $job ) {
if ( $job['jctime'] < $_oldest_job_timestamp ) {
// we need to catch the oldest job's timestamp.
$_oldest_job_timestamp = $job['jctime'];
}
if ( 'NEW' !== $job['jqstat'] && 'firstrun' !== $job['ljid'] ) {
// we have a non-"NEW" job which is not our pending firstrun job either, break the loop.
$_jobs_all_new = false;
break;
}
}
}
// is the oldest job too old (4h)?
if ( $_oldest_job_timestamp > microtime( true ) - 60 * 60 * 4 ) {
$_jobs_too_old = false;
}
if ( $_jobs_all_new && ! $this->ao_ccss_has_autorules() && $_jobs_too_old ) {
$_warn_cron = 'on';
$_transient_multiplier = 1; // store for 1 hour.
} else {
$_warn_cron = 'off';
$_transient_multiplier = 4; // store for 4 hours.
}
// and set transient.
set_transient( 'ao_ccss_cronwarning', $_warn_cron, $_transient_multiplier * HOUR_IN_SECONDS );
}
if ( ! empty( $ao_ccss_key ) && 'on' == $_warn_cron && PAnD::is_admin_notice_active( 'i-know-about-cron-30' ) ) {
?>
<div data-dismissible="i-know-about-cron-30" class="notice-warning notice is-dismissible"><p>
<?php
_e( 'It looks like there might be a problem with WordPress cron (task scheduling). Have a look at <a href="https://blog.futtta.be/2023/03/17/how-to-fix-autoptimize-critical-css-cron-issue/" target="_blank">the FAQ</a> or the info in the Job Queue instructions if all jobs remain in "N" status and no rules are created.', 'autoptimize' );
?>
</p></div>
<?php
} elseif ( ! empty( $ao_ccss_key ) && '2' == $ao_ccss_keyst && 'on' != $_warn_cron && ! $this->ao_ccss_has_autorules() ) {
?>
<div class="notice-success notice"><p>
<?php
esc_html_e( 'Great, Autoptimize will now automatically start creating new critical CSS rules, you should see those appearing below in the next couple of hours.', 'autoptimize' );
echo ' ';
_e( 'In the meantime you might want to <strong>edit default rule CSS now</strong>, to avoid all CSS being inlined when no (applicable) rules are found.', 'autoptimize' );
?>
</p></div>
<?php
}
// warn if service is down.
if ( ! empty( $ao_ccss_key ) && ! empty( $ao_ccss_servicestatus ) && is_array( $ao_ccss_servicestatus ) && 'down' === $ao_ccss_servicestatus['critcss']['status'] ) {
?>
<div class="notice-warning notice"><p>
<?php
esc_html_e( 'The critical CSS service has been reported to be down. Although no new rules will be created for now, this does not prevent existing rules from being applied.', 'autoptimize' );
?>
</p></div>
<?php
}
// warn if too many rules (based on length of ao_ccss_rules option) as that might cause issues at e.g. wpengine
// see https://wpengine.com/support/database-optimization-best-practices/#Autoloaded_Data .
$_raw_rules_length = strlen( get_option( 'autoptimize_ccss_rules', '' ) );
if ( $_raw_rules_length > apply_filters( 'autoptimize_ccss_rules_length_warning', 500000 ) ) {
?>
<div class="notice-warning notice"><p>
<?php
esc_html_e( 'It looks like the amount of Critical CSS rules is very high, it is recommended to reconfigure Autoptimize (e.g. by manually creating broader rules) to ensure less rules are created.', 'autoptimize' );
?>
</p></div>
<?php
}
// Settings Form.
?>
<form id="settings" method="post" action="options.php">
<?php
settings_fields( 'ao_ccss_options_group' );
// Get API key status.
$key = $this->criticalcss->key_status( true );
if ( $this->is_multisite_network_admin() ) {
?>
<ul id="key-panel">
<li class="itemDetail">
<?php
// translators: the placesholder is for a line of code in wp-config.php.
echo sprintf( esc_html__( '<p>Critical CSS settings cannot be set at network level as critical CSS is specific to each sub-site.</p><p>You can however provide the critical CSS API key for use by all sites by adding this your wp-config.php as %s</p>', 'autoptimize' ), '<br/><code>define(\'AUTOPTIMIZE_CRITICALCSS_API_KEY\', \'eyJhbGmorestringsherexHa7MkOQFtDFkZgLmBLe-LpcHx4\');</code>' );
?>
</li>
</ul>
<?php
} else {
if ( 'valid' == $key['status'] || ( defined( 'AO_PRO_VERSION' ) && has_filter( 'autoptimize_filter_ccss_key' ) ) ) {
// If key status is valid, render other panels.
// Render rules section.
ao_ccss_render_rules();
// Render queue section.
ao_ccss_render_queue();
// Render advanced panel.
ao_ccss_render_adv();
} else {
if ( apply_filters( 'autoptimize_filter_ccss_rules_without_api', true ) ) {
// Render rules section for manual rules.
ao_ccss_render_rules();
} else {
echo "<input class='hidden' name='autoptimize_ccss_rules' value='" . json_encode( $ao_ccss_rules, JSON_FORCE_OBJECT ) . "'>";
}
// But if key is other than valid, add hidden fields to persist settings when submitting form
// Show explanation of why and how to get a API key.
ao_ccss_render_explain();
// Get viewport size.
$viewport = $this->criticalcss->viewport();
// Add hidden fields.
echo "<input class='hidden' name='autoptimize_ccss_queue' value='" . json_encode( $ao_ccss_queue, JSON_FORCE_OBJECT ) . "'>";
echo '<input class="hidden" name="autoptimize_ccss_viewport[w]" value="' . esc_attr( $viewport['w'] ) . '">';
echo '<input class="hidden" name="autoptimize_ccss_viewport[h]" value="' . esc_attr( $viewport['h'] ) . '">';
echo '<input class="hidden" name="autoptimize_ccss_finclude" value="' . esc_attr( $ao_ccss_finclude ) . '">';
echo '<input class="hidden" name="autoptimize_ccss_rtimelimit" value="' . esc_attr( $ao_ccss_rtimelimit ) . '">';
echo '<input class="hidden" name="autoptimize_ccss_debug" value="' . esc_attr( $ao_ccss_debug ) . '">';
echo '<input class="hidden" name="autoptimize_ccss_noptimize" value="' . esc_attr( $ao_ccss_noptimize ) . '">';
echo '<input class="hidden" name="autoptimize_css_defer_inline" value="' . esc_attr( $ao_css_defer_inline ) . '">';
echo '<input class="hidden" name="autoptimize_ccss_loggedin" value="' . esc_attr( $ao_ccss_loggedin ) . '">';
echo '<input class="hidden" name="autoptimize_ccss_forcepath" value="' . esc_attr( $ao_ccss_forcepath ) . '">';
echo '<input class="hidden" name="autoptimize_ccss_domain" id="autoptimize_ccss_domain" value="' . esc_attr( $ao_ccss_domain ) . '">';
}
// Render key panel unconditionally.
ao_ccss_render_key( $ao_ccss_key, $key['status'], $key['stmsg'], $key['msg'], $key['color'] );
?>
<p class="submit left">
<input type="submit" class="button-primary" value="<?php esc_html_e( 'Save Changes', 'autoptimize' ); ?>" />
</p>
<?php
}
?>
</form>
<script>
jQuery("form#settings").submit(function(){
var input = jQuery("#autoptimize_ccss_domain");
input.val(rot(input.val(), 13));
});
// rot JS from http://stackoverflow.com/a/617685/987044 .
function rot(domainstring, itype) {
return domainstring.toString().replace(/[a-zA-Z]/g, function (letter) {
return String.fromCharCode((letter <= 'Z' ? 90 : 122) >= (letter = letter.charCodeAt(0) + itype) ? letter : letter - 26);
});
}
</script>
<form id="importSettingsForm"<?php if ( $this->is_multisite_network_admin() ) { echo ' class="hidden"'; } // @codingStandardsIgnoreLine ?>>
<span id="exportSettings" class="button-secondary"><?php esc_html_e( 'Export Settings', 'autoptimize' ); ?></span>
<input class="button-secondary" id="importSettings" type="button" value="<?php esc_html_e( 'Import Settings', 'autoptimize' ); ?>" onclick="upload();return false;" />
<input class="button-secondary" id="settingsfile" name="settingsfile" type="file" />
</form>
<div id="importdialog"></div>
</div><!-- /#autoptimize_main -->
</div><!-- /#wrap -->
<?php
if ( ! $this->is_multisite_network_admin() ) {
// Include debug panel if debug mode is enable.
if ( $ao_ccss_debug ) {
?>
<div id="debug">
<?php
// Include debug panel.
if ( true === apply_filters( 'autoptimize_filter_ccss_debug_browser_output', true ) ) {
include( 'critcss-inc/admin_settings_debug.php' );
}
?>
</div><!-- /#debug -->
<?php
}
echo '<script>';
include( 'critcss-inc/admin_settings_rules.js.php' );
include( 'critcss-inc/admin_settings_queue.js.php' );
include( 'critcss-inc/admin_settings_impexp.js.php' );
echo '</script>';
}
}
public function ao_ccss_has_autorules() {
static $_has_auto_rules = null;
if ( null === $_has_auto_rules ) {
$ao_ccss_rules = $this->criticalcss->get_option( 'rules' );
$_has_auto_rules = false;
if ( ! empty( $ao_ccss_rules ) ) {
foreach ( array( 'types', 'paths' ) as $_typat ) {
foreach ( $ao_ccss_rules[ $_typat ] as $rule ) {
if ( ! empty( $rule['hash'] ) ) {
// we have at least one AUTO job, so all is fine.
$_has_auto_rules = true;
break;
}
}
if ( $_has_auto_rules ) {
break;
}
}
}
}
return $_has_auto_rules;
}
public static function is_multisite_network_admin() {
static $_multisite_network_admin = null;
if ( null === $_multisite_network_admin ) {
if ( is_multisite() && is_network_admin() ) {
$_multisite_network_admin = true;
} else {
$_multisite_network_admin = false;
}
}
return $_multisite_network_admin;
}
}

View File

@@ -0,0 +1,597 @@
<?php
/**
* Critical CSS settings AJAX logic.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeCriticalCSSSettingsAjax {
/**
* Critical CSS object.
*
* @var object
*/
protected $criticalcss;
public function __construct() {
$this->criticalcss = autoptimize()->criticalcss();
$this->run();
}
public function run() {
// add filters.
add_action( 'wp_ajax_fetch_critcss', array( $this, 'critcss_fetch_callback' ) );
add_action( 'wp_ajax_save_critcss', array( $this, 'critcss_save_callback' ) );
add_action( 'wp_ajax_rm_critcss', array( $this, 'critcss_rm_callback' ) );
add_action( 'wp_ajax_rm_critcss_all', array( $this, 'critcss_rm_all_callback' ) );
add_action( 'wp_ajax_ao_ccss_export', array( $this, 'ao_ccss_export_callback' ) );
add_action( 'wp_ajax_ao_ccss_import', array( $this, 'ao_ccss_import_callback' ) );
add_action( 'wp_ajax_ao_ccss_queuerunner', array( $this, 'ao_ccss_queuerunner_callback' ) );
add_action( 'wp_ajax_ao_ccss_saverules', array( $this, 'ao_ccss_saverules_callback' ) );
}
public function critcss_fetch_callback() {
// Ajax handler to obtain a critical CSS file from the filesystem.
// Check referer.
check_ajax_referer( 'fetch_critcss_nonce', 'critcss_fetch_nonce' );
// Initialize error flag.
$error = true;
// Allow no content for MANUAL rules (as they may not exist just yet).
if ( current_user_can( 'manage_options' ) && empty( $_POST['critcssfile'] ) ) {
$content = '';
$error = false;
} elseif ( current_user_can( 'manage_options' ) && $this->critcss_check_filename( $_POST['critcssfile'] ) ) {
// Or check user permissios and filename.
// Set file path and obtain its content.
$critcssfile = AO_CCSS_DIR . strip_tags( $_POST['critcssfile'] );
if ( file_exists( $critcssfile ) ) {
$content = file_get_contents( $critcssfile );
$error = false;
}
}
// Prepare response.
if ( $error ) {
$response['code'] = '500';
$response['string'] = 'Error reading file ' . $critcssfile . '.';
} else {
$response['code'] = '200';
$response['string'] = $content;
}
// Dispatch respose.
echo json_encode( $response );
// Close ajax request.
wp_die();
}
public function critcss_save_callback() {
$error = false;
$status = false;
$response = array();
// Ajax handler to write a critical CSS to the filesystem
// Check referer.
check_ajax_referer( 'save_critcss_nonce', 'critcss_save_nonce' );
// Allow empty contents for MANUAL rules (as they are fetched later).
if ( current_user_can( 'manage_options' ) && empty( $_POST['critcssfile'] ) ) {
$critcssfile = false;
$status = true;
} elseif ( current_user_can( 'manage_options' ) && $this->critcss_check_filename( $_POST['critcssfile'] ) ) {
// Or check user permissios and filename
// Set critical CSS content.
$critcsscontents = stripslashes( $_POST['critcsscontents'] );
// If there is content and it's valid, write the file.
if ( $critcsscontents && $this->criticalcss->check_contents( $critcsscontents ) ) {
// Set file path and status.
$critcssfile = AO_CCSS_DIR . strip_tags( $_POST['critcssfile'] );
$status = file_put_contents( $critcssfile, $critcsscontents, LOCK_EX );
// Or set as error.
} else {
$error = true;
$critcssfile = 'CCSS content not acceptable.';
}
// Or just set an error.
} else {
$error = true;
$critcssfile = 'Not allowed or problem with CCSS filename.';
}
// Prepare response.
if ( ! $status || $error ) {
$response['code'] = '500';
$response['string'] = 'Error saving file ' . $critcssfile . '.';
} else {
$response['code'] = '200';
if ( $critcssfile ) {
$response['string'] = 'File ' . $critcssfile . ' saved.';
if ( true === apply_filters( 'autoptimize_filter_ccss_ajax_do_actions', true ) ) {
$rule_identifiers = $this->fetch_rule_from_ccssfile( $critcssfile );
if ( ! empty( $rule_identifiers ) && is_array( $rule_identifiers ) ) {
do_action( 'autoptimize_action_ccss_ajax_css_changed', $rule_identifiers[0], $critcssfile, $rule_identifiers[1] );
}
}
} else {
$response['string'] = 'Empty content does not need to be saved.';
}
}
// Dispatch respose.
echo json_encode( $response );
// Close ajax request.
wp_die();
}
public function critcss_rm_callback() {
// Ajax handler to delete a critical CSS from the filesystem
// Check referer.
check_ajax_referer( 'rm_critcss_nonce', 'critcss_rm_nonce' );
// Initialize error and status flags.
$error = true;
$status = false;
// Allow no file for MANUAL rules (as they may not exist just yet).
if ( current_user_can( 'manage_options' ) && empty( $_POST['critcssfile'] ) ) {
$error = false;
} elseif ( current_user_can( 'manage_options' ) && $this->critcss_check_filename( $_POST['critcssfile'] ) ) {
// Or check user permissios and filename
// Set file path and delete it.
$critcssfile = AO_CCSS_DIR . strip_tags( $_POST['critcssfile'] );
if ( file_exists( $critcssfile ) ) {
$status = unlink( $critcssfile );
$error = false;
}
}
// Prepare response.
if ( $error ) {
$response['code'] = '500';
$response['string'] = 'Error removing file ' . $critcssfile . '.';
} else {
$response['code'] = '200';
if ( $status ) {
$response['string'] = 'File ' . $critcssfile . ' removed.';
if ( true === apply_filters( 'autoptimize_filter_ccss_ajax_do_actions', true ) ) {
$rule_identifiers = $this->fetch_rule_from_ccssfile( $critcssfile );
if ( ! empty( $rule_identifiers ) && is_array( $rule_identifiers ) ) {
do_action( 'autoptimize_action_ccss_ajax_css_removed', $rule_identifiers[0], $critcssfile, $rule_identifiers[1] );
}
}
} else {
$response['string'] = 'No file to be removed.';
}
}
// Dispatch respose.
echo json_encode( $response );
// Close ajax request.
wp_die();
}
public function critcss_rm_all_callback() {
// Ajax handler to delete a critical CSS from the filesystem
// Check referer.
check_ajax_referer( 'rm_critcss_all_nonce', 'critcss_rm_all_nonce' );
// Initialize error and status flags.
$error = true;
$status = false;
// Remove all ccss files on filesystem.
if ( current_user_can( 'manage_options' ) ) {
if ( file_exists( AO_CCSS_DIR ) && is_dir( AO_CCSS_DIR ) ) {
array_map( 'unlink', glob( AO_CCSS_DIR . 'ccss_*.css', GLOB_BRACE ) );
$error = false;
$status = true;
if ( true === apply_filters( 'autoptimize_filter_ccss_ajax_do_actions', true ) ) {
do_action( 'autoptimize_action_ccss_ajax_all_css_removed' );
}
}
}
// Prepare response.
if ( $error ) {
$response['code'] = '500';
$response['string'] = 'Error removing all critical CSS files.';
} else {
$response['code'] = '200';
if ( $status ) {
$response['string'] = 'Critical CSS Files removed.';
} else {
$response['string'] = 'No file removed.';
}
}
// Dispatch respose.
echo json_encode( $response );
// Close ajax request.
wp_die();
}
public function ao_ccss_export_callback() {
// Ajax handler export settings
// Check referer.
check_ajax_referer( 'ao_ccss_export_nonce', 'ao_ccss_export_nonce' );
if ( ! class_exists( 'ZipArchive' ) ) {
$response['code'] = '500';
$response['msg'] = 'PHP ZipArchive not present, cannot create zipfile';
echo json_encode( $response );
wp_die();
}
// Init array, get options and prepare the raw object.
$settings = array();
// CCSS settings.
$settings['ccss']['rules'] = get_option( 'autoptimize_ccss_rules' );
$settings['ccss']['additional'] = get_option( 'autoptimize_ccss_additional' );
$settings['ccss']['viewport'] = get_option( 'autoptimize_ccss_viewport' );
$settings['ccss']['finclude'] = get_option( 'autoptimize_ccss_finclude' );
$settings['ccss']['rtimelimit'] = get_option( 'autoptimize_ccss_rtimelimit' );
$settings['ccss']['noptimize'] = get_option( 'autoptimize_ccss_noptimize' );
$settings['ccss']['debug'] = get_option( 'autoptimize_ccss_debug' );
$settings['ccss']['key'] = get_option( 'autoptimize_ccss_key' );
$settings['ccss']['deferjquery'] = get_option( 'autoptimize_ccss_deferjquery' );
$settings['ccss']['domain'] = get_option( 'autoptimize_ccss_domain' );
$settings['ccss']['forcepath'] = get_option( 'autoptimize_ccss_forcepath' );
$settings['ccss']['loggedin'] = get_option( 'autoptimize_ccss_loggedin' );
$settings['ccss']['rlimit'] = get_option( 'autoptimize_ccss_rlimit' );
$settings['ccss']['unloadccss'] = get_option( 'autoptimize_ccss_unloadccss' );
// JS settings.
$settings['js']['root'] = get_option( 'autoptimize_js' );
$settings['js']['aggregate'] = get_option( 'autoptimize_js_aggregate' );
$settings['js']['defer_not_aggregate'] = get_option( 'autoptimize_js_defer_not_aggregate' );
$settings['js']['defer_inline'] = get_option( 'autoptimize_js_defer_inline' );
$settings['js']['exclude'] = get_option( 'autoptimize_js_exclude' );
$settings['js']['forcehead'] = get_option( 'autoptimize_js_forcehead' );
$settings['js']['justhead'] = get_option( 'autoptimize_js_justhead' );
$settings['js']['trycatch'] = get_option( 'autoptimize_js_trycatch' );
$settings['js']['include_inline'] = get_option( 'autoptimize_js_include_inline' );
// CSS settings.
$settings['css']['root'] = get_option( 'autoptimize_css' );
$settings['css']['aggregate'] = get_option( 'autoptimize_css_aggregate' );
$settings['css']['datauris'] = get_option( 'autoptimize_css_datauris' );
$settings['css']['justhead'] = get_option( 'autoptimize_css_justhead' );
$settings['css']['defer'] = get_option( 'autoptimize_css_defer' );
$settings['css']['defer_inline'] = get_option( 'autoptimize_css_defer_inline' );
$settings['css']['inline'] = get_option( 'autoptimize_css_inline' );
$settings['css']['exclude'] = get_option( 'autoptimize_css_exclude' );
$settings['css']['include_inline'] = get_option( 'autoptimize_css_include_inline' );
// Others.
$settings['other']['autoptimize_imgopt_settings'] = get_option( 'autoptimize_imgopt_settings' );
$settings['other']['autoptimize_extra_settings'] = get_option( 'autoptimize_extra_settings' );
$settings['other']['autoptimize_cache_fallback'] = get_option( 'autoptimize_cache_fallback' );
$settings['other']['autoptimize_cache_nogzip'] = get_option( 'autoptimize_cache_nogzip' );
$settings['other']['autoptimize_cdn_url'] = get_option( 'autoptimize_cdn_url' );
$settings['other']['autoptimize_enable_meta_ao_settings'] = get_option( 'autoptimize_enable_meta_ao_settings' );
$settings['other']['autoptimize_enable_site_config'] = get_option( 'autoptimize_enable_site_config' );
$settings['other']['autoptimize_html'] = get_option( 'autoptimize_html' );
$settings['other']['autoptimize_html_keepcomments'] = get_option( 'autoptimize_html_keepcomments' );
$settings['other']['autoptimize_minify_excluded'] = get_option( 'autoptimize_minify_excluded' );
$settings['other']['autoptimize_optimize_checkout'] = get_option( 'autoptimize_optimize_checkout' );
$settings['other']['autoptimize_optimize_logged'] = get_option( 'autoptimize_optimize_logged' );
if ( defined( 'AO_PRO_VERSION' ) ) {
$settings['pro']['boosters'] = get_option( 'autoptimize_pro_boosters' );
$settings['pro']['pagecache'] = get_option( 'autoptimize_pro_pagecache' );
}
// Initialize error flag.
$error = true;
// Check user permissions.
if ( current_user_can( 'manage_options' ) ) {
// Prepare settings file path and content.
$exportfile = AO_CCSS_DIR . 'settings.json';
$contents = json_encode( $settings );
$status = file_put_contents( $exportfile, $contents, LOCK_EX );
$error = false;
}
// Prepare archive.
$zipfile = AO_CCSS_DIR . str_replace( array( '.', '/' ), '_', parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST ) ) . '_' . date( 'Ymd-H\hi' ) . '_ao_ccss_settings.zip'; // @codingStandardsIgnoreLine
$file = pathinfo( $zipfile, PATHINFO_BASENAME );
$zip = new ZipArchive();
$ret = $zip->open( $zipfile, ZipArchive::CREATE );
if ( true !== $ret ) {
$error = true;
} else {
$zip->addFile( AO_CCSS_DIR . 'settings.json', 'settings.json' );
if ( file_exists( AO_CCSS_DIR . 'queue.json' ) ) {
$zip->addFile( AO_CCSS_DIR . 'queue.json', 'queue.json' );
}
$options = array(
'add_path' => './',
'remove_all_path' => true,
);
$zip->addGlob( AO_CCSS_DIR . '*.css', 0, $options );
$zip->close();
}
// settings.json has been added to zipfile, so can be removed now.
if ( file_exists( $exportfile ) ) {
unlink( $exportfile );
}
// Prepare response.
if ( ! $status || $error ) {
$response['code'] = '500';
$response['msg'] = 'Error saving file ' . $file . ', code: ' . $ret;
} else {
$response['code'] = '200';
$response['msg'] = 'File ' . $file . ' saved.';
$response['file'] = $file;
}
// Dispatch respose.
echo json_encode( $response );
// Close ajax request.
wp_die();
}
public function ao_ccss_import_callback() {
// Ajax handler import settings
// Check referer.
check_ajax_referer( 'ao_ccss_import_nonce', 'ao_ccss_import_nonce' );
// Initialize error flag.
$error = false;
// Process an uploaded file with no errors.
if ( current_user_can( 'manage_options' ) && ! $_FILES['file']['error'] && $_FILES['file']['size'] < 500001 && strpos( $_FILES['file']['name'], '.zip' ) === strlen( $_FILES['file']['name'] ) - 4 ) {
// create tmp dir with hard guess name in AO_CCSS_DIR.
$_secret_dir = wp_hash( uniqid( md5( AUTOPTIMIZE_CACHE_URL ), true ) );
$_import_tmp_dir = trailingslashit( AO_CCSS_DIR . $_secret_dir );
mkdir( $_import_tmp_dir, 0774, true );
// Save file to that tmp directory but give it our own name to prevent directory traversal risks when using original name.
$zipfile = $_import_tmp_dir . uniqid( 'import_settings-', true ) . '.zip';
move_uploaded_file( $_FILES['file']['tmp_name'], $zipfile );
// Extract archive in the tmp directory.
$zip = new ZipArchive;
if ( $zip->open( $zipfile ) === true ) {
// loop through all files in the zipfile.
for ( $i = 0; $i < $zip->numFiles; $i++ ) { // @codingStandardsIgnoreLine
// but only extract known good files.
if ( preg_match( '/^settings\.json$|^\.\/ccss_[a-z0-9]{32}\.css$/', $zip->getNameIndex( $i ) ) > 0 ) {
$zip->extractTo( AO_CCSS_DIR, $zip->getNameIndex( $i ) );
}
}
$zip->close();
} else {
$error = 'could not extract';
}
// and remove temp. dir with all contents (the import-zipfile).
$this->rrmdir( $_import_tmp_dir );
if ( ! $error ) {
// Archive extraction ok, continue importing settings from AO_CCSS_DIR.
// Settings file.
$importfile = AO_CCSS_DIR . 'settings.json';
if ( file_exists( $importfile ) ) {
// Get settings and turn them into an object.
$settings = json_decode( file_get_contents( $importfile ), true );
// Update options from settings, but only for known options.
// CCSS.
foreach ( array( 'rules', 'additional', 'viewport', 'finclude', 'rtimelimit', 'noptimize', 'debug', 'key', 'deferjquery', 'domain', 'forcepath', 'loggedin', 'rlimit', 'unloadccss' ) as $ccss_setting ) {
if ( false === array_key_exists( 'ccss', $settings ) || false === array_key_exists( $ccss_setting, $settings['ccss'] ) ) {
continue;
} else {
update_option( 'autoptimize_ccss_' . $ccss_setting, autoptimizeUtils::strip_tags_array( $settings['ccss'][ $ccss_setting ] ) );
}
}
// JS.
foreach ( array( 'root', 'aggregate', 'defer_not_aggregate', 'defer_inline', 'exclude', 'forcehead', 'trycatch', 'include_inline' ) as $js_setting ) {
if ( false === array_key_exists( 'js', $settings ) || false === array_key_exists( $js_setting, $settings['js'] ) ) {
continue;
} else if ( 'root' === $js_setting ) {
update_option( 'autoptimize_js', $settings['js']['root'] );
} else {
update_option( 'autoptimize_js_' . $js_setting, $settings['js'][ $js_setting ] );
}
}
// CSS.
foreach ( array( 'root', 'aggregate', 'datauris', 'justhead', 'defer', 'defer_inline', 'inline', 'exclude', 'include_inline' ) as $css_setting ) {
if ( false === array_key_exists( 'css', $settings ) || false === array_key_exists( $css_setting, $settings['css'] ) ) {
continue;
} else if ( 'root' === $css_setting ) {
update_option( 'autoptimize_css', $settings['css']['root'] );
} else {
update_option( 'autoptimize_css_' . $css_setting, $settings['css'][ $css_setting ] );
}
}
// Other.
foreach ( array( 'autoptimize_imgopt_settings', 'autoptimize_extra_settings', 'autoptimize_cache_fallback', 'autoptimize_cache_nogzip', 'autoptimize_cdn_url', 'autoptimize_enable_meta_ao_settings', 'autoptimize_enable_site_config', 'autoptimize_html', 'autoptimize_html_keepcomments', 'autoptimize_minify_excluded', 'autoptimize_optimize_checkout', 'autoptimize_optimize_logged' ) as $other_setting ) {
if ( false === array_key_exists( 'other', $settings ) || false === array_key_exists( $other_setting, $settings['other'] ) ) {
continue;
} else {
update_option( $other_setting, $settings['other'][ $other_setting ] );
}
}
// AO Pro.
if ( defined( 'AO_PRO_VERSION' ) && array_key_exists( 'pro', $settings ) ) {
update_option( 'autoptimize_pro_boosters', $settings['pro']['boosters'] );
update_option( 'autoptimize_pro_pagecache', $settings['pro']['pagecache'] );
}
// settings.json has been imported, so can be removed now.
if ( file_exists( $importfile ) ) {
unlink( $importfile );
}
} else {
// Settings file doesn't exist, update error flag.
$error = 'settings file does not exist';
}
}
} else {
$error = 'file could not be saved';
}
// Prepare response.
if ( $error ) {
$response['code'] = '500';
$response['msg'] = 'Error importing settings: ' . $error;
} else {
$response['code'] = '200';
$response['msg'] = 'Settings imported successfully';
}
// Dispatch respose.
echo json_encode( $response );
// Close ajax request.
wp_die();
}
public function ao_ccss_queuerunner_callback() {
check_ajax_referer( 'ao_ccss_queuerunner_nonce', 'ao_ccss_queuerunner_nonce' );
// Process an uploaded file with no errors.
if ( current_user_can( 'manage_options' ) ) {
if ( ! file_exists( AO_CCSS_LOCK ) ) {
$ccss_cron = new autoptimizeCriticalCSSCron();
$ccss_cron->ao_ccss_queue_control();
$response['code'] = '200';
$response['msg'] = 'Queue processing done';
} else {
$response['code'] = '302';
$response['msg'] = 'Lock file found';
}
} else {
$response['code'] = '500';
$response['msg'] = 'Not allowed';
}
// Dispatch respose.
echo json_encode( $response );
// Close ajax request.
wp_die();
}
public function ao_ccss_saverules_callback() {
check_ajax_referer( 'ao_ccss_saverules_nonce', 'ao_ccss_saverules_nonce' );
// save rules over AJAX, too many users forget to press "save changes".
if ( current_user_can( 'manage_options' ) ) {
if ( array_key_exists( 'critcssrules', $_POST ) ) {
$rules = stripslashes( $_POST['critcssrules'] ); // ugly, but seems correct as per https://developer.wordpress.org/reference/functions/stripslashes_deep/#comment-1045 .
if ( ! empty( $rules ) ) {
$_unsafe_rules_array = json_decode( wp_strip_all_tags( $rules ), true );
if ( ! empty( $_unsafe_rules_array ) && is_array( $_unsafe_rules_array ) ) {
$_safe_rules_array = array();
if ( array_key_exists( 'paths', $_unsafe_rules_array ) ) {
$_safe_rules_array['paths'] = $_unsafe_rules_array['paths'];
}
if ( array_key_exists( 'types', $_unsafe_rules_array ) ) {
$_safe_rules_array['types'] = $_unsafe_rules_array['types'];
}
$_safe_rules = json_encode( $_safe_rules_array, JSON_FORCE_OBJECT );
if ( ! empty( $_safe_rules ) ) {
update_option( 'autoptimize_ccss_rules', $_safe_rules );
$response['code'] = '200';
$response['msg'] = 'Rules saved';
} else {
$_error = 'Could not auto-save rules (safe rules empty)';
}
} else {
$_error = 'Could not auto-save rules (rules could not be json_decoded)';
}
} else {
$_error = 'Could not auto-save rules (rules empty)';
}
} else {
$_error = 'Could not auto-save rules (rules not in $_POST)';
}
} else {
$_error = 'Not allowed';
}
if ( ! isset( $response ) && $_error ) {
$response['code'] = '500';
$response['msg'] = $_error;
}
// Dispatch respose.
echo json_encode( $response );
// Close ajax request.
wp_die();
}
public function critcss_check_filename( $filename ) {
// Try to avoid directory traversal when reading/writing/deleting critical CSS files.
if ( strpos( $filename, 'ccss_' ) !== 0 ) {
return false;
} elseif ( substr( $filename, -4, 4 ) !== '.css' ) {
return false;
} elseif ( sanitize_file_name( $filename ) !== $filename ) {
// Use WordPress core's sanitize_file_name to see if anything fishy is going on.
return false;
} else {
return true;
}
}
public function rrmdir( $path ) {
// recursively remove a directory as found on
// https://andy-carter.com/blog/recursively-remove-a-directory-in-php.
$files = glob( $path . '/*' );
foreach ( $files as $file ) {
is_dir( $file ) ? $this->rrmdir( $file ) : unlink( $file );
}
rmdir( $path );
return;
}
public function fetch_rule_from_ccssfile( $ccss_file = '' ) {
if ( empty( $ccss_file ) ) {
return false;
}
$ccss_file = str_replace( AO_CCSS_DIR, '', $ccss_file );
static $rules = null;
if ( null === $rules ) {
$rules = $this->criticalcss->get_option( 'rules' );
}
foreach ( $rules as $ruletype => $rulechilds ) {
foreach ( $rulechilds as $identifier => $properties ) {
if ( $properties['file'] === $ccss_file ) {
return array( $ruletype, $identifier );
}
}
}
return false;
}
}

View File

@@ -0,0 +1,118 @@
<?php
/**
* Add exit-survey logic to plugins-page.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeExitSurvey
{
function __construct() {
global $pagenow;
if ( 'plugins.php' != $pagenow ) {
return;
}
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_survey_scripts' ) );
add_action( 'admin_footer', array( $this, 'render_survey_model' ) );
}
function enqueue_survey_scripts() {
wp_enqueue_script( 'ao_exit_survey', plugins_url( '/static/exit-survey/exit-survey.js', __FILE__ ), array( 'jquery' ), AUTOPTIMIZE_PLUGIN_VERSION, true );
wp_enqueue_style( 'ao_exit_survey', plugins_url( '/static/exit-survey/exit-survey.css', __FILE__ ), null, AUTOPTIMIZE_PLUGIN_VERSION );
}
function render_survey_model() {
global $wp_version;
$data = array(
'home' => home_url(),
'dest' => 'aHR0cHM6Ly9taXNjLm9wdGltaXppbmdtYXR0ZXJzLmNvbS9hb19leGl0X3N1cnZleS9pbmRleC5waHA=',
);
?>
<div class="ao-plugin-uninstall-feedback-popup ao-feedback" id="ao_uninstall_feedback_popup" data-modal="<?php echo base64_encode( json_encode( $data ) ); ?>">
<div class="popup--header">
<h5><?php esc_html_e( 'Sorry to see you go!', 'autoptimize' ); ?></h5>
</div><!--/.popup--header-->
<div class="popup--body">
<p><strong><?php esc_html_e( 'We would appreciate if you let us know why you\'re deactivating Autoptimize!', 'autoptimize' ); ?></strong></p>
<ul class="popup--form">
<li ao-option-id="5">
<input type="radio" name="ao-deactivate-option" id="ao_feedback5">
<label for="ao_feedback5">
<?php esc_html_e( 'I don\'t see a performance improvement.', 'autoptimize' ); ?>
</label>
<p class="last-attempt"><?php _e( 'As Autoptimize does not do page caching, you might have to install e.g. KeyCDN Cache Enabler or WP Super Cache as well. Feel free to create a topic on <a href="https://wordpress.org/support/plugin/autoptimize/#new-topic-0" target="_blank">the support forum here</a> to get pointers on how get the most out of Autoptimize!', 'autoptimize' ); ?></p>
</li>
<li ao-option-id="6">
<input type="radio" name="ao-deactivate-option" id="ao_feedback6">
<label for="ao_feedback6" data-reason="broke site">
<?php esc_html_e( 'It broke my site.', 'autoptimize' ); ?>
</label>
<p class="last-attempt"><?php _e( 'Ouch, sorry about that! But almost all problems can be fixed with the right configuration, have a look at <a href="https://blog.futtta.be/2022/05/05/what-to-do-when-autoptimize-breaks-your-site/" target="_blank">this short troubleshooting howto</a> or create a topic on <a href="https://wordpress.org/support/plugin/autoptimize/#new-topic-0" target="_blank">the support forum here</a>!', 'autoptimize' ); ?></p>
<li ao-option-id="4">
<input type="radio" name="ao-deactivate-option" id="ao_feedback4">
<label for="ao_feedback4" data-reason="found better">
<?php esc_html_e( 'I found a better solution.', 'autoptimize' ); ?>
</label>
<li ao-option-id="3">
<input type="radio" name="ao-deactivate-option" id="ao_feedback3">
<label for="ao_feedback3" data-reason="just temporarily">
<?php esc_html_e( 'I\'m just disabling temporarily.', 'autoptimize' ); ?>
</label>
<li ao-option-id="999">
<input type="radio" name="ao-deactivate-option" id="ao_feedback999">
<label for="ao_feedback999" data-reason="other">
<?php esc_html_e( 'Other (please specify below)', 'autoptimize' ); ?>
</label>
<textarea width="100%" rows="2" name="comments" placeholder="What can we do better?"></textarea></li>
<hr />
<li ao-option-id="998">
<label for="ao_feedback_email_toggle" data-reason="other detail">
<input type="checkbox" id="ao_feedback_email_toggle" name="ao_feedback_email_toggle" />
<?php esc_html_e( 'I would like be contacted about my experience with Autoptimize.', 'autoptimize' ); ?>
</label>
<input type="email" name="ao-deactivate-option" id="ao_feedback998" placeholder="mymail@domain.xyz" class="hidden">
</li>
</ul>
</div><!--/.popup--body-->
<div class="popup--footer">
<div class="actions">
<a href="#" class="info-disclosure-link"><?php esc_html_e( 'What info do we collect?', 'autoptimize' ); ?></a>
<div class="info-disclosure-content"><p><?php esc_html_e( 'Below is a detailed view of all data that Optimizing Matters will receive if you fill in this survey. Your email address is only shared if you explicitly fill it in, your IP addres is never sent.', 'autoptimize' ); ?></p>
<ul>
<li><strong><?php esc_html_e( 'Plugin version', 'autoptimize' ); ?> </strong> <code id="ao_plugin_version"> <?php echo AUTOPTIMIZE_PLUGIN_VERSION; ?> </code></li>
<li><strong><?php esc_html_e( 'WordPress version', 'autoptimize' ); ?> </strong> <code id="core_version"> <?php echo $wp_version; ?> </code></li>
<li><strong><?php esc_html_e( 'Current website:', 'autoptimize' ); ?></strong> <code> <?php echo trailingslashit( get_site_url() ); ?> </code></li>
<li><strong><?php esc_html_e( 'Uninstall reason', 'autoptimize' ); ?> </strong> <i> <?php esc_html_e( 'Selected reason from the above survey', 'autoptimize' ); ?> </i></li>
</ul>
</div>
<div class="buttons">
<input type="submit"
name="ao-deactivate-no"
id="ao-deactivate-no"
class="button"
value="Just Deactivate">
<input type="submit"
name="ao-deactivate-cancel"
id="ao-deactivate-cancel"
class="button"
value="Cancel">
<input type="submit"
name="ao-deactivate-yes"
id="ao-deactivate-yes"
class="button button-primary"
value="Submit &amp; Deactivate"
data-after-text="Submit &amp; Deactivate"
disabled="1"></div>
</div><!--/.actions-->
</div><!--/.popup--footer-->
</div>
<?php
}
}

View File

@@ -0,0 +1,662 @@
<?php
/**
* Handles autoptimizeExtra frontend features + admin options page
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeExtra
{
/**
* Options
*
* @var array
*/
protected $options = array();
/**
* Singleton instance.
*
* @var self|null
*/
protected static $instance = null;
/**
* Creates an instance and calls run().
*
* @param array $options Optional. Allows overriding options without having to specify them via admin options page.
*/
public function __construct( $options = array() )
{
if ( empty( $options ) ) {
$options = self::fetch_options();
}
$this->options = $options;
}
/**
* Helper for getting a singleton instance. While being an
* anti-pattern generally, it comes in handy for now from a
* readability/maintainability perspective, until we get some
* proper dependency injection going.
*
* @return self
*/
public static function instance()
{
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
public function run()
{
if ( is_admin() ) {
if ( is_multisite() && is_network_admin() && autoptimizeOptionWrapper::is_ao_active_for_network() ) {
add_action( 'network_admin_menu', array( $this, 'admin_menu' ) );
} else {
add_action( 'admin_menu', array( $this, 'admin_menu' ) );
}
add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_extra_tab' ) );
} else {
add_action( 'wp', array( $this, 'run_on_frontend' ) );
}
}
public function set_options( array $options )
{
$this->options = $options;
return $this;
}
public static function fetch_options()
{
$value = autoptimizeOptionWrapper::get_option( 'autoptimize_extra_settings' );
if ( empty( $value ) ) {
// Fallback to returning defaults when no stored option exists yet.
$value = autoptimizeConfig::get_ao_extra_default_options();
}
return $value;
}
public function disable_emojis()
{
// Removing all actions related to emojis!
remove_action( 'admin_print_styles', 'print_emoji_styles' );
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
// Removes TinyMCE emojis.
add_filter( 'tiny_mce_plugins', array( $this, 'filter_disable_emojis_tinymce' ) );
// Removes emoji dns-preftech.
add_filter( 'emoji_svg_url', '__return_false' );
}
public function filter_disable_emojis_tinymce( $plugins )
{
if ( is_array( $plugins ) ) {
return array_diff( $plugins, array( 'wpemoji' ) );
} else {
return array();
}
}
public function filter_remove_qs( $src )
{
if ( ! empty( $src ) ) {
if ( strpos( $src, '?ver=' ) ) {
$src = remove_query_arg( 'ver', $src );
} elseif ( strpos( $src, '?v=' ) ) {
$src = remove_query_arg( 'v', $src );
}
}
return $src;
}
public function extra_async_js( $in )
{
$exclusions = array();
if ( ! empty( $in ) ) {
$exclusions = array_fill_keys( array_filter( array_map( 'trim', explode( ',', $in ) ) ), '' );
}
$settings = wp_strip_all_tags( $this->options['autoptimize_extra_text_field_3'] );
$async = array_fill_keys( array_filter( array_map( 'trim', explode( ',', $settings ) ) ), '' );
$attr = apply_filters( 'autoptimize_filter_extra_async', 'async' );
foreach ( $async as $k => $v ) {
$async[ $k ] = $attr;
}
// Merge exclusions & asyncs in one array and return to AO API.
$merged = array_merge( $exclusions, $async );
return $merged;
}
public function run_on_frontend()
{
// only run the Extra optimizations on frontend if general conditions
// for optimizations are met, this to ensure e.g. removing querystrings
// is not done when optimizing for logged in users is off, breaking
// some pagebuilders (Divi & Elementor).
if ( false === autoptimizeMain::should_buffer() ) {
return;
}
$options = $this->options;
// Disable emojis if specified.
if ( ! empty( $options['autoptimize_extra_checkbox_field_1'] ) ) {
$this->disable_emojis();
}
// Remove version query parameters.
if ( ! empty( $options['autoptimize_extra_checkbox_field_0'] ) ) {
add_filter( 'script_loader_src', array( $this, 'filter_remove_qs' ), 15, 1 );
add_filter( 'style_loader_src', array( $this, 'filter_remove_qs' ), 15, 1 );
}
// Avoiding conflicts of interest when async-javascript plugin is active!
$async_js_plugin_active = autoptimizeUtils::is_plugin_active( 'async-javascript/async-javascript.php' );
if ( ! empty( $options['autoptimize_extra_text_field_3'] ) && ! $async_js_plugin_active ) {
add_filter( 'autoptimize_filter_js_exclude', array( $this, 'extra_async_js' ), 10, 1 );
}
// Optimize google fonts!
if ( ! empty( $options['autoptimize_extra_radio_field_4'] ) && ( '1' !== $options['autoptimize_extra_radio_field_4'] ) ) {
add_filter( 'wp_resource_hints', array( $this, 'filter_remove_gfonts_dnsprefetch' ), 10, 2 );
add_filter( 'autoptimize_html_after_minify', array( $this, 'filter_optimize_google_fonts' ), 10, 1 );
add_filter( 'autoptimize_extra_filter_tobepreconn', array( $this, 'filter_preconnect_google_fonts' ), 10, 1 );
if ( '2' === $options['autoptimize_extra_radio_field_4'] ) {
// remove Google Fonts, adding filters to also remove Google Fonts from 3rd party themes/ plugins.
// inspired by https://wordpress.org/plugins/disable-remove-google-fonts/.
remove_action( 'wp_footer', 'et_builder_print_font' ); // Divi.
remove_action( 'wp_footer', array( 'RevSliderFront', 'load_google_fonts' ) ); // Revslider.
add_filter( 'elementor/frontend/print_google_fonts', '__return_false' ); // Elementor.
add_filter( 'fl_builder_google_fonts_pre_enqueue', '__return_empty_array' ); // Beaver Builder.
}
}
// Preconnect!
if ( ! empty( $options['autoptimize_extra_text_field_2'] ) || has_filter( 'autoptimize_extra_filter_tobepreconn' ) ) {
add_filter( 'wp_resource_hints', array( $this, 'filter_preconnect' ), 10, 2 );
}
// Preload!
if ( ! empty( $options['autoptimize_extra_text_field_7'] ) || has_filter( 'autoptimize_filter_extra_tobepreloaded' ) || ! empty( autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_preload' ) ) ) {
add_filter( 'autoptimize_html_after_minify', array( $this, 'filter_preload' ), 10, 2 );
}
// Remove global styles.
if ( ! empty( $options['autoptimize_extra_checkbox_field_8'] ) ) {
$this->disable_global_styles();
}
}
public function filter_remove_gfonts_dnsprefetch( $urls, $relation_type )
{
return $this->filter_remove_dns_prefetch( $urls, $relation_type, 'fonts.googleapis.com' );
}
public function filter_remove_dns_prefetch( $urls, $relation_type, $url_to_remove )
{
$url_to_remove = (string) $url_to_remove;
if ( ! empty( $url_to_remove ) && 'dns-prefetch' === $relation_type ) {
$cnt = 0;
foreach ( $urls as $url ) {
// $url can be an array, in which case we need to fetch the value of the href key.
if ( is_array( $url ) ) {
if ( isset( $url['href'] ) ) {
$url = $url['href'];
} else {
continue;
}
}
if ( is_string( $url ) && false !== strpos( $url, $url_to_remove ) ) {
unset( $urls[ $cnt ] );
}
$cnt++;
}
}
return $urls;
}
public function filter_optimize_google_fonts( $in )
{
// Extract fonts, partly based on wp rocket's extraction code.
$markup = preg_replace( '/<!--(.*)-->/Uis', '', $in );
preg_match_all( '#<link(?:\s+(?:(?!href\s*=\s*)[^>])+)?(?:\s+href\s*=\s*([\'"])((?:https?:)?\/\/fonts\.googleapis\.com\/css(?:(?!\1).)+)\1)(?:\s+[^>]*)?>#iU', $markup, $matches );
$fonts_collection = array();
if ( ! $matches[2] ) {
return $in;
}
// Store them in $fonts array.
$i = 0;
foreach ( $matches[2] as $font ) {
if ( ! preg_match( '/rel=["\']dns-prefetch["\']/', $matches[0][ $i ] ) ) {
// Get fonts name.
$font = str_replace( array( '%7C', '%7c' ), '|', $font );
if ( strpos( $font, 'fonts.googleapis.com/css2' ) !== false ) {
// (Somewhat) change Google Fonts APIv2 syntax back to v1.
// todo: support for 100..900
$font = rawurldecode( $font );
$font = str_replace( array( 'css2?', 'ital,wght@', 'wght@', 'ital@', '0,', '1,', ':1', ';', '&family=' ), array( 'css?', '', '', '', '', 'italic', ':italic', ',', '%7C' ), $font );
}
$font = explode( 'family=', $font );
$font = ( isset( $font[1] ) ) ? explode( '&', $font[1] ) : array();
// Add font to $fonts[$i] but make sure not to pollute with an empty family!
$_thisfont = array_values( array_filter( explode( '|', reset( $font ) ) ) );
if ( ! empty( $_thisfont ) ) {
$fonts_collection[ $i ]['fonts'] = $_thisfont;
// And add subset if any!
$subset = ( is_array( $font ) ) ? end( $font ) : '';
if ( false !== strpos( $subset, 'subset=' ) ) {
$subset = str_replace( array( '%2C', '%2c' ), ',', $subset );
$subset = explode( 'subset=', $subset );
$fonts_collection[ $i ]['subsets'] = explode( ',', $subset[1] );
}
}
// And remove Google Fonts.
$in = str_replace( $matches[0][ $i ], '', $in );
}
$i++;
}
$options = $this->options;
$fonts_markup = '';
if ( '2' === $options['autoptimize_extra_radio_field_4'] ) {
// Remove Google Fonts.
unset( $fonts_collection );
return $in;
} elseif ( '3' === $options['autoptimize_extra_radio_field_4'] || '5' === $options['autoptimize_extra_radio_field_4'] ) {
// Aggregate & link!
$fonts_string = '';
$subset_string = '';
foreach ( $fonts_collection as $font ) {
$fonts_string .= '|' . trim( implode( '|', $font['fonts'] ), '|' );
if ( ! empty( $font['subsets'] ) ) {
$subset_string .= ',' . trim( implode( ',', $font['subsets'] ), ',' );
}
}
if ( ! empty( $subset_string ) ) {
$subset_string = str_replace( ',', '%2C', ltrim( $subset_string, ',' ) );
$fonts_string = $fonts_string . '&#038;subset=' . $subset_string;
}
$fonts_string = apply_filters( 'autoptimize_filter_extra_gfont_fontstring', str_replace( '|', '%7C', ltrim( $fonts_string, '|' ) ) );
// only add display parameter if there is none in $fonts_string (by virtue of the filter).
if ( strpos( $fonts_string, 'display=' ) === false ) {
$fonts_string .= apply_filters( 'autoptimize_filter_extra_gfont_display', '&amp;display=swap' );
}
if ( ! empty( $fonts_string ) ) {
if ( '5' === $options['autoptimize_extra_radio_field_4'] ) {
$rel_string = 'rel="stylesheet" media="print" onload="' . autoptimizeConfig::get_ao_css_preload_onload() . '"';
} else {
$rel_string = 'rel="stylesheet"';
}
$fonts_markup = '<link ' . $rel_string . ' id="ao_optimized_gfonts" href="https://fonts.googleapis.com/css?family=' . $fonts_string . '">';
}
} elseif ( '4' === $options['autoptimize_extra_radio_field_4'] ) {
// Aggregate & load async (webfont.js impl.)!
$fonts_array = array();
foreach ( $fonts_collection as $_fonts ) {
if ( ! empty( $_fonts['subsets'] ) ) {
$_subset = implode( ',', $_fonts['subsets'] );
foreach ( $_fonts['fonts'] as $key => $_one_font ) {
$_one_font = $_one_font . ':' . $_subset;
$_fonts['fonts'][ $key ] = $_one_font;
}
}
$fonts_array = array_merge( $fonts_array, $_fonts['fonts'] );
}
$fonts_array = array_map( 'urldecode', $fonts_array );
$fonts_array = array_map(
function( $_f ) {
return trim( $_f, ',' );
},
$fonts_array
);
// type attrib on <script not added by default.
$type_js = '';
if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
$type_js = 'type="text/javascript" ';
}
$fonts_markup = '<script ' . $type_js . 'data-cfasync="false" id="ao_optimized_gfonts_config">WebFontConfig={google:{families:' . wp_json_encode( $fonts_array ) . ' },classes:false, events:false, timeout:1500};</script>';
$fonts_library_markup = '<script ' . $type_js . 'data-cfasync="false" id="ao_optimized_gfonts_webfontloader">(function() {var wf = document.createElement(\'script\');wf.src=\'https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js\';wf.type=\'text/javascript\';wf.async=\'true\';var s=document.getElementsByTagName(\'script\')[0];s.parentNode.insertBefore(wf, s);})();</script>';
$in = substr_replace( $in, $fonts_library_markup . '</head>', strpos( $in, '</head>' ), strlen( '</head>' ) );
}
// Replace back in markup.
$inject_point = apply_filters( 'autoptimize_filter_extra_gfont_injectpoint', '<link' );
$out = substr_replace( $in, $fonts_markup . $inject_point, strpos( $in, $inject_point ), strlen( $inject_point ) );
unset( $fonts_collection );
return $out;
}
public function filter_preconnect( $hints, $relation_type )
{
$options = $this->options;
$preconns = array();
// Get settings and store in array.
if ( array_key_exists( 'autoptimize_extra_text_field_2', $options ) ) {
$preconns = array_filter( array_map( 'trim', explode( ',', wp_strip_all_tags( $options['autoptimize_extra_text_field_2'] ) ) ) );
}
$preconns = apply_filters( 'autoptimize_extra_filter_tobepreconn', $preconns );
// Walk array, extract domain and add to new array with crossorigin attribute.
foreach ( $preconns as $preconn ) {
$domain = '';
$parsed = parse_url( $preconn );
if ( is_array( $parsed ) && ! empty( $parsed['host'] ) && empty( $parsed['scheme'] ) ) {
$domain = '//' . $parsed['host'];
} elseif ( is_array( $parsed ) && ! empty( $parsed['host'] ) ) {
$domain = $parsed['scheme'] . '://' . $parsed['host'];
}
if ( ! empty( $domain ) ) {
$hint = array( 'href' => $domain );
// Fonts don't get preconnected unless crossorigin flag is set, non-fonts don't get preconnected if origin flag is set
// so hardcode fonts.gstatic.com to come with crossorigin and have filter to add other domains if needed.
$crossorigins = apply_filters( 'autoptimize_extra_filter_preconn_crossorigin', array( 'https://fonts.gstatic.com' ) );
if ( in_array( $domain, $crossorigins ) ) {
$hint['crossorigin'] = 'anonymous';
}
$new_hints[] = $hint;
}
}
// Merge in WP's preconnect hints.
if ( 'preconnect' === $relation_type && ! empty( $new_hints ) ) {
$hints = array_merge( $hints, $new_hints );
}
return $hints;
}
public function filter_preconnect_google_fonts( $in )
{
if ( '2' !== $this->options['autoptimize_extra_radio_field_4'] ) {
// Preconnect to fonts.gstatic.com unless we remove gfonts.
$in[] = 'https://fonts.gstatic.com';
}
if ( '4' === $this->options['autoptimize_extra_radio_field_4'] ) {
// Preconnect even more hosts for webfont.js!
$in[] = 'https://ajax.googleapis.com';
$in[] = 'https://fonts.googleapis.com';
}
return $in;
}
public function filter_preload( $in ) {
// make array from comma separated list.
$options = $this->options;
$preloads = array();
if ( array_key_exists( 'autoptimize_extra_text_field_7', $options ) ) {
$preloads = array_filter( array_map( 'trim', explode( ',', wp_strip_all_tags( $options['autoptimize_extra_text_field_7'] ) ) ) );
}
if ( false === autoptimizeImages::imgopt_active() && false === autoptimizeImages::should_lazyload_wrapper() ) {
// only do this here if imgopt/ lazyload are not active?
$metabox_preloads = array_filter( array_map( 'trim', explode( ',', wp_strip_all_tags( autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_preload' ) ) ) ) );
if ( ! empty( $metabox_preloads ) ) {
$preloads = array_merge( $preloads, $metabox_preloads );
}
}
$preloads = apply_filters( 'autoptimize_filter_extra_tobepreloaded', $preloads );
// immediately return if nothing to be preloaded.
if ( empty( $preloads ) ) {
return $in;
}
// iterate through array and add preload link to tmp string.
$preload_output = '';
foreach ( $preloads as $preload ) {
if ( filter_var( $preload, FILTER_VALIDATE_URL ) !== $preload ) {
continue;
}
$preload = esc_url_raw( $preload );
$crossorigin = '';
$preload_as = '';
$mime_type = '';
$_preload = strtok( $preload, '?' );
if ( autoptimizeUtils::str_ends_in( $_preload, '.css' ) ) {
$preload_as = 'style';
} elseif ( autoptimizeUtils::str_ends_in( $_preload, '.js' ) ) {
$preload_as = 'script';
} elseif ( autoptimizeUtils::str_ends_in( $_preload, '.woff' ) || autoptimizeUtils::str_ends_in( $_preload, '.woff2' ) || autoptimizeUtils::str_ends_in( $_preload, '.ttf' ) || autoptimizeUtils::str_ends_in( $_preload, '.eot' ) || autoptimizeUtils::str_ends_in( $_preload, '.otf' ) ) {
$preload_as = 'font';
$crossorigin = ' crossorigin';
$mime_type = ' type="font/' . pathinfo( $_preload, PATHINFO_EXTENSION ) . '"';
if ( ' type="font/eot"' === $mime_type ) {
$mime_type = 'application/vnd.ms-fontobject';
}
} elseif ( autoptimizeUtils::str_ends_in( $_preload, '.jpeg' ) || autoptimizeUtils::str_ends_in( $_preload, '.jpg' ) || autoptimizeUtils::str_ends_in( $_preload, '.webp' ) || autoptimizeUtils::str_ends_in( $_preload, '.png' ) || autoptimizeUtils::str_ends_in( $_preload, '.gif' ) || autoptimizeUtils::str_ends_in( $_preload, '.svg' ) ) {
$preload_as = 'image';
} else {
$preload_as = 'other';
}
$preload_output .= '<link rel="preload" fetchpriority="high" href="' . $preload . '" as="' . $preload_as . '"' . $mime_type . $crossorigin . '>';
}
$preload_output = apply_filters( 'autoptimize_filter_extra_preload_output', $preload_output );
return $this->inject_preloads( $preload_output, $in );
}
public static function inject_preloads( $preloads, $html ) {
// add string to head (before first link node by default).
$preload_inject = apply_filters( 'autoptimize_filter_extra_preload_inject', '<link' );
$position = autoptimizeUtils::strpos( $html, $preload_inject );
return autoptimizeUtils::substr_replace( $html, $preloads . $preload_inject, $position, strlen( $preload_inject ) );
}
public function disable_global_styles()
{
remove_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles' );
remove_action( 'wp_footer', 'wp_enqueue_global_styles', 1 );
remove_action( 'wp_body_open', 'wp_global_styles_render_svg_filters' );
if ( true === apply_filters( 'autoptimize_filter_extra_global_styles_and_block_css', true ) ) {
add_action(
'wp_enqueue_scripts',
function() {
wp_dequeue_style( 'wp-block-library' );
wp_dequeue_style( 'wp-block-library-theme' );
}
);
}
if ( true === apply_filters( 'autoptimize_filter_extra_remove_woocommerce_block_css', true ) ) {
add_action(
'wp_enqueue_scripts',
function() {
wp_dequeue_style( 'wc-blocks-style' );
}
);
}
}
public function admin_menu()
{
// no acces if multisite and not network admin and no site config allowed.
if ( autoptimizeConfig::should_show_menu_tabs() ) {
add_submenu_page(
'',
'autoptimize_extra',
'autoptimize_extra',
'manage_options',
'autoptimize_extra',
array( $this, 'options_page' )
);
}
register_setting( 'autoptimize_extra_settings', 'autoptimize_extra_settings' );
}
public function add_extra_tab( $in )
{
if ( autoptimizeConfig::should_show_menu_tabs() ) {
$in = array_merge( $in, array( 'autoptimize_extra' => esc_html__( 'Extra', 'autoptimize' ) ) );
}
return $in;
}
public function options_page()
{
// phpcs:disable Squiz.ControlStructures.ControlSignature.NewlineAfterOpenBrace
// Working with actual option values from the database here.
// That way any saves are still processed as expected, but we can still
// override behavior by using `new autoptimizeExtra($custom_options)` and not have that custom
// behavior being persisted in the DB even if save is done here.
$options = $this->fetch_options();
$gfonts = $options['autoptimize_extra_radio_field_4'];
?>
<style>
#ao_settings_form {background: white;border: 1px solid #ccc;padding: 1px 15px;margin: 15px 10px 10px 0;}
#ao_settings_form .form-table th {font-weight: normal;}
#autoptimize_extra_descr{font-size: 120%;}
</style>
<script>document.title = "Autoptimize: <?php esc_html_e( 'Extra', 'autoptimize' ); ?> " + document.title;</script>
<div class="wrap">
<h1><?php apply_filters( 'autoptimize_filter_settings_is_pro', false ) ? esc_html_e( 'Autoptimize Pro Settings', 'autoptimize' ) : esc_html_e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
<?php echo autoptimizeConfig::ao_admin_tabs(); ?>
<?php if ( 'on' !== autoptimizeOptionWrapper::get_option( 'autoptimize_js' ) && 'on' !== autoptimizeOptionWrapper::get_option( 'autoptimize_css' ) && 'on' !== autoptimizeOptionWrapper::get_option( 'autoptimize_html' ) && ! autoptimizeImages::imgopt_active() ) { ?>
<div class="notice-warning notice"><p>
<?php esc_html_e( 'Most of below Extra optimizations require at least one of HTML, JS, CSS or Image autoptimizations being active.', 'autoptimize' ); ?>
</p></div>
<?php } ?>
<form id='ao_settings_form' action='<?php echo admin_url( 'options.php' ); ?>' method='post'>
<?php settings_fields( 'autoptimize_extra_settings' ); ?>
<h2><?php esc_html_e( 'Extra Auto-Optimizations', 'autoptimize' ); ?></h2>
<span id='autoptimize_extra_descr'><?php esc_html_e( 'The following settings can improve your site\'s performance even more.', 'autoptimize' ); ?></span>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Google Fonts', 'autoptimize' ); ?></th>
<td>
<input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="1" <?php if ( ! in_array( $gfonts, array( 2, 3, 4, 5 ) ) ) { echo 'checked'; } ?> ><?php esc_html_e( 'Leave as is', 'autoptimize' ); ?><br/>
<input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="2" <?php checked( 2, $gfonts, true ); ?> ><?php esc_html_e( 'Remove Google Fonts', 'autoptimize' ); ?><br/>
<?php // translators: "display:swap" should remain untranslated, will be shown in code tags. ?>
<input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="3" <?php checked( 3, $gfonts, true ); ?> ><?php echo esc_html__( 'Combine and link in head (fonts load fast but are render-blocking)', 'autoptimize' ) . ', ' . sprintf( esc_html__( 'includes %1$sdisplay:swap%2$s.', 'autoptimize' ), '<code>', '</code>' ); ?><br/>
<?php // translators: "display:swap" should remain untranslated, will be shown in code tags. ?>
<input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="5" <?php checked( 5, $gfonts, true ); ?> ><?php echo esc_html__( 'Combine and link deferred in head (fonts load late, but are not render-blocking)', 'autoptimize' ) . ', ' . sprintf( esc_html__( 'includes %1$sdisplay:swap%2$s.', 'autoptimize' ), '<code>', '</code>' ); ?>
<span <?php if ( '4' !== $gfonts ){ echo "style='display:none;' "; } ?> ><br/><input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="4" <?php checked( 4, $gfonts, true ); ?> ><?php echo sprintf( esc_html__( 'Combine and load fonts asynchronously with %1$swebfont.js%2$s', 'autoptimize' ), '<a href="https://github.com/typekit/webfontloader#readme" target="_blank">', '</a>' ) . ' ' . esc_html__( '(deprecated)', 'autoptimize' ); ?></span><br/>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Remove emojis', 'autoptimize' ); ?></th>
<td>
<label><input type='checkbox' name='autoptimize_extra_settings[autoptimize_extra_checkbox_field_1]' <?php if ( ! empty( $options['autoptimize_extra_checkbox_field_1'] ) && '1' === $options['autoptimize_extra_checkbox_field_1'] ) { echo 'checked="checked"'; } ?> value='1'><?php esc_html_e( 'Removes WordPress\' core emojis\' inline CSS, inline JavaScript, and an otherwise un-autoptimized JavaScript file.', 'autoptimize' ); ?></label>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Remove query strings from static resources', 'autoptimize' ); ?></th>
<td>
<label><input type='checkbox' name='autoptimize_extra_settings[autoptimize_extra_checkbox_field_0]' <?php if ( ! empty( $options['autoptimize_extra_checkbox_field_0'] ) && '1' === $options['autoptimize_extra_checkbox_field_0'] ) { echo 'checked="checked"'; } ?> value='1'>
<?php
// translators: just a code tag around "ver" which is the parameter added to CSS/ JS URL's by wordpress.
printf( esc_html__( 'Removing query strings (or more specifically the %1$sver%2$s parameter) will not improve load time, but might improve performance scores.', 'autoptimize' ), '<code>', '</code>' );
?>
</label>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Remove WordPress block CSS', 'autoptimize' ); ?></th>
<td>
<label><input type='checkbox' name='autoptimize_extra_settings[autoptimize_extra_checkbox_field_8]' <?php if ( ! empty( $options['autoptimize_extra_checkbox_field_8'] ) && '1' === $options['autoptimize_extra_checkbox_field_8'] ) { echo 'checked="checked"'; } ?> value='1'><?php esc_html_e( 'WordPress adds block CSS and global styles to improve easy styling of block-based sites, but which can add a significant amount of CSS and SVG. If you are sure your site can do without the block CSS and "global styles", you can disable them here.', 'autoptimize' ); ?></label>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Preconnect to 3rd party domains (advanced users)', 'autoptimize' ); ?></th>
<td>
<label><input type='text' style='width:80%' name='autoptimize_extra_settings[autoptimize_extra_text_field_2]' value='<?php if ( array_key_exists( 'autoptimize_extra_text_field_2', $options ) ) { echo esc_attr( $options['autoptimize_extra_text_field_2'] ); } ?>'><br />
<?php
// Translators; link to a page on keycdn blog about preconnecting.
printf( esc_html__( 'Add 3rd party domains you want the browser to %1$spreconnect%2$s to, separated by comma\'s. Make sure to include the correct protocol (HTTP or HTTPS).', 'autoptimize' ), '<a href="https://www.keycdn.com/support/preconnect/#primary" target="_blank">', '</a>' );
?>
</label>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Preload specific requests (advanced users)', 'autoptimize' ); ?></th>
<td>
<label><input type='text' style='width:80%' name='autoptimize_extra_settings[autoptimize_extra_text_field_7]' value='<?php if ( array_key_exists( 'autoptimize_extra_text_field_7', $options ) ) { echo esc_attr( $options['autoptimize_extra_text_field_7'] ); } ?>'><br /><?php esc_html_e( 'Comma-separated list with full URL\'s of to to-be-preloaded resources. To be used sparingly!', 'autoptimize' ); ?></label>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Async Javascript-files (advanced users)', 'autoptimize' ); ?></th>
<td>
<?php
if ( autoptimizeUtils::is_plugin_active( 'async-javascript/async-javascript.php' ) ) {
// translators: link points Async Javascript settings page.
printf( esc_html__( 'You have "Async JavaScript" installed, %1$sconfiguration of async javascript is best done there%2$s.', 'autoptimize' ), '<a href="' . 'options-general.php?page=async-javascript' . '">', '</a>' );
} else {
?>
<input type='text' style='width:80%' name='autoptimize_extra_settings[autoptimize_extra_text_field_3]' value='<?php if ( array_key_exists( 'autoptimize_extra_text_field_3', $options ) ) { echo esc_attr( $options['autoptimize_extra_text_field_3'] ); } ?>'>
<br />
<?php
printf( esc_html__( 'Comma-separated list of local or 3rd party JS-files that should loaded with the %1$sasync%2$s flag. JS-files from your own site will be automatically excluded if added here. ', 'autoptimize' ), '<code>', '</code>' );
// translators: %s will be replaced by a link to the "async javascript" plugin.
echo sprintf( esc_html__( 'Configuration of async javascript is easier and more flexible using the %s plugin.', 'autoptimize' ), '"<a href="https://wordpress.org/plugins/async-javascript" target="_blank">Async Javascript</a>"' );
$asj_install_url = network_admin_url() . 'plugin-install.php?s=async+javascript&tab=search&type=term';
echo sprintf( ' <a href="' . $asj_install_url . '">%s</a>', esc_html__( 'Click here to install and activate it.', 'autoptimize' ) );
}
?>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Optimize YouTube videos', 'autoptimize' ); ?></th>
<td>
<?php
if ( autoptimizeUtils::is_plugin_active( 'wp-youtube-lyte/wp-youtube-lyte.php' ) ) {
esc_html_e( 'Great, you have WP YouTube Lyte installed.', 'autoptimize' );
$lyte_config_url = 'options-general.php?page=lyte_settings_page';
echo sprintf( ' <a href="' . $lyte_config_url . '">%s</a>', esc_html__( 'Click here to configure it.', 'autoptimize' ) );
} else {
// translators: %s will be replaced by a link to "wp youtube lyte" plugin.
echo sprintf( esc_html__( '%s allows you to “lazy load” your videos, by inserting responsive “Lite YouTube Embeds". ', 'autoptimize' ), '<a href="https://wordpress.org/plugins/wp-youtube-lyte" target="_blank">WP YouTube Lyte</a>' );
$lyte_install_url = network_admin_url() . 'plugin-install.php?s=lyte&tab=search&type=term';
echo sprintf( ' <a href="' . $lyte_install_url . '">%s</a>', esc_html__( 'Click here to install and activate it.', 'autoptimize' ) );
}
?>
</td>
</tr>
</table>
<p class="submit"><input type="submit" name="submit" id="submit" class="button button-primary" value="<?php esc_html_e( 'Save Changes', 'autoptimize' ); ?>" /></p>
</form>
<?php
}
}

View File

@@ -0,0 +1,150 @@
<?php
/**
* Handles minifying HTML markup.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeHTML extends autoptimizeBase
{
/**
* Whether HTML comments are kept.
*
* @var bool
*/
private $keepcomments = false;
/**
* Whether inline CSS/ JS is minified.
*
* @var bool
*/
private $minify_inline = false;
/**
* Whether to force xhtml compatibility.
*
* @var bool
*/
private $forcexhtml = false;
/**
* Things to exclude from being minifed.
*
* @var array
*/
private $exclude = array(
'<!-- ngg_resource_manager_marker -->',
'<!--noindex-->',
'<!--/noindex-->',
);
public function read( $options )
{
// Remove the HTML comments?
$this->keepcomments = (bool) $options['keepcomments'];
// Filter to force xhtml.
$this->forcexhtml = (bool) apply_filters( 'autoptimize_filter_html_forcexhtml', false );
// minify inline JS/ CSS.
$this->minify_inline = (bool) apply_filters( 'autoptimize_html_minify_inline_js_css', $options['minify_inline'] );
// Filterable strings to be excluded from HTML minification.
$exclude = apply_filters( 'autoptimize_filter_html_exclude', '' );
if ( '' !== $exclude ) {
$exclude_arr = array_filter( array_map( 'trim', explode( ',', $exclude ) ) );
$this->exclude = array_merge( $exclude_arr, $this->exclude );
}
// Nothing else for HTML!
return true;
}
/**
* Minifies HTML.
*
* @return bool
*/
public function minify()
{
$noptimize = apply_filters( 'autoptimize_filter_html_noptimize', false, $this->content );
if ( $noptimize ) {
return false;
}
// Wrap the to-be-excluded strings in noptimize tags.
foreach ( $this->exclude as $str ) {
if ( false !== strpos( $this->content, $str ) ) {
$replacement = '<!--noptimize-->' . $str . '<!--/noptimize-->';
$this->content = str_replace( $str, $replacement, $this->content );
}
}
// Noptimize.
$this->content = $this->hide_noptimize( $this->content );
// Preparing options for Minify_HTML.
$options = array( 'keepComments' => $this->keepcomments );
if ( $this->forcexhtml ) {
$options['xhtml'] = true;
}
// Optionally minify inline JS & CSS.
if ( $this->minify_inline ) {
if ( false != autoptimizeOptionWrapper::get_option( 'autoptimize_js' ) && false != autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_js_optimize' ) && true !== apply_filters( 'autoptimize_filter_js_noptimize', false, $this->content ) && false === strpos( $this->content, 'text/template' ) ) {
$options['jsMinifier'] = 'JSMin::minify';
}
if ( false != autoptimizeOptionWrapper::get_option( 'autoptimize_css' ) && false != autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_css_optimize' ) && true !== apply_filters( 'autoptimize_filter_css_noptimize', false, $this->content ) ) {
$options['cssMinifier'] = 'autoptimizeCSSmin::minify';
}
}
$tmp_content = AO_Minify_HTML::minify( $this->content, $options );
if ( ! empty( $tmp_content ) ) {
$this->content = $tmp_content;
unset( $tmp_content );
}
// Restore noptimize.
$this->content = $this->restore_noptimize( $this->content );
// Remove the noptimize-wrapper from around the excluded strings.
foreach ( $this->exclude as $str ) {
$replacement = '<!--noptimize-->' . $str . '<!--/noptimize-->';
if ( false !== strpos( $this->content, $replacement ) ) {
$this->content = str_replace( $replacement, $str, $this->content );
}
}
// Revslider data attribs somehow suffer from HTML optimization, this fixes that!
if ( class_exists( 'RevSlider' ) && apply_filters( 'autoptimize_filter_html_dataattrib_cleanup', false ) ) {
$this->content = preg_replace( '#\n(data-.*$)\n#Um', ' $1 ', $this->content );
$this->content = preg_replace( '#<[^>]*(=\"[^"\'<>\s]*\")(\w)#', '$1 $2', $this->content );
}
return true;
}
/**
* Doesn't do much in case of HTML (no cache in css/js sense there)
*
* @return true
*/
public function cache()
{
return true;
}
/**
* Returns the HTML markup.
*
* @return string
*/
public function getcontent()
{
return $this->content;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,855 @@
<?php
/**
* Wraps base plugin logic/hooks and handles activation/deactivation/uninstall.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeMain
{
const INIT_EARLIER_PRIORITY = -1;
const DEFAULT_HOOK_PRIORITY = 2;
/**
* Version string.
*
* @var string
*/
protected $version = null;
/**
* Main plugin filepath.
* Used for activation/deactivation/uninstall hooks.
*
* @var string
*/
protected $filepath = null;
/**
* Critical CSS base object
*
* @var object
*/
protected $_criticalcss = null;
/**
* Constructor.
*
* @param string $version Version.
* @param string $filepath Filepath. Needed for activation/deactivation/uninstall hooks.
*/
public function __construct( $version, $filepath )
{
$this->version = $version;
$this->filepath = $filepath;
}
public function run()
{
$this->add_hooks();
// Runs cache size checker.
$checker = new autoptimizeCacheChecker();
$checker->run();
}
protected function add_hooks()
{
if ( ! defined( 'AUTOPTIMIZE_SETUP_INITHOOK' ) ) {
define( 'AUTOPTIMIZE_SETUP_INITHOOK', 'plugins_loaded' );
}
add_action( AUTOPTIMIZE_SETUP_INITHOOK, array( $this, 'setup' ) );
add_action( AUTOPTIMIZE_SETUP_INITHOOK, array( $this, 'hook_page_cache_purge' ) );
add_action( 'autoptimize_setup_done', array( $this, 'version_upgrades_check' ) );
add_action( 'autoptimize_setup_done', array( $this, 'check_cache_and_run' ) );
add_action( 'autoptimize_setup_done', array( $this, 'maybe_run_ao_compat' ), 10 );
add_action( 'autoptimize_setup_done', array( $this, 'maybe_run_ao_extra' ), 15 );
add_action( 'autoptimize_setup_done', array( $this, 'maybe_run_admin_only_trinkets' ), 20 );
add_action( 'autoptimize_setup_done', array( $this, 'maybe_run_criticalcss' ), 11 );
add_action( 'autoptimize_setup_done', array( $this, 'maybe_run_notfound_fallback' ), 10 );
add_action( 'init', array( $this, 'load_textdomain' ) );
if ( is_multisite() && is_admin() ) {
// Only if multisite and if in admin we want to check if we need to save options on network level.
add_action( 'init', 'autoptimizeOptionWrapper::check_multisite_on_saving_options' );
}
// register uninstall & deactivation hooks.
register_uninstall_hook( $this->filepath, 'autoptimizeMain::on_uninstall' );
register_deactivation_hook( $this->filepath, 'autoptimizeMain::on_deactivation' );
}
public function load_textdomain()
{
load_plugin_textdomain( 'autoptimize' );
}
public function setup()
{
// Do we gzip in php when caching or is the webserver doing it?
define( 'AUTOPTIMIZE_CACHE_NOGZIP', (bool) autoptimizeOptionWrapper::get_option( 'autoptimize_cache_nogzip' ) );
// These can be overridden by specifying them in wp-config.php or such.
if ( ! defined( 'AUTOPTIMIZE_WP_CONTENT_NAME' ) ) {
define( 'AUTOPTIMIZE_WP_CONTENT_NAME', '/' . wp_basename( WP_CONTENT_DIR ) );
}
if ( ! defined( 'AUTOPTIMIZE_CACHE_CHILD_DIR' ) ) {
define( 'AUTOPTIMIZE_CACHE_CHILD_DIR', '/cache/autoptimize/' );
}
if ( ! defined( 'AUTOPTIMIZE_CACHEFILE_PREFIX' ) ) {
define( 'AUTOPTIMIZE_CACHEFILE_PREFIX', 'autoptimize_' );
}
// Note: trailing slash is not optional!
if ( ! defined( 'AUTOPTIMIZE_CACHE_DIR' ) ) {
define( 'AUTOPTIMIZE_CACHE_DIR', autoptimizeCache::get_pathname() );
}
define( 'WP_ROOT_DIR', substr( WP_CONTENT_DIR, 0, strlen( WP_CONTENT_DIR ) - strlen( AUTOPTIMIZE_WP_CONTENT_NAME ) ) );
if ( ! defined( 'AUTOPTIMIZE_WP_SITE_URL' ) ) {
if ( function_exists( 'domain_mapping_siteurl' ) ) {
define( 'AUTOPTIMIZE_WP_SITE_URL', domain_mapping_siteurl( get_current_blog_id() ) );
} else {
define( 'AUTOPTIMIZE_WP_SITE_URL', site_url() );
}
}
if ( ! defined( 'AUTOPTIMIZE_WP_CONTENT_URL' ) ) {
if ( function_exists( 'get_original_url' ) ) {
define( 'AUTOPTIMIZE_WP_CONTENT_URL', str_replace( get_original_url( AUTOPTIMIZE_WP_SITE_URL ), AUTOPTIMIZE_WP_SITE_URL, content_url() ) );
} else {
define( 'AUTOPTIMIZE_WP_CONTENT_URL', content_url() );
}
}
if ( ! defined( 'AUTOPTIMIZE_CACHE_URL' ) ) {
if ( is_multisite() && apply_filters( 'autoptimize_separate_blog_caches', true ) ) {
$blog_id = get_current_blog_id();
define( 'AUTOPTIMIZE_CACHE_URL', AUTOPTIMIZE_WP_CONTENT_URL . AUTOPTIMIZE_CACHE_CHILD_DIR . $blog_id . '/' );
} else {
define( 'AUTOPTIMIZE_CACHE_URL', AUTOPTIMIZE_WP_CONTENT_URL . AUTOPTIMIZE_CACHE_CHILD_DIR );
}
}
if ( ! defined( 'AUTOPTIMIZE_WP_ROOT_URL' ) ) {
define( 'AUTOPTIMIZE_WP_ROOT_URL', str_replace( AUTOPTIMIZE_WP_CONTENT_NAME, '', AUTOPTIMIZE_WP_CONTENT_URL ) );
}
if ( ! defined( 'AUTOPTIMIZE_HASH' ) ) {
define( 'AUTOPTIMIZE_HASH', wp_hash( AUTOPTIMIZE_CACHE_URL ) );
}
if ( ! defined( 'AUTOPTIMIZE_SITE_DOMAIN' ) ) {
define( 'AUTOPTIMIZE_SITE_DOMAIN', parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST ) );
}
// Multibyte-capable string replacements are available with a filter.
// Also requires 'mbstring' extension.
$with_mbstring = apply_filters( 'autoptimize_filter_main_use_mbstring', false );
if ( $with_mbstring ) {
autoptimizeUtils::mbstring_available( \extension_loaded( 'mbstring' ) );
} else {
autoptimizeUtils::mbstring_available( false );
}
do_action( 'autoptimize_setup_done' );
}
/**
* Checks if there's a need to upgrade/update options and whatnot,
* in which case we might need to do stuff and flush the cache
* to avoid old versions of aggregated files lingering around.
*/
public function version_upgrades_check()
{
autoptimizeVersionUpdatesHandler::check_installed_and_update( $this->version );
}
public function check_cache_and_run()
{
if ( autoptimizeCache::cacheavail() ) {
$conf = autoptimizeConfig::instance();
if ( $conf->get( 'autoptimize_html' ) || $conf->get( 'autoptimize_js' ) || $conf->get( 'autoptimize_css' ) || autoptimizeImages::imgopt_active() || autoptimizeImages::should_lazyload_wrapper() ) {
if ( ! defined( 'AUTOPTIMIZE_NOBUFFER_OPTIMIZE' ) ) {
// Hook into WordPress frontend.
if ( defined( 'AUTOPTIMIZE_INIT_EARLIER' ) ) {
add_action(
'init',
array( $this, 'start_buffering' ),
self::INIT_EARLIER_PRIORITY
);
} else {
if ( ! defined( 'AUTOPTIMIZE_HOOK_INTO' ) ) {
define( 'AUTOPTIMIZE_HOOK_INTO', 'template_redirect' );
}
add_action(
constant( 'AUTOPTIMIZE_HOOK_INTO' ),
array( $this, 'start_buffering' ),
self::DEFAULT_HOOK_PRIORITY
);
}
}
// And disable Jetpack's site accelerator if JS or CSS opt. are active.
if ( class_exists( 'Jetpack' ) && apply_filters( 'autoptimize_filter_main_disable_jetpack_cdn', true ) && ( $conf->get( 'autoptimize_js' ) || $conf->get( 'autoptimize_css' ) || autoptimizeImages::imgopt_active() ) ) {
add_filter( 'jetpack_force_disable_site_accelerator', '__return_true' ); // this does not seemt to work any more?
if ( true === autoptimizeImages::imgopt_active() ) {
// only disable photon if AO is optimizing images.
add_filter( 'jetpack_photon_skip_for_url', '__return_true' );
}
}
// Add "no cache found" notice.
add_action( 'admin_notices', 'autoptimizeMain::notice_nopagecache', 99 );
add_action( 'admin_notices', 'autoptimizeMain::notice_potential_conflict', 99 );
}
} else {
add_action( 'admin_notices', 'autoptimizeMain::notice_cache_unavailable' );
}
}
public function maybe_run_ao_extra()
{
if ( apply_filters( 'autoptimize_filter_extra_activate', true ) ) {
$ao_imgopt = new autoptimizeImages();
$ao_imgopt->run();
$ao_extra = new autoptimizeExtra();
$ao_extra->run();
// And show the imgopt notice.
add_action( 'admin_notices', 'autoptimizeMain::notice_plug_imgopt' );
add_action( 'admin_notices', 'autoptimizeMain::notice_imgopt_issue' );
}
}
public function maybe_run_admin_only_trinkets()
{
// Loads partners tab and exit survey code if in admin (and not in admin-ajax.php)!
if ( autoptimizeConfig::is_admin_and_not_ajax() ) {
new autoptimizePartners();
new autoptimizeExitSurvey();
new autoptimizeProTab();
}
}
public function criticalcss()
{
if ( apply_filters( 'autoptimize_filter_criticalcss_active', true ) && ! autoptimizeUtils::is_plugin_active( 'autoptimize-criticalcss/ao_criticss_aas.php' ) ) {
return $this->_criticalcss;
} else {
return false;
}
}
public function maybe_run_criticalcss()
{
// Loads criticalcss if the filter returns true & old power-up is not active.
if ( apply_filters( 'autoptimize_filter_criticalcss_active', true ) && ! autoptimizeUtils::is_plugin_active( 'autoptimize-criticalcss/ao_criticss_aas.php' ) ) {
$this->_criticalcss = new autoptimizeCriticalCSSBase();
$this->_criticalcss->setup();
$this->_criticalcss->load_requires();
}
}
public function maybe_run_notfound_fallback()
{
if ( autoptimizeCache::do_fallback() ) {
add_action( 'template_redirect', array( 'autoptimizeCache', 'wordpress_notfound_fallback' ) );
}
}
public function maybe_run_ao_compat()
{
$conf = autoptimizeConfig::instance();
// Condtionally loads the compatibility-class to ensure more out-of-the-box compatibility with big players.
$_run_compat = true;
if ( 'on' === $conf->get( 'autoptimize_installed_before_compatibility' ) ) {
// If AO was already running before Compatibility logic was added, don't run compat by default
// because it can be assumed everything works and we want to avoid (perf) regressions that
// could occur due to compatibility code.
$_run_compat = false;
}
if ( apply_filters( 'autoptimize_filter_init_compatibility', $_run_compat ) ) {
new autoptimizeCompatibility();
}
}
public function hook_page_cache_purge()
{
// hook into a collection of page cache purge actions if filter allows.
if ( apply_filters( 'autoptimize_filter_main_hookpagecachepurge', true ) ) {
$page_cache_purge_actions = array(
'after_rocket_clean_domain', // exists.
'hyper_cache_purged', // Stefano confirmed this will be added.
'w3tc_flush_posts', // exits.
'w3tc_flush_all', // exists.
'ce_action_cache_cleared', // Sven confirmed this will be added.
'aoce_action_cache_cleared', // Some other cache enabler.
'comet_cache_wipe_cache', // still to be confirmed by Raam.
'wp_cache_cleared', // cfr. https://github.com/Automattic/wp-super-cache/pull/537.
'wpfc_delete_cache', // Emre confirmed this will be added this.
'swift_performance_after_clear_all_cache', // swift perf. yeah!
'wpo_cache_flush', // wp-optimize.
'rt_nginx_helper_after_fastcgi_purge_all', // nginx helper.
);
$page_cache_purge_actions = apply_filters( 'autoptimize_filter_main_pagecachepurgeactions', $page_cache_purge_actions );
foreach ( $page_cache_purge_actions as $purge_action ) {
add_action( $purge_action, 'autoptimizeCache::clearall_actionless' );
}
}
}
/**
* Setup output buffering if needed.
*
* @return void
*/
public function start_buffering()
{
if ( $this->should_buffer() ) {
// Load speedupper conditionally (true by default).
if ( apply_filters( 'autoptimize_filter_speedupper', true ) ) {
$ao_speedupper = new autoptimizeSpeedupper();
}
$conf = autoptimizeConfig::instance();
if ( $conf->get( 'autoptimize_js' ) ) {
if ( ! defined( 'CONCATENATE_SCRIPTS' ) ) {
define( 'CONCATENATE_SCRIPTS', false );
}
if ( ! defined( 'COMPRESS_SCRIPTS' ) ) {
define( 'COMPRESS_SCRIPTS', false );
}
}
if ( $conf->get( 'autoptimize_css' ) ) {
if ( ! defined( 'COMPRESS_CSS' ) ) {
define( 'COMPRESS_CSS', false );
}
}
if ( apply_filters( 'autoptimize_filter_obkiller', false ) ) {
while ( ob_get_level() > 0 ) {
ob_end_clean();
}
}
// Now, start the real thing!
ob_start( array( $this, 'end_buffering' ) );
}
}
/**
* Returns true if all the conditions to start output buffering are satisfied.
*
* @param bool $doing_tests Allows overriding the optimization of only
* deciding once per request (for use in tests).
* @return bool
*/
public static function should_buffer( $doing_tests = false )
{
static $do_buffering = null;
// Only check once in case we're called multiple times by others but
// still allows multiple calls when doing tests.
if ( null === $do_buffering || $doing_tests ) {
$ao_noptimize = false;
// Checking for DONOTMINIFY constant as used by e.g. WooCommerce POS.
if ( defined( 'DONOTMINIFY' ) && ( constant( 'DONOTMINIFY' ) === true || constant( 'DONOTMINIFY' ) === 'true' ) ) {
$ao_noptimize = true;
}
// Skip checking query strings if they're disabled.
if ( apply_filters( 'autoptimize_filter_honor_qs_noptimize', true ) ) {
// Check for `ao_noptimize` (and other) keys in the query string
// to get non-optimized page for debugging.
$keys = array(
'ao_noptimize',
'ao_noptirocket',
);
foreach ( $keys as $key ) {
if ( array_key_exists( $key, $_GET ) && '1' === $_GET[ $key ] ) {
$ao_noptimize = true;
break;
}
}
}
// If setting says not to optimize logged in user and user is logged in...
if ( false === $ao_noptimize && 'on' !== autoptimizeOptionWrapper::get_option( 'autoptimize_optimize_logged', 'on' ) && is_user_logged_in() && current_user_can( 'edit_posts' ) ) {
$ao_noptimize = true;
}
// If setting says not to optimize cart/checkout.
if ( false === $ao_noptimize && 'on' !== autoptimizeOptionWrapper::get_option( 'autoptimize_optimize_checkout', 'off' ) ) {
// Checking for woocommerce, easy digital downloads and wp ecommerce...
foreach ( array( 'is_checkout', 'is_cart', 'is_account_page', 'edd_is_checkout', 'wpsc_is_cart', 'wpsc_is_checkout' ) as $func ) {
if ( function_exists( $func ) && $func() ) {
$ao_noptimize = true;
break;
}
}
}
// Misc. querystring paramaters that will stop AO from doing optimizations (pagebuilders +
// 2 generic parameters that could/ should become standard between optimization plugins?).
if ( false === $ao_noptimize ) {
$_qs_showstoppers = array( 'no_cache', 'no_optimize', 'tve', 'elementor-preview', 'fl_builder', 'vc_action', 'et_fb', 'bt-beaverbuildertheme', 'ct_builder', 'fb-edit', 'siteorigin_panels_live_editor', 'preview', 'td_action' );
// doing Jonathan a quick favor to allow correct unused CSS generation ;-) .
if ( apply_filters( 'autoptimize_filter_main_showstoppers_do_wp_rocket_a_favor', true ) ) {
$_qs_showstoppers[] = 'nowprocket';
}
foreach ( $_qs_showstoppers as $_showstopper ) {
if ( array_key_exists( $_showstopper, $_GET ) ) {
$ao_noptimize = true;
break;
}
}
}
// Also honor PageSpeed=off parameter as used by mod_pagespeed, in use by some pagebuilders,
// see https://www.modpagespeed.com/doc/experiment#ModPagespeed for info on that.
if ( false === $ao_noptimize && array_key_exists( 'PageSpeed', $_GET ) && 'off' === $_GET['PageSpeed'] ) {
$ao_noptimize = true;
}
// If page/ post check post_meta to see if optimize is off.
if ( false === autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_optimize' ) ) {
$ao_noptimize = true;
}
// And finally allows blocking of autoptimization on your own terms regardless of above decisions.
$ao_noptimize = (bool) apply_filters( 'autoptimize_filter_noptimize', $ao_noptimize );
// Check for site being previewed in the Customizer (available since WP 4.0).
$is_customize_preview = false;
if ( function_exists( 'is_customize_preview' ) && is_customize_preview() ) {
$is_customize_preview = is_customize_preview();
}
// explicitly disable when is_login exists and is true but don't use it direclty because older versions of WordPress don't have that yet.
$is_login = false;
if ( function_exists( 'is_login' ) && true === is_login() ) {
$is_login = true;
}
/**
* We only buffer the frontend requests (and then only if not a feed
* and not turned off explicitly and not when being previewed in Customizer)!
* NOTE: Tests throw a notice here due to is_feed() being called
* while the main query hasn't been ran yet. Thats why we use
* AUTOPTIMIZE_INIT_EARLIER in tests.
*/
$do_buffering = ( ! is_admin() && ! is_feed() && ! is_embed() && ! $is_login && ! $is_customize_preview && ! $ao_noptimize );
}
return $do_buffering;
}
/**
* Returns true if given markup is considered valid/processable/optimizable.
*
* @param string $content Markup.
*
* @return bool
*/
public function is_valid_buffer( $content )
{
// Defaults to true.
$valid = true;
$has_no_html_tag = ( false === stripos( $content, '<html' ) );
$has_xsl_stylesheet = ( false !== stripos( $content, '<xsl:stylesheet' ) || false !== stripos( $content, '<?xml-stylesheet' ) );
$has_html5_doctype = ( preg_match( '/^<!DOCTYPE.+html>/i', ltrim( $content ) ) > 0 );
$has_noptimize_page = ( false !== stripos( $content, '<!-- noptimize-page -->' ) );
if ( $has_no_html_tag ) {
// Can't be valid amp markup without an html tag preceding it.
$is_amp_markup = false;
} else {
$is_amp_markup = self::is_amp_markup( $content );
}
// If it's not html, or if it's amp or contains xsl stylesheets we don't touch it.
if ( $has_no_html_tag && ! $has_html5_doctype || $is_amp_markup || $has_xsl_stylesheet || $has_noptimize_page ) {
$valid = false;
}
return $valid;
}
/**
* Returns true if given $content is considered to be AMP markup.
* This is far from actual validation against AMP spec, but it'll do for now.
*
* @param string $content Markup to check.
*
* @return bool
*/
public static function is_amp_markup( $content )
{
// Short-circuit if the page is already AMP from the start.
if (
preg_match(
sprintf(
'#^(?:<!.*?>|\s+)*+<html(?=\s)[^>]*?\s(%1$s|%2$s|%3$s)(\s|=|>)#is',
'amp',
"\xE2\x9A\xA1", // From \AmpProject\Attribute::AMP_EMOJI.
"\xE2\x9A\xA1\xEF\xB8\x8F" // From \AmpProject\Attribute::AMP_EMOJI_ALT, per https://github.com/ampproject/amphtml/issues/25990.
),
$content
)
) {
return true;
}
// Or else short-circuit if the AMP plugin will be processing the output to be an AMP page.
if ( function_exists( 'amp_is_request' ) ) {
return amp_is_request(); // For AMP plugin v2.0+.
} elseif ( function_exists( 'is_amp_endpoint' ) ) {
return is_amp_endpoint(); // For older/other AMP plugins (still supported in 2.0 as an alias).
}
return false;
}
/**
* Processes/optimizes the output-buffered content and returns it.
* If the content is not processable, it is returned unmodified.
*
* @param string $content Buffered content.
*
* @return string
*/
public function end_buffering( $content )
{
// Bail early without modifying anything if we can't handle the content.
if ( ! $this->is_valid_buffer( $content ) ) {
return $content;
}
$conf = autoptimizeConfig::instance();
// Determine what needs to be ran.
$classes = array();
if ( $conf->get( 'autoptimize_js' ) ) {
$classes[] = 'autoptimizeScripts';
}
if ( $conf->get( 'autoptimize_css' ) ) {
$classes[] = 'autoptimizeStyles';
}
if ( $conf->get( 'autoptimize_html' ) ) {
$classes[] = 'autoptimizeHTML';
}
$classoptions = array(
'autoptimizeScripts' => array(
'aggregate' => $conf->get( 'autoptimize_js_aggregate' ),
'defer_not_aggregate' => $conf->get( 'autoptimize_js_defer_not_aggregate' ),
'defer_inline' => $conf->get( 'autoptimize_js_defer_inline' ),
'justhead' => $conf->get( 'autoptimize_js_justhead' ),
'forcehead' => $conf->get( 'autoptimize_js_forcehead' ),
'trycatch' => $conf->get( 'autoptimize_js_trycatch' ),
'js_exclude' => $conf->get( 'autoptimize_js_exclude' ),
'cdn_url' => $conf->get( 'autoptimize_cdn_url' ),
'include_inline' => $conf->get( 'autoptimize_js_include_inline' ),
'minify_excluded' => $conf->get( 'autoptimize_minify_excluded' ),
),
'autoptimizeStyles' => array(
'aggregate' => $conf->get( 'autoptimize_css_aggregate' ),
'justhead' => $conf->get( 'autoptimize_css_justhead' ),
'datauris' => $conf->get( 'autoptimize_css_datauris' ),
'defer' => $conf->get( 'autoptimize_css_defer' ),
'defer_inline' => $conf->get( 'autoptimize_css_defer_inline' ),
'inline' => $conf->get( 'autoptimize_css_inline' ),
'css_exclude' => $conf->get( 'autoptimize_css_exclude' ),
'cdn_url' => $conf->get( 'autoptimize_cdn_url' ),
'include_inline' => $conf->get( 'autoptimize_css_include_inline' ),
'nogooglefont' => $conf->get( 'autoptimize_css_nogooglefont' ),
'minify_excluded' => $conf->get( 'autoptimize_minify_excluded' ),
),
'autoptimizeHTML' => array(
'keepcomments' => $conf->get( 'autoptimize_html_keepcomments' ),
'minify_inline' => $conf->get( 'autoptimize_html_minify_inline' ),
),
);
$content = apply_filters( 'autoptimize_filter_html_before_minify', $content );
// Run the classes!
foreach ( $classes as $name ) {
$instance = new $name( $content );
if ( $instance->read( $classoptions[ $name ] ) ) {
$instance->minify();
$instance->cache();
$content = $instance->getcontent();
}
unset( $instance );
}
$content = apply_filters( 'autoptimize_html_after_minify', $content );
return $content;
}
public static function autoptimize_nobuffer_optimize( $html_in ) {
$html_out = $html_in;
if ( apply_filters( 'autoptimize_filter_speedupper', true ) ) {
$ao_speedupper = new autoptimizeSpeedupper();
}
$self = new self( AUTOPTIMIZE_PLUGIN_VERSION, AUTOPTIMIZE_PLUGIN_FILE );
if ( $self->should_buffer() ) {
$html_out = $self->end_buffering( $html_in );
}
return $html_out;
}
public static function on_uninstall()
{
// clear the cache.
autoptimizeCache::clearall();
// remove postmeta if active.
if ( autoptimizeConfig::is_ao_meta_settings_active() ) {
delete_post_meta_by_key( 'ao_post_optimize' );
}
// remove all options.
$delete_options = array(
'autoptimize_cache_clean',
'autoptimize_cache_nogzip',
'autoptimize_css',
'autoptimize_css_aggregate',
'autoptimize_css_datauris',
'autoptimize_css_justhead',
'autoptimize_css_defer',
'autoptimize_css_defer_inline',
'autoptimize_css_inline',
'autoptimize_css_exclude',
'autoptimize_html',
'autoptimize_html_keepcomments',
'autoptimize_html_minify_inline',
'autoptimize_enable_site_config',
'autoptimize_enable_meta_ao_settings',
'autoptimize_js',
'autoptimize_js_aggregate',
'autoptimize_js_defer_not_aggregate',
'autoptimize_js_defer_inline',
'autoptimize_js_exclude',
'autoptimize_js_forcehead',
'autoptimize_js_justhead',
'autoptimize_js_trycatch',
'autoptimize_version',
'autoptimize_show_adv',
'autoptimize_cdn_url',
'autoptimize_cachesize_notice',
'autoptimize_css_include_inline',
'autoptimize_js_include_inline',
'autoptimize_optimize_logged',
'autoptimize_optimize_checkout',
'autoptimize_extra_settings',
'autoptimize_service_availablity',
'autoptimize_imgopt_provider_stat',
'autoptimize_imgopt_launched',
'autoptimize_imgopt_settings',
'autoptimize_minify_excluded',
'autoptimize_cache_fallback',
'autoptimize_ccss_rules',
'autoptimize_ccss_additional',
'autoptimize_ccss_queue',
'autoptimize_ccss_viewport',
'autoptimize_ccss_finclude',
'autoptimize_ccss_rlimit',
'autoptimize_ccss_rtimelimit',
'autoptimize_ccss_noptimize',
'autoptimize_ccss_debug',
'autoptimize_ccss_key',
'autoptimize_ccss_keyst',
'autoptimize_ccss_version',
'autoptimize_ccss_loggedin',
'autoptimize_ccss_forcepath',
'autoptimize_ccss_deferjquery',
'autoptimize_ccss_domain',
'autoptimize_ccss_unloadccss',
'autoptimize_installed_before_compatibility',
);
if ( ! is_multisite() ) {
foreach ( $delete_options as $del_opt ) {
delete_option( $del_opt );
}
autoptimizeMain::remove_cronjobs();
} else {
global $wpdb;
$blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
$original_blog_id = get_current_blog_id();
foreach ( $blog_ids as $blog_id ) {
switch_to_blog( $blog_id );
foreach ( $delete_options as $del_opt ) {
delete_option( $del_opt );
}
autoptimizeMain::remove_cronjobs();
}
switch_to_blog( $original_blog_id );
}
// Remove AO CCSS cached files and directory.
$ao_ccss_dir = WP_CONTENT_DIR . '/uploads/ao_ccss/';
if ( file_exists( $ao_ccss_dir ) && is_dir( $ao_ccss_dir ) && defined( 'GLOB_BRACE' ) ) {
// fixme: should check for subdirs when in multisite and remove contents of those as well.
// fixme: if GLOB_BRACE is not avaible we need to remove AO_CCSS_DIR differently?
array_map( 'unlink', glob( $ao_ccss_dir . '*.{css,html,json,log,zip,lock}', GLOB_BRACE ) );
rmdir( $ao_ccss_dir );
}
// Remove 404-handler (although that should have been removed in clearall already).
$_fallback_php = trailingslashit( WP_CONTENT_DIR ) . 'autoptimize_404_handler.php';
if ( file_exists( $_fallback_php ) ) {
unlink( $_fallback_php );
}
}
public static function on_deactivation()
{
if ( is_multisite() && is_network_admin() ) {
global $wpdb;
$blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
$original_blog_id = get_current_blog_id();
foreach ( $blog_ids as $blog_id ) {
switch_to_blog( $blog_id );
autoptimizeMain::remove_cronjobs();
}
switch_to_blog( $original_blog_id );
} else {
autoptimizeMain::remove_cronjobs();
}
autoptimizeCache::clearall();
}
public static function remove_cronjobs() {
// Remove scheduled events.
foreach ( array( 'ao_cachechecker', 'ao_ccss_queue', 'ao_ccss_maintenance', 'ao_ccss_keychecker' ) as $_event ) {
if ( wp_get_schedule( $_event ) ) {
wp_clear_scheduled_hook( $_event );
}
}
}
public static function notice_cache_unavailable()
{
echo '<div class="error"><p>';
// Translators: %s is the cache directory location.
printf( esc_html__( 'Autoptimize cannot write to the cache directory (%s), please fix to enable CSS/ JS optimization!', 'autoptimize' ), AUTOPTIMIZE_CACHE_DIR );
echo '</p></div>';
}
public static function notice_installed()
{
echo '<div class="updated"><p>';
// translators: the variables contain opening and closing <a> tags to link to the settings page.
printf( esc_html__( 'Thank you for installing and activating Autoptimize. Your site is being optimized immediately, please test the frontend to ensure everything still works as expected. If needed you can change JavaScript or CSS optimization settings under %1$sSettings -> Autoptimize%2$s .', 'autoptimize' ), '<a href="options-general.php?page=autoptimize">', '</a>' );
echo '</p></div>';
}
public static function notice_updated()
{
echo '<div class="updated"><p>';
printf( esc_html__( 'Autoptimize has just been updated. Please %1$stest your site now%2$s and adapt Autoptimize config if needed.', 'autoptimize' ), '<strong>', '</strong>' );
echo '</p></div>';
}
public static function notice_plug_imgopt()
{
// Translators: the URL added points to the Autopmize Extra settings.
$_ao_imgopt_plug_notice = sprintf( esc_html__( 'Did you know that Autoptimize offers on-the-fly image optimization (with support for WebP and AVIF) and CDN via ShortPixel? Check out the %1$sAutoptimize Image settings%2$s to enable this option.', 'autoptimize' ), '<a href="options-general.php?page=autoptimize_imgopt">', '</a>' );
$_ao_imgopt_plug_notice = apply_filters( 'autoptimize_filter_main_imgopt_plug_notice', $_ao_imgopt_plug_notice );
$_ao_imgopt_launch_ok = autoptimizeImages::launch_ok_wrapper();
$_ao_imgopt_plug_dismissible = 'ao-img-opt-plug-123';
$_ao_imgopt_active = autoptimizeImages::imgopt_active();
$_is_ao_settings_page = autoptimizeUtils::is_ao_settings();
if ( current_user_can( 'manage_options' ) && ! defined( 'AO_PRO_VERSION' ) && $_is_ao_settings_page && '' !== $_ao_imgopt_plug_notice && ! $_ao_imgopt_active && $_ao_imgopt_launch_ok && PAnD::is_admin_notice_active( $_ao_imgopt_plug_dismissible ) ) {
echo '<div class="notice notice-info is-dismissible" data-dismissible="' . $_ao_imgopt_plug_dismissible . '"><p>';
echo $_ao_imgopt_plug_notice;
echo '</p></div>';
}
}
public static function notice_imgopt_issue()
{
// Translators: the URL added points to the Autopmize Extra settings.
$_ao_imgopt_issue_notice = sprintf( esc_html__( 'Shortpixel reports it cannot always reach your site, which might mean some images are not optimized. You can %1$sread more about why this happens and how you can fix that problem here%2$s.', 'autoptimize' ), '<a href="https://shortpixel.com/knowledge-base/article/469-i-received-an-e-mail-that-says-some-of-my-images-are-not-accessible-what-should-i-do#fullarticle" target="_blank">', '</a>' );
$_ao_imgopt_issue_notice = apply_filters( 'autoptimize_filter_main_imgopt_issue_notice', $_ao_imgopt_issue_notice );
$_ao_imgopt_issue_dismissible = 'ao-img-opt-issue-14';
$_ao_imgopt_active = autoptimizeImages::imgopt_active();
$_ao_imgopt_status = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_provider_stat', '' );
if ( is_array( $_ao_imgopt_status ) && array_key_exists( 'TemporaryRedirectOrigin', $_ao_imgopt_status ) && ( $_ao_imgopt_status['TemporaryRedirectOrigin'] === "true" || $_ao_imgopt_status['TemporaryRedirectOrigin'] === true ) ) {
$_ao_imgopt_status_redirect_warning = true;
} else {
$_ao_imgopt_status_redirect_warning = false;
}
if ( current_user_can( 'manage_options' ) && $_ao_imgopt_active && $_ao_imgopt_status_redirect_warning && '' !== $_ao_imgopt_issue_notice && PAnD::is_admin_notice_active( $_ao_imgopt_issue_dismissible ) ) {
echo '<div class="notice notice-info is-dismissible" data-dismissible="' . $_ao_imgopt_issue_dismissible . '"><p>';
echo $_ao_imgopt_issue_notice;
echo '</p></div>';
}
}
public static function notice_nopagecache()
{
/*
* Autoptimize does not do page caching (yet) but not everyone knows, so below logic tries to find out if page caching is available and if not show a notice on the AO Settings pages.
*
* uses helper function in autoptimizeUtils.php
*/
// translators: strong tags and a break.
$_ao_nopagecache_notice = sprintf( esc_html__( 'It looks like your site might not have %1$spage caching%2$s which is a %1$smust-have for performance%2$s. If you are sure you have a page cache, you can close this notice.%3$sWhen in doubt check with your host if they offer this or install a free page caching plugin like for example KeyCDN Cache Enabler', 'autoptimize' ), '<strong>', '</strong>', '<br />' );
// translators: strong tags.
$_ao_nopagecache_notice .= ' ' . esc_html__('or consider ', 'autoptimize') . '<strong><a href="https://autoptimize.com/pro/">Autoptimize Pro</a></strong>' . sprintf( esc_html__( ' which not only has page caching but also image optimization, critical CSS and advanced booster options %1$sto make your site significantly faster%2$s!', 'autoptimize' ), '<strong>', '</strong>' );
$_ao_nopagecache_dismissible = 'ao-nopagecache-forever'; // the notice is only shown once and will not re-appear when dismissed.
$_is_ao_settings_page = autoptimizeUtils::is_ao_settings();
if ( current_user_can( 'manage_options' ) && $_is_ao_settings_page && PAnD::is_admin_notice_active( $_ao_nopagecache_dismissible ) && true === apply_filters( 'autoptimize_filter_main_show_pagecache_notice', true ) ) {
if ( false === autoptimizeUtils::find_pagecache() ) {
echo '<div class="notice notice-info is-dismissible" data-dismissible="' . $_ao_nopagecache_dismissible . '"><p>';
echo $_ao_nopagecache_notice;
echo '</p></div>';
}
}
}
public static function notice_potential_conflict()
{
/*
* Using other plugins to do CSS/ JS optimization can cause unexpected and hard to troubleshoot issues, warn users who seem to be in that situation.
*/
// Translators: some strong tags + the sentence will be finished with the name of the offending plugin and a final stop.
$_ao_potential_conflict_notice = sprintf( esc_html__( 'It looks like you have %1$sanother plugin also doing CSS and/ or JS optimization%2$s, which can result in hard to troubleshoot %1$sconflicts%2$s. For this reason it is recommended to disable this functionality in', 'autoptimize' ), '<strong>', '</strong>' ) . ' ';
$_ao_potential_conflict_dismissible = 'ao-potential-conflict-forever'; // the notice is only shown once and will not re-appear when dismissed.
$_is_ao_settings_page = autoptimizeUtils::is_ao_settings();
if ( current_user_can( 'manage_options' ) && $_is_ao_settings_page && PAnD::is_admin_notice_active( $_ao_potential_conflict_dismissible ) && true === apply_filters( 'autoptimize_filter_main_show_potential_conclict_notice', true ) ) {
$_potential_conflicts = autoptimizeUtils::find_potential_conflicts();
if ( false !== $_potential_conflicts ) {
$_ao_potential_conflict_notice .= '<strong>' . $_potential_conflicts . '</strong>.';
echo '<div class="notice notice-info is-dismissible" data-dismissible="' . $_ao_potential_conflict_dismissible . '"><p>';
echo $_ao_potential_conflict_notice;
echo '</p></div>';
}
}
}
}

View File

@@ -0,0 +1,351 @@
<?php
/**
* Handles meta box to disable optimizations.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeMetabox
{
public function __construct()
{
$this->run();
}
public function run()
{
add_action( 'add_meta_boxes', array( $this, 'ao_metabox_add_box' ) );
add_action( 'save_post', array( $this, 'ao_metabox_save' ) );
add_action( 'wp_ajax_ao_metabox_ccss_addjob', array( $this, 'ao_metabox_generateccss_callback' ) );
}
public function ao_metabox_add_box()
{
$screens = array(
'post',
'page',
// add extra types e.g. product or ... ?
);
$screens = apply_filters( 'autoptimize_filter_metabox_screens', $screens );
foreach ( $screens as $screen ) {
add_meta_box(
'ao_metabox',
esc_html__( 'Autoptimize this page', 'autoptimize' ),
array( $this, 'ao_metabox_content' ),
$screen,
'side'
);
}
}
/**
* Prints the box content.
*
* @param WP_Post $post The object for the current post/page.
*/
function ao_metabox_content( $post )
{
// phpcs:disable Squiz.ControlStructures.ControlSignature.NewlineAfterOpenBrace
wp_nonce_field( 'ao_metabox', 'ao_metabox_nonce' );
$ao_opt_value = $this->check_ao_opt_sanity( get_post_meta( $post->ID, 'ao_post_optimize', true ) );
$_ao_meta_sub_opacity = '';
if ( 'on' !== $ao_opt_value['ao_post_optimize'] ) {
$_ao_meta_sub_opacity = 'opacity:.33;';
}
?>
<p >
<input type="checkbox" id="autoptimize_post_optimize" class="ao_meta_main" name="ao_post_optimize" <?php echo 'on' !== $ao_opt_value['ao_post_optimize'] ? '' : 'checked="checked" '; ?> />
<label for="autoptimize_post_optimize">
<?php esc_html_e( 'Optimize this page?', 'autoptimize' ); ?>
</label>
</p>
<?php
$_ao_meta_js_style = '';
if ( 'on' !== autoptimizeOptionWrapper::get_option( 'autoptimize_js', false ) ) {
$_ao_meta_js_style = 'display:none;';
}
echo '<p class="ao_meta_sub" style="' . $_ao_meta_sub_opacity . $_ao_meta_js_style . '">';
?>
<input type="checkbox" id="autoptimize_post_optimize_js" name="ao_post_js_optimize" <?php echo 'on' !== $ao_opt_value['ao_post_js_optimize'] ? '' : 'checked="checked" '; ?> />
<label for="autoptimize_post_optimize_js">
<?php esc_html_e( 'Optimize JS?', 'autoptimize' ); ?>
</label>
</p>
<?php
$_ao_meta_css_style = '';
if ( 'on' !== autoptimizeOptionWrapper::get_option( 'autoptimize_css', false ) ) {
$_ao_meta_css_style = 'display:none;';
}
echo '<p class="ao_meta_sub" style="' . $_ao_meta_sub_opacity . $_ao_meta_css_style . '">';
?>
<input type="checkbox" id="autoptimize_post_optimize_css" name="ao_post_css_optimize" <?php echo 'on' !== $ao_opt_value['ao_post_css_optimize'] ? '' : 'checked="checked" '; ?> />
<label for="autoptimize_post_optimize_css">
<?php esc_html_e( 'Optimize CSS?', 'autoptimize' ); ?>
</label>
</p>
<?php
$_ao_meta_ccss_style = '';
if ( 'on' !== autoptimizeOptionWrapper::get_option( 'autoptimize_css_defer', false ) || 'on' !== autoptimizeOptionWrapper::get_option( 'autoptimize_css', false ) ) {
$_ao_meta_ccss_style = 'display:none;';
}
if ( 'on' !== $ao_opt_value['ao_post_css_optimize'] ) {
$_ao_meta_ccss_style .= 'opacity:.33;';
}
echo '<p class="ao_meta_sub ao_meta_sub_css" style="' . $_ao_meta_sub_opacity . $_ao_meta_ccss_style . '">';
?>
<input type="checkbox" id="autoptimize_post_ccss" name="ao_post_ccss" <?php echo 'on' !== $ao_opt_value['ao_post_ccss'] ? '' : 'checked="checked" '; ?> />
<label for="autoptimize_post_ccss">
<?php esc_html_e( 'Inline critical CSS?', 'autoptimize' ); ?>
</label>
</p>
<?php
$_ao_meta_lazyload_style = '';
if ( false === autoptimizeImages::should_lazyload_wrapper( true ) ) {
$_ao_meta_lazyload_style = 'display:none;';
}
echo '<p class="ao_meta_sub" style="' . $_ao_meta_sub_opacity . $_ao_meta_lazyload_style . '">';
?>
<input type="checkbox" id="autoptimize_post_lazyload" name="ao_post_lazyload" <?php echo 'on' !== $ao_opt_value['ao_post_lazyload'] ? '' : 'checked="checked" '; ?> />
<label for="autoptimize_post_lazyload">
<?php esc_html_e( 'Lazyload images?', 'autoptimize' ); ?>
</label>
</p>
<?php
$_ao_meta_preload_style = '';
if ( false === autoptimizeImages::should_lazyload_wrapper() && false === autoptimizeImages::imgopt_active() ) {
// img preload requires imgopt and/ or lazyload to be active.
$_ao_meta_preload_style = 'opacity:.33;';
}
?>
<p class="ao_meta_sub ao_meta_preload" style="<?php echo $_ao_meta_sub_opacity . $_ao_meta_preload_style; ?>">
<label for="autoptimize_post_preload">
<?php esc_html_e( 'LCP Image to preload', 'autoptimize' ); ?>
</label>
<?php
if ( is_array( $ao_opt_value ) && array_key_exists( 'ao_post_preload', $ao_opt_value ) ) {
$_preload_img = esc_attr( $ao_opt_value['ao_post_preload'] );
} else {
$_preload_img = '';
}
?>
<input type="text" id="autoptimize_post_preload" name="ao_post_preload" value="<?php echo $_preload_img; ?>">
</p>
<?php
echo apply_filters( 'autoptimize_filter_metabox_extra_ui', '');
?>
<p>&nbsp;</p>
<p>
<?php
// Get path + check if button should be enabled or disabled.
$_generate_disabled = true;
$_slug = false;
$_type = 'is_single';
// harvest post ID from URL, get permalink from that and extract path from that.
if ( array_key_exists( 'post', $_GET ) ) {
$_slug = str_replace( AUTOPTIMIZE_WP_SITE_URL, '', get_permalink( $_GET['post'] ) );
}
// override the default 'is_single' if post.
global $post;
if ( 'page' === $post->post_type ) {
$_type = 'is_page';
}
// if CSS opt and inline & defer are on and if we have a slug, the button can be active.
if ( false !== $_slug && 'on' === autoptimizeOptionWrapper::get_option( 'autoptimize_css', false ) && 'on' === autoptimizeOptionWrapper::get_option( 'autoptimize_css_defer', false ) && ! empty( apply_filters( 'autoptimize_filter_ccss_key', autoptimizeOptionWrapper::get_option( 'autoptimize_ccss_key', false ) ) ) && '2' === autoptimizeOptionWrapper::get_option( 'autoptimize_ccss_keyst', false ) ) {
$_generate_disabled = false;
}
?>
<button class="button ao_meta_sub ao_meta_sub_css" id="generateccss" style="<?php echo $_ao_meta_sub_opacity . $_ao_meta_ccss_style; ?>" <?php if ( true === $_generate_disabled ) { echo 'disabled'; } ?>><?php esc_html_e( 'Generate Critical CSS', 'autoptimize' ); ?></button>
</p>
<script>
jQuery(document).ready(function() {
jQuery( "#autoptimize_post_optimize" ).change(function() {
if (this.checked) {
jQuery(".ao_meta_sub:visible").fadeTo("fast",1);
} else {
jQuery(".ao_meta_sub:visible").fadeTo("fast",.33);
}
});
jQuery( "#autoptimize_post_optimize_css" ).change(function() {
if (this.checked) {
jQuery(".ao_meta_sub_css:visible").fadeTo("fast",1);
} else {
jQuery(".ao_meta_sub_css:visible").fadeTo("fast",.33);
}
});
jQuery( "#autoptimize_post_ccss" ).change(function() {
if (this.checked) {
jQuery("#generateccss:visible").fadeTo("fast",1);
} else {
jQuery("#generateccss:visible").fadeTo("fast",.33);
}
});
<?php
if ( true === autoptimizeImages::should_lazyload_wrapper() && false === autoptimizeImages::imgopt_active() ) {
?>
jQuery( "#autoptimize_post_lazyload" ).change(function() {
if (this.checked) {
jQuery(".ao_meta_preload:visible").fadeTo("fast",1);
} else {
jQuery(".ao_meta_preload:visible").fadeTo("fast",.33);
}
});
<?php
}
?>
jQuery("#generateccss").click(function(e){
e.preventDefault();
// disable button to avoid it being clicked several times.
jQuery("#generateccss").prop('disabled', true);
var data = {
'action': 'ao_metabox_ccss_addjob',
'path' : '<?php echo $_slug; ?>',
'type' : '<?php echo $_type; ?>',
'ao_ccss_addjob_nonce': '<?php echo wp_create_nonce( 'ao_ccss_addjob_nonce' ); ?>',
};
jQuery.post(ajaxurl, data, function(response) {
response_array=JSON.parse(response);
if (response_array['code'] == 200) {
setCritCSSbutton("<?php esc_html_e( 'Added to CCSS job queue.', 'autoptimize' ); ?>", "green");
} else {
setCritCSSbutton("<?php esc_html_e( 'Could not add to CCSS job queue.', 'autoptimize' ); ?>", "orange");
}
}).fail(function() {
setCritCSSbutton("<?php esc_html_e( 'Sorry, something went wrong.', 'autoptimize' ); ?>", "orange");
});
});
});
function setCritCSSbutton( message, color) {
jQuery("#generateccss").html(message);
jQuery("#generateccss").prop("style","border-color:" + color + "!important; color:" + color + "!important");
}
</script>
<?php
}
/**
* When the post is saved, saves our custom data.
*
* @param int $post_id The ID of the post being saved.
*/
public function ao_metabox_save( $post_id )
{
// If this is an autosave, our form has not been submitted, so we don't want to do anything.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return $post_id;
}
// check if from our own data.
if ( ! isset( $_POST['ao_metabox_nonce'] ) ) {
return $post_id;
}
// Check if our nonce is set and verify if valid.
$nonce = $_POST['ao_metabox_nonce'];
if ( ! wp_verify_nonce( $nonce, 'ao_metabox' ) ) {
return $post_id;
}
// Check the user's permissions.
if ( 'page' === $_POST['post_type'] ) {
if ( ! current_user_can( 'edit_page', $post_id ) ) {
return $post_id;
}
} else {
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return $post_id;
}
}
// OK, we can have a look at the actual data now.
// Sanitize user input.
foreach ( apply_filters( 'autoptimize_filter_meta_valid_optims', array( 'ao_post_optimize', 'ao_post_js_optimize', 'ao_post_css_optimize', 'ao_post_ccss', 'ao_post_lazyload', 'ao_post_preload' ) ) as $opti_type ) {
if ( in_array( $opti_type, apply_filters( 'autoptimize_filter_meta_optim_nonbool', array( 'ao_post_preload' ) ) ) ) {
if ( isset( $_POST[ $opti_type ] ) ) {
$ao_meta_result[ $opti_type ] = sanitize_text_field( $_POST[ $opti_type ] );
} else {
$ao_meta_result[ $opti_type ] = false;
}
} else if ( ! isset( $_POST[ $opti_type ] ) ) {
$ao_meta_result[ $opti_type ] = '';
} else if ( 'on' === $_POST[ $opti_type ] ) {
$ao_meta_result[ $opti_type ] = 'on';
}
}
// Update the meta field in the database.
update_post_meta( $post_id, 'ao_post_optimize', $ao_meta_result );
}
public function ao_metabox_generateccss_callback()
{
check_ajax_referer( 'ao_ccss_addjob_nonce', 'ao_ccss_addjob_nonce' );
if ( current_user_can( 'manage_options' ) && array_key_exists( 'path', $_POST ) && ! empty( $_POST['path'] ) ) {
if ( array_key_exists( 'type', $_POST ) && 'is_page' === $_POST['type'] ) {
$type = 'is_page';
} else {
$type = 'is_single';
}
$path = wp_strip_all_tags( $_POST['path'] );
$criticalcss = autoptimize()->criticalcss();
$_result = $criticalcss->enqueue( '', $path, $type );
if ( $_result ) {
$response['code'] = '200';
$response['string'] = $path . ' added to job queue.';
} else {
$response['code'] = '404';
$response['string'] = 'could not add ' . $path . ' to job queue.';
}
} else {
$response['code'] = '500';
$response['string'] = 'nok';
}
// Dispatch respose.
echo json_encode( $response );
// Close ajax request.
wp_die();
}
public function get_metabox_default_values()
{
$ao_metabox_defaults = array(
'ao_post_optimize' => 'on',
'ao_post_js_optimize' => 'on',
'ao_post_css_optimize' => 'on',
'ao_post_ccss' => 'on',
'ao_post_lazyload' => 'on',
'ao_post_preload' => '',
);
return $ao_metabox_defaults;
}
public function check_ao_opt_sanity( $ao_opt_val ) {
if ( empty( $ao_opt_val ) || ! is_array( $ao_opt_val ) ) {
$ao_opt_val = $this->get_metabox_default_values();
} else {
foreach ( array( 'ao_post_optimize', 'ao_post_js_optimize', 'ao_post_css_optimize', 'ao_post_ccss', 'ao_post_lazyload' ) as $key ) {
if ( ! array_key_exists( $key, $ao_opt_val ) ) {
$ao_opt_val[ $key ] = 'off';
}
}
}
return $ao_opt_val;
}
}

View File

@@ -0,0 +1,150 @@
<?php
/**
* Autoptimize options handler.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* This class takes care of the set and get of option for standalone and multisite WordPress instances.
*/
class autoptimizeOptionWrapper {
/**
* Constructor, add filter on saving options.
*/
public function __construct() {
}
/**
* Ensure that is_plugin_active_for_network function is declared.
*/
public static function maybe_include_plugin_functions() {
if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
include_once ABSPATH . 'wp-admin/includes/plugin.php';
}
}
/**
* Retrieves the option in standalone and multisite instances.
*
* @param string $option Name of option to retrieve. Expected to not be SQL-escaped.
* @param mixed $default Optional. Default value to return if the option does not exist.
* @return mixed Value set for the option.
*/
public static function get_option( $option, $default = false ) {
if ( is_multisite() && self::is_ao_active_for_network() ) {
static $configuration_per_site = null;
if ( null === $configuration_per_site || defined( 'TEST_MULTISITE_FORCE_AO_ON_NETWORK' ) ) {
$configuration_per_site = get_network_option( get_main_network_id(), 'autoptimize_enable_site_config', 'on' );
if ( null === $configuration_per_site ) {
// Config per site is off, set as empty string to make sure the var it is not null any more so it can be cached.
$configuration_per_site = '';
}
}
} else {
// Kind of dummy value as when not on multisite or if AO not network enabled, config is always on site-level.
$configuration_per_site = 'on';
}
// This is always a network setting, it is on by default to ensure settings are available at site level unless explicitly turned off.
if ( 'autoptimize_enable_site_config' === $option ) {
return $configuration_per_site;
}
// If the plugin is network activated and our per site setting is not on, use the network configuration.
if ( is_multisite() && self::is_ao_active_for_network() && ( 'on' !== $configuration_per_site || is_network_admin() ) ) {
return get_network_option( get_main_network_id(), $option, $default );
}
return get_option( $option, $default );
}
/**
* Saves the option in standalone and multisite instances.
*
* @param string $option Option name. Expected to not be SQL-escaped.
* @param mixed $value Option value. Must be serializable if non-scalar. Expected to not be SQL-escaped.
* @param string|bool $autoload Optional. Whether to load the option when WordPress starts up. For existing options,
* `$autoload` can only be updated using `update_option()` if `$value` is also changed.
* Accepts 'yes'|true to enable or 'no'|false to disable. For non-existent options,
* the default value is 'yes'. Default null.
* @return bool False if value was not updated and true if value was updated.
*/
public static function update_option( $option, $value, $autoload = null ) {
if ( self::is_ao_active_for_network() && is_network_admin() ) {
return update_network_option( get_main_network_id(), $option, $value );
} elseif ( 'autoptimize_enable_site_config' !== $option ) {
return update_option( $option, $value, $autoload );
}
}
/**
* Use the pre_update_option filter to check if the option to be saved if from autoptimize and
* in that case, take care of multisite case.
*/
public static function check_multisite_on_saving_options() {
if ( self::is_ao_active_for_network() ) {
add_filter( 'pre_update_option', 'autoptimizeOptionWrapper::update_autoptimize_option_on_network', 10, 3 );
}
}
/**
* The actual magic to differentiate between network options and per-site options.
*
* @param mixed $value Option value.
* @param string $option Option name.
* @param string $old_value Old value.
*/
public static function update_autoptimize_option_on_network( $value, $option, $old_value ) {
if ( strpos( $option, 'autoptimize_' ) === 0 && self::is_options_from_network_admin() ) {
if ( self::is_ao_active_for_network() ) {
update_network_option( get_main_network_id(), $option, $value );
// Return old value, to stop update_option logic.
return $old_value;
}
if ( apply_filters( 'autoptimize_filter_optionwrapper_wp_cache_delete', false ) ) {
// in some (rare) cases options seem to get stuck in WP's Object cache, this should clear it there.
wp_cache_delete( $option );
}
}
return $value;
}
/**
* As options are POST-ed to wp-admin/options.php checking is_network_admin() does not
* work (yet). Instead we compare the network_admin_url with the _wp_http_referer
* (which should always be available as part of a hidden form field).
*/
public static function is_options_from_network_admin() {
static $_really_is_network_admin = null;
if ( null === $_really_is_network_admin ) {
if ( array_key_exists( '_wp_http_referer', $_POST ) && strpos( network_admin_url( 'settings.php' ), strtok( $_POST['_wp_http_referer'], '?' ) ) !== false ) {
$_really_is_network_admin = true;
} else {
$_really_is_network_admin = false;
}
}
return $_really_is_network_admin;
}
/**
* Function to check if AO (including beta) is active for network.
*/
public static function is_ao_active_for_network() {
static $_is_ao_active_for_network = null;
if ( null === $_is_ao_active_for_network || defined( 'TEST_MULTISITE_FORCE_AO_ON_NETWORK' ) ) {
self::maybe_include_plugin_functions();
if ( is_plugin_active_for_network( 'autoptimize/autoptimize.php' ) || is_plugin_active_for_network( 'autoptimize-beta/autoptimize.php' ) || defined( 'TEST_MULTISITE_FORCE_AO_ON_NETWORK' ) ) {
$_is_ao_active_for_network = true;
} else {
$_is_ao_active_for_network = false;
}
}
return $_is_ao_active_for_network;
}
}
new autoptimizeOptionWrapper();

View File

@@ -0,0 +1,153 @@
<?php
/**
* Handles adding "more tools" tab in AO admin settings page which promotes (future) AO
* addons and/or affiliate services.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizePartners
{
public function __construct()
{
$this->run();
}
public function run()
{
if ( $this->enabled() ) {
add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_partner_tabs' ), 10, 1 );
}
if ( is_multisite() && is_network_admin() && autoptimizeOptionWrapper::is_ao_active_for_network() ) {
add_action( 'network_admin_menu', array( $this, 'add_admin_menu' ) );
} else {
add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
}
}
protected function enabled()
{
return apply_filters( 'autoptimize_filter_show_partner_tabs', true );
}
public function add_partner_tabs( $in )
{
$in = array_merge(
$in,
array(
'ao_partners' => esc_html__( 'Optimize More!', 'autoptimize' ),
)
);
return $in;
}
public function add_admin_menu()
{
if ( $this->enabled() ) {
add_submenu_page( '', 'AO partner', 'AO partner', 'manage_options', 'ao_partners', array( $this, 'ao_partners_page' ) );
}
}
protected function get_ao_partner_feed_markup()
{
$no_feed_text = sprintf( esc_html__( 'Have a look at %1$sAutoptimize Pro%2$s to power-up your site!', 'autoptimize' ), '<a href="http://autoptimize.com/pro">', '</a>' );
$output = '';
if ( apply_filters( 'autoptimize_settingsscreen_remotehttp', true ) ) {
$rss = fetch_feed( 'http://feeds.feedburner.com/OptimizingMattersDownloads' );
$maxitems = 0;
if ( ! is_wp_error( $rss ) ) {
$maxitems = $rss->get_item_quantity( 20 );
$rss_items = $rss->get_items( 0, $maxitems );
}
if ( 0 == $maxitems ) {
$output .= $no_feed_text;
} else {
$output .= '<ul>';
foreach ( $rss_items as $item ) {
$item_url = esc_url( $item->get_permalink() );
$enclosure = $item->get_enclosure();
$output .= '<li class="itemDetail">';
$output .= '<h3 class="itemTitle"><a href="' . $item_url . '" target="_blank">' . esc_html( $item->get_title() ) . '</a></h3>';
if ( $enclosure && ( false !== strpos( $enclosure->get_type(), 'image' ) ) ) {
$img_url = esc_url( $enclosure->get_link() );
$output .= '<div class="itemImage"><a href="' . $item_url . '" target="_blank"><img src="' . $img_url . '"></a></div>';
}
$output .= '<div class="itemDescription">' . wp_kses_post( $item->get_description() ) . '</div>';
$output .= '<div class="itemButtonRow"><div class="itemButton button-secondary"><a href="' . $item_url . '" target="_blank">' . esc_html__( 'More info', 'autoptimize' ) . '</a></div></div>';
$output .= '</li>';
}
$output .= '</ul>';
}
} else {
$output .= $no_feed_text;
}
return $output;
}
public function ao_partners_page()
{
?>
<style>
.itemDetail {
background: #fff;
width: 250px;
min-height: 290px;
border: 1px solid #ccc;
float: left;
padding: 15px;
position: relative;
margin: 0 10px 10px 0;
}
.itemTitle {
margin-top:0px;
margin-bottom:10px;
}
.itemImage {
text-align: center;
}
.itemImage img {
max-width: 95%;
max-height: 150px;
}
.itemDescription {
margin-bottom:30px;
}
.itemButtonRow {
position: absolute;
bottom: 10px;
right: 10px;
width:100%;
}
.itemButton {
float:right;
}
.itemButton a {
text-decoration: none;
color: #555;
}
.itemButton a:hover {
text-decoration: none;
color: #23282d;
}
</style>
<script>document.title = "Autoptimize: <?php esc_html_e( 'Optimize More!', 'autoptimize' ); ?> " + document.title;</script>
<div class="wrap">
<h1><?php apply_filters( 'autoptimize_filter_settings_is_pro', false ) ? esc_html_e( 'Autoptimize Pro Settings', 'autoptimize' ) : esc_html_e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
<?php echo autoptimizeConfig::ao_admin_tabs(); ?>
<?php echo '<h2>' . esc_html__( "These Autoptimize power-ups and related services will improve your site's performance even more!", 'autoptimize' ) . '</h2>'; ?>
<div>
<?php echo $this->get_ao_partner_feed_markup(); ?>
</div>
</div>
<?php
}
}

View File

@@ -0,0 +1,133 @@
<?php
/**
* Handles adding "more tools" tab in AO admin settings page which promotes (future) AO
* addons and/or affiliate services.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeProTab
{
/**
* Random title string.
*
* @var string
*/
protected $rnd_title = null;
public function __construct()
{
$this->run();
}
public function run()
{
if ( $this->enabled() ) {
add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_pro_tabs' ), 10, 1 );
}
if ( is_multisite() && is_network_admin() && autoptimizeOptionWrapper::is_ao_active_for_network() ) {
add_action( 'network_admin_menu', array( $this, 'add_admin_menu' ) );
} else {
add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
}
}
protected function enabled()
{
return apply_filters( 'autoptimize_filter_show_partner_tabs', true );
}
public function add_pro_tabs( $in )
{
$in = array_merge(
$in,
array(
'ao_protab' => '&#x1F680; ' . $this->get_rnd_title()
)
);
return $in;
}
public function add_admin_menu()
{
if ( $this->enabled() ) {
add_submenu_page( '', 'AO pro', 'AO pro', 'manage_options', 'ao_protab', array( $this, 'ao_pro_page' ) );
}
}
public function ao_pro_page()
{
?>
<style>
.ao_settings_div {background: white;border: 1px solid #ccc;padding: 1px 15px;margin: 15px 10px 10px 0;font-size: 120% !important; padding-bottom:20px;}
.ao_settings_div p {font-size:110%;}
#aoprocontainer{width:100%;overflow:hidden;}
#aoprotxt{width:68%;float:left;}
#aoprobuy { background:#ba4102;text-align:center;border-radius:25px; }
#aoprobuy p {margin:.25em 1em}
#aoprobuy p#cta {font-size:150%;}
#aoproimg {width:28%;float:right;}
@media (max-width:699px) {
#aoproimg{display:none;}
#aoprotxt{width:100% !important;}
#aoprobuy{font-size:70%;}
}
</style>
<script>document.title = "Autoptimize: <?php echo $this->get_rnd_title() ?> " + document.title;</script>
<div class="wrap">
<h1><?php apply_filters( 'autoptimize_filter_settings_is_pro', false ) ? esc_html_e( 'Autoptimize Pro Settings', 'autoptimize' ) : esc_html_e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
<?php
echo autoptimizeConfig::ao_admin_tabs();
$aopro_explanation = '';
$_transient = 'aopro_explain';
$_explain_html = 'https://misc.optimizingmatters.com/aopro_explain.html?ao_ver=';
// get the HTML with the explanation of what AOPro is.
if ( apply_filters( 'autoptimize_settingsscreen_remotehttp', true ) ) {
$aopro_explanation = get_transient( $_transient );
if ( empty( $aopro_explanation ) ) {
$ccss_expl_resp = wp_remote_get( $_explain_html . AUTOPTIMIZE_PLUGIN_VERSION );
if ( ! is_wp_error( $ccss_expl_resp ) ) {
if ( '200' == wp_remote_retrieve_response_code( $ccss_expl_resp ) ) {
$aopro_explanation = wp_kses_post( wp_remote_retrieve_body( $ccss_expl_resp ) );
set_transient( $_transient, $aopro_explanation, WEEK_IN_SECONDS );
}
}
}
}
// placeholder text in case HTML is empty.
if ( empty( $aopro_explanation ) ) {
// translators: h2, strong but also 2 links.
$aopro_explanation = sprintf( esc_html__( '%1$sAdd more power to Autoptimize with Pro!%2$s%3$sAs a user of Autoptimize you understand %5$sthe importance of having a fast site%6$s. Autoptimize Pro is a premium Power-Up extending AO by adding %5$simage optimization, CDN, automatic critical CSS rules generation and page caching but also providing extra “booster” options%6$s, all in one handy subscription to make your site even faster!%4$s%3$sHave a look at %7$shttps://autoptimize.com/pro/%8$s for more info or %9$sclick here to buy now%10$s!%4$s', 'autoptimize' ), '<h2>', '</h2>', '<p>', '</p>', '<strong>', '</strong>', '<a href="https://autoptimize.com/pro/" target="_blank">', '</a>', '<a href="https://checkout.freemius.com/mode/dialog/plugin/10906/plan/18508/?currency=auto" target="_blank">', '</a>' );
} else {
// we were able to fetch the explenation, so add the JS to show correct language.
$aopro_explanation .= "<script>jQuery('.ao_i18n').hide();d=document;lang=d.getElementsByTagName('html')[0].getAttribute('lang').substring(0,2);if(d.getElementById(lang)!= null){jQuery('#'+lang).show();}else{jQuery('#default').show();}</script>";
}
?>
<div class="ao_settings_div">
<?php
// and echo it.
echo $aopro_explanation;
?>
</div>
</div>
<?php
}
public function get_rnd_title() {
// alternate between tab title every 5 minutes.
if ( floor( date( "i", time() ) / 5 ) %2 === 0 ) {
$this->rnd_title = esc_html__( 'Page Cache', 'autoptimize' );
} else {
$this->rnd_title = esc_html__( 'Pro Boosters', 'autoptimize' );
}
return $this->rnd_title;
}
}

View File

@@ -0,0 +1,861 @@
<?php
/**
* Class for JS optimization.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeScripts extends autoptimizeBase
{
/**
* Stores founds scripts.
*
* @var array
*/
private $scripts = array();
/**
* Stores to be moved JS.
*
* @var array
*/
private $move = array(
'first' => array(),
'last' => array(),
);
/**
* List of not to be moved JS.
*
* @var array
*/
private $dontmove = array(
'document.write',
'html5.js',
'show_ads.js',
'google_ad',
'histats.com/js',
'statcounter.com/counter/counter.js',
'ws.amazon.com/widgets',
'media.fastclick.net',
'/ads/',
'comment-form-quicktags/quicktags.php',
'edToolbar',
'intensedebate.com',
'scripts.chitika.net/',
'_gaq.push',
'jotform.com/',
'admin-bar.min.js',
'GoogleAnalyticsObject',
'plupload.full.min.js',
'syntaxhighlighter',
'adsbygoogle',
'gist.github.com',
'_stq',
'nonce',
'post_id',
'data-noptimize',
'data-cfasync',
'data-pagespeed-no-defer',
'logHuman',
'amp-mobile-version-switcher',
'data-rocketlazyloadscript',
'rocket-browser-checker-js-after',
);
/**
* List of to be moved JS.
*
* @var array
*/
private $domove = array(
'gaJsHost',
'load_cmc',
'jd.gallery.transitions.js',
'swfobject.embedSWF(',
'tiny_mce.js',
'tinyMCEPreInit.go',
);
/**
* List of JS that can be moved last (not used any more).
*
* @var array
*/
private $domovelast = array(
'addthis.com',
'/afsonline/show_afs_search.js',
'disqus.js',
'networkedblogs.com/getnetworkwidget',
'infolinks.com/js/',
'jd.gallery.js.php',
'jd.gallery.transitions.js',
'swfobject.embedSWF(',
'linkwithin.com/widget.js',
'tiny_mce.js',
'tinyMCEPreInit.go',
);
/**
* Setting CDN base URL.
*
* @var string
*/
public $cdn_url = '';
/**
* Setting; aggregate or not.
*
* @var bool
*/
private $aggregate = true;
/**
* Setting; if not aggregated, should we defer?
*
* @var bool
*/
private $defer_not_aggregate = false;
/**
* Setting; defer inline JS?
*
* @var bool
*/
private $defer_inline = false;
/**
* Setting; try/catch wrapping or not.
*
* @var bool
*/
private $trycatch = false;
/**
* State; is JS already minified.
*
* @var bool
*/
private $alreadyminified = false;
/**
* Setting; force JS in head or not.
*
* @var bool
*/
private $forcehead = true;
/**
* Setting; aggregate inline JS or not.
*
* @var bool
*/
private $include_inline = false;
/**
* State; holds JS code.
*
* @var string
*/
private $jscode = '';
/**
* State; holds URL of JS-file.
*
* @var string
*/
private $url = '';
/**
* State; stores rest of HTML if (old) option "only in head" is on.
*
* @var string
*/
private $restofcontent = '';
/**
* State; holds md5-hash.
*
* @var string
*/
private $md5hash = '';
/**
* Setting (filter); allowlist of to be aggregated JS.
*
* @var string
*/
private $allowlist = '';
/**
* Setting (filter); holds JS that should be removed.
*
* @var array
*/
private $jsremovables = array();
/**
* Setting (filter); can we inject already minified files after the
* unminified aggregate JS has been minified.
*
* @var bool
*/
private $inject_min_late = true;
/**
* Setting; should excluded JS be minified (if not already).
*
* @var bool
*/
private $minify_excluded = true;
/**
* Reads the page and collects script tags.
*
* @param array $options all options.
*/
public function read( $options )
{
$noptimize_js = false;
// If page/ post check post_meta to see if optimize is off.
if ( false === autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_js_optimize' ) ) {
$noptimize_js = true;
}
// And a filter to enforce JS noptimize.
$noptimize_js = apply_filters( 'autoptimize_filter_js_noptimize', $noptimize_js, $this->content );
// And finally bail if noptimize_js is true.
if ( $noptimize_js ) {
return false;
}
// only optimize known good JS?
$allowlist_js = apply_filters( 'autoptimize_filter_js_allowlist', '', $this->content );
if ( ! empty( $allowlist_js ) ) {
$this->allowlist = array_filter( array_map( 'trim', explode( ',', $allowlist_js ) ) );
}
// is there JS we should simply remove?
$removable_js = apply_filters( 'autoptimize_filter_js_removables', '', $this->content );
if ( ! empty( $removable_js ) ) {
$this->jsremovables = array_filter( array_map( 'trim', explode( ',', $removable_js ) ) );
}
// only header?
if ( apply_filters( 'autoptimize_filter_js_justhead', $options['justhead'] ) ) {
$content = explode( '</head>', $this->content, 2 );
$this->content = $content[0] . '</head>';
$this->restofcontent = $content[1];
}
// Determine whether we're doing JS-files aggregation or not.
if ( ! $options['aggregate'] ) {
$this->aggregate = false;
}
// Returning true for "dontaggregate" turns off aggregation.
if ( $this->aggregate && apply_filters( 'autoptimize_filter_js_dontaggregate', false ) ) {
$this->aggregate = false;
}
// and the filter that should have been there to begin with.
$this->aggregate = apply_filters( 'autoptimize_filter_js_aggregate', $this->aggregate );
// Defer when not aggregating.
if ( false === $this->aggregate && apply_filters( 'autoptimize_filter_js_defer_not_aggregate', $options['defer_not_aggregate'] ) ) {
$this->defer_not_aggregate = true;
}
// Defer inline JS?
if ( ( true === $this->defer_not_aggregate && apply_filters( 'autoptimize_js_filter_defer_inline', $options['defer_inline'] ) ) || apply_filters( 'autoptimize_js_filter_force_defer_inline', false ) ) {
$this->defer_inline = true;
}
// include inline?
if ( apply_filters( 'autoptimize_js_include_inline', $options['include_inline'] ) ) {
$this->include_inline = true;
}
// filter to "late inject minified JS", default to true for now (it is faster).
$this->inject_min_late = apply_filters( 'autoptimize_filter_js_inject_min_late', true );
// filters to override hardcoded do(nt)move(last) array contents (array in, array out!).
$this->dontmove = apply_filters( 'autoptimize_filter_js_dontmove', $this->dontmove );
$this->domovelast = apply_filters( 'autoptimize_filter_js_movelast', $this->domovelast );
$this->domove = apply_filters( 'autoptimize_filter_js_domove', $this->domove );
// Determine whether excluded files should be minified if not yet so.
if ( ! $options['minify_excluded'] && $options['aggregate'] ) {
$this->minify_excluded = false;
}
$this->minify_excluded = apply_filters( 'autoptimize_filter_js_minify_excluded', $this->minify_excluded, '' );
// get extra exclusions settings or filter.
$exclude_js = $options['js_exclude'];
$exclude_js = apply_filters( 'autoptimize_filter_js_exclude', $exclude_js, $this->content );
if ( '' !== $exclude_js ) {
if ( is_array( $exclude_js ) ) {
$remove_keys = array_keys( $exclude_js, 'remove' );
if ( false !== $remove_keys ) {
foreach ( $remove_keys as $remove_key ) {
unset( $exclude_js[ $remove_key ] );
$this->jsremovables[] = $remove_key;
}
}
$excl_js_arr = array_keys( $exclude_js );
} else {
$excl_js_arr = array_filter( array_map( 'trim', explode( ',', $exclude_js ) ) );
}
$this->dontmove = array_merge( $excl_js_arr, $this->dontmove );
}
// Should we add try-catch?
if ( $options['trycatch'] ) {
$this->trycatch = true;
}
// force js in head?
if ( $options['forcehead'] ) {
$this->forcehead = true;
} else {
$this->forcehead = false;
}
$this->forcehead = apply_filters( 'autoptimize_filter_js_forcehead', $this->forcehead );
// get cdn url.
$this->cdn_url = $options['cdn_url'];
// noptimize me.
$this->content = $this->hide_noptimize( $this->content );
// Save IE hacks.
$this->content = $this->hide_iehacks( $this->content );
// comments.
$this->content = $this->hide_comments( $this->content );
// Get script files.
if ( preg_match_all( '#<script.*</script>#Usmi', $this->content, $matches ) ) {
foreach ( $matches[0] as $tag ) {
// only consider script aggregation for types allowlisted in should_aggregate-function.
$should_aggregate = $this->should_aggregate( $tag );
if ( ! $should_aggregate ) {
$tag = '';
continue;
}
if ( preg_match( '#<script[^>]*src=("|\')([^>]*)("|\')#Usmi', $tag, $source ) ) {
// non-inline script.
if ( $this->isremovable( $tag, $this->jsremovables ) ) {
$this->content = str_replace( $tag, '', $this->content );
continue;
}
$orig_tag = null;
$url = current( explode( '?', $source[2], 2 ) );
$path = $this->getpath( $url );
if ( false !== $path && preg_match( '#\.js$#', $path ) && $this->ismergeable( $tag ) ) {
// ok to optimize, add to array.
$this->scripts[] = $path;
} else {
$orig_tag = $tag;
$new_tag = $tag;
// non-mergeable script (excluded or dynamic or external).
if ( is_array( $exclude_js ) ) {
// should we add flags?
foreach ( $exclude_js as $excl_tag => $excl_flags ) {
if ( false !== strpos( $orig_tag, $excl_tag ) && in_array( $excl_flags, array( 'async', 'defer' ) ) ) {
$new_tag = str_replace( '<script ', '<script ' . $excl_flags . ' ', $new_tag );
}
}
}
// not aggregating but deferring?
if ( $this->defer_not_aggregate && false === $this->aggregate && ( str_replace( $this->dontmove, '', $path ) === $path || ( apply_filters( 'autoptimize_filter_js_defer_external', true ) && str_replace( $this->dontmove, '', $orig_tag ) === $orig_tag ) ) && strpos( $new_tag, ' defer' ) === false ) {
if ( false !== strpos( $new_tag, ' async' ) && true === apply_filters( 'autoptimize_filter_js_defer_trumps_async', true ) ) {
// remove async flag to ensure JS is properly deferred, otherwise the asynced JS might fire
// before deferred inlined JS is executed, off course except filter is set to false which
// re-institutes previous behavior.
$new_tag = str_replace( array( " async='async'", ' async="async"', ' async=async', ' async' ), '', $new_tag );
}
if ( false === strpos( $new_tag, ' async' ) ) {
// either async wasn't there to begin with or it was removed.
// if async is there, the autoptimize_filter_js_defer_trumps_async
// filter was set to false and in that case defer should not be added.
$new_tag = str_replace( '<script ', '<script defer ', $new_tag );
}
}
// Should we minify the non-aggregated script?
// -> if aggregate is on and exclude minify is on
// -> if aggregate is off and the file is not in dontmove.
if ( $path && $this->minify_excluded ) {
$consider_minified_array = apply_filters( 'autoptimize_filter_js_consider_minified', false );
if ( ( false === $this->aggregate && str_replace( $this->dontmove, '', $path ) === $path ) || ( true === $this->aggregate && ( false === $consider_minified_array || str_replace( $consider_minified_array, '', $path ) === $path ) ) ) {
$minified_url = $this->minify_single( $path );
if ( ! empty( $minified_url ) ) {
// Replace original URL with minified URL from cache.
$new_tag = str_replace( $url, $minified_url, $new_tag );
} elseif ( apply_filters( 'autoptimize_filter_ccsjs_remove_empty_minified_url', false ) ) {
// Remove the original script tag, because cache content is empty but only if filter
// is trued because $minified_url is also false if original JS is minified already.
$new_tag = '';
}
}
}
// Check if we still need to CDN (esp. for already minified resources).
if ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) {
$new_tag = str_replace( $url, $this->url_replace_cdn( $url ), $new_tag );
}
if ( $this->ismovable( $new_tag ) ) {
// can be moved, flags and all.
if ( $this->movetolast( $new_tag ) ) {
$this->move['last'][] = $new_tag;
} else {
$this->move['first'][] = $new_tag;
}
} else {
// cannot be moved, so if flag was added re-inject altered tag immediately.
if ( ( '' !== $new_tag && $orig_tag !== $new_tag ) || ( '' === $new_tag && apply_filters( 'autoptimize_filter_js_remove_empty_files', false ) ) ) {
$this->content = str_replace( $orig_tag, $new_tag, $this->content );
$orig_tag = '';
}
// and forget about the $tag (not to be touched any more).
$tag = '';
}
}
} else {
// Inline script.
if ( $this->isremovable( $tag, $this->jsremovables ) ) {
$this->content = str_replace( $tag, '', $this->content );
continue;
}
// unhide comments, as javascript may be wrapped in comment-tags for old times' sake.
$tag = $this->restore_comments( $tag );
if ( $this->ismergeable( $tag ) && $this->include_inline ) {
preg_match( '#<script.*>(.*)</script>#Usmi', $tag, $code );
$code = preg_replace( '#.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*#sm', '$1', $code[1] );
$code = preg_replace( '/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $code );
$this->scripts[] = 'INLINE;' . $code;
} else {
$_inline_deferable = apply_filters( 'autoptimize_filters_js_inline_deferable', array( 'nonce', 'post_id', 'syntaxhighlighter' ) );
$_inline_dontmove = array_values( array_diff( $this->dontmove, $_inline_deferable ) );
if ( false === $this->defer_inline ) {
// Can we move this?
$autoptimize_js_moveable = apply_filters( 'autoptimize_js_moveable', '', $tag );
if ( $this->ismovable( $tag ) || '' !== $autoptimize_js_moveable ) {
if ( $this->movetolast( $tag ) || 'last' === $autoptimize_js_moveable ) {
$this->move['last'][] = $tag;
} else {
$this->move['first'][] = $tag;
}
} else {
$tag = '';
}
} else if ( str_replace( $_inline_dontmove, '', $tag ) === $tag && strlen( $tag ) < apply_filters( 'autoptimize_filter_script_defer_inline_maxsize', 200000 ) ) {
// defer inline JS by base64 encoding it but only if string is not ridiculously huge (to avoid issues with below regex mainly).
preg_match( '#<script(?:[^>](?!id=))*\s*(?:id=(["\'])([^"\']+)\1)*+[^>]*+>(.*?)<\/script>#is', $tag, $match );
if ( $match[2] ) {
$_id = 'id="' . $match[2] . '" ';
} else {
$_id = '';
}
// if "minify inline" is on and if more then 9 spaces or 4 line breaks are found
// in the inline JS then it is likely not minified, so minify before base64-encoding.
$_script_contents = $match[3];
if ( 'on' === autoptimizeOptionWrapper::get_option( 'autoptimize_html_minify_inline', 'off' ) && substr_count( $_script_contents, ' ' ) > 9 && substr_count( $_script_contents, "\n" ) > 4 && true === apply_filters( 'autoptimize_filter_script_defer_inline_minify', true ) ) {
$_tmp_script_contents = trim( JSMin::minify( $_script_contents ) );
if ( ! empty( $_tmp_script_contents ) ) {
$_script_contents = $_tmp_script_contents;
}
}
// base64 and defer the lot already.
$new_tag = '<script defer ' . $_id . 'src="data:text/javascript;base64,' . base64_encode( $_script_contents ) . '"></script>';
$this->content = str_replace( $this->hide_comments( $tag ), $new_tag, $this->content );
$tag = '';
} else {
$tag = '';
}
}
// Re-hide comments to be able to do the removal based on tag from $this->content.
$tag = $this->hide_comments( $tag );
}
// Remove the original script tag.
$this->content = str_replace( $tag, '', $this->content );
}
return true;
}
// No script files, great ;-) .
return false;
}
/**
* Determines wheter a certain `<script>` $tag should be aggregated or not.
*
* We consider these as "aggregation-safe" currently:
* - script tags without a `type` attribute
* - script tags with these `type` attribute values: `text/javascript`, `text/ecmascript`, `application/javascript`,
* and `application/ecmascript`
*
* Everything else should return false.
*
* @link https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type
*
* @param string $tag Script node & child(ren).
* @return bool
*/
public static function should_aggregate( $tag )
{
if ( empty( $tag ) ) {
return false;
}
// We're only interested in the type attribute of the <script> tag itself, not any possible
// inline code that might just contain the 'type=' string...
$tag_parts = array();
preg_match( '#<(script[^>]*)>#i', $tag, $tag_parts );
$tag_without_contents = null;
if ( ! empty( $tag_parts[1] ) ) {
$tag_without_contents = $tag_parts[1];
}
$has_type = ( strpos( $tag_without_contents, ' type' ) !== false );
$type_valid = false;
if ( $has_type ) {
$type_valid = (bool) preg_match( '/type\s*=\s*[\'"]?(?:text|application)\/(?:javascript|ecmascript)[\'"]?/i', $tag_without_contents );
}
$should_aggregate = false;
if ( ! $has_type || $type_valid ) {
$should_aggregate = true;
}
return $should_aggregate;
}
/**
* Joins and optimizes JS.
*/
public function minify()
{
foreach ( $this->scripts as $script ) {
if ( empty( $script ) ) {
continue;
}
// TODO/FIXME: some duplicate code here, can be reduced/simplified.
if ( preg_match( '#^INLINE;#', $script ) ) {
// Inline script.
$script = preg_replace( '#^INLINE;#', '', $script );
$script = rtrim( $script, ";\n\t\r" ) . ';';
// Add try-catch?
if ( $this->trycatch ) {
$script = 'try{' . $script . '}catch(e){}';
}
$tmpscript = apply_filters( 'autoptimize_js_individual_script', $script, '' );
if ( has_filter( 'autoptimize_js_individual_script' ) && ! empty( $tmpscript ) ) {
$script = $tmpscript;
$this->alreadyminified = true;
}
$this->jscode .= "\n" . $script;
} else {
// External script.
if ( false !== $script && file_exists( $script ) && is_readable( $script ) ) {
$scriptsrc = file_get_contents( $script );
$scriptsrc = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $scriptsrc );
$scriptsrc = rtrim( $scriptsrc, ";\n\t\r" ) . ';';
// Add try-catch?
if ( $this->trycatch ) {
$scriptsrc = 'try{' . $scriptsrc . '}catch(e){}';
}
$tmpscriptsrc = apply_filters( 'autoptimize_js_individual_script', $scriptsrc, $script );
if ( has_filter( 'autoptimize_js_individual_script' ) && ! empty( $tmpscriptsrc ) ) {
$scriptsrc = $tmpscriptsrc;
$this->alreadyminified = true;
} elseif ( $this->can_inject_late( $script ) ) {
$scriptsrc = self::build_injectlater_marker( $script, md5( $scriptsrc ) );
}
$this->jscode .= "\n" . $scriptsrc;
}
}
}
// Check for already-minified code.
$this->md5hash = md5( $this->jscode );
$ccheck = new autoptimizeCache( $this->md5hash, 'js' );
if ( $ccheck->check() ) {
$this->jscode = $ccheck->retrieve();
return true;
}
unset( $ccheck );
// $this->jscode has all the uncompressed code now.
if ( true !== $this->alreadyminified ) {
if ( apply_filters( 'autoptimize_js_do_minify', true ) ) {
$tmp_jscode = trim( JSMin::minify( $this->jscode ) );
if ( ! empty( $tmp_jscode ) ) {
$this->jscode = $tmp_jscode;
unset( $tmp_jscode );
}
$this->jscode = $this->inject_minified( $this->jscode );
$this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode );
return true;
} else {
$this->jscode = $this->inject_minified( $this->jscode );
return false;
}
}
$this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode );
return true;
}
/**
* Caches the JS in uncompressed, deflated and gzipped form.
*/
public function cache()
{
$cache = new autoptimizeCache( $this->md5hash, 'js' );
if ( ! $cache->check() ) {
// Cache our code.
$cache->cache( $this->jscode, 'text/javascript' );
}
$this->url = AUTOPTIMIZE_CACHE_URL . $cache->getname();
$this->url = $this->url_replace_cdn( $this->url );
}
/**
* Returns the content.
*/
public function getcontent()
{
// Restore the full content.
if ( ! empty( $this->restofcontent ) ) {
$this->content .= $this->restofcontent;
$this->restofcontent = '';
}
// Add the scripts taking forcehead/ deferred (default) into account.
if ( $this->forcehead ) {
$replace_tag = array( '</head>', 'before' );
$defer = '';
} else {
$replace_tag = array( '</body>', 'before' );
$defer = 'defer ';
}
$defer = apply_filters( 'autoptimize_filter_js_defer', $defer );
$type_js = '';
if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
$type_js = 'type="text/javascript" ';
}
$bodyreplacementpayload = '<script ' . $type_js . $defer . 'src="' . $this->url . '"></script>';
$bodyreplacementpayload = apply_filters( 'autoptimize_filter_js_bodyreplacementpayload', $bodyreplacementpayload );
$bodyreplacement = implode( '', $this->move['first'] );
$bodyreplacement .= $bodyreplacementpayload;
$bodyreplacement .= implode( '', $this->move['last'] );
$replace_tag = apply_filters( 'autoptimize_filter_js_replacetag', $replace_tag );
if ( strlen( $this->jscode ) > 0 ) {
$this->inject_in_html( $bodyreplacement, $replace_tag );
}
// Restore comments.
$this->content = $this->restore_comments( $this->content );
// Restore IE hacks.
$this->content = $this->restore_iehacks( $this->content );
// Restore noptimize.
$this->content = $this->restore_noptimize( $this->content );
// Return the modified HTML.
return $this->content;
}
/**
* Checks against the allow- and blocklists.
*
* @param string $tag JS tag.
*/
private function ismergeable( $tag )
{
if ( empty( $tag ) || ! $this->aggregate ) {
return false;
}
if ( ! empty( $this->allowlist ) ) {
foreach ( $this->allowlist as $match ) {
if ( false !== strpos( $tag, $match ) ) {
return true;
}
}
// No match with allowlist.
return false;
} else {
foreach ( $this->domove as $match ) {
if ( false !== strpos( $tag, $match ) ) {
// Matched something.
return false;
}
}
if ( $this->movetolast( $tag ) ) {
return false;
}
foreach ( $this->dontmove as $match ) {
if ( false !== strpos( $tag, $match ) ) {
// Matched something.
return false;
}
}
// If we're here it's safe to merge.
return true;
}
}
/**
* Checks agains the blocklist.
*
* @param string $tag tag to check for blocklist (exclusions).
*/
private function ismovable( $tag )
{
if ( empty( $tag ) || true !== $this->include_inline || apply_filters( 'autoptimize_filter_js_unmovable', true ) ) {
return false;
}
foreach ( $this->domove as $match ) {
if ( false !== strpos( $tag, $match ) ) {
// Matched something.
return true;
}
}
if ( $this->movetolast( $tag ) ) {
return true;
}
foreach ( $this->dontmove as $match ) {
if ( false !== strpos( $tag, $match ) ) {
// Matched something.
return false;
}
}
// If we're here it's safe to move.
return true;
}
private function movetolast( $tag )
{
if ( empty( $tag ) ) {
return false;
}
foreach ( $this->domovelast as $match ) {
if ( false !== strpos( $tag, $match ) ) {
// Matched, return true.
return true;
}
}
// Should be in 'first'.
return false;
}
/**
* Determines wheter a <script> $tag can be excluded from minification (as already minified) based on:
* - inject_min_late being active
* - filename ending in `min.js`
* - filename matching one passed in the consider minified filter
*
* @param string $js_path Path to JS file.
* @return bool
*/
private function can_inject_late( $js_path ) {
$consider_minified_array = apply_filters( 'autoptimize_filter_js_consider_minified', false );
if ( true !== $this->inject_min_late ) {
// late-inject turned off.
return false;
} elseif ( ( false === strpos( $js_path, 'min.js' ) ) && ( str_replace( $consider_minified_array, '', $js_path ) === $js_path ) ) {
// file not minified based on filename & filter.
return false;
} else {
// phew, all is safe, we can late-inject.
return true;
}
}
/**
* Returns whether we're doing aggregation or not.
*
* @return bool
*/
public function aggregating()
{
return $this->aggregate;
}
/**
* Minifies a single local js file and returns its (cached) url.
*
* @param string $filepath Filepath.
* @param bool $cache_miss Optional. Force a cache miss. Default false.
*
* @return bool|string Url pointing to the minified js file or false.
*/
public function minify_single( $filepath, $cache_miss = false )
{
$contents = $this->prepare_minify_single( $filepath );
if ( empty( $contents ) ) {
return false;
}
// Check cache.
$hash = 'single_' . md5( $contents );
$cache = new autoptimizeCache( $hash, 'js' );
// If not in cache already, minify...
if ( ! $cache->check() || $cache_miss ) {
$contents = trim( JSMin::minify( $contents ) );
// Check if minified cache content is empty.
if ( empty( $contents ) ) {
return false;
}
// Filter contents of excluded minified CSS.
$contents = apply_filters( 'autoptimize_filter_js_single_after_minify', $contents );
// Store in cache.
$cache->cache( $contents, 'text/javascript' );
}
$url = $this->build_minify_single_url( $cache );
return $url;
}
}

View File

@@ -0,0 +1,110 @@
<?php
/**
* Autoptimize SpeedUp; minify & cache each JS/ CSS separately
* new in Autoptimize 2.2
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeSpeedupper
{
public function __construct()
{
$this->add_hooks();
}
public function add_hooks()
{
if ( apply_filters( 'autoptimize_js_do_minify', true ) ) {
add_filter( 'autoptimize_js_individual_script', array( $this, 'js_snippetcacher' ), 10, 2 );
add_filter( 'autoptimize_js_after_minify', array( $this, 'js_cleanup' ), 10, 1 );
}
if ( apply_filters( 'autoptimize_css_do_minify', true ) ) {
add_filter( 'autoptimize_css_individual_style', array( $this, 'css_snippetcacher' ), 10, 2 );
add_filter( 'autoptimize_css_after_minify', array( $this, 'css_cleanup' ), 10, 1 );
}
}
public function js_snippetcacher( $jsin, $jsfilename )
{
$md5hash = 'snippet_' . md5( $jsin );
$ccheck = new autoptimizeCache( $md5hash, 'js' );
if ( $ccheck->check() ) {
$scriptsrc = $ccheck->retrieve();
} else {
if ( false === ( strpos( $jsfilename, 'min.js' ) ) && ( str_replace( apply_filters( 'autoptimize_filter_js_consider_minified', false ), '', $jsfilename ) === $jsfilename ) ) {
$tmp_jscode = trim( JSMin::minify( $jsin ) );
if ( ! empty( $tmp_jscode ) ) {
$scriptsrc = $tmp_jscode;
unset( $tmp_jscode );
} else {
$scriptsrc = $jsin;
}
} else {
// Removing comments, linebreaks and stuff!
$scriptsrc = preg_replace( '#^\s*\/\/.*$#Um', '', $jsin );
$scriptsrc = preg_replace( '#^\s*\/\*[^!].*\*\/\s?#Us', '', $scriptsrc );
$scriptsrc = preg_replace( "#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#", "\n", $scriptsrc );
}
$last_char = substr( $scriptsrc, -1, 1 );
if ( ';' !== $last_char && '}' !== $last_char ) {
$scriptsrc .= ';';
}
if ( ! empty( $jsfilename ) && str_replace( apply_filters( 'autoptimize_filter_js_speedup_cache', false ), '', $jsfilename ) === $jsfilename ) {
// Don't cache inline CSS or if filter says no!
$ccheck->cache( $scriptsrc, 'text/javascript' );
}
}
unset( $ccheck );
return $scriptsrc;
}
public function css_snippetcacher( $cssin, $cssfilename )
{
$md5hash = 'snippet_' . md5( $cssin );
$ccheck = new autoptimizeCache( $md5hash, 'css' );
if ( $ccheck->check() ) {
$stylesrc = $ccheck->retrieve();
} else {
if ( ( false === strpos( $cssfilename, 'min.css' ) ) && ( str_replace( apply_filters( 'autoptimize_filter_css_consider_minified', false ), '', $cssfilename ) === $cssfilename ) ) {
$cssmin = new autoptimizeCSSmin();
$tmp_code = trim( $cssmin->run( $cssin ) );
if ( ! empty( $tmp_code ) ) {
$stylesrc = $tmp_code;
unset( $tmp_code );
} else {
$stylesrc = $cssin;
}
} else {
// .min.css -> no heavy-lifting, just some cleanup!
$stylesrc = preg_replace( '#^\s*\/\*[^!].*\*\/\s?#Us', '', $cssin );
$stylesrc = preg_replace( "#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#", "\n", $stylesrc );
$stylesrc = autoptimizeStyles::fixurls( $cssfilename, $stylesrc );
}
if ( ! empty( $cssfilename ) && ( str_replace( apply_filters( 'autoptimize_filter_css_speedup_cache', false ), '', $cssfilename ) === $cssfilename ) ) {
// Only caching CSS if it's not inline and is allowed by filter!
$ccheck->cache( $stylesrc, 'text/css' );
}
}
unset( $ccheck );
return $stylesrc;
}
public function css_cleanup( $cssin )
{
// Speedupper results in aggregated CSS not being minified, so the filestart-marker AO adds when aggregating needs to be removed.
return trim( str_replace( array( '/*FILESTART*/', '/*FILESTART2*/' ), '', $cssin ) );
}
public function js_cleanup( $jsin )
{
return trim( $jsin );
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,167 @@
<?php
/**
* Handles toolbar-related stuff.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeToolbar
{
public function __construct()
{
// If Cache is not available we don't add the toolbar.
if ( ! autoptimizeCache::cacheavail() ) {
return;
}
// Load admin toolbar feature once WordPress, all plugins, and the theme are fully loaded and instantiated.
if ( is_admin() ) {
add_action( 'wp_loaded', array( $this, 'load_toolbar' ) );
} else {
// to avoid AMP complaining about the AMP conditional being checked too early.
add_action( 'wp', array( $this, 'load_toolbar' ) );
}
}
public function load_toolbar()
{
// Check permissions and that toolbar is not hidden via filter.
if ( current_user_can( 'manage_options' ) && apply_filters( 'autoptimize_filter_toolbar_show', true ) && ! autoptimizeMain::is_amp_markup( '' ) ) {
// Create a handler for the AJAX toolbar requests.
add_action( 'wp_ajax_autoptimize_delete_cache', array( $this, 'delete_cache' ) );
// Load custom styles, scripts and menu only when needed.
if ( is_admin_bar_showing() ) {
if ( is_admin() ) {
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
} else {
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
}
// Add the Autoptimize Toolbar to the Admin bar.
add_action( 'admin_bar_menu', array( $this, 'add_toolbar' ), 100 );
}
}
}
public function add_toolbar()
{
global $wp_admin_bar;
// Retrieve the Autoptimize Cache Stats information.
$stats = autoptimizeCache::stats();
// Set the Max Size recommended for cache files.
$max_size = apply_filters( 'autoptimize_filter_cachecheck_maxsize', 512 * 1024 * 1024 );
// Retrieve the current Total Files in cache.
$files = $stats[0];
// Retrieve the current Total Size of the cache.
$bytes = $stats[1];
$size = $this->format_filesize( $bytes );
// Calculate the percentage of cache used.
$percentage = ceil( $bytes / $max_size * 100 );
if ( $percentage > 100 ) {
$percentage = 100;
}
/**
* We define the type of color indicator for the current state of cache size:
* - "green" if the size is less than 80% of the total recommended.
* - "orange" if over 80%.
* - "red" if over 100%.
*/
$color = ( 100 == $percentage ) ? 'red' : ( ( $percentage > 80 ) ? 'orange' : 'green' );
// Create or add new items into the Admin Toolbar.
// Main "Autoptimize" node.
$_my_name = apply_filters( 'autoptimize_filter_settings_is_pro', false ) ? esc_html__( 'Autoptimize Pro', 'autoptimize' ) : esc_html__( 'Autoptimize', 'autoptimize' );
$wp_admin_bar->add_node(
array(
'id' => 'autoptimize',
'title' => '<span class="ab-icon"></span><span class="ab-label">' . $_my_name . '</span>',
'href' => admin_url( 'options-general.php?page=autoptimize' ),
'meta' => array( 'class' => 'bullet-' . $color ),
)
);
// "Cache Info" node.
$wp_admin_bar->add_node(
array(
'id' => 'autoptimize-cache-info',
'title' => '<p>' . esc_html__( 'CSS/ JS Cache Info', 'autoptimize' ) . '</p>' .
'<div class="autoptimize-radial-bar" percentage="' . $percentage . '">' .
'<div class="autoptimize-circle">' .
'<div class="mask full"><div class="fill bg-' . $color . '"></div></div>' .
'<div class="mask half"><div class="fill bg-' . $color . '"></div></div>' .
'<div class="shadow"></div>' .
'</div>' .
'<div class="inset"><div class="percentage"><div class="numbers ' . $color . '">' . $percentage . '%</div></div></div>' .
'</div>' .
'<table>' .
'<tr><td>' . esc_html__( 'Size', 'autoptimize' ) . ':</td><td class="size ' . $color . '">' . $size . '</td></tr>' .
'<tr><td>' . esc_html__( 'Files', 'autoptimize' ) . ':</td><td class="files white">' . $files . '</td></tr>' .
'</table>',
'parent' => 'autoptimize',
)
);
// "Delete Cache" node.
$wp_admin_bar->add_node(
array(
'id' => 'autoptimize-delete-cache',
'title' => esc_html__( 'Clear CSS/ JS Cache', 'autoptimize' ),
'parent' => 'autoptimize',
)
);
}
public function delete_cache()
{
check_ajax_referer( 'ao_delcache_nonce', 'nonce' );
$result = false;
if ( current_user_can( 'manage_options' ) ) {
// We call the function for cleaning the Autoptimize cache.
$result = autoptimizeCache::clearall();
}
wp_send_json( $result );
}
public function enqueue_scripts()
{
// Autoptimize Toolbar Styles.
wp_enqueue_style( 'autoptimize-toolbar', plugins_url( '/static/toolbar.min.css', __FILE__ ), array(), AUTOPTIMIZE_PLUGIN_VERSION, 'all' );
// Autoptimize Toolbar Javascript.
wp_enqueue_script( 'autoptimize-toolbar', plugins_url( '/static/toolbar.min.js', __FILE__ ), array( 'jquery' ), AUTOPTIMIZE_PLUGIN_VERSION, true );
// Localizes a registered script with data for a JavaScript variable.
// Needed for the AJAX to work properly on the frontend.
wp_localize_script(
'autoptimize-toolbar',
'autoptimize_ajax_object',
array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
// translators: links to the Autoptimize settings page.
'error_msg' => sprintf( esc_html__( 'Your Autoptimize cache might not have been purged successfully, please check on the %1$sAutoptimize settings page%2$s.', 'autoptimize' ), '<a href="' . admin_url( 'options-general.php?page=autoptimize' ) . '" style="white-space:nowrap;">', '</a>' ),
'dismiss_msg' => esc_html__( 'Dismiss this notice.' ),
'nonce' => wp_create_nonce( 'ao_delcache_nonce' ),
)
);
}
public function format_filesize( $bytes, $decimals = 2 )
{
$units = array( 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' );
for ( $i = 0; ( $bytes / 1024) > 0.9; $i++, $bytes /= 1024 ) {} // @codingStandardsIgnoreLine
return sprintf( "%1.{$decimals}f %s", round( $bytes, $decimals ), $units[ $i ] );
}
}

View File

@@ -0,0 +1,595 @@
<?php
/**
* General helpers.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeUtils
{
/**
* Returns true when mbstring is available.
*
* @param bool|null $override Allows overriding the decision.
*
* @return bool
*/
public static function mbstring_available( $override = null )
{
static $available = null;
if ( null === $available ) {
$available = \extension_loaded( 'mbstring' );
}
if ( null !== $override ) {
$available = $override;
}
return $available;
}
/**
* Multibyte-capable strpos() if support is available on the server.
* If not, it falls back to using \strpos().
*
* @param string $haystack Haystack.
* @param string $needle Needle.
* @param int $offset Offset.
* @param string|null $encoding Encoding. Default null.
*
* @return int|false
*/
public static function strpos( $haystack, $needle, $offset = 0, $encoding = null )
{
if ( self::mbstring_available() ) {
return ( null === $encoding ) ? \mb_strpos( $haystack, $needle, $offset ) : \mb_strpos( $haystack, $needle, $offset, $encoding );
} else {
return \strpos( $haystack, $needle, $offset );
}
}
/**
* Attempts to return the number of characters in the given $string if
* mbstring is available. Returns the number of bytes
* (instead of characters) as fallback.
*
* @param string $string String.
* @param string|null $encoding Encoding.
*
* @return int Number of characters or bytes in given $string
* (characters if/when supported, bytes otherwise).
*/
public static function strlen( $string, $encoding = null )
{
if ( self::mbstring_available() ) {
return ( null === $encoding ) ? \mb_strlen( $string ) : \mb_strlen( $string, $encoding );
} else {
return \strlen( $string );
}
}
/**
* Our wrapper around implementations of \substr_replace()
* that attempts to not break things horribly if at all possible.
* Uses mbstring if available, before falling back to regular
* substr_replace() (which works just fine in the majority of cases).
*
* @param string $string String.
* @param string $replacement Replacement.
* @param int $start Start offset.
* @param int|null $length Length.
* @param string|null $encoding Encoding.
*
* @return string
*/
public static function substr_replace( $string, $replacement, $start, $length = null, $encoding = null )
{
if ( self::mbstring_available() ) {
$strlen = self::strlen( $string, $encoding );
if ( $start < 0 ) {
if ( -$start < $strlen ) {
$start = $strlen + $start;
} else {
$start = 0;
}
} elseif ( $start > $strlen ) {
$start = $strlen;
}
if ( null === $length || '' === $length ) {
$start2 = $strlen;
} elseif ( $length < 0 ) {
$start2 = $strlen + $length;
if ( $start2 < $start ) {
$start2 = $start;
}
} else {
$start2 = $start + $length;
}
if ( null === $encoding ) {
$leader = $start ? \mb_substr( $string, 0, $start ) : '';
$trailer = ( $start2 < $strlen ) ? \mb_substr( $string, $start2, null ) : '';
} else {
$leader = $start ? \mb_substr( $string, 0, $start, $encoding ) : '';
$trailer = ( $start2 < $strlen ) ? \mb_substr( $string, $start2, null, $encoding ) : '';
}
return "{$leader}{$replacement}{$trailer}";
}
return ( null === $length ) ? \substr_replace( $string, $replacement, $start ) : \substr_replace( $string, $replacement, $start, $length );
}
/**
* Decides whether this is a "subdirectory site" or not.
*
* @param bool $override Allows overriding the decision when needed.
*
* @return bool
*/
public static function siteurl_not_root( $override = null )
{
static $subdir = null;
if ( null === $subdir ) {
$parts = self::get_ao_wp_site_url_parts();
$subdir = ( isset( $parts['path'] ) && ( '/' !== $parts['path'] ) );
}
if ( null !== $override ) {
$subdir = $override;
}
return $subdir;
}
/**
* Parse AUTOPTIMIZE_WP_SITE_URL into components using \parse_url(), but do
* so only once per request/lifecycle.
*
* @return array
*/
public static function get_ao_wp_site_url_parts()
{
static $parts = array();
if ( empty( $parts ) ) {
$parts = \parse_url( AUTOPTIMIZE_WP_SITE_URL );
}
return $parts;
}
/**
* Modify given $cdn_url to include the site path when needed.
*
* @param string $cdn_url CDN URL to tweak.
* @param bool $force_cache_miss Force a cache miss in order to be able
* to re-run the filter.
*
* @return string
*/
public static function tweak_cdn_url_if_needed( $cdn_url, $force_cache_miss = false )
{
static $results = array();
if ( ! isset( $results[ $cdn_url ] ) || $force_cache_miss ) {
// In order to return unmodified input when there's no need to tweak.
$results[ $cdn_url ] = $cdn_url;
// Behind a default true filter for backcompat, and only for sites
// in a subfolder/subdirectory, but still easily turned off if
// not wanted/needed...
if ( autoptimizeUtils::siteurl_not_root() ) {
$check = apply_filters( 'autoptimize_filter_cdn_magic_path_check', true, $cdn_url );
if ( $check ) {
$site_url_parts = autoptimizeUtils::get_ao_wp_site_url_parts();
$cdn_url_parts = \parse_url( $cdn_url );
$schemeless = self::is_protocol_relative( $cdn_url );
$cdn_url_parts = self::maybe_replace_cdn_path( $site_url_parts, $cdn_url_parts );
if ( false !== $cdn_url_parts ) {
$results[ $cdn_url ] = self::assemble_parsed_url( $cdn_url_parts, $schemeless );
}
}
}
}
return $results[ $cdn_url ];
}
/**
* When siteurl contains a path other than '/' and the CDN URL does not have
* a path or it's path is '/', this will modify the CDN URL's path component
* to match that of the siteurl.
* This is to support "magic" CDN urls that worked that way before v2.4...
*
* @param array $site_url_parts Site URL components array.
* @param array $cdn_url_parts CDN URL components array.
*
* @return array|false
*/
public static function maybe_replace_cdn_path( array $site_url_parts, array $cdn_url_parts )
{
if ( isset( $site_url_parts['path'] ) && '/' !== $site_url_parts['path'] ) {
if ( ! isset( $cdn_url_parts['path'] ) || '/' === $cdn_url_parts['path'] ) {
$cdn_url_parts['path'] = $site_url_parts['path'];
return $cdn_url_parts;
}
}
return false;
}
/**
* Given an array or components returned from \parse_url(), assembles back
* the complete URL.
* If optional
*
* @param array $parsed_url URL components array.
* @param bool $schemeless Whether the assembled URL should be
* protocol-relative (schemeless) or not.
*
* @return string
*/
public static function assemble_parsed_url( array $parsed_url, $schemeless = false )
{
$scheme = isset( $parsed_url['scheme'] ) ? $parsed_url['scheme'] . '://' : '';
if ( $schemeless ) {
$scheme = '//';
}
$host = isset( $parsed_url['host'] ) ? $parsed_url['host'] : '';
$port = isset( $parsed_url['port'] ) ? ':' . $parsed_url['port'] : '';
$user = isset( $parsed_url['user'] ) ? $parsed_url['user'] : '';
$pass = isset( $parsed_url['pass'] ) ? ':' . $parsed_url['pass'] : '';
$pass = ( $user || $pass ) ? "$pass@" : '';
$path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '';
$query = isset( $parsed_url['query'] ) ? '?' . $parsed_url['query'] : '';
$fragment = isset( $parsed_url['fragment'] ) ? '#' . $parsed_url['fragment'] : '';
return "$scheme$user$pass$host$port$path$query$fragment";
}
/**
* Returns true if given $url is protocol-relative.
*
* @param string $url URL to check.
*
* @return bool
*/
public static function is_protocol_relative( $url )
{
$result = false;
if ( ! empty( $url ) ) {
$result = ( 0 === strpos( $url, '//' ) );
}
return $result;
}
/**
* Canonicalizes the given path regardless of it existing or not.
*
* @param string $path Path to normalize.
*
* @return string
*/
public static function path_canonicalize( $path )
{
$patterns = array(
'~/{2,}~',
'~/(\./)+~',
'~([^/\.]+/(?R)*\.{2,}/)~',
'~\.\./~',
);
$replacements = array(
'/',
'/',
'',
'',
);
return preg_replace( $patterns, $replacements, $path );
}
/**
* Checks to see if 3rd party services are available and stores result in option
*
* TODO This should be two separate methods.
*
* @param string $return_result should we return resulting service status array (default no).
*
* @return null|array Service status or null.
*/
public static function check_service_availability( $return_result = false )
{
$service_availability_resp = wp_remote_get( 'https://misc.optimizingmatters.com/api/autoptimize_service_availablity.json?from=aomain&ver=' . AUTOPTIMIZE_PLUGIN_VERSION );
if ( ! is_wp_error( $service_availability_resp ) ) {
if ( '200' == wp_remote_retrieve_response_code( $service_availability_resp ) ) {
$availabilities = json_decode( wp_remote_retrieve_body( $service_availability_resp ), true );
if ( is_array( $availabilities ) ) {
autoptimizeOptionWrapper::update_option( 'autoptimize_service_availablity', $availabilities );
if ( $return_result ) {
return $availabilities;
}
}
}
}
return null;
}
/**
* Returns true if the string is a valid regex.
*
* @param string $string String, duh.
*
* @return bool
*/
public static function str_is_valid_regex( $string )
{
set_error_handler( function() {}, E_WARNING );
$is_regex = ( false !== preg_match( $string, '' ) );
restore_error_handler();
return $is_regex;
}
/**
* Returns true if a certain WP plugin is active/loaded.
*
* @param string $plugin_file Main plugin file.
*
* @return bool
*/
public static function is_plugin_active( $plugin_file )
{
static $ipa_exists = null;
if ( null === $ipa_exists ) {
if ( ! function_exists( '\is_plugin_active' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$ipa_exists = function_exists( '\is_plugin_active' );
}
return $ipa_exists && \is_plugin_active( $plugin_file );
}
/**
* Returns a node without ID attrib for use in noscript tags
*
* @param string $node an html tag.
*
* @return string
*/
public static function remove_id_from_node( $node ) {
if ( strpos( $node, 'id=' ) === false || apply_filters( 'autoptimize_filter_utils_keep_ids', false ) ) {
return $node;
} else {
return preg_replace( '#(.*) id=[\'|"].*[\'|"] (.*)#Um', '$1 $2', $node );
}
}
/**
* Returns true if given $str ends with given $test.
*
* @param string $str String to check.
* @param string $test Ending to match.
*
* @return bool
*/
public static function str_ends_in( $str, $test )
{
// @codingStandardsIgnoreStart
// substr_compare() is bugged on 5.5.11: https://3v4l.org/qGYBH
// return ( 0 === substr_compare( $str, $test, -strlen( $test ) ) );
// @codingStandardsIgnoreEnd
$length = strlen( $test );
return ( substr( $str, -$length, $length ) === $test );
}
/**
* Returns true if a pagecache is found, false if not.
* Now used to show notice, might be used later on to (un)hide page caching in AO if no page cache found.
*
* @param bool $disregard_transient False by default, but can be used to ignore the transient and retest.
*
* @return bool
*/
public static function find_pagecache( $disregard_transient = false ) {
static $_found_pagecache = null;
if ( null === $_found_pagecache ) {
$_page_cache_constants = array(
'NgInx' => 'NGINX_HELPER_BASENAME',
'Kinsta' => 'KINSTAMU_VERSION',
'Presslabs' => 'PL_INSTANCE_REF',
'Cache Enabler' => 'CACHE_ENABLER_VERSION',
'Speed Booster Pack' => 'SBP_PLUGIN_NAME',
'Servebolt' => 'SERVEBOLT_PLUGIN_FILE',
'WP CloudFlare Super Page Cache' => 'SWCFPC_PLUGIN_PATH',
'Cachify' => 'CACHIFY_CACHE_DIR',
'WP Rocket' => 'WP_ROCKET_CACHE_PATH',
'WP Optimize' => 'WPO_VERSION',
'Autoptimize Pro' => 'AO_PRO_PAGECACHE_CACHE_DIR',
);
$_page_cache_classes = array(
'Pressidium' => 'Ninukis_Plugin',
'Swift Performance' => 'Swift_Performance_Cache',
'WP Fastest Cache' => 'WpFastestCache',
'Quick Cache' => 'c_ws_plugin__qcache_purging_routines',
'ZenCache' => 'zencache',
'Comet Cache' => 'comet_cache',
'WP Engine' => 'WpeCommon',
'Flywheel' => 'FlywheelNginxCompat',
'Pagely' => 'PagelyCachePurge',
);
$_page_cache_functions = array(
'WP Super Cache' => 'wp_cache_clear_cache',
'W3 Total Cache' => 'w3tc_pgcache_flush',
'WP Fast Cache' => 'wp_fast_cache_bulk_delete_all',
'Rapidcache' => 'rapidcache_clear_cache',
'Siteground' => 'sg_cachepress_purge_cache',
'WP Super Cache' => 'prune_super_cache',
);
$_found_pagecache = false;
if ( true !== $disregard_transient ) {
$_ao_pagecache_transient = 'autoptimize_pagecache_check';
$_found_pagecache = get_transient( $_ao_pagecache_transient );
}
if ( current_user_can( 'manage_options' ) && false === $_found_pagecache ) {
// loop through known pagecache constants.
foreach ( $_page_cache_constants as $_name => $_constant ) {
if ( defined( $_constant ) ) {
$_found_pagecache = $_name;
break;
}
}
// and loop through known pagecache classes.
if ( false === $_found_pagecache ) {
foreach ( $_page_cache_classes as $_name => $_class ) {
if ( class_exists( $_class ) ) {
$_found_pagecache = $_name;
break;
}
}
}
// and loop through known pagecache functions.
if ( false === $_found_pagecache ) {
foreach ( $_page_cache_functions as $_name => $_function ) {
if ( function_exists( $_function ) ) {
$_found_pagecache = $_name;
break;
}
}
}
// store in transient for 1 week if pagecache found.
if ( true === $_found_pagecache && true !== $disregard_transient ) {
set_transient( $_ao_pagecache_transient, true, WEEK_IN_SECONDS );
}
}
}
return $_found_pagecache;
}
/**
* Returns true if on one of the AO settings tabs, false if not.
* Used to limit notifications to AO settings pages.
*
* @return bool
*/
public static function is_ao_settings() {
$_is_ao_settings = ( str_replace( array( 'autoptimize', 'autoptimize_imgopt', 'ao_critcss', 'autoptimize_extra', 'ao_partners', 'ao_pro_boosters', 'ao_pro_pagecache', 'ao_protab' ), '', $_SERVER['REQUEST_URI'] ) !== $_SERVER['REQUEST_URI'] ? true : false );
return $_is_ao_settings;
}
/**
* Returns false if no conflicting plugins are found, the name if the plugin if found.
*
* @return bool|string
*/
public static function find_potential_conflicts() {
if ( defined( 'WPFC_WP_CONTENT_BASENAME' ) ) {
$_wpfc_options = json_decode( get_option( 'WpFastestCache' ) );
foreach ( array( 'wpFastestCacheMinifyCss', 'wpFastestCacheCombineCss', 'wpFastestCacheCombineJs' ) as $_wpfc_conflicting ) {
if ( isset( $_wpfc_options->$_wpfc_conflicting ) && 'on' === $_wpfc_options->$_wpfc_conflicting ) {
return 'WP Fastest Cache';
}
}
} elseif ( defined( 'W3TC_VERSION' ) ) {
$w3tc_config = file_get_contents( WP_CONTENT_DIR . '/w3tc-config/master.php' );
$w3tc_minify_on = strpos( $w3tc_config, '"minify.enabled": true' );
if ( $w3tc_minify_on ) {
return 'W3 Total Cache';
}
} elseif ( defined( 'SiteGround_Optimizer\VERSION' ) ) {
if ( get_option( 'siteground_optimizer_optimize_css' ) == 1 || get_option( 'siteground_optimizer_optimize_javascript' ) == 1 || get_option( 'siteground_optimizer_combine_javascript' ) == 1 || get_option( 'siteground_optimizer_combine_css' ) == 1 ) {
return 'Siteground Optimizer';
}
} elseif ( defined( 'WPO_VERSION' ) ) {
$_wpo_options = get_site_option( 'wpo_minify_config' );
if ( is_array( $_wpo_options ) && 1 == $_wpo_options['enabled'] && ( 1 == $_wpo_options['enable_css'] || 1 == $_wpo_options['enable_js'] ) ) {
return 'WP Optimize';
}
} elseif ( defined( 'WPACU_PLUGIN_VERSION' ) || defined( 'WPACU_PRO_PLUGIN_VERSION' ) ) {
$wpacu_settings_class = new \WpAssetCleanUp\Settings();
$wpacu_settings = $wpacu_settings_class->getAll();
if ( $wpacu_settings['minify_loaded_css'] || $wpacu_settings['minify_loaded_js'] || $wpacu_settings['combine_loaded_js'] || $wpacu_settings['combine_loaded_css'] ) {
return 'Asset Cleanup';
}
} elseif ( defined( 'WP_ROCKET_VERSION' ) && function_exists( 'get_rocket_option' ) ) {
if ( get_rocket_option( 'minify_js' ) || get_rocket_option( 'minify_concatenate_js' ) || get_rocket_option( 'minify_css' ) || get_rocket_option( 'minify_concatenate_css' ) || get_rocket_option( 'async_css' ) ) {
return 'WP Rocket';
}
} elseif ( function_exists( 'fvm_get_settings' ) ) {
return 'Fast Velocity Minify';
}
return false;
}
/**
* Returns true if false if on a local dev environment, true if not.
* Used to disallow image opt/ critcss for local dev environments.
*
* @return bool
*/
public static function is_local_server( $_domain = AUTOPTIMIZE_SITE_DOMAIN ) {
static $_is_local_server = null;
if ( null === $_is_local_server ) {
if ( false === strpos( $_domain, '.' ) && false === strpos( $_domain, ':' ) ) {
// no dots in domain or colon (ipv6 address), so impossible to reach, this also matches 'localhost' or any other single-word domain.
$_is_local_server = true;
} elseif ( in_array( $_domain, array( '127.0.0.1', '0000:0000:0000:0000:0000:0000:0000:0001', '0:0:0:0:0:0:0:1', '::1' ) ) ) {
// localhost IPv4/ IPv6.
$_is_local_server = true;
} elseif ( 0 === strpos( $_domain, '127.' ) || 0 === strpos( $_domain, '192.168.' ) || 0 === strpos( $_domain, '10.' ) || 0 === strpos( $_domain, 'fd' ) ) {
// private ranges so unreachable for imgopt/ CCSS.
// fixme; 172.16.0.0172.31.255.255 also private.
$_is_local_server = true;
} elseif ( autoptimizeUtils::str_ends_in( $_domain, '.local') ) {
// matches 'whatever.local'.
$_is_local_server = true;
} else {
// likely OK.
$_is_local_server = false;
}
}
// filter to override result for testing purposes.
return apply_filters( 'autoptimize_filter_utils_is_local_server', $_is_local_server );
}
public static function strip_tags_array( $array ) {
// strip all tags in an array (use case: avoid XSS in CCSS rules both when importing and when outputting).
// based on https://stackoverflow.com/a/44732196/237449 but heavily tweaked.
if ( is_array( $array ) ) {
$result = array();
foreach ( $array as $key => $value ){
if ( is_array( $value ) ) {
$result[$key] = autoptimizeUtils::strip_tags_array( $value );
} else if ( is_string( $value ) ) {
$result[$key] = wp_strip_all_tags( $value );
} else {
$result[$key] = $value;
}
}
} else {
$result = wp_strip_all_tags( $array );
}
return $result;
}
}

View File

@@ -0,0 +1,300 @@
<?php
/**
* Handles version updates and should only be instantiated in autoptimize.php if/when needed.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class autoptimizeVersionUpdatesHandler
{
/**
* The current major version string.
*
* @var string
*/
protected $current_major_version = null;
public function __construct( $current_version )
{
$this->current_major_version = substr( $current_version, 0, 3 );
}
/**
* Runs all needed upgrade procedures (depending on the
* current major version specified during class instantiation)
*/
public function run_needed_major_upgrades()
{
$major_update = false;
switch ( $this->current_major_version ) {
case '1.6':
$this->upgrade_from_1_6();
$major_update = true;
// No break, intentionally, so all upgrades are ran during a single request...
case '1.7':
$this->upgrade_from_1_7();
$major_update = true;
// No break, intentionally, so all upgrades are ran during a single request...
case '1.9':
$this->upgrade_from_1_9();
$major_update = true;
// No break, intentionally, so all upgrades are ran during a single request...
case '2.2':
$this->upgrade_from_2_2();
$major_update = true;
// No break, intentionally, so all upgrades are ran during a single request...
case '2.4':
if ( autoptimizeOptionWrapper::get_option( 'autoptimize_version', 'none' ) == '2.4.2' ) {
$this->upgrade_from_2_4_2();
}
$this->upgrade_from_2_4();
$major_update = false;
// No break, intentionally, so all upgrades are ran during a single request...
case '2.7':
$this->upgrade_from_2_7();
$major_update = true;
// No break, intentionally, so all upgrades are ran during a single request...
case '2.8':
// nothing.
case '2.9':
if ( version_compare( autoptimizeOptionWrapper::get_option( 'autoptimize_version', 'none' ), '2.9.999', 'lt' ) ) {
$this->upgrade_from_2_9_before_compatibility();
}
$major_update = false;
// No break, intentionally, so all upgrades are ran during a single request...
case '3.0':
// nothing.
case '3.1':
$this->upgrade_from_3_1();
$major_update = false;
}
if ( true === $major_update ) {
$this->on_major_version_update();
}
}
/**
* Checks specified version against the one stored in the database under `autoptimize_version` and performs
* any major upgrade routines if needed.
* Updates the database version to the specified $target if it's different to the one currently stored there.
*
* @param string $target Target version to check against (ie., the currently running one).
*/
public static function check_installed_and_update( $target )
{
$db_version = autoptimizeOptionWrapper::get_option( 'autoptimize_version', 'none' );
if ( $db_version !== $target ) {
if ( 'none' === $db_version ) {
add_action( 'admin_notices', 'autoptimizeMain::notice_installed' );
} else {
$updater = new self( $db_version );
$updater->run_needed_major_upgrades();
}
// Versions differed, upgrades happened if needed, store the new version.
autoptimizeOptionWrapper::update_option( 'autoptimize_version', $target );
}
}
/**
* Called after any major version update (and it's accompanying upgrade procedure)
* has happened. Clears cache and sets an admin notice.
*/
protected function on_major_version_update()
{
// The transients guard here prevents stale object caches from busting the cache on every request.
if ( false == get_transient( 'autoptimize_stale_option_buster' ) ) {
set_transient( 'autoptimize_stale_option_buster', 'Mamsie & Liessie zehhe: ZWIJH!', HOUR_IN_SECONDS );
autoptimizeCache::clearall();
add_action( 'admin_notices', 'autoptimizeMain::notice_updated' );
}
}
/**
* From back in the days when I did not yet consider multisite.
*/
private function upgrade_from_1_6()
{
// If user was on version 1.6.x, force advanced options to be shown by default.
autoptimizeOptionWrapper::update_option( 'autoptimize_show_adv', '1' );
// And remove old options.
$to_delete_options = array(
'autoptimize_cdn_css',
'autoptimize_cdn_css_url',
'autoptimize_cdn_js',
'autoptimize_cdn_js_url',
'autoptimize_cdn_img',
'autoptimize_cdn_img_url',
'autoptimize_css_yui',
);
foreach ( $to_delete_options as $del_opt ) {
delete_option( $del_opt );
}
}
/**
* Forces WP 3.8 dashicons in CSS exclude options when upgrading from 1.7 to 1.8
*
* @global $wpdb
*/
private function upgrade_from_1_7()
{
if ( ! is_multisite() ) {
$css_exclude = autoptimizeOptionWrapper::get_option( 'autoptimize_css_exclude' );
if ( empty( $css_exclude ) ) {
$css_exclude = 'admin-bar.min.css, dashicons.min.css';
} elseif ( false === strpos( $css_exclude, 'dashicons.min.css' ) ) {
$css_exclude .= ', dashicons.min.css';
}
autoptimizeOptionWrapper::update_option( 'autoptimize_css_exclude', $css_exclude );
} else {
global $wpdb;
$blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
$original_blog_id = get_current_blog_id();
foreach ( $blog_ids as $blog_id ) {
switch_to_blog( $blog_id );
$css_exclude = autoptimizeOptionWrapper::get_option( 'autoptimize_css_exclude' );
if ( empty( $css_exclude ) ) {
$css_exclude = 'admin-bar.min.css, dashicons.min.css';
} elseif ( false === strpos( $css_exclude, 'dashicons.min.css' ) ) {
$css_exclude .= ', dashicons.min.css';
}
autoptimizeOptionWrapper::update_option( 'autoptimize_css_exclude', $css_exclude );
}
switch_to_blog( $original_blog_id );
}
}
/**
* 2.0 will not aggregate inline CSS/JS by default, but we want users
* upgrading from 1.9 to keep their inline code aggregated by default.
*
* @global $wpdb
*/
private function upgrade_from_1_9()
{
if ( ! is_multisite() ) {
autoptimizeOptionWrapper::update_option( 'autoptimize_css_include_inline', 'on' );
autoptimizeOptionWrapper::update_option( 'autoptimize_js_include_inline', 'on' );
} else {
global $wpdb;
$blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
$original_blog_id = get_current_blog_id();
foreach ( $blog_ids as $blog_id ) {
switch_to_blog( $blog_id );
autoptimizeOptionWrapper::update_option( 'autoptimize_css_include_inline', 'on' );
autoptimizeOptionWrapper::update_option( 'autoptimize_js_include_inline', 'on' );
}
switch_to_blog( $original_blog_id );
}
}
/**
* 2.3 has no "remove google fonts" in main screen, moved to "extra"
*
* @global $wpdb
*/
private function upgrade_from_2_2()
{
if ( ! is_multisite() ) {
$this->do_2_2_settings_update();
} else {
global $wpdb;
$blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
$original_blog_id = get_current_blog_id();
foreach ( $blog_ids as $blog_id ) {
switch_to_blog( $blog_id );
$this->do_2_2_settings_update();
}
switch_to_blog( $original_blog_id );
}
}
/**
* Helper for 2.2 autoptimize_extra_settings upgrade to avoid duplicate code
*/
private function do_2_2_settings_update()
{
$nogooglefont = autoptimizeOptionWrapper::get_option( 'autoptimize_css_nogooglefont', '' );
$ao_extrasetting = autoptimizeOptionWrapper::get_option( 'autoptimize_extra_settings', '' );
if ( ( $nogooglefont ) && ( empty( $ao_extrasetting ) ) ) {
autoptimizeOptionWrapper::update_option( 'autoptimize_extra_settings', autoptimizeConfig::get_ao_extra_default_options() );
}
delete_option( 'autoptimize_css_nogooglefont' );
}
/**
* 2.4.2 introduced too many cronned ao_cachecheckers, make this right
*/
private function upgrade_from_2_4_2() {
// below code by Thomas Sjolshagen (http://eighty20results.com/)
// as found on https://www.paidmembershipspro.com/deleting-oldextra-cron-events/.
$jobs = _get_cron_array();
// Remove all ao_cachechecker cron jobs (for now).
foreach ( $jobs as $when => $job ) {
$name = key( $job );
if ( false !== strpos( $name, 'ao_cachechecker' ) ) {
unset( $jobs[ $when ] );
}
}
// Save the data.
_set_cron_array( $jobs );
}
/**
* Migrate imgopt options from autoptimize_extra_settings to autoptimize_imgopt_settings
*/
private function upgrade_from_2_4() {
$extra_settings = autoptimizeOptionWrapper::get_option( 'autoptimize_extra_settings', '' );
$imgopt_settings = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_settings', '' );
if ( empty( $imgopt_settings ) && ! empty( $extra_settings ) ) {
$imgopt_settings = autoptimizeConfig::get_ao_imgopt_default_options();
if ( array_key_exists( 'autoptimize_extra_checkbox_field_5', $extra_settings ) ) {
$imgopt_settings['autoptimize_imgopt_checkbox_field_1'] = $extra_settings['autoptimize_extra_checkbox_field_5'];
}
if ( array_key_exists( 'autoptimize_extra_select_field_6', $extra_settings ) ) {
$imgopt_settings['autoptimize_imgopt_select_field_2'] = $extra_settings['autoptimize_extra_select_field_6'];
}
autoptimizeOptionWrapper::update_option( 'autoptimize_imgopt_settings', $imgopt_settings );
}
}
/**
* Remove CCSS request limit option + update jquery exclusion to include WordPress 5.6 jquery.min.js.
*/
private function upgrade_from_2_7() {
delete_option( 'autoptimize_ccss_rlimit' );
$js_exclusions = get_option( 'autoptimize_js_exclude', '' );
if ( strpos( $js_exclusions, 'js/jquery/jquery.js' ) !== false && strpos( $js_exclusions, 'js/jquery/jquery.min.js' ) === false ) {
$js_exclusions .= ', js/jquery/jquery.min.js';
autoptimizeOptionWrapper::update_option( 'autoptimize_js_exclude', $js_exclusions );
}
}
/**
* Set an option to indicate the AO installation predates the compatibility logic, this way we
* can avoid adding compatibility code that is likely not needed and maybe not wanted as it
* can introduce performance regressions.
*/
private function upgrade_from_2_9_before_compatibility() {
autoptimizeOptionWrapper::update_option( 'autoptimize_installed_before_compatibility', true );
}
/**
* If the 404 handler is active, delete the current PHP-file so it can be re-created to fix the double underscore bug.
*/
private function upgrade_from_3_1() {
$_version_from_db = autoptimizeOptionWrapper::get_option( 'autoptimize_version', 'none' );
if ( autoptimizeCache::do_fallback() && 'none' !== $_version_from_db && version_compare( $_version_from_db, '3.1.2', 'lt' ) ) {
$_fallback_php = trailingslashit( WP_CONTENT_DIR ) . 'autoptimize_404_handler.php';
@unlink( $_fallback_php ); // @codingStandardsIgnoreLine
}
}
}

View File

@@ -0,0 +1,161 @@
<?php
/**
* Contains the function to render the advanced panel.
*/
/**
* Function to render the advanced panel.
*/
function ao_ccss_render_adv() {
$criticalcss = autoptimize()->criticalcss();
$ao_ccss_debug = esc_attr( $criticalcss->get_option( 'debug' ) );
$ao_ccss_finclude = esc_textarea( $criticalcss->get_option( 'finclude' ) );
$ao_ccss_rtimelimit = esc_attr( $criticalcss->get_option( 'rtimelimit' ) );
$ao_ccss_noptimize = esc_attr( $criticalcss->get_option( 'noptimize' ) );
$ao_ccss_loggedin = esc_attr( $criticalcss->get_option( 'loggedin' ) );
$ao_ccss_forcepath = esc_attr( $criticalcss->get_option( 'forcepath' ) );
$ao_ccss_deferjquery = esc_attr( $criticalcss->get_option( 'deferjquery' ) );
$ao_ccss_domain = esc_attr( $criticalcss->get_option( 'domain' ) );
$ao_ccss_unloadccss = esc_attr( $criticalcss->get_option( 'unloadccss' ) );
// In case domain is not set yet (done in cron.php).
if ( empty( $ao_ccss_domain ) ) {
$ao_ccss_domain = get_site_url();
}
// Get viewport size.
$viewport = $criticalcss->viewport();
?>
<ul id="adv-panel">
<li class="itemDetail">
<h2 class="itemTitle fleft"><?php esc_html_e( 'Advanced Settings', 'autoptimize' ); ?></h2>
<button type="button" class="toggle-btn">
<span class="toggle-indicator dashicons dashicons-arrow-up dashicons-arrow-down"></span>
</button>
<div class="collapsible hidden">
<table id="key" class="form-table">
<tr>
<th scope="row">
<?php esc_html_e( 'Viewport Size', 'autoptimize' ); ?>
</th>
<td>
<label for="autoptimize_ccss_vw"><?php esc_html_e( 'Width', 'autoptimize' ); ?>:</label> <input type="number" id="autoptimize_ccss_vw" name="autoptimize_ccss_viewport[w]" min="800" max="4096" placeholder="1400" value="<?php echo $viewport['w']; ?>" />&nbsp;&nbsp;
<label for="autoptimize_ccss_vh"><?php esc_html_e( 'Height', 'autoptimize' ); ?>:</label> <input type="number" id="autoptimize_ccss_vh" name="autoptimize_ccss_viewport[h]" min="600" max="2160" placeholder="1080" value="<?php echo $viewport['h']; ?>" />
<p class="notes">
<?php _e( '<a href="https://criticalcss.com/account/api-keys?aff=1" target="_blank">criticalcss.com</a> default viewport size is 1400x1080 pixels (width x height). You can change this size by typing a desired width and height values above. Allowed value ranges are from 800 to 4096 for width and from 600 to 2160 for height.', 'autoptimize' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<?php esc_html_e( 'Force Include CSS selectors', 'autoptimize' ); ?>
</th>
<td>
<textarea id="autoptimize_ccss_finclude" name="autoptimize_ccss_finclude" rows='3' maxlenght='500' style="width:100%;" placeholder="<?php esc_html_e( '.button-special,//#footer', 'autoptimize' ); ?>"><?php echo trim( esc_textarea( $ao_ccss_finclude ) ); ?></textarea>
<p class="notes">
<?php _e( 'Force include CSS selectors can be used to style dynamic content that is not part of the HTML that is seen during the Critical CSS generation. To use this feature, add comma separated values with both simple strings and/or regular expressions to match the desired selectors. Regular expressions must be preceeded by two forward slashes. For instance: <code>.button-special,//#footer</code>. In this example <code>.button-special</code> will match <code>.button-special</code> selector only, while <code>//#footer</code> will match <code>#footer</code>, <code>#footer-address</code> and <code>#footer-phone</code> selectors in case they exist.<br />Do take into account that changing this setting will only affect new/ updated rules, so you might want to remove old rules and clear your page cache to expedite the forceIncludes becoming used.', 'autoptimize' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<?php esc_html_e( 'Queue processing time limit', 'autoptimize' ); ?>
</th>
<td>
<input type="number" id="autoptimize_ccss_rtimelimit" name="autoptimize_ccss_rtimelimit" min="0" max="240" placeholder="0" value="<?php echo $ao_ccss_rtimelimit; ?>" />
<p class="notes">
<?php esc_html_e( 'The cronned queue processing is an asynchronous process triggerd by (WordPress) cron. To avoid this process from running too long and potentially getting killed, you can set the number of seconds here, 0 means no limit.', 'autoptimize' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<?php esc_html_e( 'Fetch Original CSS', 'autoptimize' ); ?>
</th>
<td>
<input type="checkbox" id="autoptimize_ccss_noptimize" name="autoptimize_ccss_noptimize" value="1" <?php checked( 1 == $ao_ccss_noptimize ); ?>>
<p class="notes">
<?php esc_html_e( 'In some (rare) cases the generation of critical CSS works better with the original CSS instead of the Autoptimized one, this option enables that behavior.', 'autoptimize' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<?php esc_html_e( 'Add CCSS for logged in users?', 'autoptimize' ); ?>
</th>
<td>
<input type="checkbox" id="autoptimize_ccss_loggedin" name="autoptimize_ccss_loggedin" value="1" <?php checked( 1 == $ao_ccss_loggedin ); ?>>
<p class="notes">
<?php esc_html_e( 'Critical CSS is generated by criticalcss.com from your pages as seen by an "anonymous visitor". Disable this option if you don\'t want the "visitor" critical CSS to be used for logged in users.', 'autoptimize' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<?php esc_html_e( 'Force path-based rules to be generated for pages?', 'autoptimize' ); ?>
</th>
<td>
<input type="checkbox" id="autoptimize_ccss_forcepath" name="autoptimize_ccss_forcepath" value="1" <?php checked( 1 == $ao_ccss_forcepath ); ?>>
<p class="notes">
<?php esc_html_e( 'By default for each page a separate rule is generated. If your pages have (semi-)identical above the fold look and feel and you want to keep the rules lean, you can disable that so one rule is created to all pages.', 'autoptimize' ); ?>
</p>
</td>
</tr>
<?php if ( 1 == $ao_ccss_deferjquery ) { ?>
<tr>
<th scope="row">
<?php esc_html_e( 'Defer jQuery and other non-aggregated JS-files? (deprecated)', 'autoptimize' ); ?>
</th>
<td>
<input type="checkbox" id="autoptimize_ccss_deferjquery" name="autoptimize_ccss_deferjquery" value="1" <?php checked( 1 == $ao_ccss_deferjquery ); ?>>
<p class="notes">
<?php esc_html_e( 'Defer all non-aggregated JS, including jQuery and inline JS to fix remaining render-blocking issues. Make sure to test your site thoroughly when activating this option!', 'autoptimize' ); ?>
<?php esc_html_e( '<b>This functionality will be removed in a next major version of Autoptimize</b>, being replaced by the combination of the "do not aggregate but defer JS" + "defer inline JS" options on the main settings page.', 'autoptimize' ); ?>
</p>
</td>
</tr>
<?php } ?>
<tr>
<th scope="row">
<?php esc_html_e( 'Unload critical CSS after page load?', 'autoptimize' ); ?>
</th>
<td>
<input type="checkbox" id="autoptimize_ccss_unloadccss" name="autoptimize_ccss_unloadccss" value="1" <?php checked( 1 == $ao_ccss_unloadccss ); ?>>
<p class="notes">
<?php esc_html_e( 'In rare cases the critical CSS needs to be removed once the full CSS loads, this option makes it so!', 'autoptimize' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<?php esc_html_e( 'Bound domain', 'autoptimize' ); ?>
</th>
<td>
<input type="text" id="autoptimize_ccss_domain" name="autoptimize_ccss_domain" style="width:100%;" placeholder="<?php esc_html_e( 'Don\'t leave this empty, put e.g. https://example.net/ or simply \'none\' to disable domain binding.', 'autoptimize' ); ?>" value="<?php echo trim( esc_attr( $ao_ccss_domain ) ); ?>">
<p class="notes">
<?php esc_html_e( 'Only requests from this domain will be sent for Critical CSS generation (pricing is per domain/ month).', 'autoptimize' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<?php esc_html_e( 'Debug Mode', 'autoptimize' ); ?>
</th>
<td>
<input type="checkbox" id="autoptimize_ccss_debug" name="autoptimize_ccss_debug" value="1" <?php checked( 1 == $ao_ccss_debug ); ?>>
<p class="notes">
<?php
_e( '<strong>CAUTION! Only use debug mode on production/live environments for ad-hoc troubleshooting and remember to turn it back off after</strong>, as this generates a lot of log-data.<br />Check the box above to enable Autoptimize CriticalCSS Power-Up debug mode. It provides debug facilities in this screen, to the browser console and to this file: ', 'autoptimize' );
echo '<code>' . AO_CCSS_LOG . '</code>';
?>
</p>
</td>
</tr>
</table>
</div>
</li>
</ul>
<?php
}
?>

View File

@@ -0,0 +1,87 @@
<?php
/**
* Debug panel.
*/
// Attach wpdb() object.
global $wpdb;
// Query AO's options.
$ao_options = $wpdb->get_results(
'
SELECT option_name AS name,
option_value AS value
FROM ' . $wpdb->options . '
WHERE option_name LIKE "autoptimize_%%"
ORDER BY name
',
ARRAY_A
);
// Query AO's transients.
$ao_trans = $wpdb->get_results(
'
SELECT option_name AS name,
option_value AS value
FROM ' . $wpdb->options . '
WHERE option_name LIKE "_transient_autoptimize_%%"
OR option_name LIKE "_transient_timeout_autoptimize_%%"
',
ARRAY_A
);
// Render debug panel if there's something to show.
if ( $ao_options || $ao_trans ) {
?>
<!-- BEGIN: Settings Debug -->
<ul>
<li class="itemDetail">
<h2 class="itemTitle"><?php esc_html_e( 'Debug Information', 'autoptimize' ); ?></h2>
<?php
// Render options.
if ( $ao_options ) {
?>
<h4><?php esc_html_e( 'Options', 'autoptimize' ); ?>:</h4>
<table class="form-table debug">
<?php
foreach ( $ao_options as $option ) {
?>
<tr>
<th scope="row">
<?php echo wp_strip_all_tags( $option['name'] ); ?>
</th>
<td>
<?php
if ( 'autoptimize_ccss_queue' == $option['name'] || 'autoptimize_ccss_rules' == $option['name'] ) {
$value = print_r( json_decode( wp_strip_all_tags( $option['value'] ), true ), true );
if ( $value ) {
echo "Raw JSON:\n<pre>" . wp_strip_all_tags( $option['value'] ) . "</pre>\n\nDecoded JSON:\n<pre>" . wp_strip_all_tags( $value ) . '</pre>';
} else {
echo 'Empty';
}
} else {
echo wp_strip_all_tags( $option['value'] );
}
?>
</td>
</tr>
<?php
}
?>
</table>
<hr />
<?php
}
// Render WP-Cron intervals and scheduled events.
?>
<h4><?php esc_html_e( 'WP-Cron Intervals', 'autoptimize' ); ?>:</h4>
<pre><?php print_r( wp_get_schedules() ); ?></pre>
<hr />
<h4><?php esc_html_e( 'WP-Cron Scheduled Events', 'autoptimize' ); ?>:</h4>
<pre><?php print_r( _get_cron_array() ); ?></pre>
</li>
</ul>
<!-- END: Settings Debug -->
<?php
}

View File

@@ -0,0 +1,57 @@
<?php
/**
* Explain what CCSS is (visible if no API key is stored).
*/
/**
* Actual function that explains.
*/
function ao_ccss_render_explain() {
?>
<style>
.ao_settings_div {background: white;border: 1px solid #ccc;padding: 1px 15px;margin: 15px 10px 10px 0;}
.ao_settings_div .form-table th {font-weight: normal;}
</style>
<script>document.title = "Autoptimize: <?php esc_html_e( 'Critical CSS', 'autoptimize' ); ?> " + document.title;</script>
<ul id="explain-panel">
<div class="ao_settings_div">
<?php
$ccss_explanation = '';
if ( apply_filters( 'autoptimize_filter_ccss_rules_without_api', true ) ) {
$_transient = 'ao3_ccss_explain';
$_explain_html = 'https://misc.optimizingmatters.com/autoptimize_ccss_explain_ao30_i18n.html?ao_ver=';
} else {
$_transient = 'ao_ccss_explain';
$_explain_html = 'https://misc.optimizingmatters.com/autoptimize_ccss_explain_i18n.html?ao_ver=';
}
// get the HTML with the explanation of what critical CSS is.
if ( apply_filters( 'autoptimize_settingsscreen_remotehttp', true ) ) {
$ccss_explanation = get_transient( $_transient );
if ( empty( $ccss_explanation ) ) {
$ccss_expl_resp = wp_remote_get( $_explain_html . AUTOPTIMIZE_PLUGIN_VERSION );
if ( ! is_wp_error( $ccss_expl_resp ) ) {
if ( '200' == wp_remote_retrieve_response_code( $ccss_expl_resp ) ) {
$ccss_explanation = wp_kses_post( wp_remote_retrieve_body( $ccss_expl_resp ) );
set_transient( $_transient, $ccss_explanation, WEEK_IN_SECONDS );
}
}
}
}
// placeholder text in case HTML is empty.
if ( empty( $ccss_explanation ) ) {
$ccss_explanation = sprintf( esc_html__( '%1$sFix render-blocking CSS!%2$s%3$sSignificantly improve your first-paint times by making CSS non-render-blocking.%4$s%3$sThe %5$snext step is to sign up at %7$shttps://criticalcss.com%8$s%6$s (this is a premium service, priced 2 GBP/month for membership and 5 GBP/month per domain) %5$sand get the API key%6$s, which you can copy from %7$sthe API-keys page%8$s and paste below.%4$s%3$sIf you have any questions or need support, head on over to %9$sour support forum%10$s and we\'ll help you get up and running in no time!%4$s', 'autoptimize' ), '<h2>', '</h2>', '<p>', '</p>', '<strong>', '</strong>', '<a href="https://criticalcss.com/?aff=1" target="_blank">', '</a>', '<a href="https://wordpress.org/support/plugin/autoptimize" target="_blank">', '</a>' );
} else {
// we were able to fetch the explenation, so add the JS to show correct language.
$ccss_explanation .= "<script>jQuery('.ao_i18n').hide();d=document;lang=d.getElementsByTagName('html')[0].getAttribute('lang').substring(0,2);if(d.getElementById(lang)!= null){jQuery('#'+lang).show();}else{jQuery('#default').show();}</script>";
}
// and echo it.
echo $ccss_explanation;
?>
</div>
</ul>
<?php
}

View File

@@ -0,0 +1,68 @@
<?php
/**
* Javascript to import and export AO CCSS settings.
*/
?>
// Export and download settings
function exportSettings( idToEdit ) {
console.log('Exporting...');
var data = {
'action': 'ao_ccss_export',
'ao_ccss_export_nonce': '<?php echo wp_create_nonce( 'ao_ccss_export_nonce' ); ?>',
};
jQuery.post(ajaxurl, data, function(response) {
response_array=JSON.parse(response);
if (response_array['code'] == 200) {
<?php
if ( is_multisite() ) {
$blog_id = '/' . get_current_blog_id() . '/';
} else {
$blog_id = '/';
}
?>
export_url = '<?php echo content_url(); ?>/uploads/ao_ccss' + '<?php echo $blog_id; ?>' + response_array['file'];
msg = "Download export-file from: <a href=\"" + export_url + "\" target=\"_blank\">"+ export_url + "</a>";
} else {
msg = response_array['msg'];
}
jQuery("#importdialog").html(msg);
jQuery("#importdialog").dialog({
autoOpen: true,
height: 210,
width: 700,
title: "<?php esc_html_e( 'Export settings result', 'autoptimize' ); ?>",
modal: true,
buttons: {
OK: function() {
jQuery( this ).dialog( "close" );
}
}
});
});
}
// Upload and import settings
function upload(){
var fd = new FormData();
var file = jQuery(document).find('#settingsfile');
var settings_file = file[0].files[0];
fd.append('file', settings_file);
fd.append('action', 'ao_ccss_import');
fd.append('ao_ccss_import_nonce', '<?php echo wp_create_nonce( 'ao_ccss_import_nonce' ); ?>');
jQuery.ajax({
url: ajaxurl,
type: 'POST',
data: fd,
contentType: false,
processData: false,
success: function(response) {
response_array=JSON.parse(response);
if (response_array['code'] == 200) {
window.location.reload();
}
}
});
}

View File

@@ -0,0 +1,67 @@
<?php
/**
* Render key panel.
*/
/**
* Function that renders key panel.
*
* @param string $key API Key.
* @param string $status API Key status.
* @param string $status_msg Status message.
* @param string $message Message.
* @param string $color Color to highlight message in.
*/
function ao_ccss_render_key( $key, $status, $status_msg, $message, $color ) {
if ( defined( 'AO_PRO_VERSION' ) && has_filter( 'autoptimize_filter_ccss_key' ) ) {
?>
<input type="hidden" id="autoptimize_ccss_key" name="autoptimize_ccss_key" value="">
<?php
return;
}
if ( defined( 'AUTOPTIMIZE_CRITICALCSS_API_KEY' ) ) {
$key = esc_html__( 'API key provided by your host/ WordPress administrator, no need to enter anything here. In case of problems with the API key, contact your host/ WordPress administrator.', 'autoptimize' );
} else if ( has_filter( 'autoptimize_filter_ccss_key' ) ) {
$key = esc_html__( 'API Key provided by a filter, no need to enter anything here.', 'autoptimize' );
}
?>
<ul id="key-panel">
<li class="itemDetail">
<h2 class="itemTitle fleft"><?php esc_html_e( 'API Key', 'autoptimize' ); ?>: <span style="color:<?php echo $color; ?>;"><?php echo $status_msg; ?></span></h2>
<button type="button" class="toggle-btn">
<?php if ( 'valid' != $status ) { ?>
<span class="toggle-indicator dashicons dashicons-arrow-up"></span>
<?php } else { ?>
<span class="toggle-indicator dashicons dashicons-arrow-up dashicons-arrow-down"></span>
<?php } ?>
</button>
<?php if ( 'valid' != $status ) { ?>
<div class="collapsible">
<?php } else { ?>
<div class="collapsible hidden">
<?php } ?>
<?php if ( 'valid' != $status ) { ?>
<div style="clear:both;padding:2px 10px;border-left:solid;border-left-width:5px;border-left-color:<?php echo $color; ?>;background-color:white;">
<p><?php echo $message; ?></p>
</div>
<?php } ?>
<table id="key" class="form-table">
<tr>
<th scope="row">
<?php esc_html_e( 'Your API Key', 'autoptimize' ); ?>
</th>
<td>
<textarea id="autoptimize_ccss_key" name="autoptimize_ccss_key" rows='3' style="width:100%;" placeholder="<?php esc_html_e( 'Please enter your criticalcss.com API key here.', 'autoptimize' ); ?>"><?php echo trim( esc_textarea( $key ) ); ?></textarea>
<p class="notes">
<?php _e( 'Enter your <a href="https://criticalcss.com/account/api-keys?aff=1" target="_blank">criticalcss.com</a> API key above. The key is revalidated every time a new job is sent to it.<br />To obtain your API key, go to <a href="https://criticalcss.com/account/api-keys?aff=1" target="_blank">criticalcss.com</a> > Account > API Keys.<br />Requests to generate a critical CSS via the API are priced at £5 per domain per month.<br /><strong>Not sure yet? With the <a href="https://criticalcss.com/faq/?aff=1#trial" target="_blank">30 day money back guarantee</a>, you have nothing to lose!</strong>', 'autoptimize' ); ?>
</p>
</td>
</tr>
</table>
</div>
</li>
</ul>
<?php
}

View File

@@ -0,0 +1,249 @@
<?php
/**
* JS code to manage queue.
*/
?>
// Hide object text box
var queueOriginEl = document.getElementById('ao-ccss-queue' );
if (queueOriginEl) {
queueOriginEl.style.display = 'none';
// Get queue object and call table renderer
jQuery(document).ready(function() {
// Instance and parse queue object
var aoCssQueueRaw = document.getElementById('ao-ccss-queue').value;
var aoCssQueue = aoCssQueueRaw.indexOf('{"') === 0 ?
JSON.parse(aoCssQueueRaw) :
"";
var aoCssQueueLog = aoCssQueue === "" ?
"empty" :
aoCssQueue;
<?php
if ( $ao_ccss_debug ) {
echo "console.log( 'Queue Object:', aoCssQueueLog );\n";
}
?>
// hook up "remove all jobs" button to the JS action.
jQuery("#removeAllJobs").click(function(){removeAllJobs();});
// Render queue table
drawQueueTable(aoCssQueue);
// Make queue table sortable if there are any elements
var queueBodyEl = jQuery('#queue > tr').length;
if (queueBodyEl > 0) {
jQuery('#queue-tbl').tablesorter({
sortList: [[0,0]],
headers: {6: {sorter: false}}
});
}
// unhide queuerunner button conditionally (we don't want people running the queue continuously) and attach event to it.
if (queueBodyEl > 4 || ( queueBodyEl > 0 && jQuery('#rules > tr').length < 1 ) ) {
jQuery('#queuerunner-container').show();
jQuery("#queuerunner").click(function(){queuerunner();});
}
});
}
// Render the queue in a table
function drawQueueTable(queue) {
jQuery('#queue').empty();
rowNumber=0;
jQuery.each(queue, function(path, keys) {
// Prepare commom job values
ljid = keys.ljid;
targetArr = keys.rtarget.split('|' );
target = targetArr[1];
type = keys.ptype;
ctime = EpochToDate(keys.jctime);
rbtn = false;
dbtn = false;
hbtn = false;
// don't list jobs that don't have a type, they are irrelevant and this also avoids "type.replace is not a function".
if ( type == false ) {
return;
}
// Prepare job statuses
if (keys.jqstat === 'NEW') {
// Status: NEW (N, sort order 1)
status = '<span class="hidden">1</span>N';
statusClass = 'new';
title = '<?php esc_html_e( 'New', 'autoptimize' ); ?> (' + ljid + ')';
buttons = '<?php esc_html_e( 'None', 'autoptimize' ); ?>';
} else if (keys.jqstat === 'JOB_QUEUED' || keys.jqstat === 'JOB_ONGOING') {
// Status: PENDING (P, sort order 2)
status = '<span class="hidden">2</span>P';
statusClass = 'pending';
title = '<?php esc_html_e( 'PENDING', 'autoptimize' ); ?> (' + ljid + ')';
buttons = '<?php esc_html_e( 'None', 'autoptimize' ); ?>';
} else if (keys.jqstat === 'JOB_DONE' && keys.jrstat === 'GOOD' && (keys.jvstat === 'WARN' || keys.jvstat === 'BAD')) {
// Status: REVIEW (R, sort order 5)
status = '<span class="hidden">5</span>R';
statusClass = 'review';
title = "<?php esc_html_e( 'REVIEW', 'autoptimize' ); ?> (" + ljid + ")\n<?php esc_html_e( 'Info from criticalcss.com:', 'autoptimize' ); ?>\n<?php esc_html_e( '- Job ID: ', 'autoptimize' ); ?>" + keys.jid + "\n<?php esc_html_e( '- Status: ', 'autoptimize' ); ?>" + keys.jqstat + "\n<?php esc_html_e( '- Result: ', 'autoptimize' ); ?>" + keys.jrstat + "\n<?php esc_html_e( '- Validation: ', 'autoptimize' ); ?>" + keys.jvstat;
buttons = '<span class="button-secondary" id="' + ljid + '_remove" title="<?php esc_html_e( 'Delete Job', 'autoptimize' ); ?>"><span class="dashicons dashicons-trash"></span></span>';
dbtn = true;
} else if (keys.jqstat === 'JOB_DONE') {
// Status: DONE (D, sort order 6)
status = '<span class="hidden">6</span>D';
statusClass = 'done';
title = '<?php esc_html_e( 'DONE', 'autoptimize' ); ?> (' + ljid + ')';
buttons = '<span class="button-secondary" id="' + ljid + '_remove" title="<?php esc_html_e( 'Delete Job', 'autoptimize' ); ?>"><span class="dashicons dashicons-trash"></span></span>';
dbtn = true;
} else if (keys.jqstat === 'JOB_FAILED' || keys.jqstat === 'STATUS_JOB_BAD' || keys.jqstat === 'INVALID_JWT_TOKEN' || keys.jqstat === 'NO_CSS' || keys.jqstat === 'NO_RESPONSE') {
// Status: ERROR (E, sort order 4)
status = '<span class="hidden">4</span>E';
statusClass = 'error';
title = "<?php esc_html_e( 'ERROR', 'autoptimize' ); ?> (" + ljid + ")\n<?php esc_html_e( 'Info from criticalcss.com:', 'autoptimize' ); ?>\n<?php esc_html_e( '- Job ID: ', 'autoptimize' ); ?>" + keys.jid + "\n<?php esc_html_e( '- Status: ', 'autoptimize' ); ?>" + keys.jqstat + "\n<?php esc_html_e( '- Result: ', 'autoptimize' ); ?>" + keys.jrstat + "\n<?php esc_html_e( '- Validation: ', 'autoptimize' ); ?>" + keys.jvstat;
buttons = '<span class="button-secondary" id="' + ljid + '_retry" title="<?php esc_html_e( 'Retry Job', 'autoptimize' ); ?>"><span class="dashicons dashicons-update"></span></span><span class="button-secondary to-right" id="' + ljid + '_remove" title="<?php esc_html_e( 'Delete Job', 'autoptimize' ); ?>"><span class="dashicons dashicons-trash"></span></span><span class="button-secondary to-right" id="' + ljid + '_help" title="<?php esc_html_e( 'Get Help', 'autoptimize' ); ?>"><span class="dashicons dashicons-sos"></span></span>';
rbtn = true;
dbtn = true;
hbtn = true;
} else {
// Status: UNKNOWN (U, sort order 5)
status = '<span class="hidden">5</span>U';
statusClass = 'unknown';
title = "<?php esc_html_e( 'UNKNOWN', 'autoptimize' ); ?> (" + ljid + ")\n<?php esc_html_e( 'Info from criticalcss.com:', 'autoptimize' ); ?>\n<?php esc_html_e( '- Job ID: ', 'autoptimize' ); ?>" + keys.jid + "\n<?php esc_html_e( '- Status: ', 'autoptimize' ); ?>" + keys.jqstat + "\n<?php esc_html_e( '- Result: ', 'autoptimize' ); ?>" + keys.jrstat + "\n<?php esc_html_e( '- Validation: ', 'autoptimize' ); ?>" + keys.jvstat;
buttons = '<span class="button-secondary" id="' + ljid + '_remove" title="<?php esc_html_e( 'Delete Job', 'autoptimize' ); ?>"><span class="dashicons dashicons-trash"></span></span><span class="button-secondary to-right" id="' + ljid + '_help" title="<?php esc_html_e( 'Get Help', 'autoptimize' ); ?>"><span class="dashicons dashicons-sos"></span></span>';
dbtn = true;
hbtn = true;
}
// Prepare job finish time
if (keys.jftime === null) {
ftime = '<?php esc_html_e( 'N/A', 'autoptimize' ); ?>';
} else {
ftime = EpochToDate(keys.jftime);
}
// Append job entry
jQuery("#queue").append("<tr id='" + ljid + "' class='job " + statusClass + "'><td class='status'><span class='badge " + statusClass + "' title='<?php esc_html_e( 'Job status is ', 'autoptimize' ); ?>" + title + "'>" + status + "</span></td><td>" + target.replace(/(woo_|template_|custom_post_|edd_|bp_|bbp_)/,'') + "</td><td>" + path + "</td><td>" + type.replace(/(woo_|template_|custom_post_|edd_|bp_|bbp_)/,'') + "</td><td>" + ctime + "</td><td>" + ftime + "</td><td class='btn'>" + buttons + "</td></tr>");
// Attach button actions
if (rbtn) {
jQuery('#' + ljid + '_retry').click(function(){retryJob(queue, this.id, path);});
}
if (dbtn) {
jQuery('#' + ljid + '_remove').click(function(){delJob(queue, this.id, path);});
}
if (hbtn) {
jQuery('#' + ljid + '_help').click(function(){jid=this.id.split('_' );window.open('https://criticalcss.com/faq?aoid=' + jid[0], '_blank' );});
}
});
}
// Delete a job from the queue
function delJob(queue, jid, jpath) {
jid = jid.split('_' );
jQuery('#queue-confirm-rm').dialog({
resizable: false,
height: 180,
modal: true,
buttons: {
"<?php esc_html_e( 'Delete', 'autoptimize' ); ?>": function() {
delete queue[jpath];
updateQueue(queue);
jQuery(this).dialog('close' );
},
"<?php esc_html_e( 'Cancel', 'autoptimize' ); ?>": function() {
jQuery(this).dialog('close' );
}
}
});
}
function removeAllJobs() {
jQuery( "#queue-confirm-rm-all" ).dialog({
resizable: false,
height:235,
modal: true,
buttons: {
"<?php esc_html_e( 'Delete all jobs?', 'autoptimize' ); ?>": function() {
queue=[];
updateQueue(queue);
jQuery( this ).dialog( "close" );
},
"<?php esc_html_e( 'Cancel', 'autoptimize' ); ?>": function() {
jQuery( this ).dialog( "close" );
}
}
});
}
// Retry jobs with error
function retryJob(queue, jid, jpath) {
jid = jid.split('_' );
jQuery('#queue-confirm-retry').dialog({
resizable: false,
height: 180,
modal: true,
buttons: {
"<?php esc_html_e( 'Retry', 'autoptimize' ); ?>": function() {
<?php
if ( $ao_ccss_debug ) {
echo "console.log( 'SHOULD retry job:', jid[0], jpath );\n";
}
?>
queue[jpath].jid = null;
queue[jpath].jqstat = 'NEW';
queue[jpath].jrstat = null;
queue[jpath].jvstat = null;
queue[jpath].jctime = (new Date).getTime() / 1000;
queue[jpath].jftime = null;
updateQueue(queue);
jQuery(this).dialog('close' );
},
"<?php esc_html_e( 'Cancel', 'autoptimize' ); ?>": function() {
jQuery(this).dialog('close' );
}
}
});
}
// Refresh queue
function updateQueue(queue) {
document.getElementById('ao-ccss-queue').value=JSON.stringify(queue);
drawQueueTable(queue);
jQuery('#unSavedWarning').show();
document.getElementById('ao_title_and_button').scrollIntoView();
<?php
if ( $ao_ccss_debug ) {
echo "console.log('Updated Queue Object:', queue);\n";
}
?>
}
// Run the queue manually (in case of cron issues/ impatient users).
function queuerunner() {
var data = {
'action': 'ao_ccss_queuerunner',
'ao_ccss_queuerunner_nonce': '<?php echo wp_create_nonce( 'ao_ccss_queuerunner_nonce' ); ?>',
};
jQuery.post(ajaxurl, data, function(response) {
response_array=JSON.parse(response);
if (response_array['code'] == 200) {
displayNotice( '<?php esc_html_e( 'Queue processed, reloading page.', 'autoptimize' ); ?>', 'success' )
setTimeout(window.location.reload.bind(window.location), 1.5*1000);
} else if ( response_array['code'] == 302 ) {
displayNotice( '<?php esc_html_e( 'The queue is locked, retry in a couple of minutes. If this problem persists and the queue is not moving at all remove the <code>wp-content/uploads/ao_ccss/queue.lock</code> file.', 'autoptimize' ); ?>', 'warning' )
} else {
displayNotice( '<?php esc_html_e( 'Could not process queue.', 'autoptimize' ); ?>', 'error' )
}
});
}
// Convert epoch to date for job times
function EpochToDate(epoch) {
if (epoch < 10000000000)
epoch *= 1000;
var epoch = epoch + (new Date().getTimezoneOffset() * -1); //for timeZone
var sdate = new Date(epoch);
var ldate = sdate.toLocaleString();
return ldate;
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* Render the queue panel.
*/
/**
* Function to render the queue panel.
*/
function ao_ccss_render_queue() {
// Attach required arrays.
$criticalcss = autoptimize()->criticalcss();
$ao_ccss_queue = $criticalcss->get_option( 'queue' );
// Prepare the queue object.
if ( empty( $ao_ccss_queue ) ) {
$ao_ccss_queue = '';
} else {
$ao_ccss_queue = json_encode( $ao_ccss_queue );
}
?>
<ul id="queue-panel">
<li class="itemDetail">
<h2 class="itemTitle fleft"><?php esc_html_e( 'Job Queue', 'autoptimize' ); ?></h2>
<button type="button" class="toggle-btn">
<span class="toggle-indicator dashicons dashicons-arrow-up dashicons-arrow-down"></span>
</button>
<?php
if ( $criticalcss->has_autorules() ) {
$_queue_visibility = 'hidden';
} else {
$_queue_visibility = 'visible';
}
?>
<div class="collapsible <?php echo $_queue_visibility; ?>">
<!-- BEGIN Queue dialogs -->
<!-- Retry dialog -->
<div id="queue-confirm-retry" title="<?php esc_html_e( 'Retry Job', 'autoptimize' ); ?>" class="hidden">
<p><?php esc_html_e( 'Are you sure you want to retry this job?', 'autoptimize' ); ?></p>
</div>
<!-- Remove dialog -->
<div id="queue-confirm-rm" title="<?php esc_html_e( 'Delete Job', 'autoptimize' ); ?>" class="hidden">
<p><?php esc_html_e( 'Are you sure you want to delete this job?', 'autoptimize' ); ?></p>
</div>
<!-- Remove all dialog -->
<div id="queue-confirm-rm-all" title="<?php esc_html_e( 'Delete all jobs', 'autoptimize' ); ?>" class="hidden">
<p><?php esc_html_e( 'This will delete all jobs, are you sure?', 'autoptimize' ); ?></p>
</div>
<!-- END Queue dialogs -->
<!-- BEGIN Queue UI -->
<div class="howto">
<div class="title-wrap">
<h4 class="title"><?php esc_html_e( 'How To Use Autoptimize CriticalCSS Queue', 'autoptimize' ); ?></h4>
<p class="subtitle"><?php esc_html_e( 'Click the side arrow to toggle instructions', 'autoptimize' ); ?></p>
</div>
<button type="button" class="toggle-btn">
<span class="toggle-indicator dashicons dashicons-arrow-up dashicons-arrow-down"></span>
</button>
<div class="howto-wrap hidden">
<p><?php _e( 'TL;DR:<br /><strong>Queue runs every 10 minutes.</strong> Job statuses are <span class="badge new">N</span> for NEW, <span class="badge pending">P</span> for PENDING, <span class="badge error">E</span> for ERROR and <span class="badge unknown">U</span> for UNKOWN.', 'autoptimize' ); ?></p>
<ol>
<li><?php _e( 'The queue operates <strong>automatically, asynchronously and on regular intervals of 10 minutes.</strong> To view updated queue status, refresh this page.', 'autoptimize' ); ?></li>
<li><?php _e( 'When the conditions to create a job are met (i.e. user not logged in, no matching <span class="badge manual">MANUAL</span> rule or CSS files has changed for an <span class="badge auto">AUTO</span> rule), a <span class="badge new">N</span> job is created in the queue.', 'autoptimize' ); ?></li>
<li><?php _e( "Autoptimize constantly queries the queue for <span class='badge new'>N</span> jobs. When it finds one, gears spins and jobs becomes <span class='badge pending'>P</span> while they are running and <a href='https://criticalcss.com/?aff=1' target='_blank'>criticalcss.com</a> doesn't return a result.", 'autoptimize' ); ?></li>
<li><?php _e( 'As soon as <a href="https://criticalcss.com/?aff=1" target="_blank">criticalcss.com</a> returns a valid critical CSS file, the job is then finished and removed from the queue.', 'autoptimize' ); ?></li>
<li><?php _e( 'When things go wrong, a job is marked as <span class="badge error">E</span>. You can retry faulty jobs, delete them or get in touch with <a href="https://criticalcss.com/?aff=1" target="_blank">criticalcss.com</a> for assistance.', 'autoptimize' ); ?></li>
<li><?php _e( 'Sometimes an unknown condition can happen. In this case, the job status becomes <span class="badge unknown">U</span> and you may want to ask <a href="https://criticalcss.com/?aff=1" target="_blank">criticalcss.com</a> for help or just delete it.', 'autoptimize' ); ?></li>
<li><?php _e( 'To get more information about jobs statuses, specially the ones with <span class="badge error">E</span> and <span class="badge unknown">U</span> status, hover your mouse in the status badge of that job. This information might be crucial when contacting <a href="https://criticalcss.com/?aff=1" target="_blank">criticalcss.com</a> for assistance.', 'autoptimize' ); ?></li>
<li><?php _e( '<strong>A word about WordPress cron:</strong> Autoptimize watches the queue by using WordPress Cron (or WP-Cron for short.) It <a href="https://www.smashingmagazine.com/2013/10/schedule-events-using-wordpress-cron/#limitations-of-wordpress-cron-and-solutions-to-fix-em" target="_blank">could be faulty</a> on very light or very heavy loads. If your site receives just a few or thousands visits a day, it might be a good idea to <a href="https://developer.wordpress.org/plugins/cron/hooking-wp-cron-into-the-system-task-scheduler/" target="_blank">turn WP-Cron off and use your system task scheduler</a> to fire it instead.', 'autoptimize' ); ?></li>
</ol>
</div>
</div>
<table id="queue-tbl" class="queue tablesorter" cellspacing="0">
<thead>
<tr><th class="status"><?php esc_html_e( 'Status', 'autoptimize' ); ?></th><th><?php esc_html_e( 'Target Rule', 'autoptimize' ); ?></th><th><?php esc_html_e( 'Page Path', 'autoptimize' ); ?></th><th><?php esc_html_e( 'Page Type', 'autoptimize' ); ?></th><th><?php esc_html_e( 'Creation Date', 'autoptimize' ); ?></th><th><?php esc_html_e( 'Finish Date', 'autoptimize' ); ?></th><th class="btn"><?php esc_html_e( 'Actions', 'autoptimize' ); ?></th></tr>
</thead>
<tbody id="queue"></tbody>
</table>
<input class="hidden" type="text" id="ao-ccss-queue" name="autoptimize_ccss_queue" value='<?php echo( $ao_ccss_queue ); ?>'>
<div class="submit jobs-btn">
<div id="queuerunner-container" class="alignleft hidden">
<span id="queuerunner" class="button-secondary"><?php esc_html_e( 'Manually process the job queue', 'autoptimize' ); ?></span>
</div>
<div class="alignright">
<span id="removeAllJobs" class="button-secondary" style="color:red;"><?php esc_html_e( 'Remove all jobs', 'autoptimize' ); ?></span>
</div>
</div>
</div>
<!-- END Queue UI -->
</li>
</ul>
<?php
}

View File

@@ -0,0 +1,455 @@
<?php
/**
* Outputs JS code for the rules panel.
*/
if ( $ao_ccss_debug ) {
echo "console.log('[WARN] Autoptimize CriticalCSS is in debug mode!');\n";
echo "console.log('[WARN] Avoid using debug mode on production/live environments unless for ad-hoc troubleshooting purposes and make sure to disable it after!');\n";
}
?>
var rulesOriginEl = document.getElementById("critCssOrigin");
var deferInlineEl = document.getElementById("autoptimize_css_defer_inline");
var additionalEl = document.getElementById("autoptimize_ccss_additional");
if (rulesOriginEl)
rulesOriginEl.style.display = 'none';
if (deferInlineEl)
deferInlineEl.style.display = 'none';
if (additionalEl)
additionalEl.style.display = 'none';
if (rulesOriginEl) {
jQuery(document).ready(function() {
critCssArray=JSON.parse(document.getElementById("critCssOrigin").value);
<?php
if ( $ao_ccss_debug ) {
echo "console.log('Rules Object:', critCssArray);\n";
}
?>
drawTable(critCssArray);
jQuery("#addCritCssButton").click(function(){addEditRow();});
jQuery("#editDefaultButton").click(function(){editDefaultCritCss();});
jQuery("#editAdditionalButton").click(function(){editAdditionalCritCss();});
jQuery("#removeAllRules").click(function(){removeAllRules();});
});
}
function drawTable(critCssArray) {
jQuery("#rules-list").empty();
rnotice = 0;
jQuery.each(critCssArray,function(k,v) {
if (k=="paths") {
kstring="<?php esc_html_e( 'Path Based Rules', 'autoptimize' ); ?>";
} else {
kstring="<?php esc_html_e( 'Conditional Tags, Custom Post Types and Page Templates Rules', 'autoptimize' ); ?>";
}
if (!(jQuery.isEmptyObject(v))) {
jQuery("#rules-list").append("<tr><td colspan='5'><h4>" + kstring + "</h4></td></tr>");
jQuery("#rules-list").append("<tr class='header "+k+"Rule'><th><?php esc_html_e( 'Type', 'autoptimize' ); ?></th><th><?php esc_html_e( 'Target', 'autoptimize' ); ?></th><th><?php esc_html_e( 'Critical CSS File', 'autoptimize' ); ?></th><th colspan='2'><?php esc_html_e( 'Actions', 'autoptimize' ); ?></th></tr>");
}
nodeNumber=0;
jQuery.each(v,function(i,nv){
nodeNumber++;
nodeId=k + "_" + nodeNumber;
hash=nv.hash;
file=nv.file;
filest=nv.file;
auto_style = '';
<?php
$criticalcss = new autoptimizeCriticalCSSBase();
if ( $criticalcss->is_api_active() ) {
echo 'api_active = 1;' . "\n";
} else {
echo 'api_active = 0;' . "\n";
}
?>
if (file == 0) {
file='<?php esc_html_e( 'To be fetched from criticalcss.com in the next queue run...', 'autoptimize' ); ?>';
}
if (nv.hash === 0 && filest != 0) {
type='<?php esc_html_e( 'MANUAL', 'autoptimize' ); ?>';
typeClass = 'manual';
} else {
type='<?php esc_html_e( 'AUTO', 'autoptimize' ); ?>';
typeClass = 'auto';
if ( api_active != 1 ) {
auto_style = ' style="opacity:.5;" '
}
}
if (file && typeof file == 'string') {
rmark_find=file.split('_');
if (rmark_find[2] || rmark_find[2] == 'R.css') {
rnotice = rnotice + 1;
}
}
if ( k == "paths" ) {
<?php
if ( apply_filters( 'autoptimize_filter_ccss_paths_clickable', true ) ) {
?>
target = '<a href="<?php echo AUTOPTIMIZE_WP_SITE_URL; ?>' + i + '" target="_blank">' + i + '</a>';
<?php
} else {
?>
target = i;
<?php
}
?>
} else {
target = i.replace(/(woo_|template_|custom_post_|edd_|bp_|bbp_)/,'');
}
jQuery("#rules-list").append("<tr " + auto_style + "class='rule "+k+"Rule'><td class='type'><span class='badge " + typeClass + "'>" + type + "</span></td><td class='target'>" + target + "</td><td class='file'>" + file + "</td><td class='btn edit'><span class=\"button-secondary\" id=\"" + nodeId + "_edit\"><?php esc_html_e( 'Edit', 'autoptimize' ); ?></span></td><td class='btn delete'><span class=\"button-secondary\" id=\"" + nodeId + "_remove\"><?php esc_html_e( 'Remove', 'autoptimize' ); ?></span></td></tr>");
if ( typeClass == 'manual' || api_active == 1 ) {
jQuery("#" + nodeId + "_edit").click(function(){addEditRow(this.id);});
}
jQuery("#" + nodeId + "_remove").click(function(){confirmRemove(this.id);});
})
});
if ( rnotice && rnotice != 0 ) {
// R rules were found, show a notice!
// and add some JS magic to ensure the notice works as a notice, but is shown inline
// in the rules panel instead of in the notice area where it would be too prominent.
<?php
$_ao_ccss_review_notice_id = 'autoptimize-ccss-review-rules-notice-30';
// Translators: before the 1st word a number + a space will be displayed, as in e.g. "2 of above rules".
$_ao_ccss_review_notice_copy = esc_html__( 'of the above rules got flagged by criticalcss.com as possibly needing review. This is often due to font-related issues which can be safely ignored, but in case of doubt do a visual test or check for Cumulative Layout Shift issues in e.g. Pagespeed Insights.', 'autoptimize' );
if ( PAnD::is_admin_notice_active( $_ao_ccss_review_notice_id ) ) {
?>
jQuery("#rules-notices").append( "&nbsp;<div class='rnotice notice notice-info is-dismissible hidden' data-dismissible='<?php echo $_ao_ccss_review_notice_id; ?>'><p>" + rnotice + " <?php echo $_ao_ccss_review_notice_copy; ?>" + "</p></div>");
jQuery( document ).ready(function() {
jQuery("div.rnotice").detach().appendTo('#rules-notices');
jQuery("div.rnotice").show();
});
<?php
} else if ( $ao_ccss_debug ) {
?>
console.log( "Autoptimize: " + rnotice + " <?php echo $_ao_ccss_review_notice_copy; ?>" );
<?php
}
?>
}
}
function confirmRemove(idToRemove) {
jQuery( "#confirm-rm" ).dialog({
resizable: false,
height:235,
modal: true,
buttons: {
"<?php esc_html_e( 'Delete', 'autoptimize' ); ?>": function() {
removeRow(idToRemove);
updateAfterChange();
jQuery( this ).dialog( "close" );
},
"<?php esc_html_e( 'Cancel', 'autoptimize' ); ?>": function() {
jQuery( this ).dialog( "close" );
}
}
});
}
function removeAllRules() {
jQuery( "#confirm-rm-all" ).dialog({
resizable: false,
height:235,
modal: true,
buttons: {
"<?php esc_html_e( 'Delete All', 'autoptimize' ); ?>": function() {
critCssArray={'paths':[],'types':[]};
drawTable(critCssArray);
updateAfterChange();
removeAllCcssFilesOnServer();
jQuery( this ).dialog( "close" );
},
"<?php esc_html_e( 'Cancel', 'autoptimize' ); ?>": function() {
jQuery( this ).dialog( "close" );
}
}
});
}
function removeRow(idToRemove) {
splits=idToRemove.split(/_/);
crit_type=splits[0];
crit_item=splits[1];
crit_key=Object.keys(critCssArray[crit_type])[crit_item-1];
crit_file=critCssArray[crit_type][crit_key].file;
delete critCssArray[crit_type][crit_key];
var data = {
'action': 'rm_critcss',
'critcss_rm_nonce': '<?php echo wp_create_nonce( 'rm_critcss_nonce' ); ?>',
'cachebustingtimestamp': new Date().getTime(),
'critcssfile': crit_file
};
jQuery.ajaxSetup({
async: false
});
jQuery.post(ajaxurl, data, function(response) {
response_array=JSON.parse(response);
if (response_array["code"]!=200) {
// not displaying notice, as the end result is OK; the file isn't there
// displayNotice(response_array["string"]);
}
});
}
function removeAllCcssFilesOnServer() {
var data = {
'action': 'rm_critcss_all',
'critcss_rm_all_nonce': '<?php echo wp_create_nonce( 'rm_critcss_all_nonce' ); ?>',
'cachebustingtimestamp': new Date().getTime()
};
jQuery.ajaxSetup({
async: false
});
jQuery.post(ajaxurl, data, function(response) {
response_array=JSON.parse(response);
if (response_array["code"]!=200) {
// not displaying notice, as the end result is OK; the file isn't there
// displayNotice(response_array["string"]);
}
});
}
function addEditRow(idToEdit) {
resetForm();
if (idToEdit) {
dialogTitle="<?php esc_html_e( 'Edit Critical CSS Rule', 'autoptimize' ); ?>";
splits=idToEdit.split(/_/);
crit_type=splits[0];
crit_item=splits[1];
crit_key=Object.keys(critCssArray[crit_type])[crit_item-1];
crit_file=critCssArray[crit_type][crit_key].file;
jQuery("#critcss_addedit_id").val(idToEdit);
jQuery("#critcss_addedit_type").val(crit_type);
jQuery("#critcss_addedit_file").val(crit_file);
jQuery("#critcss_addedit_css").attr("placeholder", "<?php esc_html_e( 'Loading critical CSS...', 'autoptimize' ); ?>");
jQuery("#critcss_addedit_css").attr("spellcheck",false);
jQuery("#critcss_addedit_type").attr("disabled",true);
if (crit_type==="paths") {
jQuery("#critcss_addedit_path").val(crit_key);
jQuery("#critcss_addedit_path_wrapper").show();
jQuery("#critcss_addedit_pagetype_wrapper").hide();
} else {
jQuery("#critcss_addedit_pagetype").val(crit_key);
jQuery("#critcss_addedit_pagetype_wrapper").show();
jQuery("#critcss_addedit_path_wrapper").hide();
}
var data = {
'action': 'fetch_critcss',
'critcss_fetch_nonce': '<?php echo wp_create_nonce( 'fetch_critcss_nonce' ); ?>',
'cachebustingtimestamp': new Date().getTime(),
'critcssfile': crit_file
};
jQuery.post(ajaxurl, data, function(response) {
response_array=JSON.parse(response);
if (response_array["code"]==200) {
jQuery("#critcss_addedit_css").val(response_array["string"]);
} else {
jQuery("#critcss_addedit_css").attr("placeholder", "").focus();
}
});
} else {
dialogTitle="<?php esc_html_e( 'Add Critical CSS Rule', 'autotimize' ); ?>";
// default: paths, hide content type field
jQuery("#critcss_addedit_type").val("paths");
jQuery("#critcss_addedit_css").attr("spellcheck",false);
jQuery("#critcss_addedit_pagetype_wrapper").hide();
// event handler on type to switch display
jQuery("#critcss_addedit_type").on('change', function (e) {
if(this.value==="types") {
jQuery("#critcss_addedit_pagetype_wrapper").show();
jQuery("#critcss_addedit_path_wrapper").hide();
jQuery("#critcss_addedit_css").attr("placeholder", "<?php esc_html_e( 'For type based rules, paste your specific and minified critical CSS here and hit submit to save. If you want to create a rule to exclude from critical CSS injection, enter \"none\".', 'autoptimize' ); ?>");
} else {
jQuery("#critcss_addedit_path_wrapper").show();
jQuery("#critcss_addedit_pagetype_wrapper").hide();
jQuery("#critcss_addedit_css").attr("placeholder", "<?php esc_html_e( 'For path based rules, paste your specific and minified critical CSS here or leave this empty to fetch it from criticalcss.com and hit submit to save. If you want to create a rule to exclude from critical CSS injection, enter \"none\"', 'autoptimize' ); ?>");
}
});
}
jQuery("#addEditCritCss").dialog({
autoOpen: true,
height: 500,
width: 700,
title: dialogTitle,
modal: true,
buttons: {
"<?php esc_html_e( 'Submit', 'autoptimize' ); ?>": function() {
rpath = jQuery("#critcss_addedit_path").val();
rtype = jQuery("#critcss_addedit_pagetype option:selected").val();
rccss = jQuery("#critcss_addedit_css").val();
<?php if ( $ao_ccss_debug ) { ?>
console.log('rpath: ' + rpath, 'rtype: ' + rtype, 'rccss: ' + rccss);
<?php } ?>
if (rpath === '' && rtype === '') {
alert('<?php esc_html_e( "Rule validation error:\\n\\nBased on your rule type, you should set a path or conditional tag.", 'autoptimize' ); ?>');
} else if (rtype !== '' && rccss == '') {
alert('<?php esc_html_e( "Rule validation error:\\n\\nType based rules requires a minified critical CSS.", 'autoptimize' ); ?>');
} else if (rpath !== rpath.replace(/("|\'|<|>|\[|\]|{|}|\|)/,'')) {
alert('<?php esc_html_e( "Path validation error:\\n\\nThe path contains characters that are not permitted, remove or encode the unsafe characters.", 'autoptimize' ); ?>');
} else {
saveEditCritCss();
jQuery(this).dialog('close');
}
},
"<?php esc_html_e( 'Cancel', 'autoptimize' ); ?>": function() {
resetForm();
jQuery(this).dialog("close");
}
}
});
}
function editDefaultCritCss(){
document.getElementById("dummyDefault").value=document.getElementById("autoptimize_css_defer_inline").value;
jQuery("#dummyDefault").attr("spellcheck",false);
jQuery("#default_critcss_wrapper").dialog({
autoOpen: true,
height: 505,
width: 700,
title: "<?php esc_html_e( 'Default Critical CSS', 'autoptimize' ); ?>",
modal: true,
buttons: {
"<?php esc_html_e( 'Submit', 'autoptimize' ); ?>": function() {
document.getElementById("autoptimize_css_defer_inline").value=document.getElementById("dummyDefault").value;
jQuery("#unSavedWarning").show();
jQuery("#default_critcss_wrapper").dialog( "close" );
},
"<?php esc_html_e( 'Cancel', 'autoptimize' ); ?>": function() {
jQuery("#default_critcss_wrapper").dialog( "close" );
}
}
});
}
function editAdditionalCritCss(){
document.getElementById("dummyAdditional").value=document.getElementById("autoptimize_ccss_additional").value;
jQuery("#dummyAdditional").attr("spellcheck",false);
jQuery("#additional_critcss_wrapper").dialog({
autoOpen: true,
height: 505,
width: 700,
title: "<?php esc_html_e( 'Additional Critical CSS', 'autoptimize' ); ?>",
modal: true,
buttons: {
"<?php esc_html_e( 'Submit', 'autoptimize' ); ?>": function() {
document.getElementById("autoptimize_ccss_additional").value=document.getElementById("dummyAdditional").value;
jQuery("#unSavedWarning").show();
jQuery("#additional_critcss_wrapper").dialog( "close" );
},
"<?php esc_html_e( 'Cancel', 'autoptimize' ); ?>": function() {
jQuery("#additional_critcss_wrapper").dialog( "close" );
}
}
});
}
function saveEditCritCss(){
critcssfile=jQuery("#critcss_addedit_file").val();
critcsscontents=jQuery("#critcss_addedit_css").val();
critcsstype=jQuery("#critcss_addedit_type").val();
critcssid=jQuery("#critcss_addedit_id").val();
if (critcssid) {
// this was an "edit" action, so remove original
// will also remove the file, but that will get rewritten anyway
removeRow(critcssid);
}
if (critcsstype==="types") {
critcsstarget=jQuery("#critcss_addedit_pagetype").val();
} else {
critcsstarget=jQuery("#critcss_addedit_path").val();
}
if (!critcssfile && !critcsscontents) {
critcssfile=0;
} else if (!critcssfile && critcsscontents) {
critcssfile="ccss_" + md5(critcsscontents+critcsstarget) + ".css";
}
// Compose the rule object
critCssArray[critcsstype][critcsstarget]={};
critCssArray[critcsstype][critcsstarget].hash=0;
critCssArray[critcsstype][critcsstarget].file=critcssfile;
<?php
if ( $ao_ccss_debug ) {
echo "console.log('[RULE PROPERTIES] Type:', critcsstype, ', Target:', critcsstarget, ', Hash:', 0, ', File:', critcssfile);";
}
?>
updateAfterChange();
var data = {
'action': 'save_critcss',
'critcss_save_nonce': '<?php echo wp_create_nonce( 'save_critcss_nonce' ); ?>',
'critcssfile': critcssfile,
'critcsscontents': critcsscontents
};
jQuery.post(ajaxurl, data, function(response) {
response_array=JSON.parse(response);
if (response_array["code"]!=200) {
displayNotice(response_array["string"]);
}
});
}
function updateAfterChange() {
document.getElementById("critCssOrigin").value=JSON.stringify(critCssArray);
drawTable(critCssArray);
<?php
// autosave rules is on by default, but can be disabled with a filter.
if ( apply_filters( 'autoptimize_filter_ccss_settings_rules_autosave', true ) ) {
?>
var data = {
'action': 'ao_ccss_saverules',
'ao_ccss_saverules_nonce': '<?php echo wp_create_nonce( 'ao_ccss_saverules_nonce' ); ?>',
'critcssrules': document.getElementById("critCssOrigin").value
};
jQuery.post(ajaxurl, data, function(response) {
response_array=JSON.parse(response);
if (response_array["code"]!=200) {
displayNotice(response_array["msg"]);
jQuery("#unSavedWarning").show();
}
});
<?php } else { ?>
jQuery("#unSavedWarning").show();
<?php } ?>
document.getElementById('ao_title_and_button').scrollIntoView();
}
function displayNotice(textIn, level = 'error') {
jQuery('<div class="notice notice-' + level + ' notice is-dismissible"><p>'+textIn+'</p></div>').insertBefore("#unSavedWarning");
document.getElementById('ao_title_and_button').scrollIntoView();
}
function resetForm() {
jQuery("#critcss_addedit_css").attr("placeholder", "<?php esc_html_e( 'For path based rules, paste your specific and minified critical CSS. If you want to create a rule to exclude from critical CSS injection, enter \"none\"', 'autoptimize' ); ?>");
jQuery("#critcss_addedit_type").attr("disabled",false);
jQuery("#critcss_addedit_path_wrapper").show();
jQuery("#critcss_addedit_id").val("");
jQuery("#critcss_addedit_path").val("");
jQuery("#critcss_addedit_file").val("");
jQuery("#critcss_addedit_pagetype").val("");
jQuery("#critcss_addedit_css").val("");
}

View File

@@ -0,0 +1,238 @@
<?php
/**
* Render the rules panel.
*/
/**
* Main function to render the rules panel.
*/
function ao_ccss_render_rules() {
// Attach required arrays.
$criticalcss = autoptimize()->criticalcss();
$ao_ccss_rules = sanitize_rules( $criticalcss->get_option( 'rules' ) );
$ao_ccss_types = $criticalcss->get_types();
if ( empty( $ao_ccss_types ) || ! is_array( $ao_ccss_types ) ) {
$ao_ccss_types = array( 'No conditionals, check CSS optimization settings.' );
}
?>
<ul id="rules-panel">
<li class="itemDetail">
<h2 class="itemTitle"><?php esc_html_e( 'Rules', 'autoptimize' ); ?></h2>
<!-- BEGIN Rule dialogs -->
<!-- Unsaved dialog -->
<div id="unSavedWarning" class="hidden updated settings-error notice notice-warning is-dismissible">
<p><?php _e( "<strong>Rules or Queue changed!</strong> Don't forget to save your changes!", 'autoptimize' ); ?></p>
</div>
<!-- Create/edit rule dialog -->
<div id="addEditCritCss" class="hidden">
<table class="form-table rules">
<tr id="critcss_addedit_type_wrapper">
<th scope="row">
<?php esc_html_e( 'Rule Type', 'autoptimize' ); ?>
</th>
<td>
<select id="critcss_addedit_type" style="width:100%;">
<option value="paths"><?php esc_html_e( 'Path', 'autoptimize' ); ?></option>
<option value="types"><?php esc_html_e( 'Conditional Tag', 'autoptimize' ); ?></option>
</select>
</td>
</tr>
<tr id="critcss_addedit_path_wrapper">
<th scope="row">
<?php esc_html_e( 'String in Path', 'autoptimize' ); ?>
</th>
<td>
<input type="text" id="critcss_addedit_path" placeholder="<?php esc_html_e( "Enter a part of the URL that identifies the page(s) you're targetting.", 'autoptimize' ); ?>" style="width:100%;" value="">
</td>
</tr>
<tr id="critcss_addedit_pagetype_wrapper">
<th scope="row">
<?php esc_html_e( 'Conditional Tag, Custom Post Type or Page Template', 'autoptimize' ); ?>
</th>
<td>
<select id="critcss_addedit_pagetype" style="width:100%;">
<option value="" disabled selected><?php esc_html_e( 'Select from the list below...', 'autoptimize' ); ?></option>
<optgroup label="<?php esc_html_e( 'Standard Conditional Tags', 'autoptimize' ); ?>">
<?php
// Render grouped simple conditional tags.
foreach ( $ao_ccss_types as $ctag ) {
$optgrp = substr( $ctag, 0, 3 );
if ( substr( $ctag, 0, 3 ) === 'is_' ) {
echo '<option value="' . $ctag . '">' . $ctag . '</option>';
}
$prevgrp = substr( $ctag, 0, 3 );
}
// Render grouped custom post types, templates and specific conditional tags.
foreach ( $ao_ccss_types as $type ) {
$optgrp = substr( $type, 0, 3 );
// Option groups labels.
if ( $optgrp !== $prevgrp && 'is_' !== $optgrp ) {
?>
</optgroup>
<?php
if ( substr( $type, 0, 12 ) === 'custom_post_' ) {
?>
<optgroup label="<?php esc_html_e( 'Custom Post Types', 'autoptimize' ); ?>">
<?php
} elseif ( substr( $type, 0, 9 ) === 'template_' ) {
?>
<optgroup label="<?php esc_html_e( 'Page Templates', 'autoptimize' ); ?>">
<?php
} elseif ( substr( $type, 0, 4 ) === 'bbp_' ) {
?>
<optgroup label="<?php esc_html_e( 'BBPress Conditional Tags', 'autoptimize' ); ?>">
<?php
} elseif ( substr( $type, 0, 3 ) === 'bp_' ) {
?>
<optgroup label="<?php esc_html_e( 'BuddyPress Conditional Tags', 'autoptimize' ); ?>">
<?php
} elseif ( substr( $type, 0, 4 ) === 'edd_' ) {
?>
<optgroup label="<?php esc_html_e( 'Easy Digital Downloads Conditional Tags', 'autoptimize' ); ?>">
<?php
} elseif ( substr( $type, 0, 4 ) === 'woo_' ) {
?>
<optgroup label="<?php esc_html_e( 'WooCommerce Conditional Tags', 'autoptimize' ); ?>">
<?php
}
}
// Options.
if ( 'is_' !== $optgrp ) {
// Remove prefix from custom post types, templates and some specific conditional tags.
if ( substr( $type, 0, 12 ) === 'custom_post_' ) {
$_type = str_replace( 'custom_post_', '', $type );
} elseif ( substr( $type, 0, 9 ) === 'template_' ) {
$_type = str_replace( 'template_', '', $type );
} elseif ( 'bbp_is_bbpress' === $type ) {
$_type = str_replace( 'bbp_', '', $type );
} elseif ( 'bp_is_buddypress' === $type ) {
$_type = str_replace( 'bp_', '', $type );
} elseif ( substr( $type, 0, 4 ) === 'woo_' ) {
$_type = str_replace( 'woo_', '', $type );
} elseif ( substr( $type, 0, 4 ) === 'edd_' ) {
$_type = str_replace( 'edd_', '', $type );
} else {
$_type = $type;
}
echo '<option value="' . $type . '">' . $_type . '</option>';
$prevgrp = $optgrp;
}
}
?>
</optgroup>
</select>
</td>
</tr>
<tr>
<th scope="row">
<?php esc_html_e( 'Custom Critical CSS', 'autoptimize' ); ?>
</th>
<td>
<textarea id="critcss_addedit_css" rows="13" cols="10" style="width:100%;" placeholder="<?php esc_html_e( 'Paste your specific critical CSS here and hit submit to save.', 'autoptimize' ); ?>"></textarea>
<input type="hidden" id="critcss_addedit_file">
<input type="hidden" id="critcss_addedit_id">
</td>
</tr>
</table>
</div>
<!-- Remove dialog -->
<div id="confirm-rm" title="<?php esc_html_e( 'Delete Rule', 'autoptimize' ); ?>" class="hidden">
<p><?php _e( 'This Critical CSS rule will be deleted immediately and cannot be recovered.<br /><br /><strong>Are you sure?</strong>', 'autoptimize' ); ?></p>
</div>
<!-- Remove All dialog -->
<div id="confirm-rm-all" title="<?php esc_html_e( 'Delete all Rules and Jobs', 'autoptimize' ); ?>" class="hidden">
<p><?php _e( 'All Critical CSS rules will be deleted immediately and cannot be recovered.<br /><br /><strong>Are you sure?</strong>', 'autoptimize' ); ?></p>
</div>
<!-- Add/edit default critical CSS dialog -->
<div id="default_critcss_wrapper" class="hidden">
<textarea id="dummyDefault" rows="19" cols="10" style="width:100%;" placeholder="<?php esc_html_e( 'Paste your minified default critical CSS here and hit submit to save. This is the critical CSS to be used for every page not matching any rule.', 'autoptimize' ); ?>"></textarea>
</div>
<!-- Add/edit additional critical CSS dialog -->
<div id="additional_critcss_wrapper" class="hidden">
<textarea id="dummyAdditional" rows="19" cols="10" style="width:100%;" placeholder="<?php esc_html_e( 'Paste your minified additional critical CSS here and hit submit to save. This is the CSS to be added AT THE END of every critical CSS provided by a matching rule, or the default one.', 'autoptimize' ); ?>"></textarea>
</div>
<!-- BEGIN Rules UI -->
<div class="howto">
<div class="title-wrap">
<h4 class="title"><?php esc_html_e( 'How To Use Autoptimize CriticalCSS Rules', 'autoptimize' ); ?></h4>
<p class="subtitle"><?php esc_html_e( 'Click the side arrow to toggle instructions', 'autoptimize' ); ?></p>
</div>
<button type="button" class="toggle-btn">
<span class="toggle-indicator dashicons dashicons-arrow-up dashicons-arrow-down"></span>
</button>
<div class="howto-wrap hidden">
<p><?php _e( "TL;DR:<br />Critical CSS files from <span class='badge auto'>AUTO</span> <strong>rules are updated automatically</strong> while from <span class='badge manual'>MANUAL</span> <strong>rules are not.</strong>", 'autoptimize' ); ?></p>
<ol>
<li><?php _e( 'When a valid <a href="https://criticalcss.com/?aff=1" target="_blank">criticalcss.com</a> API key is in place, Autoptimize starts to operate <strong>automatically</strong>.', 'autoptimize' ); ?></li>
<li><?php _e( 'Upon a request to any of the frontend pages made by a <strong>not logged in user</strong>, it will <strong>asynchronously</strong> fetch and update the critical CSS from <a href="https://criticalcss.com/?aff=1" target="_blank">criticalcss.com</a> for conditional tags you have on your site (e.g. is_page, is_single, is_archive etc.)', 'autoptimize' ); ?></li>
<li><?php _e( 'These requests also creates an <span class="badge auto">AUTO</span> rule for you. The critical CSS files from <span class="badge auto">AUTO</span> <strong>rules are updated automatically</strong> when a CSS file in your theme or frontend plugins changes.', 'autoptimize' ); ?></li>
<li><?php _e( 'If you want to make any fine tunning in the critical CSS file of an <span class="badge auto">AUTO</span> rule, click on "Edit" button of that rule, change what you need, submit and save it. The rule you\'ve just edited becomes a <span class="badge manual">MANUAL</span> rule then.', 'autoptimize' ); ?></li>
<li><?php _e( 'You can create <span class="badge manual">MANUAL</span> rules for specific page paths (URL). Longer, more specific paths have higher priority over shorter ones, which in turn have higher priority over <span class="badge auto">AUTO</span> rules. Also, critical CSS files from <span class="badge manual">MANUAL</span> <strong>rules are NEVER updated automatically.</strong>', 'autoptimize' ); ?></li>
<li><?php _e( 'At any time you can delete an <span class="badge auto">AUTO</span> or <span class="badge manual">MANUAL</span> rule by cliking on "Remove" button of the desired rule and saving your changes.', 'autoptimize' ); ?></li>
</ol>
</div>
</div>
<textarea id="autoptimize_css_defer_inline" name="autoptimize_css_defer_inline" rows="19" cols="10" style="width:100%;"><?php echo autoptimizeStyles::sanitize_css( get_option( 'autoptimize_css_defer_inline', '' ) ); ?></textarea>
<textarea id="autoptimize_ccss_additional" name="autoptimize_ccss_additional" rows="19" cols="10" style="width:100%;"><?php echo autoptimizeStyles::sanitize_css( get_option( 'autoptimize_ccss_additional', '' ) ); ?></textarea>
<table class="rules-list" cellspacing="0"><tbody id="rules-list"></tbody></table>
<input class="hidden" type="text" id="critCssOrigin" name="autoptimize_ccss_rules" value='<?php echo ( json_encode( $ao_ccss_rules, JSON_FORCE_OBJECT ) ); ?>'>
<!-- Wrapper for in screen notices -->
<div id="rules-notices"></div>
<!-- END Rule add/edit dialogs -->
<div class="submit rules-btn">
<div class="alignleft">
<span id="addCritCssButton" class="button-secondary"><?php esc_html_e( 'Add New Rule', 'autoptimize' ); ?></span>
<span id="editDefaultButton" class="button-secondary"><?php esc_html_e( 'Edit Default Rule CSS', 'autoptimize' ); ?></span>
<span id="editAdditionalButton" class="button-secondary"><?php esc_html_e( 'Add CSS To All Rules', 'autoptimize' ); ?></span>
</div>
<div class="alignright">
<span id="removeAllRules" class="button-secondary" style="color:red;"><?php esc_html_e( 'Remove all rules', 'autoptimize' ); ?></span>
</div>
</div>
<!-- END Rules UI -->
</li>
</ul>
<?php
}
/**
* Sanitize rules before rendering.
*
* @param array $rules Array with rules to be sanitized.
*/
function sanitize_rules( $rules ) {
if ( is_array( $rules ) && ! empty( $rules ) && apply_filters( 'autoptimize_filter_ccss_paths_clickable', true ) ) {
if ( array_key_exists( 'paths', $rules ) ) {
foreach ( $rules['paths'] as $key => $value ) {
$newkey = esc_url( $key );
if ( $newkey !== $key ) {
if ( 0 === strpos( $newkey, 'http://' ) && 0 !== strpos( $key, 'http://' ) ) {
// esc_url adds "http://" to any string that does not start with either a protocol or a
// slash, see https://developer.wordpress.org/reference/functions/esc_url/#more-information
// this removes that unneeded protocol again.
$newkey = substr_replace( $newkey, '', 0, 7 );
}
unset( $rules['paths'][ $key ] );
$rules['paths'][ $newkey ] = $value;
}
}
}
}
$rules = autoptimizeUtils::strip_tags_array( $rules );
return $rules;
}
?>

View File

@@ -0,0 +1,253 @@
/* form */
.itemTitle {
margin: 0;
}
.itemTitle.fleft {
float: left;
}
.toggle-btn {
float: right;
position: relative;
top: -10px;
width: 36px;
height: 36px;
margin: 0;
padding: 0;
border: 0;
background: 0 0;
cursor: pointer;
outline: none;
}
.toggle-indicator {
color: #72777c;
display: inline-block;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-decoration: none!important;
outline: none;
}
.itemDetail {
background: #fff;
border: 1px solid #ccc;
padding: 15px;
margin: 15px 10px 10px 0;
min-height: 15px;
word-break: break-word;
}
.collapsible {
clear: both;
}
.howto {
margin: 10px 0 0;
padding: 2px 10px;
min-height: 45px;
font-style: normal;
border-left: solid;
border-left-width: 5px;
border-left-color: #00a0d2;
background-color: white;
}
.howto .title-wrap {
float: left;
margin: 5px 0 15px;
}
.howto .title {
margin: 0;
}
.howto .subtitle {
margin: 0;
padding: 0;
font-weight: 100;
font-style: italic;
color: #72777c;
}
.howto .toggle-btn {
top: -4px;
left: 10px;
}
.howto-wrap {
clear: both;
width: 100%;
}
.form-table {
margin-top: 20px;
}
.form-table tr {
vertical-align: top;
}
.form-table p.notes,
.form-table p code {
font-size: 12px !important;
}
.form-table p.notes {
color: #666;
}
.form-table.rules {
margin-top: 0;
}
.form-table.rules th {
width: 175px;
}
.ui-dialog .form-table.rules th,
.ui-dialog .form-table.rules td {
padding: 5px 0px;
}
.rules-list {
width: 100%;
margin-bottom: 10px;
}
.rules-list tr.header th {
background-color: #f1f1f1;
}
.rules-list tbody .rule:nth-child(even) td {
background-color: #f9f9f9;
}
#rules-list,
#queue {
margin-bottom: 20px;
}
#rules-list h4 {
margin: 20px 0 10px;
font-size: 1.2em;
}
#rules-list .header th,
#rules-list .rule td {
padding: 4px;
}
#rules-list .rule td {
margin: 0;
border-bottom: 1px solid #ccc;
}
#rules-list .type {
width: 80px;
}
#rules-list .btn {
width: 55px;
}
#rules-list .rule td.btn {
width: 70px;
padding: 4px 0 4px 2px;
}
.badge {
padding: 0 5px;
color: #fff;
background-color: #00a0d2;
border-radius: 5px;
font-size: .8em;
font-weight: bold;
text-align: center;
}
.badge.manual {
background-color: #46b450;
}
#rules-list .btn span {
width: 67px;
text-align: center;
}
p.rules-btn {
margin: 20px 0 0;
padding: 0;
}
.queue {
table-layout: fixed;
border-collapse: collapse;
}
.queue .job td {
border-bottom: 1px solid #ccc;
}
.queue .status,
.queue .job td.status {
width: 45px;
}
.queue td.status {
text-align: center;
}
.queue .badge {
border-radius: 3px;
cursor: default;
}
.badge.new {
background-color: #666;
}
.badge.pending {
background-color: #00a0d2;
}
.badge.done {
background-color: #46b450;
}
.badge.review {
background-color: #ffb900;
}
.badge.review.rule {
margin-left: 2px;
}
.badge.error {
background-color: #dc3232;
}
.badge.unknown {
color: #666;
background-color: #ccc;
}
.queue .btn {
width: 86px;
}
.queue .button-secondary {
line-height: 17px;
height: 19px;
padding: 0 2px;
font-size: 8pt;
}
.queue .button-secondary.to-right {
margin-left: 2px;
}
.queue .button-secondary a {
color: #555;
text-decoration: none;
}
.queue .button-secondary a:hover {
color: #23282d;
}
.queue .button-secondary .dashicons {
position: relative;
top: 1px;
font-size: 15px;
}
p.submit.left {
float: left;
}
#importSettingsForm {
float: right;
text-align: left;
max-width: 100%;
margin-top: 20px;
padding-top: 10px;
position: relative;
overflow: hidden;
}
#settingsfile {
padding: 0 2px;
}
#settingsfile {
padding: 0 2px;
}
/* debug block */
#debug {
clear: both;
}
#debug .debug th,
#debug .debug td {
padding: 5px 10px;
font-size: 13px;
}
#debug pre {
margin: 0 0 1em;
font-size: 12px;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
white-space: pre-wrap;
}
#explain-panel p {
font-size:120%
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 B

View File

@@ -0,0 +1,40 @@
/* tables */
table.tablesorter {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
margin: 30px 0 5px;
font-size: 8pt;
width: 100%;
text-align: left;
}
table.tablesorter thead tr th,
table.tablesorter tfoot tr th {
background-color: #f1f1f1;
font-size: 8pt;
padding: 2px;
}
table.tablesorter thead tr .header {
background-image: url(bg.gif);
background-repeat: no-repeat;
background-position: center right;
cursor: pointer;
}
table.tablesorter tbody td {
color: #444;
padding: 4px;
background-color: #fff;
vertical-align: middle;
}
table.tablesorter tbody tr.even td,
table.tablesorter tbody tr:nth-child(even) td {
background-color: #f9f9f9;
}
table.tablesorter thead tr .headerSortUp {
background-image: url(asc.gif);
}
table.tablesorter thead tr .headerSortDown {
background-image: url(desc.gif);
}
table.tablesorter thead tr .headerSortDown,
table.tablesorter thead tr .headerSortUp {
background-color: #ccc;
}

View File

@@ -0,0 +1,11 @@
// Toggle button control for collapsible elements
jQuery(".toggle-btn").click(function () {
$header = jQuery(this);
$content = $header.next();
$content.slideToggle(250, "swing", function () {
jQuery("span.toggle-indicator", $header).toggleClass('dashicons-arrow-down');
});
});
// Attach an event to export buttons
jQuery("#exportSettings").click(function(){exportSettings();});

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function r(n,t){return n<<t|n>>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<<r%32,n[14+(r+64>>>9<<4)]=r;var e,i,a,d,h,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e<n.length;e+=16)i=l,a=g,d=v,h=m,g=f(g=f(g=f(g=f(g=c(g=c(g=c(g=c(g=u(g=u(g=u(g=u(g=o(g=o(g=o(g=o(g,v=o(v,m=o(m,l=o(l,g,v,m,n[e],7,-680876936),g,v,n[e+1],12,-389564586),l,g,n[e+2],17,606105819),m,l,n[e+3],22,-1044525330),v=o(v,m=o(m,l=o(l,g,v,m,n[e+4],7,-176418897),g,v,n[e+5],12,1200080426),l,g,n[e+6],17,-1473231341),m,l,n[e+7],22,-45705983),v=o(v,m=o(m,l=o(l,g,v,m,n[e+8],7,1770035416),g,v,n[e+9],12,-1958414417),l,g,n[e+10],17,-42063),m,l,n[e+11],22,-1990404162),v=o(v,m=o(m,l=o(l,g,v,m,n[e+12],7,1804603682),g,v,n[e+13],12,-40341101),l,g,n[e+14],17,-1502002290),m,l,n[e+15],22,1236535329),v=u(v,m=u(m,l=u(l,g,v,m,n[e+1],5,-165796510),g,v,n[e+6],9,-1069501632),l,g,n[e+11],14,643717713),m,l,n[e],20,-373897302),v=u(v,m=u(m,l=u(l,g,v,m,n[e+5],5,-701558691),g,v,n[e+10],9,38016083),l,g,n[e+15],14,-660478335),m,l,n[e+4],20,-405537848),v=u(v,m=u(m,l=u(l,g,v,m,n[e+9],5,568446438),g,v,n[e+14],9,-1019803690),l,g,n[e+3],14,-187363961),m,l,n[e+8],20,1163531501),v=u(v,m=u(m,l=u(l,g,v,m,n[e+13],5,-1444681467),g,v,n[e+2],9,-51403784),l,g,n[e+7],14,1735328473),m,l,n[e+12],20,-1926607734),v=c(v,m=c(m,l=c(l,g,v,m,n[e+5],4,-378558),g,v,n[e+8],11,-2022574463),l,g,n[e+11],16,1839030562),m,l,n[e+14],23,-35309556),v=c(v,m=c(m,l=c(l,g,v,m,n[e+1],4,-1530992060),g,v,n[e+4],11,1272893353),l,g,n[e+7],16,-155497632),m,l,n[e+10],23,-1094730640),v=c(v,m=c(m,l=c(l,g,v,m,n[e+13],4,681279174),g,v,n[e],11,-358537222),l,g,n[e+3],16,-722521979),m,l,n[e+6],23,76029189),v=c(v,m=c(m,l=c(l,g,v,m,n[e+9],4,-640364487),g,v,n[e+12],11,-421815835),l,g,n[e+15],16,530742520),m,l,n[e+2],23,-995338651),v=f(v,m=f(m,l=f(l,g,v,m,n[e],6,-198630844),g,v,n[e+7],10,1126891415),l,g,n[e+14],15,-1416354905),m,l,n[e+5],21,-57434055),v=f(v,m=f(m,l=f(l,g,v,m,n[e+12],6,1700485571),g,v,n[e+3],10,-1894986606),l,g,n[e+10],15,-1051523),m,l,n[e+1],21,-2054922799),v=f(v,m=f(m,l=f(l,g,v,m,n[e+8],6,1873313359),g,v,n[e+15],10,-30611744),l,g,n[e+6],15,-1560198380),m,l,n[e+13],21,1309151649),v=f(v,m=f(m,l=f(l,g,v,m,n[e+4],6,-145523070),g,v,n[e+11],10,-1120210379),l,g,n[e+2],15,718787259),m,l,n[e+9],21,-343485551),l=t(l,i),g=t(g,a),v=t(v,d),m=t(m,h);return[l,g,v,m]}function a(n){var t,r="",e=32*n.length;for(t=0;t<e;t+=8)r+=String.fromCharCode(n[t>>5]>>>t%32&255);return r}function d(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t<r.length;t+=1)r[t]=0;var e=8*n.length;for(t=0;t<e;t+=8)r[t>>5]|=(255&n.charCodeAt(t/8))<<t%32;return r}function h(n){return a(i(d(n),8*n.length))}function l(n,t){var r,e,o=d(n),u=[],c=[];for(u[15]=c[15]=void 0,o.length>16&&(o=i(o,8*n.length)),r=0;r<16;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(d(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="";for(r=0;r<n.length;r+=1)t=n.charCodeAt(r),e+="0123456789abcdef".charAt(t>>>4&15)+"0123456789abcdef".charAt(15&t);return e}function v(n){return unescape(encodeURIComponent(n))}function m(n){return h(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this);
//# sourceMappingURL=md5.min.js.map

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -0,0 +1 @@
<html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>

View File

@@ -0,0 +1 @@
<html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>

View File

@@ -0,0 +1,89 @@
/*jslint browser: true */ /*global jQuery: true */
/**
* jQuery Cookie plugin
*
* Copyright (c) 2010 Klaus Hartl (stilbuero.de)
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
*/
// TODO JsDoc
/**
* Create a cookie with the given key and value and other optional parameters.
*
* @example $.cookie('the_cookie', 'the_value');
* @desc Set the value of a cookie.
* @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true });
* @desc Create a cookie with all available options.
* @example $.cookie('the_cookie', 'the_value');
* @desc Create a session cookie.
* @example $.cookie('the_cookie', null);
* @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain
* used when the cookie was set.
*
* @param String key The key of the cookie.
* @param String value The value of the cookie.
* @param Object options An object literal containing key/value pairs to provide optional cookie attributes.
* @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object.
* If a negative value is specified (e.g. a date in the past), the cookie will be deleted.
* If set to null or omitted, the cookie will be a session cookie and will not be retained
* when the the browser exits.
* @option String path The value of the path attribute of the cookie (default: path of page that created the cookie).
* @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie).
* @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will
* require a secure protocol (like HTTPS).
* @type undefined
*
* @name $.cookie
* @cat Plugins/Cookie
* @author Klaus Hartl/klaus.hartl@stilbuero.de
*/
/**
* Get the value of a cookie with the given key.
*
* @example $.cookie('the_cookie');
* @desc Get the value of a cookie.
*
* @param String key The key of the cookie.
* @return The value of the cookie.
* @type String
*
* @name $.cookie
* @cat Plugins/Cookie
* @author Klaus Hartl/klaus.hartl@stilbuero.de
*/
jQuery.cookie = function (key, value, options) {
// key and value given, set cookie...
if (arguments.length > 1 && (value === null || typeof value !== "object")) {
options = jQuery.extend({}, options);
if (value === null) {
options.expires = -1;
}
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setDate(t.getDate() + days);
}
return (document.cookie = [
encodeURIComponent(key), '=',
options.raw ? String(value) : encodeURIComponent(String(value)),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}
// key and possibly options given, get cookie...
options = value || {};
var result, decode = options.raw ? function (s) { return s; } : decodeURIComponent;
return (result = new RegExp('(?:^|; )' + encodeURIComponent(key) + '=([^;]*)').exec(document.cookie)) ? decode(result[1]) : null;
};

View File

@@ -0,0 +1 @@
jQuery.cookie=function(key,value,options){if(arguments.length>1&&(value===null||typeof value!=="object")){options=jQuery.extend({},options);if(value===null){options.expires=-1;}if(typeof options.expires==='number'){var days=options.expires,t=options.expires=new Date();t.setDate(t.getDate()+days);}return(document.cookie=[encodeURIComponent(key),'=',options.raw?String(value):encodeURIComponent(String(value)),options.expires?'; expires='+options.expires.toUTCString():'',options.path?'; path='+options.path:'',options.domain?'; domain='+options.domain:'',options.secure?'; secure':''].join(''));}options=value||{};var result,decode=options.raw?function(s){return s;}:decodeURIComponent;return(result=new RegExp('(?:^|; )'+encodeURIComponent(key)+'=([^;]*)').exec(document.cookie))?decode(result[1]):null;};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,33 @@
/**
* Here's where everything gets included. You don't need
* to change anything here, and doing so might break
* stuff. Here be dragons and all that.
*/
/**
* Default variables
*
* While these can be set with JavaScript, it's probably
* better and faster to just set them here, compile to
* CSS and include that instead to use some of that
* hardware-accelerated goodness.
*/
.unslider-nav ol {
list-style: none;
text-align: center;
}
.unslider-nav ol li {
display: inline-block;
width: 6px;
height: 6px;
margin: 0 4px;
background: transparent;
border-radius: 5px;
overflow: hidden;
text-indent: -999em;
border: 2px solid #fff;
cursor: pointer;
}
.unslider-nav ol li.unslider-active {
background: #fff;
cursor: default;
}

View File

@@ -0,0 +1 @@
.unslider{overflow:auto;margin:0;padding:0}.unslider-wrap{position:relative}.unslider-wrap.unslider-carousel>li{float:left}.unslider-vertical>ul{height:100%}.unslider-vertical li{float:none;width:100%}.unslider-fade{position:relative}.unslider-fade .unslider-wrap li{position:absolute;left:0;top:0;right:0;z-index:8}.unslider-fade .unslider-wrap li.unslider-active{z-index:10}.unslider li,.unslider ol,.unslider ul{list-style:none;margin:0;padding:0;border:none}.unslider-arrow{position:absolute;left:20px;z-index:2;cursor:pointer}.unslider-arrow.next{left:auto;right:20px}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,276 @@
<?php
/**
* Class Minify_HTML
* @package Minify
*/
/**
* Compress HTML
*
* This is a heavy regex-based removal of whitespace, unnecessary comments and
* tokens. IE conditional comments are preserved. There are also options to have
* STYLE and SCRIPT blocks compressed by callback functions.
*
* A test suite is available.
*
* @package Minify
* @author Stephen Clay <steve@mrclay.org>
*/
class AO_Minify_HTML {
/** @var string */
private $_html;
/**
* "Minify" an HTML page
*
* @param string $html
*
* @param array $options
*
* 'cssMinifier' : (optional) callback function to process content of STYLE
* elements.
*
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
* elements. Note: the type attribute is ignored.
*
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
* unset, minify will sniff for an XHTML doctype.
*
* 'keepComments' : (optional boolean) should the HTML comments be kept
* in the HTML Code?
*
* @return string
*/
public static function minify($html, $options = array()) {
$min = new AO_Minify_HTML($html, $options);
return $min->process();
}
/**
* Create a minifier object
*
* @param string $html
*
* @param array $options
*
* 'cssMinifier' : (optional) callback function to process content of STYLE
* elements.
*
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
* elements. Note: the type attribute is ignored.
*
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
* unset, minify will sniff for an XHTML doctype.
*
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
* unset, minify will sniff for an XHTML doctype.
*
* @return null
*/
public function __construct($html, $options = array())
{
$this->_html = str_replace("\r\n", "\n", trim($html));
if (isset($options['xhtml'])) {
$this->_isXhtml = (bool)$options['xhtml'];
}
if (isset($options['cssMinifier'])) {
$this->_cssMinifier = $options['cssMinifier'];
}
if (isset($options['jsMinifier'])) {
$this->_jsMinifier = $options['jsMinifier'];
}
if (isset($options['keepComments'])) {
$this->_keepComments = $options['keepComments'];
}
}
/**
* Minify the markeup given in the constructor
*
* @return string
*/
public function process()
{
if ($this->_isXhtml === null) {
$this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
}
$this->_replacementHash = 'MINIFYHTML' . bin2hex( random_bytes( 16 ) );
$this->_placeholders = array();
// replace SCRIPTs (and minify) with placeholders
$this->_html = preg_replace_callback(
'/(\\s*)(<script\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
,array($this, '_removeScriptCB')
,$this->_html);
// replace STYLEs (and minify) with placeholders
$this->_html = preg_replace_callback(
'/\\s*(<style\\b[^>]*?>)([\\s\\S]*?)<\\/style>\\s*/i'
,array($this, '_removeStyleCB')
,$this->_html);
// remove HTML comments (not containing IE conditional comments).
if ($this->_keepComments == false) {
$this->_html = preg_replace_callback(
'/<!--([\\s\\S]*?)-->/'
,array($this, '_commentCB')
,$this->_html);
}
// replace PREs with placeholders
$this->_html = preg_replace_callback('/\\s*(<pre\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
,array($this, '_removePreCB')
,$this->_html);
// replace TEXTAREAs with placeholders
$this->_html = preg_replace_callback(
'/\\s*(<textarea\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
,array($this, '_removeTextareaCB')
,$this->_html);
// replace data: URIs with placeholders
$this->_html = preg_replace_callback(
'/(=("|\')data:.*\\2)/Ui'
,array($this, '_removeDataURICB')
,$this->_html);
// trim each line.
// replace by space instead of '' to avoid newline after opening tag getting zapped
$this->_html = preg_replace('/^\s+|\s+$/m', ' ', $this->_html);
// remove ws around block/undisplayed elements
$this->_html = preg_replace('/\\s+(<\\/?(?:area|article|aside|base(?:font)?|blockquote|body'
.'|canvas|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset|figcaption|figure|footer|form'
.'|frame(?:set)?|h[1-6]|head|header|hgroup|hr|html|legend|li|link|main|map|menu|meta|nav'
.'|ol|opt(?:group|ion)|output|p|param|section|t(?:able|body|head|d|h||r|foot|itle)'
.'|ul|video)\\b[^>]*>)/i', '$1', $this->_html);
// remove ws outside of all elements
$this->_html = preg_replace_callback(
'/>([^<]+)</'
,array($this, '_outsideTagCB')
,$this->_html);
// use newlines before 1st attribute in open tags (to limit line lengths)
//$this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
// reverse order while preserving keys to ensure the last replacement is done first, etc ...
$this->_placeholders = array_reverse( $this->_placeholders, true );
// fill placeholders
$this->_html = str_replace(
array_keys($this->_placeholders)
,array_values($this->_placeholders)
,$this->_html
);
return $this->_html;
}
protected function _commentCB($m)
{
return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<!['))
? $m[0]
: '';
}
protected function _reservePlace($content)
{
$placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
$this->_placeholders[$placeholder] = $content;
return $placeholder;
}
protected $_isXhtml = null;
protected $_replacementHash = null;
protected $_placeholders = array();
protected $_cssMinifier = null;
protected $_jsMinifier = null;
protected $_keepComments = false;
protected function _outsideTagCB($m)
{
return '>' . preg_replace('/^\\s+|\\s+$/', ' ', $m[1]) . '<';
}
protected function _removePreCB($m)
{
return $this->_reservePlace($m[1]);
}
protected function _removeTextareaCB($m)
{
return $this->_reservePlace($m[1]);
}
protected function _removeDataURICB($m)
{
return $this->_reservePlace($m[1]);
}
protected function _removeStyleCB($m)
{
$openStyle = $m[1];
$css = $m[2];
// remove HTML comments
$css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
// remove CDATA section markers
$css = $this->_removeCdata($css);
// minify
$minifier = $this->_cssMinifier
? $this->_cssMinifier
: 'trim';
$css = call_user_func($minifier, $css);
return $this->_reservePlace($this->_needsCdata($css)
? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
: "{$openStyle}{$css}</style>"
);
}
protected function _removeScriptCB($m)
{
$openScript = $m[2];
$js = $m[3];
// whitespace surrounding? preserve at least one space
$ws1 = ($m[1] === '') ? '' : ' ';
$ws2 = ($m[4] === '') ? '' : ' ';
if ($this->_keepComments == false) {
// remove HTML comments (and ending "//" if present)
$js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
// remove CDATA section markers
$js = $this->_removeCdata($js);
}
// minify
$minifier = $this->_jsMinifier
? $this->_jsMinifier
: 'trim';
$js = call_user_func($minifier, $js);
return $this->_reservePlace($this->_needsCdata($js)
? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
: "{$ws1}{$openScript}{$js}</script>{$ws2}"
);
}
protected function _removeCdata($str)
{
return (false !== strpos($str, '<![CDATA['))
? str_replace(array('/* <![CDATA[ */','/* ]]> */','/*<![CDATA[*/','/*]]>*/','<![CDATA[', ']]>'), '', $str)
: $str;
}
protected function _needsCdata($str)
{
return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
}
}

View File

@@ -0,0 +1 @@
<html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>

View File

@@ -0,0 +1,466 @@
<?php
//namespace JSMin;
/**
* JSMin.php - modified PHP implementation of Douglas Crockford's JSMin.
*
* <code>
* $minifiedJs = JSMin::minify($js);
* </code>
*
* This is a modified port of jsmin.c. Improvements:
*
* Does not choke on some regexp literals containing quote characters. E.g. /'/
*
* Spaces are preserved after some add/sub operators, so they are not mistakenly
* converted to post-inc/dec. E.g. a + ++b -> a+ ++b
*
* Preserves multi-line comments that begin with /*!
*
* PHP 5 or higher is required.
*
* Permission is hereby granted to use this version of the library under the
* same terms as jsmin.c, which has the following license:
*
* --
* Copyright (c) 2002 Douglas Crockford (www.crockford.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* The Software shall be used for Good, not Evil.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* --
*
* @package JSMin
* @author Ryan Grove <ryan@wonko.com> (PHP port)
* @author Steve Clay <steve@mrclay.org> (modifications + cleanup)
* @author Andrea Giammarchi <http://www.3site.eu> (spaceBeforeRegExp)
* @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
* @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port)
* @license http://opensource.org/licenses/mit-license.php MIT License
* @link http://code.google.com/p/jsmin-php/
*/
class JSMin {
const ACTION_KEEP_A = 1;
const ACTION_DELETE_A = 2;
const ACTION_DELETE_A_B = 3;
protected $a = "\n";
protected $b = '';
protected $input = '';
protected $inputIndex = 0;
protected $inputLength = 0;
protected $lookAhead = null;
protected $output = '';
protected $lastByteOut = '';
protected $keptComment = '';
/**
* Minify Javascript.
*
* @param string $js Javascript to be minified
*
* @return string
*/
public static function minify($js)
{
$jsmin = new JSMin($js);
return $jsmin->min();
}
/**
* @param string $input
*/
public function __construct($input)
{
$this->input = $input;
}
/**
* Perform minification, return result
*
* @return string
*/
public function min()
{
if ($this->output !== '') { // min already run
return $this->output;
}
$mbIntEnc = null;
if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) {
$mbIntEnc = mb_internal_encoding();
mb_internal_encoding('8bit');
}
if (isset($this->input[0]) && $this->input[0] === "\xef") {
$this->input = substr($this->input, 3);
}
$this->input = str_replace("\r\n", "\n", $this->input);
$this->inputLength = strlen($this->input);
$this->action(self::ACTION_DELETE_A_B);
while ($this->a !== null) {
// determine next command
$command = self::ACTION_KEEP_A; // default
if ($this->isWhiteSpace($this->a)) {
if (($this->lastByteOut === '+' || $this->lastByteOut === '-')
&& ($this->b === $this->lastByteOut)) {
// Don't delete this space. If we do, the addition/subtraction
// could be parsed as a post-increment
} elseif (! $this->isAlphaNum($this->b)) {
$command = self::ACTION_DELETE_A;
}
} elseif ($this->isLineTerminator($this->a)) {
if ($this->isWhiteSpace($this->b)) {
$command = self::ACTION_DELETE_A_B;
// in case of mbstring.func_overload & 2, must check for null b,
// otherwise mb_strpos will give WARNING
} elseif ($this->b === null
|| (false === strpos('{[(+-!~#', $this->b)
&& ! $this->isAlphaNum($this->b))) {
$command = self::ACTION_DELETE_A;
}
} elseif (! $this->isAlphaNum($this->a)) {
if ($this->isWhiteSpace($this->b)
|| ($this->isLineTerminator($this->b)
&& (false === strpos('}])+-"\'`', $this->a)))) {
$command = self::ACTION_DELETE_A_B;
}
}
$this->action($command);
}
$this->output = trim($this->output);
if ($mbIntEnc !== null) {
mb_internal_encoding($mbIntEnc);
}
return $this->output;
}
/**
* ACTION_KEEP_A = Output A. Copy B to A. Get the next B.
* ACTION_DELETE_A = Copy B to A. Get the next B.
* ACTION_DELETE_A_B = Get the next B.
*
* @param int $command
* @throws JSMin_UnterminatedRegExpException|UnterminatedStringException
*/
protected function action($command)
{
// make sure we don't compress "a + ++b" to "a+++b", etc.
if ($command === self::ACTION_DELETE_A_B
&& $this->b === ' '
&& ($this->a === '+' || $this->a === '-')) {
// Note: we're at an addition/substraction operator; the inputIndex
// will certainly be a valid index
if ($this->input[$this->inputIndex] === $this->a) {
// This is "+ +" or "- -". Don't delete the space.
$command = self::ACTION_KEEP_A;
}
}
switch ($command) {
case self::ACTION_KEEP_A: // 1
$this->output .= $this->a;
if ($this->keptComment) {
$this->output = rtrim($this->output, "\n");
$this->output .= $this->keptComment;
$this->keptComment = '';
}
$this->lastByteOut = $this->a;
// fallthrough intentional
case self::ACTION_DELETE_A: // 2
$this->a = $this->b;
if ($this->a === "'" || $this->a === '"' || $this->a === '`') { // string/template literal
$delimiter = $this->a;
$str = $this->a; // in case needed for exception
for(;;) {
$this->output .= $this->a;
$this->lastByteOut = $this->a;
$this->a = $this->get();
if ($this->a === $this->b) { // end quote
break;
}
if ($delimiter === '`' && $this->isLineTerminator($this->a)) {
// leave the newline
} elseif ($this->isEOF($this->a)) {
$byte = $this->inputIndex - 1;
throw new JSMin_UnterminatedStringException(
"JSMin: Unterminated String at byte {$byte}: {$str}");
}
$str .= $this->a;
if ($this->a === '\\') {
$this->output .= $this->a;
$this->lastByteOut = $this->a;
$this->a = $this->get();
$str .= $this->a;
}
}
}
// fallthrough intentional
case self::ACTION_DELETE_A_B: // 3
$this->b = $this->next();
if ($this->b === '/' && $this->isRegexpLiteral()) {
$this->output .= $this->a . $this->b;
$pattern = '/'; // keep entire pattern in case we need to report it in the exception
for(;;) {
$this->a = $this->get();
$pattern .= $this->a;
if ($this->a === '[') {
for(;;) {
$this->output .= $this->a;
$this->a = $this->get();
$pattern .= $this->a;
if ($this->a === ']') {
break;
}
if ($this->a === '\\') {
$this->output .= $this->a;
$this->a = $this->get();
$pattern .= $this->a;
}
if ($this->isEOF($this->a)) {
throw new JSMin_UnterminatedRegExpException(
"JSMin: Unterminated set in RegExp at byte "
. $this->inputIndex .": {$pattern}");
}
}
}
if ($this->a === '/') { // end pattern
break; // while (true)
} elseif ($this->a === '\\') {
$this->output .= $this->a;
$this->a = $this->get();
$pattern .= $this->a;
} elseif ($this->isEOF($this->a)) {
$byte = $this->inputIndex - 1;
throw new JSMin_UnterminatedRegExpException(
"JSMin: Unterminated RegExp at byte {$byte}: {$pattern}");
}
$this->output .= $this->a;
$this->lastByteOut = $this->a;
}
$this->b = $this->next();
}
// end case ACTION_DELETE_A_B
}
}
/**
* @return bool
*/
protected function isRegexpLiteral()
{
if (false !== strpos("(,=:[!&|?+-~*{;", $this->a)) {
// we can't divide after these tokens
return true;
}
// check if first non-ws token is "/" (see starts-regex.js)
$length = strlen($this->output);
if ($this->isWhiteSpace($this->a) || $this->isLineTerminator($this->a)) {
if ($length < 2) { // weird edge case
return true;
}
}
// if the "/" follows a keyword, it must be a regexp, otherwise it's best to assume division
$subject = $this->output . trim($this->a);
if (!preg_match('/(?:case|else|in|return|typeof)$/', $subject, $m)) {
// not a keyword
return false;
}
// can't be sure it's a keyword yet (see not-regexp.js)
$charBeforeKeyword = substr($subject, 0 - strlen($m[0]) - 1, 1);
if ($this->isAlphaNum($charBeforeKeyword)) {
// this is really an identifier ending in a keyword, e.g. "xreturn"
return false;
}
// it's a regexp. Remove unneeded whitespace after keyword
if ($this->isWhiteSpace($this->a) || $this->isLineTerminator($this->a)) {
$this->a = '';
}
return true;
}
/**
* Return the next character from stdin. Watch out for lookahead. If the character is a control character,
* translate it to a space or linefeed.
*
* @return string
*/
protected function get()
{
$c = $this->lookAhead;
$this->lookAhead = null;
if ($c === null) {
// getc(stdin)
if ($this->inputIndex < $this->inputLength) {
$c = $this->input[$this->inputIndex];
$this->inputIndex += 1;
} else {
$c = null;
}
}
if ($c === "\r") {
return "\n";
}
return $c;
}
/**
* Does $a indicate end of input?
*
* @param string $a
* @return bool
*/
protected function isEOF($a)
{
return $a === null || $this->isLineTerminator($a);
}
/**
* Get next char (without getting it). If is ctrl character, translate to a space or newline.
*
* @return string
*/
protected function peek()
{
$this->lookAhead = $this->get();
return $this->lookAhead;
}
/**
* Return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character.
*
* @param string $c
*
* @return bool
*/
protected function isAlphaNum($c)
{
return (preg_match('/^[a-z0-9A-Z_\\$\\\\]$/', $c) || ord($c) > 126);
}
/**
* Consume a single line comment from input (possibly retaining it)
*/
protected function consumeSingleLineComment()
{
$comment = '';
while (true) {
$get = $this->get();
$comment .= $get;
if ($this->isEOF($get)) {
// if IE conditional comment
if (preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
$this->keptComment .= "/{$comment}";
}
return;
}
}
}
/**
* Consume a multiple line comment from input (possibly retaining it)
*
* @throws JSMin_UnterminatedCommentException
*/
protected function consumeMultipleLineComment()
{
$this->get();
$comment = '';
for(;;) {
$get = $this->get();
if ($get === '*') {
if ($this->peek() === '/') { // end of comment reached
$this->get();
if (0 === strpos($comment, '!')) {
// preserved by YUI Compressor
if (!$this->keptComment) {
// don't prepend a newline if two comments right after one another
$this->keptComment = "\n";
}
$this->keptComment .= "/*!" . substr($comment, 1) . "*/\n";
} else if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
// IE conditional
$this->keptComment .= "/*{$comment}*/";
}
return;
}
} elseif ($get === null) {
throw new JSMin_UnterminatedCommentException(
"JSMin: Unterminated comment at byte {$this->inputIndex}: /*{$comment}");
}
$comment .= $get;
}
}
/**
* Get the next character, skipping over comments. Some comments may be preserved.
*
* @return string
*/
protected function next()
{
$get = $this->get();
if ($get === '/') {
switch ($this->peek()) {
case '/':
$this->consumeSingleLineComment();
$get = "\n";
break;
case '*':
$this->consumeMultipleLineComment();
$get = ' ';
break;
}
}
return $get;
}
protected function isWhiteSpace($s) {
// https://www.ecma-international.org/ecma-262/#sec-white-space
return $s !== null && strpos(" \t\v\f", $s) !== false;
}
protected function isLineTerminator($s) {
// https://www.ecma-international.org/ecma-262/#sec-line-terminators
return $s !== null && strpos("\n\r", $s) !== false;
}
}
class JSMin_UnterminatedStringException extends Exception {}
class JSMin_UnterminatedCommentException extends Exception {}
class JSMin_UnterminatedRegExpException extends Exception {}

View File

@@ -0,0 +1,117 @@
# Persist Admin notice Dismissals
[![Latest Stable Version](https://poser.pugx.org/collizo4sky/persist-admin-notices-dismissal/v/stable)](https://packagist.org/packages/collizo4sky/persist-admin-notices-dismissal)
[![Total Downloads](https://poser.pugx.org/collizo4sky/persist-admin-notices-dismissal/downloads)](https://packagist.org/packages/collizo4sky/persist-admin-notices-dismissal)
Simple framework library that persists the dismissal of admin notices across pages in WordPress dashboard.
## Installation
Run `composer require collizo4sky/persist-admin-notices-dismissal`
Alternatively, clone or download this repo into the `vendor/` folder in your plugin, and include/require the `persist-admin-notices-dismissal.php` file like so
```php
require __DIR__ . '/vendor/persist-admin-notices-dismissal/persist-admin-notices-dismissal.php';
add_action( 'admin_init', array( 'PAnD', 'init' ) );
```
or let Composer's autoloader do the work.
## How to Use
Firstly, install and activate this library within a plugin.
Say you have the following markup as your admin notice,
```php
function sample_admin_notice__success() {
?>
<div class="updated notice notice-success is-dismissible">
<p><?php _e( 'Done!', 'sample-text-domain' ); ?></p>
</div>
<?php
}
add_action( 'admin_notices', 'sample_admin_notice__success' );
```
To make it hidden forever when dismissed, add the following data attribute `data-dismissible="disable-done-notice-forever"` to the div markup like so:
```php
function sample_admin_notice__success() {
if ( ! PAnD::is_admin_notice_active( 'disable-done-notice-forever' ) ) {
return;
}
?>
<div data-dismissible="disable-done-notice-forever" class="updated notice notice-success is-dismissible">
<p><?php _e( 'Done!', 'sample-text-domain' ); ?></p>
</div>
<?php
}
add_action( 'admin_init', array( 'PAnD', 'init' ) );
add_action( 'admin_notices', 'sample_admin_notice__success' );
```
## Autoloaders
When using the framework with an autoloader you **must** also load the class outside of the `admin_notices` or `network_admin_notices` hooks. The reason is that these hooks come after the `admin_enqueue_script` hook that loads the javascript.
Just add the following in your main plugin file.
```php
add_action( 'admin_init', array( 'PAnD', 'init' ) );
```
#### Usage Instructions and Examples
If you have two notices displayed when certain actions are triggered; firstly, choose a string to uniquely identify them, e.g. `notice-one` and `notice-two`
To make the first notice never appear once dismissed, its `data-dismissible` attribute will be `data-dismissible="notice-one-forever"` where `notice-one` is its unique identifier and `forever` is the dismissal time period.
To make the second notice only hidden for 2 days, its `data-dismissible` attribute will be `data-dismissible="notice-two-2"` where `notice-two` is its unique identifier and the `2`, the number of days it will be hidden is the dismissal time period.
You **must** append the dismissal time period to the end of your unique identifier with a hyphen (`-`) and this value must be an integer. The only exception is the string `forever`.
To actually make the dismissed admin notice not to appear, use the `is_admin_notice_active()` function like so:
```php
function sample_admin_notice__success1() {
if ( ! PAnD::is_admin_notice_active( 'notice-one-forever' ) ) {
return;
}
?>
<div data-dismissible="notice-one-forever" class="updated notice notice-success is-dismissible">
<p><?php _e( 'Done 1!', 'sample-text-domain' ); ?></p>
</div>
<?php
}
function sample_admin_notice__success2() {
if ( ! PAnD::is_admin_notice_active( 'notice-two-2' ) ) {
return;
}
?>
<div data-dismissible="notice-two-2" class="updated notice notice-success is-dismissible">
<p><?php _e( 'Done 2!', 'sample-text-domain' ); ?></p>
</div>
<?php
}
add_action( 'admin_init', array( 'PAnD', 'init' ) );
add_action( 'admin_notices', 'sample_admin_notice__success1' );
add_action( 'admin_notices', 'sample_admin_notice__success2' );
```
You should be a good developer and add the following to your `uninstall.php` file so that we can clean up after ourselves and not leave unnecessary stuff in the options table.
```php
global $wpdb;
$table = is_multisite() ? $wpdb->base_prefix . 'sitemeta' : $wpdb->base_prefix . 'options';
$column = is_multisite() ? 'meta_key' : 'option_name';
$delete_string = 'DELETE FROM ' . $table . ' WHERE ' . $column . ' LIKE %s LIMIT 1000';
$wpdb->query( $wpdb->prepare( $delete_string, array( '%pand-%' ) ) );
```
Cool beans. Isn't it?

View File

@@ -0,0 +1,33 @@
(function ($) {
//shorthand for ready event.
$(
function () {
$( 'div[data-dismissible] button.notice-dismiss' ).on('click',
function (event) {
event.preventDefault();
var $this = $( this );
var attr_value, option_name, dismissible_length, data;
attr_value = $this.parent().attr( 'data-dismissible' ).split( '-' );
// remove the dismissible length from the attribute value and rejoin the array.
dismissible_length = attr_value.pop();
option_name = attr_value.join( '-' );
data = {
'action': 'dismiss_admin_notice',
'option_name': option_name,
'dismissible_length': dismissible_length,
'nonce': dismissible_notice.nonce
};
// We can also pass the url value separately from ajaxurl for front end AJAX implementations
$.post( ajaxurl, data );
}
);
}
)
}(jQuery));

View File

@@ -0,0 +1,159 @@
<?php
/**
* Persist Admin notices Dismissal
*
* Copyright (C) 2016 Collins Agbonghama <http://w3guy.com>
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
* @package Persist Admin notices Dismissal
* @author Collins Agbonghama
* @author Andy Fragen
* @license http://www.gnu.org/licenses GNU General Public License
* @version 1.4.1
*/
/**
* Exit if called directly.
*/
if ( ! defined( 'ABSPATH' ) ) {
die;
}
if ( ! class_exists( 'PAnD' ) ) {
/**
* Class PAnD
*/
class PAnD {
/**
* Init hooks.
*/
public static function init() {
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'load_script' ) );
add_action( 'wp_ajax_dismiss_admin_notice', array( __CLASS__, 'dismiss_admin_notice' ) );
}
/**
* Enqueue javascript and variables.
*/
public static function load_script() {
if ( is_customize_preview() ) {
return;
}
wp_enqueue_script(
'dismissible-notices',
plugins_url( 'dismiss-notice.js', __FILE__ ),
array( 'jquery', 'common' ),
false,
true
);
wp_localize_script(
'dismissible-notices',
'dismissible_notice',
array(
'nonce' => wp_create_nonce( 'dismissible-notice' ),
)
);
}
/**
* Handles Ajax request to persist notices dismissal.
* Uses check_ajax_referer to verify nonce.
*/
public static function dismiss_admin_notice() {
$option_name = sanitize_text_field( $_POST['option_name'] );
$dismissible_length = sanitize_text_field( $_POST['dismissible_length'] );
if ( 'forever' != $dismissible_length ) {
// If $dismissible_length is not an integer default to 1
$dismissible_length = ( 0 == absint( $dismissible_length ) ) ? 1 : $dismissible_length;
$dismissible_length = strtotime( absint( $dismissible_length ) . ' days' );
}
check_ajax_referer( 'dismissible-notice', 'nonce' );
self::set_admin_notice_cache( $option_name, $dismissible_length );
wp_die();
}
/**
* Is admin notice active?
*
* @param string $arg data-dismissible content of notice.
*
* @return bool
*/
public static function is_admin_notice_active( $arg ) {
$array = explode( '-', $arg );
$length = array_pop( $array );
$option_name = implode( '-', $array );
$db_record = self::get_admin_notice_cache( $option_name );
if ( 'forever' == $db_record ) {
return false;
} elseif ( absint( $db_record ) >= time() ) {
return false;
} else {
return true;
}
}
/**
* Returns admin notice cached timeout.
*
* @access public
*
* @param string|bool $id admin notice name or false.
*
* @return array|bool The timeout. False if expired.
*/
public static function get_admin_notice_cache( $id = false ) {
if ( ! $id ) {
return false;
}
$cache_key = 'pand-' . md5( $id );
$timeout = get_site_option( $cache_key );
$timeout = 'forever' === $timeout ? time() + 60 : $timeout;
if ( empty( $timeout ) || time() > $timeout ) {
return false;
}
return $timeout;
}
/**
* Sets admin notice timeout in site option.
*
* @access public
*
* @param string $id Data Identifier.
* @param string|bool $timeout Timeout for admin notice.
*
* @return bool
*/
public static function set_admin_notice_cache( $id, $timeout ) {
$cache_key = 'pand-' . md5( $id );
update_site_option( $cache_key, $timeout );
return true;
}
}
}

View File

@@ -0,0 +1,155 @@
<?php
namespace Autoptimize\tubalmartin\CssMin;
class Colors
{
public static function getHexToNamedMap()
{
// Hex colors longer than named counterpart
return array(
'#f0ffff' => 'azure',
'#f5f5dc' => 'beige',
'#ffe4c4' => 'bisque',
'#a52a2a' => 'brown',
'#ff7f50' => 'coral',
'#ffd700' => 'gold',
'#808080' => 'gray',
'#008000' => 'green',
'#4b0082' => 'indigo',
'#fffff0' => 'ivory',
'#f0e68c' => 'khaki',
'#faf0e6' => 'linen',
'#800000' => 'maroon',
'#000080' => 'navy',
'#fdf5e6' => 'oldlace',
'#808000' => 'olive',
'#ffa500' => 'orange',
'#da70d6' => 'orchid',
'#cd853f' => 'peru',
'#ffc0cb' => 'pink',
'#dda0dd' => 'plum',
'#800080' => 'purple',
'#f00' => 'red',
'#fa8072' => 'salmon',
'#a0522d' => 'sienna',
'#c0c0c0' => 'silver',
'#fffafa' => 'snow',
'#d2b48c' => 'tan',
'#008080' => 'teal',
'#ff6347' => 'tomato',
'#ee82ee' => 'violet',
'#f5deb3' => 'wheat'
);
}
public static function getNamedToHexMap()
{
// Named colors longer than hex counterpart
return array(
'aliceblue' => '#f0f8ff',
'antiquewhite' => '#faebd7',
'aquamarine' => '#7fffd4',
'black' => '#000',
'blanchedalmond' => '#ffebcd',
'blueviolet' => '#8a2be2',
'burlywood' => '#deb887',
'cadetblue' => '#5f9ea0',
'chartreuse' => '#7fff00',
'chocolate' => '#d2691e',
'cornflowerblue' => '#6495ed',
'cornsilk' => '#fff8dc',
'darkblue' => '#00008b',
'darkcyan' => '#008b8b',
'darkgoldenrod' => '#b8860b',
'darkgray' => '#a9a9a9',
'darkgreen' => '#006400',
'darkgrey' => '#a9a9a9',
'darkkhaki' => '#bdb76b',
'darkmagenta' => '#8b008b',
'darkolivegreen' => '#556b2f',
'darkorange' => '#ff8c00',
'darkorchid' => '#9932cc',
'darksalmon' => '#e9967a',
'darkseagreen' => '#8fbc8f',
'darkslateblue' => '#483d8b',
'darkslategray' => '#2f4f4f',
'darkslategrey' => '#2f4f4f',
'darkturquoise' => '#00ced1',
'darkviolet' => '#9400d3',
'deeppink' => '#ff1493',
'deepskyblue' => '#00bfff',
'dodgerblue' => '#1e90ff',
'firebrick' => '#b22222',
'floralwhite' => '#fffaf0',
'forestgreen' => '#228b22',
'fuchsia' => '#f0f',
'gainsboro' => '#dcdcdc',
'ghostwhite' => '#f8f8ff',
'goldenrod' => '#daa520',
'greenyellow' => '#adff2f',
'honeydew' => '#f0fff0',
'indianred' => '#cd5c5c',
'lavender' => '#e6e6fa',
'lavenderblush' => '#fff0f5',
'lawngreen' => '#7cfc00',
'lemonchiffon' => '#fffacd',
'lightblue' => '#add8e6',
'lightcoral' => '#f08080',
'lightcyan' => '#e0ffff',
'lightgoldenrodyellow' => '#fafad2',
'lightgray' => '#d3d3d3',
'lightgreen' => '#90ee90',
'lightgrey' => '#d3d3d3',
'lightpink' => '#ffb6c1',
'lightsalmon' => '#ffa07a',
'lightseagreen' => '#20b2aa',
'lightskyblue' => '#87cefa',
'lightslategray' => '#778899',
'lightslategrey' => '#778899',
'lightsteelblue' => '#b0c4de',
'lightyellow' => '#ffffe0',
'limegreen' => '#32cd32',
'mediumaquamarine' => '#66cdaa',
'mediumblue' => '#0000cd',
'mediumorchid' => '#ba55d3',
'mediumpurple' => '#9370db',
'mediumseagreen' => '#3cb371',
'mediumslateblue' => '#7b68ee',
'mediumspringgreen' => '#00fa9a',
'mediumturquoise' => '#48d1cc',
'mediumvioletred' => '#c71585',
'midnightblue' => '#191970',
'mintcream' => '#f5fffa',
'mistyrose' => '#ffe4e1',
'moccasin' => '#ffe4b5',
'navajowhite' => '#ffdead',
'olivedrab' => '#6b8e23',
'orangered' => '#ff4500',
'palegoldenrod' => '#eee8aa',
'palegreen' => '#98fb98',
'paleturquoise' => '#afeeee',
'palevioletred' => '#db7093',
'papayawhip' => '#ffefd5',
'peachpuff' => '#ffdab9',
'powderblue' => '#b0e0e6',
'rebeccapurple' => '#663399',
'rosybrown' => '#bc8f8f',
'royalblue' => '#4169e1',
'saddlebrown' => '#8b4513',
'sandybrown' => '#f4a460',
'seagreen' => '#2e8b57',
'seashell' => '#fff5ee',
'slateblue' => '#6a5acd',
'slategray' => '#708090',
'slategrey' => '#708090',
'springgreen' => '#00ff7f',
'steelblue' => '#4682b4',
'turquoise' => '#40e0d0',
'white' => '#fff',
'whitesmoke' => '#f5f5f5',
'yellow' => '#ff0',
'yellowgreen' => '#9acd32'
);
}
}

View File

@@ -0,0 +1,935 @@
<?php
/*!
* CssMin
* Author: Tubal Martin - http://tubalmartin.me/
* Repo: https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port
*
* This is a PHP port of the CSS minification tool distributed with YUICompressor,
* itself a port of the cssmin utility by Isaac Schlueter - http://foohack.com/
* Permission is hereby granted to use the PHP version under the same
* conditions as the YUICompressor.
*/
/*!
* YUI Compressor
* http://developer.yahoo.com/yui/compressor/
* Author: Julien Lecomte - http://www.julienlecomte.net/
* Copyright (c) 2013 Yahoo! Inc. All rights reserved.
* The copyrights embodied in the content of this file are licensed
* by Yahoo! Inc. under the BSD (revised) open source license.
*/
namespace Autoptimize\tubalmartin\CssMin;
class Minifier
{
const QUERY_FRACTION = '_CSSMIN_QF_';
const COMMENT_TOKEN = '_CSSMIN_CMT_%d_';
const COMMENT_TOKEN_START = '_CSSMIN_CMT_';
const RULE_BODY_TOKEN = '_CSSMIN_RBT_%d_';
const PRESERVED_TOKEN = '_CSSMIN_PTK_%d_';
const UNQUOTED_FONT_TOKEN = '_CSSMIN_UFT_%d_';
// Token lists
private $comments = array();
private $ruleBodies = array();
private $preservedTokens = array();
private $unquotedFontTokens = array();
// Output options
private $keepImportantComments = true;
private $keepSourceMapComment = false;
private $linebreakPosition = 0;
// PHP ini limits
private $raisePhpLimits;
private $memoryLimit;
private $maxExecutionTime = 60; // 1 min
private $pcreBacktrackLimit;
private $pcreRecursionLimit;
// Color maps
private $hexToNamedColorsMap;
private $namedToHexColorsMap;
// Regexes
private $numRegex;
private $charsetRegex = '/@charset [^;]+;/Si';
private $importRegex = '/@import [^;]+;/Si';
private $namespaceRegex = '/@namespace [^;]+;/Si';
private $namedToHexColorsRegex;
private $shortenOneZeroesRegex;
private $shortenTwoZeroesRegex;
private $shortenThreeZeroesRegex;
private $shortenFourZeroesRegex;
private $unitsGroupRegex = '(?:ch|cm|em|ex|gd|in|mm|px|pt|pc|q|rem|vh|vmax|vmin|vw|%)';
private $unquotedFontsRegex = '/(font-family:|font:)([^\'"]+?)[^};]*/Si';
/**
* @param bool|int $raisePhpLimits If true, PHP settings will be raised if needed
*/
public function __construct($raisePhpLimits = true)
{
$this->raisePhpLimits = (bool) $raisePhpLimits;
$this->memoryLimit = 128 * 1048576; // 128MB in bytes
$this->pcreBacktrackLimit = 1000 * 1000;
$this->pcreRecursionLimit = 500 * 1000;
$this->hexToNamedColorsMap = Colors::getHexToNamedMap();
$this->namedToHexColorsMap = Colors::getNamedToHexMap();
$this->namedToHexColorsRegex = sprintf(
'/([:,( ])(%s)( |,|\)|;|$)/Si',
implode('|', array_keys($this->namedToHexColorsMap))
);
$this->numRegex = sprintf('-?\d*\.?\d+%s?', $this->unitsGroupRegex);
$this->setShortenZeroValuesRegexes();
}
/**
* Parses & minifies the given input CSS string
* @param string $css
* @return string
*/
public function run($css = '')
{
if (empty($css) || !is_string($css)) {
return '';
}
$this->resetRunProperties();
if ($this->raisePhpLimits) {
$this->doRaisePhpLimits();
}
return $this->minify($css);
}
/**
* Sets whether to keep or remove sourcemap special comment.
* Sourcemap comments are removed by default.
* @param bool $keepSourceMapComment
*/
public function keepSourceMapComment($keepSourceMapComment = true)
{
$this->keepSourceMapComment = (bool) $keepSourceMapComment;
}
/**
* Sets whether to keep or remove important comments.
* Important comments outside of a declaration block are kept by default.
* @param bool $removeImportantComments
*/
public function removeImportantComments($removeImportantComments = true)
{
$this->keepImportantComments = !(bool) $removeImportantComments;
}
/**
* Sets the approximate column after which long lines will be splitted in the output
* with a linebreak.
* @param int $position
*/
public function setLineBreakPosition($position)
{
$this->linebreakPosition = (int) $position;
}
/**
* Sets the memory limit for this script
* @param int|string $limit
*/
public function setMemoryLimit($limit)
{
$this->memoryLimit = Utils::normalizeInt($limit);
}
/**
* Sets the maximum execution time for this script
* @param int|string $seconds
*/
public function setMaxExecutionTime($seconds)
{
$this->maxExecutionTime = (int) $seconds;
}
/**
* Sets the PCRE backtrack limit for this script
* @param int $limit
*/
public function setPcreBacktrackLimit($limit)
{
$this->pcreBacktrackLimit = (int) $limit;
}
/**
* Sets the PCRE recursion limit for this script
* @param int $limit
*/
public function setPcreRecursionLimit($limit)
{
$this->pcreRecursionLimit = (int) $limit;
}
/**
* Builds regular expressions needed for shortening zero values
*/
private function setShortenZeroValuesRegexes()
{
$zeroRegex = '0'. $this->unitsGroupRegex;
$numOrPosRegex = '('. $this->numRegex .'|top|left|bottom|right|center) ';
$oneZeroSafeProperties = array(
'(?:line-)?height',
'(?:(?:min|max)-)?width',
'top',
'left',
'background-position',
'bottom',
'right',
'border(?:-(?:top|left|bottom|right))?(?:-width)?',
'border-(?:(?:top|bottom)-(?:left|right)-)?radius',
'column-(?:gap|width)',
'margin(?:-(?:top|left|bottom|right))?',
'outline-width',
'padding(?:-(?:top|left|bottom|right))?'
);
// First zero regex
$regex = '/(^|;)('. implode('|', $oneZeroSafeProperties) .'):%s/Si';
$this->shortenOneZeroesRegex = sprintf($regex, $zeroRegex);
// Multiple zeroes regexes
$regex = '/(^|;)(margin|padding|border-(?:width|radius)|background-position):%s/Si';
$this->shortenTwoZeroesRegex = sprintf($regex, $numOrPosRegex . $zeroRegex);
$this->shortenThreeZeroesRegex = sprintf($regex, $numOrPosRegex . $numOrPosRegex . $zeroRegex);
$this->shortenFourZeroesRegex = sprintf($regex, $numOrPosRegex . $numOrPosRegex . $numOrPosRegex . $zeroRegex);
}
/**
* Resets properties whose value may change between runs
*/
private function resetRunProperties()
{
$this->comments = array();
$this->ruleBodies = array();
$this->preservedTokens = array();
}
/**
* Tries to configure PHP to use at least the suggested minimum settings
* @return void
*/
private function doRaisePhpLimits()
{
$phpLimits = array(
'memory_limit' => $this->memoryLimit,
'max_execution_time' => $this->maxExecutionTime,
'pcre.backtrack_limit' => $this->pcreBacktrackLimit,
'pcre.recursion_limit' => $this->pcreRecursionLimit
);
// If current settings are higher respect them.
foreach ($phpLimits as $name => $suggested) {
$current = Utils::normalizeInt(ini_get($name));
if ($current >= $suggested) {
continue;
}
// memoryLimit exception: allow -1 for "no memory limit".
if ($name === 'memory_limit' && $current === -1) {
continue;
}
// maxExecutionTime exception: allow 0 for "no memory limit".
if ($name === 'max_execution_time' && $current === 0) {
continue;
}
ini_set($name, $suggested);
}
}
/**
* Registers a preserved token
* @param string $token
* @return string The token ID string
*/
private function registerPreservedToken($token)
{
$tokenId = sprintf(self::PRESERVED_TOKEN, count($this->preservedTokens));
$this->preservedTokens[$tokenId] = $token;
return $tokenId;
}
/**
* Registers a candidate comment token
* @param string $comment
* @return string The comment token ID string
*/
private function registerCommentToken($comment)
{
$tokenId = sprintf(self::COMMENT_TOKEN, count($this->comments));
$this->comments[$tokenId] = $comment;
return $tokenId;
}
/**
* Registers a rule body token
* @param string $body the minified rule body
* @return string The rule body token ID string
*/
private function registerRuleBodyToken($body)
{
if (empty($body)) {
return '';
}
$tokenId = sprintf(self::RULE_BODY_TOKEN, count($this->ruleBodies));
$this->ruleBodies[$tokenId] = $body;
return $tokenId;
}
private function registerUnquotedFontToken($body)
{
if (empty($body)) {
return '';
}
$tokenId = sprintf(self::UNQUOTED_FONT_TOKEN, count($this->unquotedFontTokens));
$this->unquotedFontTokens[$tokenId] = $body;
return $tokenId;
}
/**
* Parses & minifies the given input CSS string
* @param string $css
* @return string
*/
private function minify($css)
{
// Process data urls
$css = $this->processDataUrls($css);
// Process comments
$css = preg_replace_callback(
'/(?<!\\\\)\/\*(.*?)\*(?<!\\\\)\//Ss',
array($this, 'processCommentsCallback'),
$css
);
// IE7: Process Microsoft matrix filters (whitespaces between Matrix parameters). Can contain strings inside.
$css = preg_replace_callback(
'/filter:\s*progid:DXImageTransform\.Microsoft\.Matrix\(([^)]+)\)/Ss',
array($this, 'processOldIeSpecificMatrixDefinitionCallback'),
$css
);
// Process quoted unquotable attribute selectors to unquote them. Covers most common cases.
// Likelyhood of a quoted attribute selector being a substring in a string: Very very low.
$css = preg_replace(
'/\[\s*([a-z][a-z-]+)\s*([\*\|\^\$~]?=)\s*[\'"](-?[a-z_][a-z0-9-_]+)[\'"]\s*\]/Ssi',
'[$1$2$3]',
$css
);
// Process strings so their content doesn't get accidentally minified
$css = preg_replace_callback(
'/(?:"(?:[^\\\\"]|\\\\.|\\\\)*")|'."(?:'(?:[^\\\\']|\\\\.|\\\\)*')/S",
array($this, 'processStringsCallback'),
$css
);
// Normalize all whitespace strings to single spaces. Easier to work with that way.
$css = preg_replace('/\s+/S', ' ', $css);
// Process import At-rules with unquoted URLs so URI reserved characters such as a semicolon may be used safely.
$css = preg_replace_callback(
'/@import url\(([^\'"]+?)\)( |;)/Si',
array($this, 'processImportUnquotedUrlAtRulesCallback'),
$css
);
// Process comments
$css = $this->processComments($css);
// Process rule bodies
$css = $this->processRuleBodies($css);
// Process at-rules and selectors
$css = $this->processAtRulesAndSelectors($css);
// Restore preserved rule bodies before splitting
$css = strtr($css, $this->ruleBodies);
// Split long lines in output if required
$css = $this->processLongLineSplitting($css);
// Restore preserved comments and strings
$css = strtr($css, $this->preservedTokens);
return trim($css);
}
/**
* Searches & replaces all data urls with tokens before we start compressing,
* to avoid performance issues running some of the subsequent regexes against large string chunks.
* @param string $css
* @return string
*/
private function processDataUrls($css)
{
$ret = '';
$searchOffset = $substrOffset = 0;
// Since we need to account for non-base64 data urls, we need to handle
// ' and ) being part of the data string.
while (preg_match('/url\(\s*(["\']?)data:/Si', $css, $m, PREG_OFFSET_CAPTURE, $searchOffset)) {
$matchStartIndex = $m[0][1];
$dataStartIndex = $matchStartIndex + 4; // url( length
$searchOffset = $matchStartIndex + strlen($m[0][0]);
$terminator = $m[1][0]; // ', " or empty (not quoted)
$terminatorRegex = '/(?<!\\\\)'. (strlen($terminator) === 0 ? '' : $terminator.'\s*') .'(\))/S';
$ret .= substr($css, $substrOffset, $matchStartIndex - $substrOffset);
// Terminator found
if (preg_match($terminatorRegex, $css, $matches, PREG_OFFSET_CAPTURE, $searchOffset)) {
$matchEndIndex = $matches[1][1];
$searchOffset = $matchEndIndex + 1;
$token = substr($css, $dataStartIndex, $matchEndIndex - $dataStartIndex);
// Remove all spaces only for base64 encoded URLs.
if (stripos($token, 'base64,') !== false) {
$token = preg_replace('/\s+/S', '', $token);
}
$ret .= 'url('. $this->registerPreservedToken(trim($token)) .')';
// No end terminator found, re-add the whole match. Should we throw/warn here?
} else {
$ret .= substr($css, $matchStartIndex, $searchOffset - $matchStartIndex);
}
$substrOffset = $searchOffset;
}
$ret .= substr($css, $substrOffset);
return $ret;
}
/**
* Registers all comments found as candidates to be preserved.
* @param array $matches
* @return string
*/
private function processCommentsCallback($matches)
{
return '/*'. $this->registerCommentToken($matches[1]) .'*/';
}
/**
* Preserves old IE Matrix string definition
* @param array $matches
* @return string
*/
private function processOldIeSpecificMatrixDefinitionCallback($matches)
{
return 'filter:progid:DXImageTransform.Microsoft.Matrix('. $this->registerPreservedToken($matches[1]) .')';
}
/**
* Preserves strings found
* @param array $matches
* @return string
*/
private function processStringsCallback($matches)
{
$match = $matches[0];
$quote = substr($match, 0, 1);
$match = substr($match, 1, -1);
// maybe the string contains a comment-like substring?
// one, maybe more? put'em back then
if (strpos($match, self::COMMENT_TOKEN_START) !== false) {
$match = strtr($match, $this->comments);
}
// minify alpha opacity in filter strings
$match = str_ireplace('progid:DXImageTransform.Microsoft.Alpha(Opacity=', 'alpha(opacity=', $match);
return $quote . $this->registerPreservedToken($match) . $quote;
}
/**
* Searches & replaces all import at-rule unquoted urls with tokens so URI reserved characters such as a semicolon
* may be used safely in a URL.
* @param array $matches
* @return string
*/
private function processImportUnquotedUrlAtRulesCallback($matches)
{
return '@import url('. $this->registerPreservedToken($matches[1]) .')'. $matches[2];
}
/**
* Preserves or removes comments found.
* @param string $css
* @return string
*/
private function processComments($css)
{
foreach ($this->comments as $commentId => $comment) {
$commentIdString = '/*'. $commentId .'*/';
// ! in the first position of the comment means preserve
// so push to the preserved tokens keeping the !
if ($this->keepImportantComments && strpos($comment, '!') === 0) {
$preservedTokenId = $this->registerPreservedToken($comment);
// Put new lines before and after /*! important comments
$css = str_replace($commentIdString, "\n/*$preservedTokenId*/\n", $css);
continue;
}
// # sourceMappingURL= in the first position of the comment means sourcemap
// so push to the preserved tokens if {$this->keepSourceMapComment} is truthy.
if ($this->keepSourceMapComment && strpos($comment, '# sourceMappingURL=') === 0) {
$preservedTokenId = $this->registerPreservedToken($comment);
// Add new line before the sourcemap comment
$css = str_replace($commentIdString, "\n/*$preservedTokenId*/", $css);
continue;
}
// Keep empty comments after child selectors (IE7 hack)
// e.g. html >/**/ body
if (strlen($comment) === 0 && strpos($css, '>/*'.$commentId) !== false) {
$css = str_replace($commentId, $this->registerPreservedToken(''), $css);
continue;
}
// in all other cases kill the comment
$css = str_replace($commentIdString, '', $css);
}
// Normalize whitespace again
$css = preg_replace('/ +/S', ' ', $css);
return $css;
}
/**
* Finds, minifies & preserves all rule bodies.
* @param string $css the whole stylesheet.
* @return string
*/
private function processRuleBodies($css)
{
$ret = '';
$searchOffset = $substrOffset = 0;
while (($blockStartPos = strpos($css, '{', $searchOffset)) !== false) {
$blockEndPos = strpos($css, '}', $blockStartPos);
// When ending curly brace is missing, let's
// behave like there was one at the end of the block...
if ( false === $blockEndPos ) {
$blockEndPos = strlen($css) - 1;
}
$nextBlockStartPos = strpos($css, '{', $blockStartPos + 1);
$ret .= substr($css, $substrOffset, $blockStartPos - $substrOffset);
if ($nextBlockStartPos !== false && $nextBlockStartPos < $blockEndPos) {
$ret .= substr($css, $blockStartPos, $nextBlockStartPos - $blockStartPos);
$searchOffset = $nextBlockStartPos;
} else {
$ruleBody = substr($css, $blockStartPos + 1, $blockEndPos - $blockStartPos - 1);
$ruleBodyToken = $this->registerRuleBodyToken($this->processRuleBody($ruleBody));
$ret .= '{'. $ruleBodyToken .'}';
$searchOffset = $blockEndPos + 1;
}
$substrOffset = $searchOffset;
}
$ret .= substr($css, $substrOffset);
return $ret;
}
/**
* Compresses non-group rule bodies.
* @param string $body The rule body without curly braces
* @return string
*/
private function processRuleBody($body)
{
$body = trim($body);
// Remove spaces before the things that should not have spaces before them.
$body = preg_replace('/ ([:=,)*\/;\n])/S', '$1', $body);
// Remove the spaces after the things that should not have spaces after them.
$body = preg_replace('/([:=,(*\/!;\n]) /S', '$1', $body);
// Replace multiple semi-colons in a row by a single one
$body = preg_replace('/;;+/S', ';', $body);
// Remove semicolon before closing brace except when:
// - The last property is prefixed with a `*` (lte IE7 hack) to avoid issues on Symbian S60 3.x browsers.
if (!preg_match('/\*[a-z0-9-]+:[^;]+;$/Si', $body)) {
$body = rtrim($body, ';');
}
// Remove important comments inside a rule body (because they make no sense here).
if (strpos($body, '/*') !== false) {
$body = preg_replace('/\n?\/\*[A-Z0-9_]+\*\/\n?/S', '', $body);
}
// Empty rule body? Exit :)
if (empty($body)) {
return '';
}
// Shorten font-weight values
$body = preg_replace(
array('/(font-weight:)bold\b/Si', '/(font-weight:)normal\b/Si'),
array('${1}700', '${1}400'),
$body
);
// Shorten background property
$body = preg_replace('/(background:)(?:none|transparent)( !|;|$)/Si', '${1}0 0$2', $body);
// Shorten opacity IE filter
$body = str_ireplace('progid:DXImageTransform.Microsoft.Alpha(Opacity=', 'alpha(opacity=', $body);
// Shorten colors from rgb(51,102,153) to #336699, rgb(100%,0%,0%) to #ff0000 (sRGB color space)
// Shorten colors from hsl(0, 100%, 50%) to #ff0000 (sRGB color space)
// This makes it more likely that it'll get further compressed in the next step.
$body = preg_replace_callback(
'/(rgb|hsl)\(([0-9,.% -]+)\)(.|$)/Si',
array($this, 'shortenHslAndRgbToHexCallback'),
$body
);
// Shorten colors from #AABBCC to #ABC or shorter color name:
// - Look for hex colors which don't have a "=" in front of them (to avoid MSIE filters)
$body = preg_replace_callback(
'/(?<!=)#([0-9a-f]{3,6})( |,|\)|;|$)/Si',
array($this, 'shortenHexColorsCallback'),
$body
);
// Tokenize unquoted font names in order to hide them from
// color name replacements.
$body = preg_replace_callback(
$this->unquotedFontsRegex,
array($this, 'preserveUnquotedFontTokens'),
$body
);
// Shorten long named colors with a shorter HEX counterpart: white -> #fff.
// Run at least 2 times to cover most cases
$body = preg_replace_callback(
array($this->namedToHexColorsRegex, $this->namedToHexColorsRegex),
array($this, 'shortenNamedColorsCallback'),
$body
);
// Restore unquoted font tokens now after colors have been changed.
$body = $this->restoreUnquotedFontTokens($body);
// Replace positive sign from numbers before the leading space is removed.
// +1.2em to 1.2em, +.8px to .8px, +2% to 2%
$body = preg_replace('/([ :,(])\+(\.?\d+)/S', '$1$2', $body);
// shorten ms to s
$body = preg_replace_callback('/([ :,(])(-?)(\d{3,})ms/Si', function ($matches) {
return $matches[1] . $matches[2] . ((int) $matches[3] / 1000) .'s';
}, $body);
// Remove leading zeros from integer and float numbers.
// 000.6 to .6, -0.8 to -.8, 0050 to 50, -01.05 to -1.05
$body = preg_replace('/([ :,(])(-?)0+([1-9]?\.?\d+)/S', '$1$2$3', $body);
// Remove trailing zeros from float numbers.
// -6.0100em to -6.01em, .0100 to .01, 1.200px to 1.2px
$body = preg_replace('/([ :,(])(-?\d?\.\d+?)0+([^\d])/S', '$1$2$3', $body);
// Remove trailing .0 -> -9.0 to -9
$body = preg_replace('/([ :,(])(-?\d+)\.0([^\d])/S', '$1$2$3', $body);
// Replace 0 length numbers with 0
$body = preg_replace('/([ :,(])-?\.?0+([^\d])/S', '${1}0$2', $body);
// Shorten zero values for safe properties only
$body = preg_replace(
array(
$this->shortenOneZeroesRegex,
$this->shortenTwoZeroesRegex,
$this->shortenThreeZeroesRegex,
$this->shortenFourZeroesRegex
),
array(
'$1$2:0',
'$1$2:$3 0',
'$1$2:$3 $4 0',
'$1$2:$3 $4 $5 0'
),
$body
);
// Replace 0 0 0; or 0 0 0 0; with 0 0 for background-position property.
$body = preg_replace('/(background-position):0(?: 0){2,3}( !|;|$)/Si', '$1:0 0$2', $body);
// Shorten suitable shorthand properties with repeated values
$body = preg_replace(
array(
'/(margin|padding|border-(?:width|radius)):('.$this->numRegex.')(?: \2)+( !|;|$)/Si',
'/(border-(?:style|color)):([#a-z0-9]+)(?: \2)+( !|;|$)/Si'
),
'$1:$2$3',
$body
);
$body = preg_replace(
array(
'/(margin|padding|border-(?:width|radius)):'.
'('.$this->numRegex.') ('.$this->numRegex.') \2 \3( !|;|$)/Si',
'/(border-(?:style|color)):([#a-z0-9]+) ([#a-z0-9]+) \2 \3( !|;|$)/Si'
),
'$1:$2 $3$4',
$body
);
$body = preg_replace(
array(
'/(margin|padding|border-(?:width|radius)):'.
'('.$this->numRegex.') ('.$this->numRegex.') ('.$this->numRegex.') \3( !|;|$)/Si',
'/(border-(?:style|color)):([#a-z0-9]+) ([#a-z0-9]+) ([#a-z0-9]+) \3( !|;|$)/Si'
),
'$1:$2 $3 $4$5',
$body
);
// Lowercase some common functions that can be values
$body = preg_replace_callback(
'/(?:attr|blur|brightness|circle|contrast|cubic-bezier|drop-shadow|ellipse|from|grayscale|'.
'hsla?|hue-rotate|inset|invert|local|minmax|opacity|perspective|polygon|rgba?|rect|repeat|saturate|sepia|'.
'steps|to|url|var|-webkit-gradient|'.
'(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|(?:repeating-)?(?:linear|radial)-gradient))\(/Si',
array($this, 'strtolowerCallback'),
$body
);
// Lowercase all uppercase properties
$body = preg_replace_callback('/(?:^|;)[A-Z-]+:/S', array($this, 'strtolowerCallback'), $body);
return $body;
}
private function preserveUnquotedFontTokens($matches)
{
return $this->registerUnquotedFontToken($matches[0]);
}
private function restoreUnquotedFontTokens($body)
{
return strtr($body, $this->unquotedFontTokens);
}
/**
* Compresses At-rules and selectors.
* @param string $css the whole stylesheet with rule bodies tokenized.
* @return string
*/
private function processAtRulesAndSelectors($css)
{
$charset = '';
$imports = '';
$namespaces = '';
// Remove spaces before the things that should not have spaces before them.
$css = preg_replace('/ ([@{};>+)\]~=,\/\n])/S', '$1', $css);
// Remove the spaces after the things that should not have spaces after them.
$css = preg_replace('/([{}:;>+(\[~=,\/\n]) /S', '$1', $css);
// Shorten shortable double colon (CSS3) pseudo-elements to single colon (CSS2)
$css = preg_replace('/::(before|after|first-(?:line|letter))(\{|,)/Si', ':$1$2', $css);
// Retain space for special IE6 cases
$css = preg_replace_callback('/:first-(line|letter)(\{|,)/Si', function ($matches) {
return ':first-'. strtolower($matches[1]) .' '. $matches[2];
}, $css);
// Find a fraction that may used in some @media queries such as: (min-aspect-ratio: 1/1)
// Add token to add the "/" back in later
$css = preg_replace('/\(([a-z-]+):([0-9]+)\/([0-9]+)\)/Si', '($1:$2'. self::QUERY_FRACTION .'$3)', $css);
// Remove empty rule blocks up to 2 levels deep.
$css = preg_replace(array_fill(0, 2, '/(\{)[^{};\/\n]+\{\}/S'), '$1', $css);
$css = preg_replace('/[^{};\/\n]+\{\}/S', '', $css);
// Two important comments next to each other? Remove extra newline.
if ($this->keepImportantComments) {
$css = str_replace("\n\n", "\n", $css);
}
// Restore fraction
$css = str_replace(self::QUERY_FRACTION, '/', $css);
// Lowercase some popular @directives
$css = preg_replace_callback(
'/(?<!\\\\)@(?:charset|document|font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframes|media|'.
'namespace|page|supports|viewport)/Si',
array($this, 'strtolowerCallback'),
$css
);
// Lowercase some popular media types
$css = preg_replace_callback(
'/[ ,](?:all|aural|braille|handheld|print|projection|screen|tty|tv|embossed|speech)[ ,;{]/Si',
array($this, 'strtolowerCallback'),
$css
);
// Lowercase some common pseudo-classes & pseudo-elements
$css = preg_replace_callback(
'/(?<!\\\\):(?:active|after|before|checked|default|disabled|empty|enabled|first-(?:child|of-type)|'.
'focus(?:-within)?|hover|indeterminate|in-range|invalid|lang\(|last-(?:child|of-type)|left|link|not\(|'.
'nth-(?:child|of-type)\(|nth-last-(?:child|of-type)\(|only-(?:child|of-type)|optional|out-of-range|'.
'read-(?:only|write)|required|right|root|:selection|target|valid|visited)/Si',
array($this, 'strtolowerCallback'),
$css
);
// @charset handling
if (preg_match($this->charsetRegex, $css, $matches)) {
// Keep the first @charset at-rule found
$charset = $matches[0];
// Delete all @charset at-rules
$css = preg_replace($this->charsetRegex, '', $css);
}
// @import handling
$css = preg_replace_callback($this->importRegex, function ($matches) use (&$imports) {
// Keep all @import at-rules found for later
$imports .= $matches[0];
// Delete all @import at-rules
return '';
}, $css);
// @namespace handling
$css = preg_replace_callback($this->namespaceRegex, function ($matches) use (&$namespaces) {
// Keep all @namespace at-rules found for later
$namespaces .= $matches[0];
// Delete all @namespace at-rules
return '';
}, $css);
// Order critical at-rules:
// 1. @charset first
// 2. @imports below @charset
// 3. @namespaces below @imports
$css = $charset . $imports . $namespaces . $css;
return $css;
}
/**
* Splits long lines after a specific column.
*
* Some source control tools don't like it when files containing lines longer
* than, say 8000 characters, are checked in. The linebreak option is used in
* that case to split long lines after a specific column.
*
* @param string $css the whole stylesheet.
* @return string
*/
private function processLongLineSplitting($css)
{
if ($this->linebreakPosition > 0) {
$l = strlen($css);
$offset = $this->linebreakPosition;
while (preg_match('/(?<!\\\\)\}(?!\n)/S', $css, $matches, PREG_OFFSET_CAPTURE, $offset)) {
$matchIndex = $matches[0][1];
$css = substr_replace($css, "\n", $matchIndex + 1, 0);
$offset = $matchIndex + 2 + $this->linebreakPosition;
$l += 1;
if ($offset > $l) {
break;
}
}
}
return $css;
}
/**
* Converts hsl() & rgb() colors to HEX format.
* @param $matches
* @return string
*/
private function shortenHslAndRgbToHexCallback($matches)
{
$type = $matches[1];
$values = explode(',', $matches[2]);
$terminator = $matches[3];
if ($type === 'hsl') {
$values = Utils::hslToRgb($values);
}
$hexColors = Utils::rgbToHex($values);
// Restore space after rgb() or hsl() function in some cases such as:
// background-image: linear-gradient(to bottom, rgb(210,180,140) 10%, rgb(255,0,0) 90%);
if (!empty($terminator) && !preg_match('/[ ,);]/S', $terminator)) {
$terminator = ' '. $terminator;
}
return '#'. implode('', $hexColors) . $terminator;
}
/**
* Compresses HEX color values of the form #AABBCC to #ABC or short color name.
* @param $matches
* @return string
*/
private function shortenHexColorsCallback($matches)
{
$hex = $matches[1];
// Shorten suitable 6 chars HEX colors
if (strlen($hex) === 6 && preg_match('/^([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3$/Si', $hex, $m)) {
$hex = $m[1] . $m[2] . $m[3];
}
// Lowercase
$hex = '#'. strtolower($hex);
// Replace Hex colors with shorter color names
$color = array_key_exists($hex, $this->hexToNamedColorsMap) ? $this->hexToNamedColorsMap[$hex] : $hex;
return $color . $matches[2];
}
/**
* Shortens all named colors with a shorter HEX counterpart for a set of safe properties
* e.g. white -> #fff
* @param array $matches
* @return string
*/
private function shortenNamedColorsCallback($matches)
{
return $matches[1] . $this->namedToHexColorsMap[strtolower($matches[2])] . $matches[3];
}
/**
* Makes a string lowercase
* @param array $matches
* @return string
*/
private function strtolowerCallback($matches)
{
return strtolower($matches[0]);
}
}

View File

@@ -0,0 +1,149 @@
<?php
namespace Autoptimize\tubalmartin\CssMin;
class Utils
{
/**
* Clamps a number between a minimum and a maximum value.
* @param int|float $n the number to clamp
* @param int|float $min the lower end number allowed
* @param int|float $max the higher end number allowed
* @return int|float
*/
public static function clampNumber($n, $min, $max)
{
return min(max($n, $min), $max);
}
/**
* Clamps a RGB color number outside the sRGB color space
* @param int|float $n the number to clamp
* @return int|float
*/
public static function clampNumberSrgb($n)
{
return self::clampNumber($n, 0, 255);
}
/**
* Converts a HSL color into a RGB color
* @param array $hslValues
* @return array
*/
public static function hslToRgb($hslValues)
{
$h = floatval($hslValues[0]);
$s = floatval(str_replace('%', '', $hslValues[1]));
$l = floatval(str_replace('%', '', $hslValues[2]));
// Wrap and clamp, then fraction!
$h = ((($h % 360) + 360) % 360) / 360;
$s = self::clampNumber($s, 0, 100) / 100;
$l = self::clampNumber($l, 0, 100) / 100;
if ($s == 0) {
$r = $g = $b = self::roundNumber(255 * $l);
} else {
$v2 = $l < 0.5 ? $l * (1 + $s) : ($l + $s) - ($s * $l);
$v1 = (2 * $l) - $v2;
$r = self::roundNumber(255 * self::hueToRgb($v1, $v2, $h + (1/3)));
$g = self::roundNumber(255 * self::hueToRgb($v1, $v2, $h));
$b = self::roundNumber(255 * self::hueToRgb($v1, $v2, $h - (1/3)));
}
return array($r, $g, $b);
}
/**
* Tests and selects the correct formula for each RGB color channel
* @param $v1
* @param $v2
* @param $vh
* @return mixed
*/
public static function hueToRgb($v1, $v2, $vh)
{
$vh = $vh < 0 ? $vh + 1 : ($vh > 1 ? $vh - 1 : $vh);
if ($vh * 6 < 1) {
return $v1 + ($v2 - $v1) * 6 * $vh;
}
if ($vh * 2 < 1) {
return $v2;
}
if ($vh * 3 < 2) {
return $v1 + ($v2 - $v1) * ((2 / 3) - $vh) * 6;
}
return $v1;
}
/**
* Convert strings like "64M" or "30" to int values
* @param mixed $size
* @return int
*/
public static function normalizeInt($size)
{
if (is_string($size)) {
$letter = substr($size, -1);
$size = intval($size);
switch ($letter) {
case 'M':
case 'm':
return (int) $size * 1048576;
case 'K':
case 'k':
return (int) $size * 1024;
case 'G':
case 'g':
return (int) $size * 1073741824;
}
}
return (int) $size;
}
/**
* Converts a string containing and RGB percentage value into a RGB integer value i.e. '90%' -> 229.5
* @param $rgbPercentage
* @return int
*/
public static function rgbPercentageToRgbInteger($rgbPercentage)
{
if (strpos($rgbPercentage, '%') !== false) {
$rgbPercentage = self::roundNumber(floatval(str_replace('%', '', $rgbPercentage)) * 2.55);
}
return intval($rgbPercentage, 10);
}
/**
* Converts a RGB color into a HEX color
* @param array $rgbColors
* @return array
*/
public static function rgbToHex($rgbColors)
{
$hexColors = array();
// Values outside the sRGB color space should be clipped (0-255)
for ($i = 0, $l = count($rgbColors); $i < $l; $i++) {
$hexColors[$i] = sprintf("%02x", self::clampNumberSrgb(self::rgbPercentageToRgbInteger($rgbColors[$i])));
}
return $hexColors;
}
/**
* Rounds a number to its closest integer
* @param $n
* @return int
*/
public static function roundNumber($n)
{
return intval(round(floatval($n)), 10);
}
}

View File

@@ -0,0 +1 @@
<html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>

View File

@@ -0,0 +1 @@
<html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>

View File

@@ -0,0 +1,221 @@
tr[data-slug="autoptimize"] span.deactivate{
position: relative;
}
.ao-feedback {
background: #fff;
max-width: 400px;
z-index: 10000;
box-shadow: 0 0 15px -5px rgba(0, 0, 0, .5);
transition: all .3s ease-out;
}
.ao-feedback .popup--header {
position: relative;
background-color: #e5e5e5;
}
.ao-feedback .popup--header h5 {
margin: 0;
font-size: 16px;
padding: 10px 15px;
color: #222;
font-weight: 900;
text-align: left;
}
.ao-feedback .popup--body {
padding: 15px;
padding-top: 5px;
}
.ao-feedback .popup--form {
margin: 0;
font-size: 13px;
padding-top: 10px;
}
.ao-feedback .popup--form input[type="radio"] {
margin: 0 10px 0 0;
}
.ao-feedback .popup--form input[type="radio"]:checked ~ textarea {
display: block;
}
.ao-feedback .popup--form textarea {
width: 100%;
margin: 10px 0 0;
display: none;
max-height: 150px;
}
.ao-feedback .popup--form input[type='email'] {
width: 100%;
margin: 10px 0 0;
}
.ao-feedback .popup--form input[type='email']:invalid {
color:red;
border-color:red;
}
.ao-feedback .popup--form p.last-attempt {
display: none;
}
.ao-feedback li {
display: flex;
align-items: center;
margin-bottom: 15px;
flex-wrap: wrap;
}
.ao-feedback li label {
max-width: 90%;
}
.ao-feedback li:last-child {
margin-bottom: 0;
}
.ao-feedback .popup--footer {
padding: 0 15px 15px;
}
.ao-feedback .actions {
display: flex;
flex-wrap: wrap;
}
.info-disclosure-link {
width: 100%;
margin-bottom: 15px;
}
.ao-feedback .info-disclosure-content {
max-height: 0;
overflow: hidden;
width: 100%;
transition: .3s ease;
}
.ao-feedback .info-disclosure-content.active {
max-height: 300px;
}
.ao-feedback .info-disclosure-content p {
margin: 0;
}
.ao-feedback .info-disclosure-content ul {
margin: 10px 0;
border-radius: 3px;
}
.ao-feedback .info-disclosure-content ul li {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0;
padding: 5px 0;
border-bottom: 1px solid #ccc;
}
.ao-feedback .buttons {
display: flex;
width: 100%;
}
.ao-feedback .buttons input:nth-child(2) {
margin: auto;
}
.ao-feedback .buttons input:last-child {
margin-left: auto;
}
.ao-plugin-uninstall-feedback-popup .popup--header:before {
content: "";
display: block;
position: absolute;
border: 20px solid #e5e5e5;
left: -10px;
top: 50%;
border-top: 20px solid transparent;
border-bottom: 20px solid transparent;
border-left: 0;
transform: translateY(-50%);
}
.ao-plugin-uninstall-feedback-popup {
display: none;
position: absolute;
white-space: normal;
width: 400px;
left: 122%;
top: -21px;
}
.ao-plugin-uninstall-feedback-popup.sending-feedback .popup--body i {
animation: rotation 2s infinite linear;
display: block;
float: none;
align-items: center;
width: 100%;
margin: 0 auto;
height: 100%;
background: transparent;
padding: 0;
}
.ao-plugin-uninstall-feedback-popup.sending-feedback .popup--body i:before {
padding: 0;
background: transparent;
box-shadow: none;
color: #b4b9be
}
.ao-plugin-uninstall-feedback-popup.active {
display: block;
}
body.ao-feedback-open .ao-feedback-overlay {
content: "";
display: block;
background-color: rgba(0, 0, 0, 0.5);
top: 0;
bottom: 0;
right: 0;
left: 0;
z-index: 10000;
position: fixed;
}
.rtl .ao-plugin-uninstall-feedback-popup {
left: -500px;
}
.rtl .ao-feedback .popup--header h5 {
text-align: right;
}
.rtl .ao-plugin-uninstall-feedback-popup .popup--header:before {
display:none;
}
@media (max-width: 768px) {
.ao-plugin-uninstall-feedback-popup, .rtl .ao-plugin-uninstall-feedback-popup {
position: fixed;
max-width: 100%;
margin: 0 auto;
left: 50%;
top: 50px;
transform: translateX(-50%);
}
.ao-plugin-uninstall-feedback-popup .popup--header:before {
display: none;
}
}

View File

@@ -0,0 +1,122 @@
(function ($) {
$(document).ready(function () {
var targetElement = 'tr[data-slug="autoptimize"] span.deactivate a';
var redirectUrl = $(targetElement).attr('href');
if ($('.ao-feedback-overlay').length === 0) {
$('body').prepend('<div class="ao-feedback-overlay"></div>');
}
$('#ao_uninstall_feedback_popup').appendTo($(targetElement).parent());
$(targetElement).on('click', function (e) {
e.preventDefault();
$('#ao_uninstall_feedback_popup ').addClass('active');
$('body').addClass('ao-feedback-open');
$('.ao-feedback-overlay').on('click', function () {
$('#ao_uninstall_feedback_popup ').removeClass('active');
$('body').removeClass('ao-feedback-open');
});
});
$('#ao_uninstall_feedback_popup .info-disclosure-link').on('click', function (e) {
e.preventDefault();
$(this).parent().find('.info-disclosure-content').toggleClass('active');
});
$('#ao_uninstall_feedback_popup input[type="radio"]').on('change', function () {
var radio = $(this);
$('p.last-attempt').hide();
if (radio.parent().find('textarea').length > 0 &&
radio.parent().find('textarea').val().length === 0) {
$('#ao_uninstall_feedback_popup #ao-deactivate-yes').attr('disabled', 'disabled');
radio.parent().find('textarea').on('keyup', function (e) {
if ($(this).val().length === 0) {
$('#ao_uninstall_feedback_popup #ao-deactivate-yes').attr('disabled', 'disabled');
} else if ( $('#ao_feedback998')[0].checkValidity() == true ) {
$('#ao_uninstall_feedback_popup #ao-deactivate-yes').removeAttr('disabled');
}
});
} else {
if ( $('#ao_feedback998')[0].checkValidity() == true ) {
$('#ao_uninstall_feedback_popup #ao-deactivate-yes').removeAttr('disabled');
}
$(this).siblings('p.last-attempt').show();
}
});
$('#ao_feedback998').on('keyup', function (e) {
email_node = $(this);
email_val = email_node.val();
if ( email_val.length > 0 && email_node[0].checkValidity() == false ) {
$('#ao_uninstall_feedback_popup #ao-deactivate-yes').attr('disabled', 'disabled');
} else if ( $( '#ao_uninstall_feedback_popup input[name="ao-deactivate-option"]:checked' ).length > 0 ) {
$('#ao_uninstall_feedback_popup #ao-deactivate-yes').removeAttr('disabled');
}
});
$('#ao_uninstall_feedback_popup #ao-deactivate-no').on('click', function (e) {
e.preventDefault();
e.stopPropagation();
$(targetElement).unbind('click');
$('body').removeClass('ao-feedback-open');
$('#ao_uninstall_feedback_popup').remove();
if (redirectUrl !== '') {
location.href = redirectUrl;
}
});
$('#ao_uninstall_feedback_popup #ao-deactivate-cancel').on('click', function (e) {
e.preventDefault();
e.stopPropagation();
$('#ao_uninstall_feedback_popup ').removeClass('active');
$('body').removeClass('ao-feedback-open');
});
$('#ao_feedback_email_toggle').on('click', function (e) {
$('#ao_feedback998').toggle();
});
$('#ao_uninstall_feedback_popup #ao-deactivate-yes').on('click', function (e) {
e.preventDefault();
e.stopPropagation();
$(targetElement).unbind('click');
var modal_data = JSON.parse(atob($('#ao_uninstall_feedback_popup').data('modal')))
var selectedOption = $( '#ao_uninstall_feedback_popup input[name="ao-deactivate-option"]:checked' );
var reason;
if( selectedOption.attr("id") === "ao_feedback999" ){
reason = 'Other: ' + selectedOption.parent().find('textarea').val().trim()
}else{
reason = selectedOption.parent().find('label').attr('data-reason').trim()
}
var data = {
'url': modal_data.home,
'reason': reason,
'type': 'WP ' + $('#core_version').text().trim(),
'version' : 'AO ' + $('#ao_plugin_version').text().trim(),
'email': $('#ao_feedback998').val().trim(),
};
$.ajax({
type: 'POST',
url: atob( modal_data.dest ),
data: data,
complete() {
$('body').removeClass('ao-feedback-open');
$('#ao_uninstall_feedback_popup').remove();
if (redirectUrl !== '') {
location.href = redirectUrl;
}
},
beforeSend() {
$('#ao_uninstall_feedback_popup').addClass('sending-feedback');
$('#ao_uninstall_feedback_popup .popup--footer').remove();
$('#ao_uninstall_feedback_popup .popup--body').html('<i class="dashicons dashicons-update-alt"></i>');
}
});
});
});
})(jQuery);

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,246 @@
/* Loading Modal */
.autoptimize-loading
{
display: none;
position: fixed;
background-color: rgba(102, 102, 102, 0.8);
background-image: url('loading.gif');
background-position: center;
background-repeat: no-repeat;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9000000000;
}
/* Toolbar Font Colors */
#wp-admin-bar-autoptimize .white
{
color: #EEE;
}
#wp-admin-bar-autoptimize .green
{
color: #26BD26;
}
#wp-admin-bar-autoptimize .orange
{
color: #EC9103;
}
#wp-admin-bar-autoptimize .red
{
color: #EA1919;
}
#wp-admin-bar-autoptimize .bg-green
{
background: #26BD26;
}
#wp-admin-bar-autoptimize .bg-orange
{
background: #EC9103;
}
#wp-admin-bar-autoptimize .bg-red
{
background: #EA1919;
}
/* Toolbar Bullet Icons */
#wp-admin-bar-autoptimize.bullet-green .ab-icon::before,
#wp-admin-bar-autoptimize.bullet-green:hover .ab-icon::before
{
content: "\f159";
color: #02CA02;
font-size: 14px;
}
#wp-admin-bar-autoptimize.bullet-orange .ab-icon::before,
#wp-admin-bar-autoptimize.bullet-orange:hover .ab-icon::before
{
content: "\f159";
color: #EC9103;
font-size: 14px;
}
#wp-admin-bar-autoptimize.bullet-red .ab-icon::before,
#wp-admin-bar-autoptimize.bullet-red:hover .ab-icon::before
{
content: "\f159";
color: #EA1919;
font-size: 14px;
-webkit-animation: blink 1s step-end infinite;
animation: blink 1s step-end infinite;
}
@-webkit-keyframes blink { 50% { visibility: hidden; }}
@keyframes blink { 50% { visibility: hidden; }}
/* Some cosmetic Toolbar things */
#wp-admin-bar-autoptimize table, #wp-admin-bar-autoptimize th, #wp-admin-bar-autoptimize td
{
border: 0px !important;
}
#wp-admin-bar-autoptimize-default
{
padding-top: 0 !important;
}
#wp-admin-bar-autoptimize-delete-cache .ab-item
{
cursor: pointer !important;
background: #464b50;
}
#wp-admin-bar-autoptimize-delete-cache .ab-item:hover
{
color: rgba(240,245,250,0.85) !important;
background: #B57373 !important;
}
#wp-admin-bar-autoptimize-cache-info
{
padding-top: 8px !important;
padding-bottom: 8px !important;
}
#wp-admin-bar-autoptimize-cache-info,
#wp-admin-bar-autoptimize-cache-info .ab-item
{
height: auto !important;
cursor: default !important;
}
#wp-admin-bar-autoptimize-cache-info td + td
{
padding-left: 3px;
}
#wp-admin-bar-autoptimize-cache-info .ab-item,
#wp-admin-bar-autoptimize-cache-info .ab-item:hover
{
color: #b4b9be !important;
}
#wp-admin-bar-autoptimize-cache-info .ab-item > p
{
display: block;
}
#wp-admin-bar-autoptimize-cache-info .ab-item p,
#wp-admin-bar-autoptimize-cache-info .ab-item td
{
font-size: 11px !important;
line-height: 16px !important;
}
#wp-admin-bar-autoptimize-cache-info .ab-item table
{
display: inline-block !important;
margin-left: 10px !important;
}
/* Radial Bar */
.autoptimize-radial-bar
{
display: inline-block !important;
margin-top: 5px !important;
}
.autoptimize-radial-bar,
.autoptimize-radial-bar .mask,
.autoptimize-radial-bar .fill,
.autoptimize-radial-bar .shadow
{
width : 36px !important;
height : 36px !important;
}
.autoptimize-radial-bar
{
background-color : #d6dadc;
}
.autoptimize-radial-bar .fill
{
background-color : #02ca02;
}
.autoptimize-radial-bar .numbers
{
color : #02ca02;
}
.autoptimize-radial-bar .mask
{
clip : rect(0px, 36px, 36px, 18px);
}
.autoptimize-radial-bar .fill
{
clip : rect(0px, 18px, 36px, 0px);
}
.autoptimize-radial-bar .inset
{
width : 26px !important;
height : 26px !important;
margin-left : 5px !important;
margin-top : 5px !important;
background-color : #32373c;
}
.autoptimize-radial-bar .percentage
{
width : 26px !important;
height : 16px !important;
line-height : 11px !important;
top : 7px !important;
left : 0px !important;
overflow : hidden;
}
.autoptimize-radial-bar .numbers
{
width : 26px !important;
font-weight : 600 !important;
font-size : 9px !important;
margin-top : -5px !important;
display : inline-block;
vertical-align : top;
text-align : center;
}
.autoptimize-radial-bar .inset
{
box-shadow : 3px 3px 5px rgba(0,0,0,0.3) !important;
}
.autoptimize-radial-bar .shadow
{
box-shadow : 3px 3px 5px rgba(0,0,0,0.3) inset !important;
}
.autoptimize-radial-bar .mask,
.autoptimize-radial-bar .fill,
.autoptimize-radial-bar .shadow,
.autoptimize-radial-bar .inset,
.autoptimize-radial-bar .percentage
{
position : absolute !important;
}
.autoptimize-radial-bar,
.autoptimize-radial-bar .mask,
.autoptimize-radial-bar .fill,
.autoptimize-radial-bar .shadow,
.autoptimize-radial-bar .inset
{
border-radius : 50% !important;
}
/* fixes for toolbar on frontend for other themes messing things up */
#wp-admin-bar-autoptimize tr{border:0 !important}
#wp-admin-bar-autoptimize td{background-color:#32373c !important}

View File

@@ -0,0 +1,82 @@
jQuery( document ).ready(function()
{
var percentage = jQuery( '#wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar' ).attr('percentage');
var rotate = percentage * 1.8;
jQuery( '#wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar .mask.full, #wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar .fill' ).css({
'-webkit-transform' : 'rotate(' + rotate + 'deg)',
'-ms-transform' : 'rotate(' + rotate + 'deg)',
'transform' : 'rotate(' + rotate + 'deg)'
});
// Fix Background color of circle percentage & delete cache to fit with the current color theme
jQuery( '#wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar .inset' ).css( 'background-color', jQuery( '#wp-admin-bar-autoptimize .ab-sub-wrapper' ).css( 'background-color') );
jQuery( '#wp-admin-bar-autoptimize-delete-cache .ab-item' ).css( 'background-color', jQuery( '#wpadminbar' ).css( 'background-color') );
jQuery( '#wp-admin-bar-autoptimize-default li' ).on('click', function(e)
{
var id = ( typeof e.target.id != 'undefined' && e.target.id ) ? e.target.id : jQuery( e.target ).parent( 'li' ).attr( 'id' );
var action = '';
if( id == 'wp-admin-bar-autoptimize-delete-cache' ){
action = 'autoptimize_delete_cache';
} else {
return;
}
// Remove the class "hover" from drop-down Autoptimize menu to hide it.
jQuery( '#wp-admin-bar-autoptimize' ).removeClass( 'hover' );
// Create and Show the Autoptimize Loading Modal
var modal_loading = jQuery( '<div class="autoptimize-loading"></div>' ).appendTo( 'body' ).show();
var success = function() {
// Reset output values & class names of cache info
jQuery( '#wp-admin-bar-autoptimize-cache-info .size' ).attr( 'class', 'size green' ).html( '0.00 B' );
jQuery( '#wp-admin-bar-autoptimize-cache-info .files' ).html( '0' );
jQuery( '#wp-admin-bar-autoptimize-cache-info .percentage .numbers' ).attr( 'class', 'numbers green' ).html( '0%' );
jQuery( '#wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar .fill' ).attr( 'class', 'fill bg-green' );
// Reset the class names of bullet icon
jQuery( '#wp-admin-bar-autoptimize' ).attr( 'class', 'menupop bullet-green' );
// Reset the Radial Bar progress
jQuery( '#wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar .mask.full, #wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar .fill' ).css({
'-webkit-transform' : 'rotate(0deg)',
'-ms-transform' : 'rotate(0deg)',
'transform' : 'rotate(0deg)'
});
};
var notice = function() {
jQuery( '<div id="ao-delete-cache-timeout" class="notice notice-error is-dismissible"><p><strong><span style="display:block;clear:both;">' + autoptimize_ajax_object.error_msg + '</span></strong></p><button type="button" class="notice-dismiss"><span class="screen-reader-text">' + autoptimize_ajax_object.dismiss_msg + '</span></button></div><br>' ).insertAfter( '#wpbody .wrap h1:first-of-type' ).show();
};
jQuery.ajax({
type : 'GET',
url : autoptimize_ajax_object.ajaxurl,
data : {'action':action, 'nonce':autoptimize_ajax_object.nonce},
dataType : 'json',
cache : false,
timeout : 9000,
success : function( cleared )
{
// Remove the Autoptimize Loading Modal
modal_loading.remove();
if ( cleared ) {
success();
} else {
notice();
}
},
error: function( jqXHR, textStatus )
{
// Remove the Autoptimize Loading Modal
modal_loading.remove();
// WordPress Admin Notice
notice();
}
});
});
});

View File

@@ -0,0 +1 @@
.autoptimize-loading{display:none;position:fixed;background-color:rgba(102,102,102,.8);background-image:url(loading.gif);background-position:center;background-repeat:no-repeat;top:0;left:0;width:100%;height:100%;z-index:9000000000}#wp-admin-bar-autoptimize .white{color:#eee}#wp-admin-bar-autoptimize .green{color:#26bd26}#wp-admin-bar-autoptimize .orange{color:#ec9103}#wp-admin-bar-autoptimize .red{color:#ea1919}#wp-admin-bar-autoptimize .bg-green{background:#26bd26}#wp-admin-bar-autoptimize .bg-orange{background:#ec9103}#wp-admin-bar-autoptimize .bg-red{background:#ea1919}#wp-admin-bar-autoptimize.bullet-green .ab-icon:before,#wp-admin-bar-autoptimize.bullet-green:hover .ab-icon:before{content:"\f159";color:#02ca02;font-size:14px}#wp-admin-bar-autoptimize.bullet-orange .ab-icon:before,#wp-admin-bar-autoptimize.bullet-orange:hover .ab-icon:before{content:"\f159";color:#ec9103;font-size:14px}#wp-admin-bar-autoptimize.bullet-red .ab-icon:before,#wp-admin-bar-autoptimize.bullet-red:hover .ab-icon:before{content:"\f159";color:#ea1919;font-size:14px;-webkit-animation:blink 1s step-end infinite;animation:blink 1s step-end infinite}@-webkit-keyframes blink{50%{visibility:hidden}}@keyframes blink{50%{visibility:hidden}}#wp-admin-bar-autoptimize table,#wp-admin-bar-autoptimize th,#wp-admin-bar-autoptimize td{border:0 !important}#wp-admin-bar-autoptimize-default{padding-top:0 !important}#wp-admin-bar-autoptimize-delete-cache .ab-item{cursor:pointer !important;background:#464b50}#wp-admin-bar-autoptimize-delete-cache .ab-item:hover{color:rgba(240,245,250,.85) !important;background:#b57373 !important}#wp-admin-bar-autoptimize-cache-info{padding-top:8px !important;padding-bottom:8px !important}#wp-admin-bar-autoptimize-cache-info,#wp-admin-bar-autoptimize-cache-info .ab-item{height:auto !important;cursor:default !important}#wp-admin-bar-autoptimize-cache-info td+td{padding-left:3px}#wp-admin-bar-autoptimize-cache-info .ab-item,#wp-admin-bar-autoptimize-cache-info .ab-item:hover{color:#b4b9be !important}#wp-admin-bar-autoptimize-cache-info .ab-item>p{display:block}#wp-admin-bar-autoptimize-cache-info .ab-item p,#wp-admin-bar-autoptimize-cache-info .ab-item td{font-size:11px !important;line-height:16px !important}#wp-admin-bar-autoptimize-cache-info .ab-item table{display:inline-block !important;margin-left:10px !important}.autoptimize-radial-bar{display:inline-block !important;margin-top:5px !important}.autoptimize-radial-bar,.autoptimize-radial-bar .mask,.autoptimize-radial-bar .fill,.autoptimize-radial-bar .shadow{width:36px !important;height:36px !important}.autoptimize-radial-bar{background-color:#d6dadc}.autoptimize-radial-bar .fill{background-color:#02ca02}.autoptimize-radial-bar .numbers{color:#02ca02}.autoptimize-radial-bar .mask{clip:rect(0px,36px,36px,18px)}.autoptimize-radial-bar .fill{clip:rect(0px,18px,36px,0px)}.autoptimize-radial-bar .inset{width:26px !important;height:26px !important;margin-left:5px !important;margin-top:5px !important;background-color:#32373c}.autoptimize-radial-bar .percentage{width:26px !important;height:16px !important;line-height:11px !important;top:7px !important;left:0 !important;overflow:hidden}.autoptimize-radial-bar .numbers{width:26px !important;font-weight:600 !important;font-size:9px !important;margin-top:-5px !important;display:inline-block;vertical-align:top;text-align:center}.autoptimize-radial-bar .inset{box-shadow:3px 3px 5px rgba(0,0,0,.3) !important}.autoptimize-radial-bar .shadow{box-shadow:3px 3px 5px rgba(0,0,0,.3) inset !important}.autoptimize-radial-bar .mask,.autoptimize-radial-bar .fill,.autoptimize-radial-bar .shadow,.autoptimize-radial-bar .inset,.autoptimize-radial-bar .percentage{position:absolute !important}.autoptimize-radial-bar,.autoptimize-radial-bar .mask,.autoptimize-radial-bar .fill,.autoptimize-radial-bar .shadow,.autoptimize-radial-bar .inset{border-radius:50% !important}#wp-admin-bar-autoptimize tr{border:0 !important}#wp-admin-bar-autoptimize td{background-color:#32373c !important}

View File

@@ -0,0 +1,6 @@
jQuery(document).ready(function()
{var percentage=jQuery('#wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar').attr('percentage');var rotate=percentage*1.8;jQuery('#wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar .mask.full, #wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar .fill').css({'-webkit-transform':'rotate('+rotate+'deg)','-ms-transform':'rotate('+rotate+'deg)','transform':'rotate('+rotate+'deg)'});jQuery('#wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar .inset').css('background-color',jQuery('#wp-admin-bar-autoptimize .ab-sub-wrapper').css('background-color'));jQuery('#wp-admin-bar-autoptimize-delete-cache .ab-item').css('background-color',jQuery('#wpadminbar').css('background-color'));jQuery('#wp-admin-bar-autoptimize-default li').on('click',function(e)
{var id=(typeof e.target.id!='undefined'&&e.target.id)?e.target.id:jQuery(e.target).parent('li').attr('id');var action='';if(id=='wp-admin-bar-autoptimize-delete-cache'){action='autoptimize_delete_cache';}else{return;}
jQuery('#wp-admin-bar-autoptimize').removeClass('hover');var modal_loading=jQuery('<div class="autoptimize-loading"></div>').appendTo('body').show();var success=function(){jQuery('#wp-admin-bar-autoptimize-cache-info .size').attr('class','size green').html('0.00 B');jQuery('#wp-admin-bar-autoptimize-cache-info .files').html('0');jQuery('#wp-admin-bar-autoptimize-cache-info .percentage .numbers').attr('class','numbers green').html('0%');jQuery('#wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar .fill').attr('class','fill bg-green');jQuery('#wp-admin-bar-autoptimize').attr('class','menupop bullet-green');jQuery('#wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar .mask.full, #wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar .fill').css({'-webkit-transform':'rotate(0deg)','-ms-transform':'rotate(0deg)','transform':'rotate(0deg)'});};var notice=function(){jQuery('<div id="ao-delete-cache-timeout" class="notice notice-error is-dismissible"><p><strong><span style="display:block;clear:both;">'+autoptimize_ajax_object.error_msg+'</span></strong></p><button type="button" class="notice-dismiss"><span class="screen-reader-text">'+autoptimize_ajax_object.dismiss_msg+'</span></button></div><br>').insertAfter('#wpbody .wrap h1:first-of-type').show();};jQuery.ajax({type:'GET',url:autoptimize_ajax_object.ajaxurl,data:{'action':action,'nonce':autoptimize_ajax_object.nonce},dataType:'json',cache:false,timeout:9000,success:function(cleared)
{modal_loading.remove();if(cleared){success();}else{notice();}},error:function(jqXHR,textStatus)
{modal_loading.remove();notice();}});});});

View File

@@ -0,0 +1,52 @@
<?php exit;
/**
* Autoptimize's magic 404 handler.
*
* Configure your webserver to have requests for files that are no longer in
* /wp-content/cache/autoptimize/ to redirect to this file. AO's .htaccess file
* will have a "Errordocument:" directive to automatically do this.
*
* This file has simple logic to redirect to the "fallback" files that are
* created automatically by AO to avoid visitors seeing broken pages or
* Googlebot getting utterly confused.
*
* Error logging is off by default (don't want to flood your php errorlog, but
* can be enabled by this code snippet:
*
* add_filter( 'autoptimize_filter_cache_fallback_log_errors', '__return_true' );
*
* Warning: the fallback files might not apply to all pages, so this is a just
* a temporary solution, you really should clear any page cache to avoid requests
* to files that don't exist in AO's cache.
*/
$original_request = strtok( $_SERVER['REQUEST_URI'], '?' );
if ( strpos( $original_request, 'uucss/uucss-' ) !== false ) {
$original_request = preg_replace( '/uucss\/uucss-[a-z0-9]{32}-/', 'css/', $original_request );
}
$fallback_target = preg_replace( '/(.*)_(?:[a-z0-9]{32})\.(js|css)$/', '${1}_fallback.${2}', $original_request );
$ao_cache_dir = '<!--ao-cache-dir-->';
$js_or_css = pathinfo( $original_request, PATHINFO_EXTENSION );
// add multisite logic.
$multisite = false;
if ( true === $multisite ) {
preg_match( '#\/([0-9]{1,5})\/(?:js|css)\/[a-z0-9]*_fallback\.(?:js|css)$#', $fallback_target, $child_site_id );
$ao_root_cache_dir = preg_replace( '#[0-9]*\/$#', '', $ao_cache_dir );
$ao_cache_dir = $ao_root_cache_dir . $child_site_id[1] . '/';
}
$fallback_path = $ao_cache_dir . $js_or_css . '/<!--ao-cachefile-prefix-->fallback.' . $js_or_css;
if ( $original_request !== $fallback_target && file_exists( $fallback_path ) ) {
// error_log( 'Autoptimize file ' . $original_request . ' not found, using fallback instead.' );
header( 'HTTP/1.1 302 Found' );
header( 'Location: ' . $fallback_target );
} else {
// error_log( 'Autoptimize file ' . $original_request . ' not found, sending 410 gone response.' );
header( 'HTTP/1.1 410 Gone' );
}
exit();

View File

@@ -0,0 +1,87 @@
<?php exit;
//Check everything exists before using it
if(!isset($_SERVER['HTTP_ACCEPT_ENCODING']))
$_SERVER['HTTP_ACCEPT_ENCODING'] = '';
if(!isset($_SERVER['HTTP_USER_AGENT']))
$_SERVER['HTTP_USER_AGENT'] = '';
// Determine supported compression method
$gzip = strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip');
$deflate = strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate');
// Determine used compression method
$encoding = $gzip ? 'gzip' : ($deflate ? 'deflate' : 'none');
// Check for buggy versions of Internet Explorer
if (!strstr($_SERVER['HTTP_USER_AGENT'], 'Opera') &&
preg_match('/^Mozilla\/4\.0 \(compatible; MSIE ([0-9]\.[0-9])/i', $_SERVER['HTTP_USER_AGENT'], $matches))
{
$version = floatval($matches[1]);
if ($version < 6)
$encoding = 'none';
if ($version == 6 && !strstr($_SERVER['HTTP_USER_AGENT'], 'EV1'))
$encoding = 'none';
}
//Some servers compress the output of PHP - Don't break in those cases
if(ini_get('output_handler') == 'ob_gzhandler' || ini_get('zlib.output_compression') == 1)
$encoding = 'none';
$iscompressed = file_exists(__FILE__.'.'.$encoding);
if($encoding != 'none' && $iscompressed == false)
{
$flag = ($encoding == 'gzip' ? FORCE_GZIP : FORCE_DEFLATE);
$code = file_get_contents(__FILE__.'.none');
$contents = gzencode($code,9,$flag);
}else{
//Get data
$contents = file_get_contents(__FILE__.'.'.$encoding);
}
// first check if we have to send 304
// inspired by http://www.jonasjohn.de/snippets/php/caching.htm
$eTag=md5($contents);
$modTime=filemtime(__FILE__.'.none');
date_default_timezone_set("UTC");
$eTagMatch = (isset($_SERVER['HTTP_IF_NONE_MATCH']) && strpos($_SERVER['HTTP_IF_NONE_MATCH'],$eTag));
$modTimeMatch = (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $modTime);
if (($modTimeMatch)||($eTagMatch)) {
header('HTTP/1.1 304 Not Modified');
header('Connection: close');
} else {
// send all sorts of headers
$expireTime=60*60*24*355; // 1y max according to RFC
if ($encoding != 'none') {
header('Content-Encoding: '.$encoding);
}
header('Vary: Accept-Encoding');
header('Content-Length: '.strlen($contents));
header('Content-type: %%CONTENT%%; charset=utf-8');
header('Cache-Control: max-age='.$expireTime.', public, must-revalidate');
header('Cache-Control: max-age='.$expireTime.', public, immutable');
header('Expires: '.gmdate('D, d M Y H:i:s', time() + $expireTime).' GMT');
header('ETag: ' . $eTag);
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $modTime).' GMT');
// send output
echo $contents;
//And write to filesystem cache if not done yet
if($encoding != 'none' && $iscompressed == false)
{
//Write the content we sent
file_put_contents(__FILE__.'.'.$encoding,$contents);
//And write the new content
$flag = ($encoding == 'gzip' ? FORCE_DEFLATE : FORCE_GZIP);
$ext = ($encoding == 'gzip' ? 'deflate' : 'gzip');
$contents = gzencode($code,9,$flag);
file_put_contents(__FILE__.'.'.$ext,$contents);
}
}

View File

@@ -0,0 +1 @@
<html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>

View File

@@ -0,0 +1 @@
<html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>

View File

@@ -0,0 +1,462 @@
=== Autoptimize ===
Contributors: futtta, optimizingmatters, zytzagoo, turl
Tags: optimize, performance, images, core web vitals, pagespeed
Donate link: http://blog.futtta.be/2013/10/21/do-not-donate-to-me/
License: GPLv3
License URI: [https://www.gnu.org/licenses/gpl-3.0.html](https://www.gnu.org/licenses/gpl-3.0.html)
Requires at least: 5.3
Tested up to: 6.9
Requires PHP: 7.1
Stable tag: 3.1.15.1
Autoptimize speeds up your website by optimizing JS, CSS, images (incl. lazy-load), HTML and Google Fonts, asyncing JS, removing emoji cruft and more.
== Description ==
Autoptimize makes optimizing your site really easy. It can aggregate, minify and cache scripts and styles, injects CSS in the page head by default but can also inline critical CSS and defer the aggregated full CSS, moves and defers scripts to the footer and minifies HTML. You can optimize and lazy-load images (with support for WebP and AVIF formats), optimize Google Fonts, async non-aggregated JavaScript, remove WordPress core emoji cruft and more. As such it can improve your site's performance even when already on HTTP/2! There is extensive API available to enable you to tailor Autoptimize to each and every site's specific needs.
If you think performance indeed is important, you should at least consider one of the many free page caching plugins (e.g. [Speed Booster pack](https://wordpress.org/plugins/speed-booster-pack/) or [KeyCDN's Cache Enabler](https://wordpress.org/plugins/cache-enabler)) to complement Autoptimize or even [consider Autoptimize Pro](https://misc.optimizingmatters.com/partners/?from=partnertab&partner=aopro) which not only has page caching but also image optimization, CDN, critical CSS and more!
> <strong>Autoptimize Pro</strong><br>
> [Autoptimize Pro is a premium Power-Up](https://misc.optimizingmatters.com/partners/?from=partnertab&partner=aopro), adding image optimization, CDN, page caching, automatic critical CSS rules and extra “booster” options, all in one handy subscription to [make your site even faster!](https://misc.optimizingmatters.com/partners/?from=partnertab&partner=aopro)!
> <strong>Premium Support</strong><br>
> We provide great [Premium Support and Web Performance Optimization services](https://misc.optimizingmatters.com/partners/?from=partnertab&partner=autoptimizepro) with Accelera, check out our offering on [https://accelerawp.com/](https://misc.optimizingmatters.com/partners/?from=partnertab&partner=autoptimizepro)!
(Speed-surfing image under creative commons [by LL Twistiti](https://www.flickr.com/photos/twistiti/818552808/))
== Installation ==
Just install from your WordPress "Plugins > Add New" screen and all will be well. Manual installation is very straightforward as well:
1. Upload the zip file and unzip it in the `/wp-content/plugins/` directory
1. Activate the plugin through the 'Plugins' menu in WordPress
1. Go to `Settings > Autoptimize` and enable the options you want. Generally this means "Optimize HTML/ CSS/ JavaScript".
== Frequently Asked Questions ==
= What does the plugin do to help speed up my site? =
It minifies all scripts and styles and configures your webserver to compresses them with good expires headers. JavaScript be default will be made non-render-blocking and CSS can be too by adding critical CSS. You can configure it to combine (aggregate) CSS & JS-files, in which case styles are moved to the page head, and scripts to the footer. It also minifies the HTML code and can also optimize images and Google Fonts, making your page really lightweight.
= But I'm on HTTP/2, so I don't need Autoptimize? =
HTTP/2 is a great step forward for sure, reducing the impact of multiple requests from the same server significantly by using the same connection to perform several concurrent requests and for that reason on new installations Autoptimize will not aggregate CSS and JS files any more. That being said, [concatenation of CSS/ JS can still make a lot of sense](http://engineering.khanacademy.org/posts/js-packaging-http2.htm), as described in [this css-tricks.com article](https://css-tricks.com/http2-real-world-performance-test-analysis/) and this [blogpost from one of the Ebay engineers](http://calendar.perfplanet.com/2015/packaging-for-performance/). The conclusion; configure, test, reconfigure, retest, tweak and look what works best in your context. Maybe it's just HTTP/2, maybe it's HTTP/2 + aggregation and minification, maybe it's HTTP/2 + minification (which AO can do as well, simply untick the "aggregate JS-files" and/ or "aggregate CSS-files" options). And Autoptimize can do a lot more then "just" optimizing your JS & CSS off course ;-)
= Will this work with my blog? =
Although Autoptimize comes without any warranties, it will in general work flawlessly if you configure it correctly. See "Troubleshooting" below for info on how to configure in case of problems. If you want you can [test Autoptimize on a new free dummy site, courtesy of tastewp.com](https://demo.tastewp.com/autoptimize).
= Why is jquery.min.js not optimized when aggregating JavaScript? =
Starting from AO 2.1 WordPress core's jquery.min.js is not optimized for the simple reason a lot of popular plugins inject inline JS that is not aggregated either (due to possible cache size issues with unique code in inline JS) which relies on jquery being available, so excluding jquery.min.js ensures that most sites will work out of the box. If you want optimize jquery as well, you can remove it from the JS optimization exclusion-list (you might have to enable "also aggregate inline JS" as well or switch to "force JS in head").
= Why is Autoptimized JS render blocking? =
This happens when aggregating JavaSCript and ticking the "force in head" option or when not aggregating and not deferring. Consider changing settings.
= Why is the autoptimized CSS still called out as render blocking? =
With the default Autoptimize configuration the CSS is linked in the head, which is a safe default but has Google PageSpeed Insights complaining. You can look into "inline all CSS" (easy) or "inline and defer CSS" (better) which are explained in this FAQ as well.
= What is the use of "inline and defer CSS"? =
CSS in general should go in the head of the document. Recently a.o. Google started promoting deferring non-essential CSS, while inlining those styles needed to build the page above the fold. This is especially important to render pages as quickly as possible on mobile devices. As from Autoptimize 1.9.0 this is easy; select "inline and defer CSS", paste the block of "above the fold CSS" in the input field (text area) and you're good to go!
= But how can one find out what the "above the fold CSS" is? =
There's no easy solution for that as "above the fold" depends on where the fold is, which in turn depends on screensize. There are some tools available however, which try to identify just what is "above the fold". [This list of tools](https://github.com/addyosmani/above-the-fold-css-tools) is a great starting point. The [Sitelocity critical CSS generator](https://www.sitelocity.com/critical-path-css-generator) and [Jonas Ohlsson's criticalpathcssgenerator](http://jonassebastianohlsson.com/criticalpathcssgenerator/) are nice basic solutions and [http://criticalcss.com/](http://misc.optimizingmatters.com/partners/?from=faq&amp;partner=critcss) is a premium solution by the same Jonas Ohlsson. Alternatively [this bookmarklet](https://gist.github.com/PaulKinlan/6284142) (Chrome-only) can be helpful as well.
= Or should you inline all CSS? =
The short answer: probably not. Although inlining all CSS will make the CSS non-render blocking, it will result in your base HTML-page getting significantly bigger thus requiring more "roundtrip times". Moreover when considering multiple pages being requested in a browsing session the inline CSS is sent over each time, whereas when not inlined it would be served from cache. Finally the inlined CSS will push the meta-tags in the HTML down to a position where Facebook or Whatsapp might not look for it any more, breaking e.g. thumbnails when sharing on these platforms.
= My cache is getting huge, doesn't Autoptimize purge the cache? =
Autoptimize does not have its proper cache purging mechanism, as this could remove optimized CSS/JS which is still referred to in other caches, which would break your site. Moreover a fast growing cache is an indication of [other problems you should avoid](http://blog.futtta.be/2016/09/15/autoptimize-cache-size-the-canary-in-the-coal-mine/).
Instead you can keep the cache size at an acceptable level by either:
* disactivating the "aggregate inline JS" and/ or "aggregate inline CSS" options
* excluding JS-variables (or sometimes CSS-selectors) that change on a per page (or per pageload) basis. You can read how you can do that [in this blogpost](http://blog.futtta.be/2014/03/19/how-to-keep-autoptimizes-cache-size-under-control-and-improve-visitor-experience/).
Despite above objections, there are 3rd party solutions to automatically purge the AO cache, e.g. using [this code](https://wordpress.org/support/topic/contribution-autoptimize-cache-size-under-control-by-schedule-auto-cache-purge/) or [this plugin](https://wordpress.org/plugins/bi-clean-cache/), but for reasons above these are to be used only if you really know what you're doing.
= "Clear cache" doesn't seem to work? =
When clicking the "Delete Cache" link in the Autoptimize dropdown in the admin toolbar, you might to get a "Your cache might not have been purged successfully". In that case go to Autoptimizes setting page and click the "Save changes & clear cache"-button.
Moreover don't worry if your cache never is down to 0 files/ 0KB, as Autoptimize (as from version 2.2) will automatically preload the cache immediately after it has been cleared to speed further minification significantly up.
= My site looks broken when I purge Autoptimize's cache! =
When clearing AO's cache, no page cache should contain pages (HTML) that refers to the removed optimized CSS/ JS. Although for that purpose there is integration between Autoptimize and some page caches, this integration does not cover 100% of setups so you might need to purge your page cache manually.
= Can I still use Cloudflare's Rocket Loader? =
Cloudflare Rocket Loader is a pretty advanced but invasive way to make JavaScript non-render-blocking, which [Cloudflare still considers Beta](https://wordpress.org/support/topic/rocket-loader-breaking-onload-js-on-linked-css/#post-9263738). Sometimes Autoptimize & Rocket Loader work together, sometimes they don't. The best approach is to disable Rocket Loader, configure Autoptimize and re-enable Rocket Loader (if you think it can help) after that and test if everything still works.
At the moment (June 2017) it seems RocketLoader might break AO's "inline & defer CSS", which is based on [Filamentgroups loadCSS](https://github.com/filamentgroup/loadCSS), resulting in the deferred CSS not loading.
= I tried Autoptimize but my Google Pagespeed Scored barely improved =
Autoptimize is not a simple "fix my Pagespeed-problems" plugin; it "only" aggregates & minifies (local) JS & CSS and images and allows for some nice extra's as removing Google Fonts and deferring the loading of the CSS. As such Autoptimize will allow you to improve your performance (load time measured in seconds) and will probably also help you tackle some specific Pagespeed warnings. If you want to improve further, you will probably also have to look into e.g. page caching and your webserver configuration, which will improve real performance (again, load time as measured by e.g. https://webpagetest.org) and your "performance best practice" pagespeed ratings.
= What can I do with the API? =
A whole lot; there are filters you can use to conditionally disable Autoptimize per request, to change the CSS- and JS-excludes, to change the limit for CSS background-images to be inlined in the CSS, to define what JS-files are moved behind the aggregated one, to change the defer-attribute on the aggregated JS script-tag, ... There are examples for some filters in autoptimize_helper.php_example and in this FAQ.
= How does CDN work? =
Starting from version 1.7.0, CDN is activated upon entering the CDN blog root directory (e.g. http://cdn.example.net/wordpress/). If that URL is present, it will used for all Autoptimize-generated files (i.e. aggregated CSS and JS), including background-images in the CSS (when not using data-uri's).
If you want your uploaded images to be on the CDN as well, you can change the upload_url_path in your WordPress configuration (/wp-admin/options.php) to the target CDN upload directory (e.g. http://cdn.example.net/wordpress/wp-content/uploads/). Do take into consideration this only works for images uploaded from that point onwards, not for images that already were uploaded. Thanks to [BeautyPirate for the tip](http://wordpress.org/support/topic/please-don%c2%b4t-remove-cdn?replies=15#post-4720048)!
= Why aren't my fonts put on the CDN as well? =
Autoptimize supports this, but it is not enabled by default because [non-local fonts might require some extra configuration](http://davidwalsh.name/cdn-fonts). But if you have your cross-origin request policy in order, you can tell Autoptimize to put your fonts on the CDN by hooking into the API, setting `autoptimize_filter_css_fonts_cdn` to `true` this way;
`add_filter( 'autoptimize_filter_css_fonts_cdn', '__return_true' );`
= I'm using Cloudflare, what should I enter as CDN root directory =
Nothing, when on Cloudflare your autoptimized CSS/ JS is on the Cloudflare's CDN automatically.
= How can I force the aggregated files to be static CSS or JS instead of PHP? =
If your webserver is properly configured to handle compression (gzip or deflate) and cache expiry (expires and cache-control with sufficient cacheability), you don't need Autoptimize to handle that for you. In that case you can check the "Save aggregated script/css as static files?"-option, which will force Autoptimize to save the aggregated files as .css and .js-files (meaning no PHP is needed to serve these files). This setting is default as of Autoptimize 1.8.
= How does "exclude from optimizing" work? =
Both CSS and JS optimization can skip code from being aggregated and minimized by adding "identifiers" to the comma-separated exclusion list. The exact identifier string to use can be determined this way:
* if you want to exclude a specific file, e.g. wp-content/plugins/funkyplugin/css/style.css, you could simply exclude "funkyplugin/css/style.css"
* if you want to exclude all files of a specific plugin, e.g. wp-content/plugins/funkyplugin/js/*, you can exclude for example "funkyplugin/js/" or "plugins/funkyplugin"
* if you want to exclude inline code, you'll have to find a specific, unique string in that block of code and add that to the exclusion list. Example: to exclude `<script>funky_data='Won\'t you take me to, Funky Town'</script>`, the identifier is "funky_data".
= Troubleshooting Autoptimize =
Have a look at the troubleshooitng instructions at https://blog.futtta.be/2022/05/05/what-to-do-when-autoptimize-breaks-your-site/
= I excluded files but they are still being autoptimized? =
AO minifies excluded JS/ CSS if the filename indicates the file is not minified yet. As of AO 2.5 you can disable this on the "JS, CSS & HTML"-tab under misc. options by unticking "minify excluded files".
= Help, I have a blank page or an internal server error after enabling Autoptimize!! =
Make sure you're not running other HTML, CSS or JS minification plugins (BWP minify, WP minify, ...) simultaneously with Autoptimize or disable that functionality your page caching plugin (W3 Total Cache, WP Fastest Cache, ...). Try enabling only CSS or only JS optimization to see which one causes the server error and follow the generic troubleshooting steps to find a workaround.
= But I still have blank autoptimized CSS or JS-files! =
If you are running Apache, the .htaccess file written by Autoptimize can in some cases conflict with the AllowOverrides settings of your Apache configuration (as is the case with the default configuration of some Ubuntu installations), which results in "internal server errors" on the autoptimize CSS- and JS-files. This can be solved by [setting AllowOverrides to All](http://httpd.apache.org/docs/2.4/mod/core.html#allowoverride).
= Can't log in on domain mapped multisites =
Domain mapped multisites require Autoptimize to be initialized at a different WordPress action, add this line of code to your wp-config.php to make it so to hook into `setup_theme` for example:
`define( 'AUTOPTIMIZE_SETUP_INITHOOK', 'setup_theme' );`
= I get no error, but my pages are not optimized at all? =
Autoptimize does a number of checks before actually optimizing. When one of the following is true, your pages won't be optimized:
* when in the customizer
* if there is no opening `<html` tag
* if there is `<xsl:stylesheet` in the response (indicating the output is not HTML but XML)
* if there is `<html amp` in the response (as AMP-pages are optimized already)
* if the output is an RSS-feed (is_feed() function)
* if the output is a WordPress administration page (is_admin() function)
* if the page is requested with ?ao_noptimize=1 appended to the URL
* if code hooks into Autoptimize to disable optimization (see topic on Visual Composer)
* if other plugins use the output buffer in an incompatible manner (disable other plugins selectively to identify the culprit)
= Visual Composer, Beaver Builder and similar page builder solutions are broken!! =
Disable the option to have Autoptimize active for logged on users and go crazy dragging and dropping ;-)
= Help, my shop checkout/ payment don't work!! =
Disable the option to optimize cart/ checkout pages (works for WooCommerce, Easy Digital Downloads and WP eCommerce).
= Revolution Slider is broken! =
Make sure `js/jquery/jquery.min.js` is in the comma-separated list of JS optimization exclusions (this is excluded in the default configuration).
= I'm getting "jQuery is not defined" errors =
In that case you have un-aggregated JavaScript that requires jQuery to be loaded, so you'll have to add `js/jquery/jquery.min.js` to the comma-separated list of JS optimization exclusions.
= I use NextGen Galleries and a lot of JS is not aggregated/ minified? =
NextGen Galleries does some nifty stuff to add JavaScript. In order for Autoptimize to be able to aggregate that, you can either disable Nextgen Gallery's resourced manage with this code snippet `add_filter( 'run_ngg_resource_manager', '__return_false' );` or you can tell Autoptimize to initialize earlier, by adding this to your wp-config.php: `define("AUTOPTIMIZE_INIT_EARLIER","true");`
= What is noptimize? =
Starting with version 1.6.6 Autoptimize excludes everything inside noptimize tags, e.g.:
`&lt;!&#45;&#45;noptimize&#45;&#45;>&lt;script>alert('this will not get autoptimized');&lt;/script>&lt;!&#45;&#45;/noptimize&#45;&#45;>`
You can do this in your page/ post content, in widgets and in your theme files (consider creating [a child theme](http://codex.wordpress.org/Child_Themes) to avoid your work being overwritten by theme updates).
= Can I change the directory & filename of cached autoptimize files? =
Yes, if you want to serve files from e.g. /wp-content/resources/aggregated_12345.css instead of the default /wp-content/cache/autoptimize/autoptimize_12345.css, then add this to wp-config.php:
`
define('AUTOPTIMIZE_CACHE_CHILD_DIR','/resources/');
define('AUTOPTIMIZE_CACHEFILE_PREFIX','aggregated_');
`
= Does this work with non-default WP_CONTENT_URL ? =
No, Autoptimize does not support a non-default WP_CONTENT_URL out-of-the-box, but this can be accomplished with a couple of lines of code hooking into Autoptimize's API.
= Can the generated JS/ CSS be pre-gzipped? =
Yes, but this is off by default. You can enable this by passing ´true´ to ´autoptimize_filter_cache_create_static_gzip´. You'll obviously still have to configure your webserver to use these files instead of the non-gzipped ones to avoid the overhead of on-the-fly compression.
= What does "remove emojis" do? =
This new option in Autoptimize 2.3 removes the inline CSS, inline JS and linked JS-file added by WordPress core. As such is can have a small positive impact on your site's performance.
= Is "remove query strings" useful? =
Although some online performance assessment tools will single out "query strings for static files" as an issue for performance, in general the impact of these is almost non-existant. As such Autoptimize, since version 2.3, allows you to have the query string (or more precisely the "ver"-parameter) removed, but ticking "remove query strings from static resources" will have little or no impact of on your site's performance as measured in (milli-)seconds.
= (How) should I optimize Google Fonts? =
Google Fonts are typically loaded by a "render blocking" linked CSS-file. If you have a theme and plugins that use Google Fonts, you might end up with multiple such CSS-files. Autoptimize (since version 2.3) now let's you lessen the impact of Google Fonts by either removing them alltogether or by optimizing the way they are loaded. There are two optimization-flavors; the first one is "combine and link", which replaces all requests for Google Fonts into one request, which will still be render-blocking but will allow the fonts to be loaded immediately (meaning you won't see fonts change while the page is loading). The alternative is "combine and load async" which uses JavaScript to load the fonts in a non-render blocking manner but which might cause a "flash of unstyled text".
= Should I use "preconnect" =
Preconnect is a somewhat advanced feature to instruct browsers ([if they support it](https://caniuse.com/#feat=link-rel-preconnect)) to make a connection to specific domains even if the connection is not immediately needed. This can be used e.g. to lessen the impact of 3rd party resources on HTTPS (as DNS-request, TCP-connection and SSL/TLS negotiation are executed early). Use with care, as preconnecting to too many domains can be counter-productive.
= When can('t) I async JS? =
JavaScript files that are not autoptimized (because they were excluded or because they are hosted elsewhere) are typically render-blocking. By adding them in the comma-separated "async JS" field, Autoptimize will add the async flag causing the browser to load those files asynchronously (i.e. non-render blocking). This can however break your site (page), e.g. if you async "js/jquery/jquery.min.js" you will very likely get "jQuery is not defined"-errors. Use with care.
= How does image optimization work? =
When image optimization is on, Autoptimize will look for png, gif, jpeg (.jpg) files in image tags and in your CSS files that are loaded from your own domain and change the src (source) to the ShortPixel CDN for those. Important: this can only work for publicly available images, otherwise the image optimization proxy will not be able to get the image to optimize it, so firewalls or proxies or password protection or even hotlinking-prevention might break image optimization.
= Can I use image optimization for my intranet/ protected site? =
No; Image optimization depends on the ability of the external image optimization service to fetch the original image from your site, optimize it and save it on the CDN. If you images cannot be downloaded by anonymous visitors (due to firewall/ proxy/ password protection/ hotlinking-protection), image optimization will not work.
= Where can I get more info on image optimization? =
Have a look at [Shortpixel's FAQ](https://shortpixel.helpscoutdocs.com/category/60-shortpixel-ai-cdn).
= Can I disable AO listening to page cache purges? =
As from AO 2.4 AO "listens" to page cache purges to clear its own cache. You can disable this behavior with this filter;
`
add_filter('autoptimize_filter_main_hookpagecachepurge','__return_false');`
= Some of the non-ASCII characters get lost after optimization =
By default AO uses non multibyte-safe string methods, but if your PHP has the mbstring extension you can enable multibyte-safe string functions with this filter;
`
add_filter('autoptimize_filter_main_use_mbstring', '__return_true');`
= I can't get Critical CSS working =
Check [the FAQ on the (legacy) "power-up" here](https://wordpress.org/plugins/autoptimize-criticalcss/#faq), this info will be integrated in this FAQ at a later date.
= Do I still need the Critical CSS power-up when I have Autoptimize 2.7 or higher? =
No, the Critical CSS power-up is not needed any more, all functionality (and many fixes/ improvements) are now part of Autoptimize.
= What does "enable 404 fallbacks" do? Why would I need this? =
Autoptimize caches aggregated & optimized CSS/ JS and links to those cached files are stored in the HTML, which will be stored in a page cache (which can be a plugin, can be at host level, can be at 3rd party, in the Google cache, in a browser). If there is HTML in a page cache that links to Autoptimized CSS/ JS that has been removed in the mean time (when the cache was cleared) then the page from cache will not look/ work as expected as the CSS or JS were not found (a 404 error).
This setting aims to prevent things from breaking by serving "fallback" CSS or JS. The fallback-files are copies of the first Autoptimized CSS & JS files created after the cache was emptied and as such will based on the homepage. This means that the CSS/ JS migth not apply 100% on other pages, but at least the impact of missing CSS/ JS will be lessened (often significantly).
When the option is enabled, Autoptimize adds an `ErrorDocument 404` to the .htaccess (as used by Apache) and will also hook into WordPress core `template_redirect` to capture 404's handled by Wordpress. When using NGINX something like below should work (I'm not an NGINX specialist, but it does work for me);
`
location ~* /wp-content/cache/autoptimize/.*\.(js|css)$ {
try_files $uri $uri/ /wp-content/autoptimize_404_handler.php;
}`
And this a nice alternative approach (provided by fboylovesyou);
`location ~* /wp-content/cache/autoptimize/.*\.(css)$ {
try_files $uri $uri/ /wp-content/cache/autoptimize/css/autoptimize_fallback.css;
}
location ~* /wp-content/cache/autoptimize/.*\.(js)$ {
try_files $uri $uri/ /wp-content/cache/autoptimize/js/autoptimize_fallback.js;
}`
= What open source software/ projects are used in Autoptimize? =
The following great open source projects are used in Autoptimize in some form or another:
* [Mr Clay's Minify](https://github.com/mrclay/minify/) for JS & HTML minification
* [YUI CSS compressor PHP Port](https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port) for CSS minification
* [Lazysizes](https://github.com/aFarkas/lazysizes) for lazyload
* [Persist Admin Notices Dismissal](https://github.com/w3guy/persist-admin-notices-dismissal) for notices in the administration screens
* [Plugin Update Checker](https://github.com/YahnisElsts/plugin-update-checker/) for automated updates from Github for the beta version
* [LoadCSS](https://github.com/filamentgroup/loadCSS) for deferring full CSS
* [jQuery cookie](https://github.com/carhartl/jquery-cookie) to store the "futtta about" category selection in a cookie
* [jQuery tablesorter](https://github.com/christianbach/tablesorter) for the critical CSS rules/ jobs display
* [jQuery unslider](https://github.com/idiot/unslider/) for the mini-slider in the top right corner on the main settings page (repo gone)
* [JavaScript-md5](https://github.com/blueimp/JavaScript-MD5) for critical CSS rules editing
* [Speed Booster Pack](https://wordpress.org/plugins/speed-booster-pack/) for advanced JS deferring
* [Disable Remove Google Fonts](https://wordpress.org/plugins/disable-remove-google-fonts/) for additional Google Font removal
= Where can I get help? =
You can get help on the [wordpress.org support forum](http://wordpress.org/support/plugin/autoptimize). If you are 100% sure this your problem cannot be solved using Autoptimize configuration and that you in fact discovered a bug in the code, you can [create an issue on GitHub](https://github.com/futtta/autoptimize/issues). If you're looking for premium support, check out our [Autoptimize Pro Support and Web Performance Optimization services](http://autoptimize.com/).
= I want out, how should I remove Autoptimize? =
* Disable the plugin (this will remove options and cache)
* Remove the plugin
* Clear any cache that might still have pages which reference Autoptimized CSS/JS (e.g. of a page caching plugin such as WP Super Cache)
= How can I help/ contribute? =
Just [fork Autoptimize on Github](https://github.com/futtta/autoptimize) and code away!
== Changelog ==
= 3.1.15.1 =
* fix for "Uncaught Error: Using $this when not in object context" when preloads are set
= 3.1.15 =
* also add fetchpriority=high to preload set on Extra tab
* improve exit survey display in RTL languages
* security enhancements for 2 authenticated stored XSS issues responsibly reported by stealhcopter and bashu
* multiple minor changes/ improvements/ bugfixes, see the [GitHub commit log](https://github.com/futtta/autoptimize/commits/beta).
= 3.1.14 =
* improve HTML output for <link rel="preload" images (based on report by Muhammad)
* let the 404-handler issue a 302 iso 301 HTTP response (as mentioned by thefitrv)
* small improvement in critical CSS cron job handling in case of an empty "time limit" (thanks for the help Jason)
* fix bug in "domain binding" in critical CSS advanced options (reported by Hazel)
* catch and report (exceptional) JS optimization issues causing preg_replace_callback to crash out (in case of a huge amount of JS code), thanks siliconforks
* confirmed OK with WordPress 6.9
= 3.1.13 =
* multiple minor changes/ improvements/ bugfixes, see the [GitHub commit log](https://github.com/futtta/autoptimize/commits/beta).
= 3.1.12 =
* image optimization: improvements to the favicon regex
* javascript optimization: integrate most recent version of jsmin.php
* critical CSS: improve blocklist (url/ paths that should not be added to the job queue)
* some other minor changes/ improvements/ filters, see the [GitHub commit log](https://github.com/futtta/autoptimize/commits/beta).
= 3.1.11 =
* code quality improvements see the [GitHub commit log](https://github.com/futtta/autoptimize/commits/beta).
* some other minor changes/ improvements/ filters, see the [GitHub commit log](https://github.com/futtta/autoptimize/commits/beta).
= 3.1.10 =
* improvement: with "don't aggregate but defer" and "also defer inline JS" on, also defer JS that had the async flag to avoid the (previously) asynced JS from executing before the inline JS has ran.
* improvement: show option to disable the default on "compatibility logic".
* fix for regression in 3.1.9 which caused JetPack Image optimization not working even if image optimization was off in AO.
* API: some extra hooks in critical CSS to enable others (and AOPro) to act on changes in critical CSS rules
* some other minor changes/ improvements/ filters, see the [GitHub commit log](https://github.com/futtta/autoptimize/commits/beta).
= 3.1.9 =
* improvement: activate JS, CSS & HTML optimization upon plugin activation (hat tip to Adam Silverstein (developer relations engineer at Google))
* improvement: also defer asynced JS (to ensure execution order remains intact; asynced JS should not execute before deferred inline JS which it might depend upon)
* improvement: exclude images from being lazyloaded if they have fetchpriority attribute set to high (as done by WordPress core since 6.3)
* bugfix: disable spellcheck on CSS textarea's (above the fold CSS/ critical CSS) which in some cases caused browser issues
* add tab to explain Autoptimize Pro.
* confirmed working with WordPress 6.4 (beta 3)
* some other minor changes/ improvements/ filters, see the [GitHub commit log](https://github.com/futtta/autoptimize/commits/beta).
= 3.1.8.1 =
* urgent fix for PHP error, sorry about that!
= 3.1.8 =
* Images: improve optmization logic for background images
* Critical CSS: don't trigger custom_post rule if not is_singular + adding debug logging for rule selection
* some other minor changes/ improvements/ filters, see the [GitHub commit log](https://github.com/futtta/autoptimize/commits/beta).
= 3.1.7 =
* security: improve validation (import) and sanitization (output) of critical CSS rules, to fix a medium severity Admin+ Stored Cross-Site Scripting vulnerability as reported by WP Scan Security.
= 3.1.6 =
* CSS: removing trailing slashes in <link tags for more W3 HTML validation love
* Extra: also dequeue WooCommerce block CSS if "remove WordPress block CSS" option is active
* imgopt: also act on non-aggregated inline CSS
* imgopt: added logic to warn users if Shortpixel can't reach their site
* backend: AO toolbar JS/ CSS is finally minified as well.
* explicitly disable optimization of login pages
* some other minor changes/ improvements/ filters, see the [GitHub commit log](https://github.com/futtta/autoptimize/commits/beta).
= 3.1.5 =
* improvements to JSMin by Robert Ehrenleitner (big thanks Robert!).
* do not consider jquery.js as minified any more (WordPress now uses jquery.min.js by default and jquery.js is the unminified version).
* fix for "undefined array key" PHP errors in autoptimizeCriticalCSSCron.php
* some other minor changes/ improvements/ filters, see the [GitHub commit log](https://github.com/futtta/autoptimize/commits/beta).
= 3.1.4 =
* Improvement: when all CSS is inlined, try doing so after SEO meta-tags (just before ld+json script tag which most SEO plugins add as last item on their list).
* Img opt: also optimize images set in data-background and data-retina attributes (+ filter to easily add other attributes)
* CSS opt: filter to enable AO to skip minification of calc formulas in CSS (as the CSS minifier on rare occasions breaks those)
* Multiple other filters added
* Some other minor changes/ improvements/ filters, see the [GitHub commit log](https://github.com/futtta/autoptimize/commits/beta).
= 3.1.3 =
* Multiple fixes for metabox LCP image preloads (thanks [Kishorchand](https://foxscribbler.com/) for notifying & providing a staging environment to debug on).
* Fix in revslider compatibility (hat tip [Waqar Ahmed for reporting & helping out](https://wordpress.org/support/topic/issue-with-latest-version-of-slider-revolution/) ).
* No image optimization or criticalcss attempts on localhost installations any more + notification of that fact if localhost detected.
* Some other minor changes/ improvements/ filters, see the [GitHub commit log](https://github.com/futtta/autoptimize/commits/beta).
= 3.1.2 =
* Google Fonts: some more removal logic
* fix for 404 fallback bug (hat tip to Asif for finding & reporting)
* Some other minor changes/ improvements/ filters, see the [GitHub commit log](https://github.com/futtta/autoptimize/commits/beta).
= 3.1.1.1 =
* Quick workaround for an autoload conflict with JetFormBuilder (and maybe other Crocoblock plugins?) that causes a critical error on the AO settings page.
= 3.1.1 =
* images: when optimizing images and lazyloading is on, then by default do not set an LQIP (low quality image placeholder) any more (reason: it might *look* nice but it comes with a small-ish perf. penalty). This can be re-enabled by returning true to the `autoptimize_filter_imgopt_lazyload_dolqip` filter.
* security: further improvements to critical CSS settings page (again with the great assistance of WPScan Security).
* some other minor changes/ improvements/ filters, see the [GitHub commit log](https://github.com/futtta/autoptimize/commits/beta).
= 3.1.0 =
* new HTML sub-option: "minify inline CSS/ JS" (off by default).
* new Misc option: permanently allow the "do not run compatibility logic" flag to be removed (which was set for users upgrading from AO 2.9.* to AO 3.0.* as the assumption was things were working anyway).
* security: improvements to the critical CSS settings page to fix authenticated cross site scripting issues as reported by WPScan Security.
* bugfix: "defer inline JS" of very large chunks of inline JS could cause server errors (PCRE crash actually) so not deferring if string is more then 200000 characters (filter available).
* some other minor changes/ improvements/ hooks, see the [GitHub commit log](https://github.com/futtta/autoptimize/commits/beta)
= 3.0.4 =
* fix for "undefined array key ao_post_preload” on post/ page edit screens
* fix for image optimization altering inline JS that contains an `<img` tag if lazyload is not active
* improvements to exit survey
* confirmed working with WordPress 6.0
= 3.0.3 =
* fix for images being preloaded without this being configured when lazyload is on and per page/post settings are off.
* ensure critical CSS schedule is always known.
* when deferring non-aggregated JS, make the optimatization exclusions take the full script-tag into account instead of just the src URL.
= 3.0.2 =
* rollback automatic "minify inline CSS/ JS" which broke more then expected, this will come back as a separate default off option later and can now be enabled with a simple filter: `add_filter( 'autoptimize_html_minify_inline_js_css', '__return_true');` .
* fix for "Call to undefined method autoptimizeOptionWrapper::delete_option()" in autoptimizeVersionUpdatesHandler.php
= 3.0.1 =
* fix for minification of inline script with type text/template breaking the template (e.g. ninja forms), hat tip to @bobsled.
* fix for regression in import of CSS-files where e.g. fontawesome CSS was broken due to being escaped again with help of @bobsled, thanks man!
= 3.0.0 =
* fundamental change for new installations: by default Autoptimize will not aggregate JS/ CSS any more (HTTP/2 is ubiquitous and there are other advantages to not aggregating esp. re. inline JS/ CSS and dependancies)
* new: no API needed any more to create manual critical CSS rules.
* new: "Remove WordPress blocks CSS" option on the "Extra" tab to remove block- and global styles (and SVG).
* new: compatibility logic for "edit with elementor", "revolution slider", for non-aggregated inline JS requiring jQuery even if not excluded (= auto-exclude of jQuery) and JS-heavy WordPress blocks (Gutenberg)
* new: configure an image to be preloaded on a per page/ post basis for better LCP.
* improvement: defer inline now also allowed if inline JS contains nonce or post_id.
* improvement: settings export/ import on critical CSS tab now takes into account all Autoptimize settings, not just the critical CSS ones.
* technical improvement: all criticalCSS classes were refactored, removing use of global variables.
* technical improvement: automated unit tests on Travis-CI for PHP versions 7.2 to 8.1.
* fix: stop Divi from clearing Autoptimize's cache [which is pretty counter-productive](https://blog.futtta.be/2018/11/17/warning-divi-purging-autoptimizes-cache/).
* misc smaller fixes/ improvements, see the [GitHub commit log](https://github.com/futtta/autoptimize/commits/beta)
= older =
* see [https://plugins.svn.wordpress.org/autoptimize/tags/2.9.5.1/readme.txt](https://plugins.svn.wordpress.org/autoptimize/tags/2.9.5.1/readme.txt)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,266 @@
/* -------------------------------------------------------------------
Microtip
Modern, lightweight css-only tooltips
Just 1kb minified and gzipped
@author Ghosh
@package Microtip
----------------------------------------------------------------------
1. Base Styles
2. Direction Modifiers
3. Position Modifiers
--------------------------------------------------------------------*/
/* ------------------------------------------------
[1] Base Styles
-------------------------------------------------*/
[aria-label][role~="tooltip"] {
position: relative;
}
[aria-label][role~="tooltip"]::before,
[aria-label][role~="tooltip"]::after {
transform: translate3d(0, 0, 0);
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
will-change: transform;
opacity: 0;
pointer-events: none;
transition: all var(--microtip-transition-duration, .18s) var(--microtip-transition-easing, ease-in-out) var(--microtip-transition-delay, 0s);
position: absolute;
box-sizing: border-box;
z-index: 10;
transform-origin: top;
}
[aria-label][role~="tooltip"]::before {
background-size: 100% auto !important;
content: "";
}
[aria-label][role~="tooltip"]::after {
background: rgba(17, 17, 17, .9);
border-radius: 4px;
color: #ffffff;
content: attr(aria-label);
font-size: var(--microtip-font-size, 13px);
font-weight: var(--microtip-font-weight, normal);
text-transform: var(--microtip-text-transform, none);
padding: .5em 1em;
white-space: nowrap;
box-sizing: content-box;
}
[aria-label][role~="tooltip"]:hover::before,
[aria-label][role~="tooltip"]:hover::after,
[aria-label][role~="tooltip"]:focus::before,
[aria-label][role~="tooltip"]:focus::after {
opacity: 1;
pointer-events: auto;
}
/* ------------------------------------------------
[2] Position Modifiers
-------------------------------------------------*/
[role~="tooltip"][data-microtip-position|="top"]::before {
background: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2236px%22%20height%3D%2212px%22%3E%3Cpath%20fill%3D%22rgba%2817,%2017,%2017,%200.9%29%22%20transform%3D%22rotate%280%29%22%20d%3D%22M2.658,0.000%20C-13.615,0.000%2050.938,0.000%2034.662,0.000%20C28.662,0.000%2023.035,12.002%2018.660,12.002%20C14.285,12.002%208.594,0.000%202.658,0.000%20Z%22/%3E%3C/svg%3E") no-repeat;
height: 6px;
width: 18px;
margin-bottom: 5px;
}
[role~="tooltip"][data-microtip-position|="top"]::after {
margin-bottom: 11px;
}
[role~="tooltip"][data-microtip-position|="top"]::before {
transform: translate3d(-50%, 0, 0);
bottom: 100%;
left: 50%;
}
[role~="tooltip"][data-microtip-position|="top"]:hover::before {
transform: translate3d(-50%, -5px, 0);
}
[role~="tooltip"][data-microtip-position|="top"]::after {
transform: translate3d(-50%, 0, 0);
bottom: 100%;
left: 50%;
}
[role~="tooltip"][data-microtip-position="top"]:hover::after {
transform: translate3d(-50%, -5px, 0);
}
/* ------------------------------------------------
[2.1] Top Left
-------------------------------------------------*/
[role~="tooltip"][data-microtip-position="top-left"]::after {
transform: translate3d(calc(-100% + 16px), 0, 0);
bottom: 100%;
}
[role~="tooltip"][data-microtip-position="top-left"]:hover::after {
transform: translate3d(calc(-100% + 16px), -5px, 0);
}
/* ------------------------------------------------
[2.2] Top Right
-------------------------------------------------*/
[role~="tooltip"][data-microtip-position="top-right"]::after {
transform: translate3d(calc(0% + -16px), 0, 0);
bottom: 100%;
}
[role~="tooltip"][data-microtip-position="top-right"]:hover::after {
transform: translate3d(calc(0% + -16px), -5px, 0);
}
/* ------------------------------------------------
[2.3] Bottom
-------------------------------------------------*/
[role~="tooltip"][data-microtip-position|="bottom"]::before {
background: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2236px%22%20height%3D%2212px%22%3E%3Cpath%20fill%3D%22rgba%2817,%2017,%2017,%200.9%29%22%20transform%3D%22rotate%28180%2018%206%29%22%20d%3D%22M2.658,0.000%20C-13.615,0.000%2050.938,0.000%2034.662,0.000%20C28.662,0.000%2023.035,12.002%2018.660,12.002%20C14.285,12.002%208.594,0.000%202.658,0.000%20Z%22/%3E%3C/svg%3E") no-repeat;
height: 6px;
width: 18px;
margin-top: 5px;
margin-bottom: 0;
}
[role~="tooltip"][data-microtip-position|="bottom"]::after {
margin-top: 11px;
}
[role~="tooltip"][data-microtip-position|="bottom"]::before {
transform: translate3d(-50%, -10px, 0);
bottom: auto;
left: 50%;
top: 100%;
}
[role~="tooltip"][data-microtip-position|="bottom"]:hover::before {
transform: translate3d(-50%, 0, 0);
}
[role~="tooltip"][data-microtip-position|="bottom"]::after {
transform: translate3d(-50%, -10px, 0);
top: 100%;
left: 50%;
}
[role~="tooltip"][data-microtip-position="bottom"]:hover::after {
transform: translate3d(-50%, 0, 0);
}
/* ------------------------------------------------
[2.4] Bottom Left
-------------------------------------------------*/
[role~="tooltip"][data-microtip-position="bottom-left"]::after {
transform: translate3d(calc(-100% + 16px), -10px, 0);
top: 100%;
}
[role~="tooltip"][data-microtip-position="bottom-left"]:hover::after {
transform: translate3d(calc(-100% + 16px), 0, 0);
}
/* ------------------------------------------------
[2.5] Bottom Right
-------------------------------------------------*/
[role~="tooltip"][data-microtip-position="bottom-right"]::after {
transform: translate3d(calc(0% + -16px), -10px, 0);
top: 100%;
}
[role~="tooltip"][data-microtip-position="bottom-right"]:hover::after {
transform: translate3d(calc(0% + -16px), 0, 0);
}
/* ------------------------------------------------
[2.6] Left
-------------------------------------------------*/
[role~="tooltip"][data-microtip-position="left"]::before,
[role~="tooltip"][data-microtip-position="left"]::after {
bottom: auto;
left: auto;
right: 100%;
top: 50%;
transform: translate3d(10px, -50%, 0);
}
[role~="tooltip"][data-microtip-position="left"]::before {
background: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2212px%22%20height%3D%2236px%22%3E%3Cpath%20fill%3D%22rgba%2817,%2017,%2017,%200.9%29%22%20transform%3D%22rotate%28-90%2018%2018%29%22%20d%3D%22M2.658,0.000%20C-13.615,0.000%2050.938,0.000%2034.662,0.000%20C28.662,0.000%2023.035,12.002%2018.660,12.002%20C14.285,12.002%208.594,0.000%202.658,0.000%20Z%22/%3E%3C/svg%3E") no-repeat;
height: 18px;
width: 6px;
margin-right: 5px;
margin-bottom: 0;
}
[role~="tooltip"][data-microtip-position="left"]::after {
margin-right: 11px;
}
[role~="tooltip"][data-microtip-position="left"]:hover::before,
[role~="tooltip"][data-microtip-position="left"]:hover::after {
transform: translate3d(0, -50%, 0);
}
/* ------------------------------------------------
[2.7] Right
-------------------------------------------------*/
[role~="tooltip"][data-microtip-position="right"]::before,
[role~="tooltip"][data-microtip-position="right"]::after {
bottom: auto;
left: 100%;
top: 50%;
transform: translate3d(-10px, -50%, 0);
}
[role~="tooltip"][data-microtip-position="right"]::before {
background: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2212px%22%20height%3D%2236px%22%3E%3Cpath%20fill%3D%22rgba%2817,%2017,%2017,%200.9%29%22%20transform%3D%22rotate%2890%206%206%29%22%20d%3D%22M2.658,0.000%20C-13.615,0.000%2050.938,0.000%2034.662,0.000%20C28.662,0.000%2023.035,12.002%2018.660,12.002%20C14.285,12.002%208.594,0.000%202.658,0.000%20Z%22/%3E%3C/svg%3E") no-repeat;
height: 18px;
width: 6px;
margin-bottom: 0;
margin-left: 5px;
}
[role~="tooltip"][data-microtip-position="right"]::after {
margin-left: 11px;
}
[role~="tooltip"][data-microtip-position="right"]:hover::before,
[role~="tooltip"][data-microtip-position="right"]:hover::after {
transform: translate3d(0, -50%, 0);
}
/* ------------------------------------------------
[3] Size
-------------------------------------------------*/
[role~="tooltip"][data-microtip-size="small"]::after {
white-space: initial;
width: 80px;
}
[role~="tooltip"][data-microtip-size="medium"]::after {
white-space: initial;
width: 150px;
}
[role~="tooltip"][data-microtip-size="large"]::after {
white-space: initial;
width: 260px;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,782 @@
/*!
Modaal - accessible modals - v0.4.4
by Humaan, for all humans.
http://humaan.com
*/
.modaal-noscroll {
overflow: hidden;
}
.modaal-accessible-hide {
position: absolute !important;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
clip: rect(1px, 1px, 1px, 1px);
padding: 0 !important;
border: 0 !important;
height: 1px !important;
width: 1px !important;
overflow: hidden;
}
.modaal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
opacity: 0;
}
.modaal-wrapper {
display: block;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9999;
overflow: auto;
opacity: 1;
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
-webkit-transition: all 0.3s ease-in-out;
transition: all 0.3s ease-in-out;
}
.modaal-wrapper * {
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-backface-visibility: hidden;
}
.modaal-wrapper .modaal-close {
border: none;
background: transparent;
padding: 0;
-webkit-appearance: none;
}
.modaal-wrapper.modaal-start_none {
display: none;
opacity: 1;
}
.modaal-wrapper.modaal-start_fade {
opacity: 0;
}
.modaal-wrapper *[tabindex="0"] {
outline: none !important;
}
.modaal-wrapper.modaal-fullscreen {
overflow: hidden;
}
.modaal-outer-wrapper {
display: table;
position: relative;
width: 100%;
height: 100%;
}
.modaal-fullscreen .modaal-outer-wrapper {
display: block;
}
.modaal-inner-wrapper {
display: table-cell;
width: 100%;
height: 100%;
position: relative;
vertical-align: middle;
text-align: center;
padding: 80px 25px;
}
.modaal-fullscreen .modaal-inner-wrapper {
padding: 0;
display: block;
vertical-align: top;
}
.modaal-container {
position: relative;
display: inline-block;
width: 100%;
margin: auto;
text-align: left;
color: #000;
max-width: 1000px;
border-radius: 0px;
background: #fff;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
cursor: auto;
}
.modaal-container.is_loading {
height: 100px;
width: 100px;
overflow: hidden;
}
.modaal-fullscreen .modaal-container {
max-width: none;
height: 100%;
overflow: auto;
}
.modaal-close {
position: fixed;
right: 20px;
top: 20px;
color: #fff;
cursor: pointer;
opacity: 1;
width: 50px;
height: 50px;
background: rgba(0, 0, 0, 0);
border-radius: 100%;
-webkit-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
}
.modaal-close:focus,
.modaal-close:hover {
outline: none;
background: #fff;
}
.modaal-close:focus:before,
.modaal-close:focus:after,
.modaal-close:hover:before,
.modaal-close:hover:after {
background: #b93d0c;
}
.modaal-close span {
position: absolute !important;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
clip: rect(1px, 1px, 1px, 1px);
padding: 0 !important;
border: 0 !important;
height: 1px !important;
width: 1px !important;
overflow: hidden;
}
.modaal-close:before,
.modaal-close:after {
display: block;
content: " ";
position: absolute;
top: 14px;
left: 23px;
width: 4px;
height: 22px;
border-radius: 4px;
background: #fff;
-webkit-transition: background 0.2s ease-in-out;
transition: background 0.2s ease-in-out;
}
.modaal-close:before {
-webkit-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.modaal-close:after {
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.modaal-fullscreen .modaal-close {
background: #afb7bc;
right: 10px;
top: 10px;
}
.modaal-content-container {
padding: 30px;
}
.modaal-confirm-wrap {
padding: 30px 0 0;
text-align: center;
font-size: 0;
}
.modaal-confirm-btn {
font-size: 14px;
display: inline-block;
margin: 0 10px;
vertical-align: middle;
cursor: pointer;
border: none;
background: transparent;
}
.modaal-confirm-btn.modaal-ok {
padding: 10px 15px;
color: #fff;
background: #555;
border-radius: 3px;
-webkit-transition: background 0.2s ease-in-out;
transition: background 0.2s ease-in-out;
}
.modaal-confirm-btn.modaal-ok:hover {
background: #2f2f2f;
}
.modaal-confirm-btn.modaal-cancel {
text-decoration: underline;
}
.modaal-confirm-btn.modaal-cancel:hover {
text-decoration: none;
color: #2f2f2f;
}
.modaal-instagram .modaal-container {
width: auto;
background: transparent;
box-shadow: none !important;
}
.modaal-instagram .modaal-content-container {
padding: 0;
background: transparent;
}
.modaal-instagram .modaal-content-container > blockquote {
width: 1px !important;
height: 1px !important;
opacity: 0 !important;
}
.modaal-instagram iframe {
opacity: 0;
margin: -6px !important;
border-radius: 0 !important;
width: 1000px !important;
max-width: 800px !important;
box-shadow: none !important;
-webkit-animation: instaReveal 1s linear forwards;
animation: instaReveal 1s linear forwards;
}
.modaal-image .modaal-inner-wrapper {
padding-left: 140px;
padding-right: 140px;
}
.modaal-image .modaal-container {
width: auto;
max-width: 100%;
}
.modaal-gallery-wrap {
position: relative;
color: #fff;
}
.modaal-gallery-item {
display: none;
}
.modaal-gallery-item img {
display: block;
}
.modaal-gallery-item.is_active {
display: block;
}
.modaal-gallery-label {
position: absolute;
left: 0;
width: 100%;
margin: 20px 0 0;
font-size: 18px;
text-align: center;
color: #fff;
}
.modaal-gallery-label:focus {
outline: none;
}
.modaal-gallery-control {
position: absolute;
top: 50%;
-webkit-transform: translateY(-50%);
-ms-transform: translateY(-50%);
transform: translateY(-50%);
opacity: 1;
cursor: pointer;
color: #fff;
width: 50px;
height: 50px;
background: rgba(0, 0, 0, 0);
border: none;
border-radius: 100%;
-webkit-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
}
.modaal-gallery-control.is_hidden {
opacity: 0;
cursor: default;
}
.modaal-gallery-control:focus,
.modaal-gallery-control:hover {
outline: none;
background: #fff;
}
.modaal-gallery-control:focus:before,
.modaal-gallery-control:focus:after,
.modaal-gallery-control:hover:before,
.modaal-gallery-control:hover:after {
background: #afb7bc;
}
.modaal-gallery-control span {
position: absolute !important;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
clip: rect(1px, 1px, 1px, 1px);
padding: 0 !important;
border: 0 !important;
height: 1px !important;
width: 1px !important;
overflow: hidden;
}
.modaal-gallery-control:before,
.modaal-gallery-control:after {
display: block;
content: " ";
position: absolute;
top: 16px;
left: 25px;
width: 4px;
height: 18px;
border-radius: 4px;
background: #fff;
-webkit-transition: background 0.2s ease-in-out;
transition: background 0.2s ease-in-out;
}
.modaal-gallery-control:before {
margin: -5px 0 0;
-webkit-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.modaal-gallery-control:after {
margin: 5px 0 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.modaal-gallery-next-inner {
left: 100%;
margin-left: 40px;
}
.modaal-gallery-next-outer {
right: 45px;
}
.modaal-gallery-prev:before,
.modaal-gallery-prev:after {
left: 22px;
}
.modaal-gallery-prev:before {
margin: 5px 0 0;
-webkit-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.modaal-gallery-prev:after {
margin: -5px 0 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.modaal-gallery-prev-inner {
right: 100%;
margin-right: 40px;
}
.modaal-gallery-prev-outer {
left: 45px;
}
.modaal-video-wrap {
margin: auto 50px;
position: relative;
}
.modaal-video-container {
position: relative;
padding-bottom: 56.25%;
height: 0;
overflow: hidden;
max-width: 100%;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
background: #000;
max-width: 1300px;
margin-left: auto;
margin-right: auto;
}
.modaal-video-container iframe,
.modaal-video-container object,
.modaal-video-container embed {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.modaal-iframe .modaal-content {
width: 100%;
height: 100%;
}
.modaal-iframe-elem {
width: 100%;
height: 100%;
display: block;
}
.modaal-loading-spinner {
background: none;
position: absolute;
width: 200px;
height: 200px;
top: 50%;
left: 50%;
margin: -100px 0 0 -100px;
-webkit-transform: scale(0.25);
-ms-transform: scale(0.25);
transform: scale(0.25);
}
.modaal-loading-spinner > div {
width: 24px;
height: 24px;
margin-left: 4px;
margin-top: 4px;
position: absolute;
}
.modaal-loading-spinner > div > div {
width: 100%;
height: 100%;
border-radius: 15px;
background: #fff;
}
.modaal-loading-spinner > div:nth-of-type(1) > div {
-webkit-animation: modaal-loading-spinner 1s linear infinite;
animation: modaal-loading-spinner 1s linear infinite;
-webkit-animation-delay: 0s;
animation-delay: 0s;
}
.modaal-loading-spinner > div:nth-of-type(2) > div,
.modaal-loading-spinner > div:nth-of-type(3) > div {
-ms-animation: modaal-loading-spinner 1s linear infinite;
-moz-animation: modaal-loading-spinner 1s linear infinite;
-webkit-animation: modaal-loading-spinner 1s linear infinite;
-o-animation: modaal-loading-spinner 1s linear infinite;
}
.modaal-loading-spinner > div:nth-of-type(1) {
-ms-transform: translate(84px, 84px) rotate(45deg) translate(70px, 0);
-webkit-transform: translate(84px, 84px) rotate(45deg) translate(70px, 0);
transform: translate(84px, 84px) rotate(45deg) translate(70px, 0);
}
.modaal-loading-spinner > div:nth-of-type(2) > div {
-webkit-animation: modaal-loading-spinner 1s linear infinite;
animation: modaal-loading-spinner 1s linear infinite;
-webkit-animation-delay: .12s;
animation-delay: .12s;
}
.modaal-loading-spinner > div:nth-of-type(2) {
-ms-transform: translate(84px, 84px) rotate(90deg) translate(70px, 0);
-webkit-transform: translate(84px, 84px) rotate(90deg) translate(70px, 0);
transform: translate(84px, 84px) rotate(90deg) translate(70px, 0);
}
.modaal-loading-spinner > div:nth-of-type(3) > div {
-webkit-animation: modaal-loading-spinner 1s linear infinite;
animation: modaal-loading-spinner 1s linear infinite;
-webkit-animation-delay: .25s;
animation-delay: .25s;
}
.modaal-loading-spinner > div:nth-of-type(4) > div,
.modaal-loading-spinner > div:nth-of-type(5) > div {
-ms-animation: modaal-loading-spinner 1s linear infinite;
-moz-animation: modaal-loading-spinner 1s linear infinite;
-webkit-animation: modaal-loading-spinner 1s linear infinite;
-o-animation: modaal-loading-spinner 1s linear infinite;
}
.modaal-loading-spinner > div:nth-of-type(3) {
-ms-transform: translate(84px, 84px) rotate(135deg) translate(70px, 0);
-webkit-transform: translate(84px, 84px) rotate(135deg) translate(70px, 0);
transform: translate(84px, 84px) rotate(135deg) translate(70px, 0);
}
.modaal-loading-spinner > div:nth-of-type(4) > div {
-webkit-animation: modaal-loading-spinner 1s linear infinite;
animation: modaal-loading-spinner 1s linear infinite;
-webkit-animation-delay: .37s;
animation-delay: .37s;
}
.modaal-loading-spinner > div:nth-of-type(4) {
-ms-transform: translate(84px, 84px) rotate(180deg) translate(70px, 0);
-webkit-transform: translate(84px, 84px) rotate(180deg) translate(70px, 0);
transform: translate(84px, 84px) rotate(180deg) translate(70px, 0);
}
.modaal-loading-spinner > div:nth-of-type(5) > div {
-webkit-animation: modaal-loading-spinner 1s linear infinite;
animation: modaal-loading-spinner 1s linear infinite;
-webkit-animation-delay: .5s;
animation-delay: .5s;
}
.modaal-loading-spinner > div:nth-of-type(6) > div,
.modaal-loading-spinner > div:nth-of-type(7) > div {
-ms-animation: modaal-loading-spinner 1s linear infinite;
-moz-animation: modaal-loading-spinner 1s linear infinite;
-webkit-animation: modaal-loading-spinner 1s linear infinite;
-o-animation: modaal-loading-spinner 1s linear infinite;
}
.modaal-loading-spinner > div:nth-of-type(5) {
-ms-transform: translate(84px, 84px) rotate(225deg) translate(70px, 0);
-webkit-transform: translate(84px, 84px) rotate(225deg) translate(70px, 0);
transform: translate(84px, 84px) rotate(225deg) translate(70px, 0);
}
.modaal-loading-spinner > div:nth-of-type(6) > div {
-webkit-animation: modaal-loading-spinner 1s linear infinite;
animation: modaal-loading-spinner 1s linear infinite;
-webkit-animation-delay: .62s;
animation-delay: .62s;
}
.modaal-loading-spinner > div:nth-of-type(6) {
-ms-transform: translate(84px, 84px) rotate(270deg) translate(70px, 0);
-webkit-transform: translate(84px, 84px) rotate(270deg) translate(70px, 0);
transform: translate(84px, 84px) rotate(270deg) translate(70px, 0);
}
.modaal-loading-spinner > div:nth-of-type(7) > div {
-webkit-animation: modaal-loading-spinner 1s linear infinite;
animation: modaal-loading-spinner 1s linear infinite;
-webkit-animation-delay: .75s;
animation-delay: .75s;
}
.modaal-loading-spinner > div:nth-of-type(7) {
-ms-transform: translate(84px, 84px) rotate(315deg) translate(70px, 0);
-webkit-transform: translate(84px, 84px) rotate(315deg) translate(70px, 0);
transform: translate(84px, 84px) rotate(315deg) translate(70px, 0);
}
.modaal-loading-spinner > div:nth-of-type(8) > div {
-webkit-animation: modaal-loading-spinner 1s linear infinite;
animation: modaal-loading-spinner 1s linear infinite;
-webkit-animation-delay: .87s;
animation-delay: .87s;
}
.modaal-loading-spinner > div:nth-of-type(8) {
-ms-transform: translate(84px, 84px) rotate(360deg) translate(70px, 0);
-webkit-transform: translate(84px, 84px) rotate(360deg) translate(70px, 0);
transform: translate(84px, 84px) rotate(360deg) translate(70px, 0);
}
@media only screen and (min-width: 1400px) {
.modaal-video-container {
padding-bottom: 0;
height: 731px;
}
}
@media only screen and (max-width: 1140px) {
.modaal-image .modaal-inner-wrapper {
padding-left: 25px;
padding-right: 25px;
}
.modaal-gallery-control {
top: auto;
bottom: 20px;
-webkit-transform: none;
-ms-transform: none;
transform: none;
background: rgba(0, 0, 0, 0.7);
}
.modaal-gallery-control:before,
.modaal-gallery-control:after {
background: #fff;
}
.modaal-gallery-next {
left: auto;
right: 20px;
}
.modaal-gallery-prev {
left: 20px;
right: auto;
}
}
@media screen and (max-width: 900px) {
.modaal-instagram iframe {
width: 500px !important;
}
}
@media only screen and (max-width: 600px) {
.modaal-instagram iframe {
width: 280px !important;
}
}
@media screen and (max-height: 1100px) {
.modaal-instagram iframe {
width: 700px !important;
}
}
@media screen and (max-height: 1000px) {
.modaal-inner-wrapper {
padding-top: 60px;
padding-bottom: 60px;
}
.modaal-instagram iframe {
width: 600px !important;
}
}
@media screen and (max-height: 900px) {
.modaal-instagram iframe {
width: 500px !important;
}
.modaal-video-container {
max-width: 900px;
max-height: 510px;
}
}
@media only screen and (max-height: 820px) {
.modaal-gallery-label {
display: none;
}
}
@keyframes instaReveal {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-webkit-keyframes instaReveal {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-webkit-keyframes modaal-loading-spinner {
0% {
opacity: 1;
-ms-transform: scale(1.5);
-webkit-transform: scale(1.5);
transform: scale(1.5);
}
100% {
opacity: .1;
-ms-transform: scale(1);
-webkit-transform: scale(1);
transform: scale(1);
}
}
@keyframes modaal-loading-spinner {
0% {
opacity: 1;
-ms-transform: scale(1.5);
-webkit-transform: scale(1.5);
transform: scale(1.5);
}
100% {
opacity: .1;
-ms-transform: scale(1);
-webkit-transform: scale(1);
transform: scale(1);
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More