Commit inicial - WordPress Análisis de Precios Unitarios

- WordPress core y plugins
- Tema Twenty Twenty-Four configurado
- Plugin allow-unfiltered-html.php simplificado
- .gitignore configurado para excluir wp-config.php y uploads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-03 21:04:30 -06:00
commit a22573bf0b
24068 changed files with 4993111 additions and 0 deletions

View File

@@ -0,0 +1,676 @@
<?php
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Allows plugins to use their own update API.
*
* @author Easy Digital Downloads
* @version 1.9.2
*/
class Perfmatters_Plugin_Updater {
private $api_url = '';
private $api_data = array();
private $plugin_file = '';
private $name = '';
private $slug = '';
private $version = '';
private $wp_override = false;
private $beta = false;
private $failed_request_cache_key;
/**
* Class constructor.
*
* @uses plugin_basename()
* @uses hook()
*
* @param string $_api_url The URL pointing to the custom API endpoint.
* @param string $_plugin_file Path to the plugin file.
* @param array $_api_data Optional data to send with API calls.
*/
public function __construct( $_api_url, $_plugin_file, $_api_data = null ) {
global $edd_plugin_data;
$this->api_url = trailingslashit( $_api_url );
$this->api_data = $_api_data;
$this->plugin_file = $_plugin_file;
$this->name = plugin_basename( $_plugin_file );
$this->slug = basename( $_plugin_file, '.php' );
$this->version = $_api_data['version'];
$this->wp_override = isset( $_api_data['wp_override'] ) ? (bool) $_api_data['wp_override'] : false;
$this->beta = ! empty( $this->api_data['beta'] ) ? true : false;
$this->failed_request_cache_key = 'edd_sl_failed_http_' . md5( $this->api_url );
$edd_plugin_data[ $this->slug ] = $this->api_data;
/**
* Fires after the $edd_plugin_data is setup.
*
* @since x.x.x
*
* @param array $edd_plugin_data Array of EDD SL plugin data.
*/
do_action( 'post_edd_sl_plugin_updater_setup', $edd_plugin_data );
// Set up hooks.
$this->init();
}
/**
* Set up WordPress filters to hook into WP's update process.
*
* @uses add_filter()
*
* @return void
*/
public function init() {
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
add_action( 'after_plugin_row', array( $this, 'show_update_notification' ), 10, 2 );
add_action( 'admin_init', array( $this, 'show_changelog' ) );
}
/**
* Check for Updates at the defined API endpoint and modify the update array.
*
* This function dives into the update API just when WordPress creates its update array,
* then adds a custom API call and injects the custom plugin data retrieved from the API.
* It is reassembled from parts of the native WordPress plugin update code.
* See wp-includes/update.php line 121 for the original wp_update_plugins() function.
*
* @uses api_request()
*
* @param array $_transient_data Update array build by WordPress.
* @return array Modified update array with custom plugin data.
*/
public function check_update( $_transient_data ) {
global $pagenow;
if ( ! is_object( $_transient_data ) ) {
$_transient_data = new stdClass();
}
if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) {
return $_transient_data;
}
$current = $this->get_repo_api_data();
if ( false !== $current && is_object( $current ) && isset( $current->new_version ) ) {
if ( version_compare( $this->version, $current->new_version, '<' ) ) {
$_transient_data->response[ $this->name ] = $current;
} else {
// Populating the no_update information is required to support auto-updates in WordPress 5.5.
$_transient_data->no_update[ $this->name ] = $current;
}
}
$_transient_data->last_checked = time();
$_transient_data->checked[ $this->name ] = $this->version;
return $_transient_data;
}
/**
* Get repo API data from store.
* Save to cache.
*
* @return \stdClass
*/
public function get_repo_api_data() {
$version_info = $this->get_cached_version_info();
if ( false === $version_info ) {
$version_info = $this->api_request(
'plugin_latest_version',
array(
'slug' => $this->slug,
'beta' => $this->beta,
)
);
if ( ! $version_info ) {
return false;
}
// This is required for your plugin to support auto-updates in WordPress 5.5.
$version_info->plugin = $this->name;
$version_info->id = $this->name;
$version_info->tested = $this->get_tested_version( $version_info );
$this->set_version_info_cache( $version_info );
}
return $version_info;
}
/**
* Gets the plugin's tested version.
*
* @since 1.9.2
* @param object $version_info
* @return null|string
*/
private function get_tested_version( $version_info ) {
// There is no tested version.
if ( empty( $version_info->tested ) ) {
return null;
}
// Strip off extra version data so the result is x.y or x.y.z.
list( $current_wp_version ) = explode( '-', get_bloginfo( 'version' ) );
// The tested version is greater than or equal to the current WP version, no need to do anything.
if ( version_compare( $version_info->tested, $current_wp_version, '>=' ) ) {
return $version_info->tested;
}
$current_version_parts = explode( '.', $current_wp_version );
$tested_parts = explode( '.', $version_info->tested );
// The current WordPress version is x.y.z, so update the tested version to match it.
if ( isset( $current_version_parts[2] ) && $current_version_parts[0] === $tested_parts[0] && $current_version_parts[1] === $tested_parts[1] ) {
$tested_parts[2] = $current_version_parts[2];
}
return implode( '.', $tested_parts );
}
/**
* Show the update notification on multisite subsites.
*
* @param string $file
* @param array $plugin
*/
public function show_update_notification( $file, $plugin ) {
// Return early if in the network admin, or if this is not a multisite install.
if ( is_network_admin() || ! is_multisite() ) {
return;
}
// Allow single site admins to see that an update is available.
if ( ! current_user_can( 'activate_plugins' ) ) {
return;
}
if ( $this->name !== $file ) {
return;
}
// Do not print any message if update does not exist.
$update_cache = get_site_transient( 'update_plugins' );
if ( ! isset( $update_cache->response[ $this->name ] ) ) {
if ( ! is_object( $update_cache ) ) {
$update_cache = new stdClass();
}
$update_cache->response[ $this->name ] = $this->get_repo_api_data();
}
// Return early if this plugin isn't in the transient->response or if the site is running the current or newer version of the plugin.
if ( empty( $update_cache->response[ $this->name ] ) || version_compare( $this->version, $update_cache->response[ $this->name ]->new_version, '>=' ) ) {
return;
}
printf(
'<tr class="plugin-update-tr %3$s" id="%1$s-update" data-slug="%1$s" data-plugin="%2$s">',
$this->slug,
$file,
in_array( $this->name, $this->get_active_plugins(), true ) ? 'active' : 'inactive'
);
echo '<td colspan="3" class="plugin-update colspanchange">';
echo '<div class="update-message notice inline notice-warning notice-alt"><p>';
$changelog_link = '';
if ( ! empty( $update_cache->response[ $this->name ]->sections->changelog ) ) {
$changelog_link = add_query_arg(
array(
'edd_sl_action' => 'view_plugin_changelog',
'plugin' => urlencode( $this->name ),
'slug' => urlencode( $this->slug ),
'TB_iframe' => 'true',
'width' => 77,
'height' => 911,
),
self_admin_url( 'index.php' )
);
}
$update_link = add_query_arg(
array(
'action' => 'upgrade-plugin',
'plugin' => urlencode( $this->name ),
),
self_admin_url( 'update.php' )
);
printf(
/* translators: the plugin name. */
esc_html__( 'There is a new version of %1$s available.', 'perfmatters' ),
esc_html( $plugin['Name'] )
);
if ( ! current_user_can( 'update_plugins' ) ) {
echo ' ';
esc_html_e( 'Contact your network administrator to install the update.', 'perfmatters' );
} elseif ( empty( $update_cache->response[ $this->name ]->package ) && ! empty( $changelog_link ) ) {
echo ' ';
printf(
/* translators: 1. opening anchor tag, do not translate 2. the new plugin version 3. closing anchor tag, do not translate. */
__( '%1$sView version %2$s details%3$s.', 'perfmatters' ),
'<a target="_blank" class="thickbox open-plugin-details-modal" href="' . esc_url( $changelog_link ) . '">',
esc_html( $update_cache->response[ $this->name ]->new_version ),
'</a>'
);
} elseif ( ! empty( $changelog_link ) ) {
echo ' ';
printf(
__( '%1$sView version %2$s details%3$s or %4$supdate now%5$s.', 'perfmatters' ),
'<a target="_blank" class="thickbox open-plugin-details-modal" href="' . esc_url( $changelog_link ) . '">',
esc_html( $update_cache->response[ $this->name ]->new_version ),
'</a>',
'<a target="_blank" class="update-link" href="' . esc_url( wp_nonce_url( $update_link, 'upgrade-plugin_' . $file ) ) . '">',
'</a>'
);
} else {
printf(
' %1$s%2$s%3$s',
'<a target="_blank" class="update-link" href="' . esc_url( wp_nonce_url( $update_link, 'upgrade-plugin_' . $file ) ) . '">',
esc_html__( 'Update now.', 'perfmatters' ),
'</a>'
);
}
do_action( "in_plugin_update_message-{$file}", $plugin, $plugin );
echo '</p></div></td></tr>';
}
/**
* Gets the plugins active in a multisite network.
*
* @return array
*/
private function get_active_plugins() {
$active_plugins = (array) get_option( 'active_plugins' );
$active_network_plugins = (array) get_site_option( 'active_sitewide_plugins' );
return array_merge( $active_plugins, array_keys( $active_network_plugins ) );
}
/**
* Updates information on the "View version x.x details" page with custom data.
*
* @uses api_request()
*
* @param mixed $_data
* @param string $_action
* @param object $_args
* @return object $_data
*/
public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
if ( 'plugin_information' !== $_action ) {
return $_data;
}
if ( ! isset( $_args->slug ) || ( $_args->slug !== $this->slug ) ) {
return $_data;
}
$to_send = array(
'slug' => $this->slug,
'is_ssl' => is_ssl(),
'fields' => array(
'banners' => array(),
'reviews' => false,
'icons' => array(),
),
);
// Get the transient where we store the api request for this plugin for 24 hours
$edd_api_request_transient = $this->get_cached_version_info();
//If we have no transient-saved value, run the API, set a fresh transient with the API value, and return that value too right now.
if ( empty( $edd_api_request_transient ) ) {
$api_response = $this->api_request( 'plugin_information', $to_send );
// Expires in 3 hours
$this->set_version_info_cache( $api_response );
if ( false !== $api_response ) {
$_data = $api_response;
}
} else {
$_data = $edd_api_request_transient;
}
// Convert sections into an associative array, since we're getting an object, but Core expects an array.
if ( isset( $_data->sections ) && ! is_array( $_data->sections ) ) {
$_data->sections = $this->convert_object_to_array( $_data->sections );
}
// Convert banners into an associative array, since we're getting an object, but Core expects an array.
if ( isset( $_data->banners ) && ! is_array( $_data->banners ) ) {
$_data->banners = $this->convert_object_to_array( $_data->banners );
}
// Convert icons into an associative array, since we're getting an object, but Core expects an array.
if ( isset( $_data->icons ) && ! is_array( $_data->icons ) ) {
$_data->icons = $this->convert_object_to_array( $_data->icons );
}
// Convert contributors into an associative array, since we're getting an object, but Core expects an array.
if ( isset( $_data->contributors ) && ! is_array( $_data->contributors ) ) {
$_data->contributors = $this->convert_object_to_array( $_data->contributors );
}
if ( ! isset( $_data->plugin ) ) {
$_data->plugin = $this->name;
}
return $_data;
}
/**
* Convert some objects to arrays when injecting data into the update API
*
* Some data like sections, banners, and icons are expected to be an associative array, however due to the JSON
* decoding, they are objects. This method allows us to pass in the object and return an associative array.
*
* @since 3.6.5
*
* @param stdClass $data
*
* @return array
*/
private function convert_object_to_array( $data ) {
if ( ! is_array( $data ) && ! is_object( $data ) ) {
return array();
}
$new_data = array();
foreach ( $data as $key => $value ) {
$new_data[ $key ] = is_object( $value ) ? $this->convert_object_to_array( $value ) : $value;
}
return $new_data;
}
/**
* Disable SSL verification in order to prevent download update failures
*
* @param array $args
* @param string $url
* @return object $array
*/
public function http_request_args( $args, $url ) {
if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
$args['sslverify'] = $this->verify_ssl();
}
return $args;
}
/**
* Calls the API and, if successfull, returns the object delivered by the API.
*
* @uses get_bloginfo()
* @uses wp_remote_post()
* @uses is_wp_error()
*
* @param string $_action The requested action.
* @param array $_data Parameters for the API action.
* @return false|object|void
*/
private function api_request( $_action, $_data ) {
$data = array_merge( $this->api_data, $_data );
if ( $data['slug'] !== $this->slug ) {
return;
}
// Don't allow a plugin to ping itself
if ( trailingslashit( home_url() ) === $this->api_url ) {
return false;
}
if ( $this->request_recently_failed() ) {
return false;
}
return $this->get_version_from_remote();
}
/**
* Determines if a request has recently failed.
*
* @since 1.9.1
*
* @return bool
*/
private function request_recently_failed() {
$failed_request_details = get_option( $this->failed_request_cache_key );
// Request has never failed.
if ( empty( $failed_request_details ) || ! is_numeric( $failed_request_details ) ) {
return false;
}
/*
* Request previously failed, but the timeout has expired.
* This means we're allowed to try again.
*/
if ( time() > $failed_request_details ) {
delete_option( $this->failed_request_cache_key );
return false;
}
return true;
}
/**
* Logs a failed HTTP request for this API URL.
* We set a timestamp for 1 hour from now. This prevents future API requests from being
* made to this domain for 1 hour. Once the timestamp is in the past, API requests
* will be allowed again. This way if the site is down for some reason we don't bombard
* it with failed API requests.
*
* @see EDD_SL_Plugin_Updater::request_recently_failed
*
* @since 1.9.1
*/
private function log_failed_request() {
update_option( $this->failed_request_cache_key, strtotime( '+1 hour' ) );
}
/**
* If available, show the changelog for sites in a multisite install.
*/
public function show_changelog() {
if ( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' !== $_REQUEST['edd_sl_action'] ) {
return;
}
if ( empty( $_REQUEST['plugin'] ) ) {
return;
}
if ( empty( $_REQUEST['slug'] ) || $this->slug !== $_REQUEST['slug'] ) {
return;
}
if ( ! current_user_can( 'update_plugins' ) ) {
wp_die( esc_html__( 'You do not have permission to install plugin updates', 'perfmatters' ), esc_html__( 'Error', 'perfmatters' ), array( 'response' => 403 ) );
}
$version_info = $this->get_repo_api_data();
if ( isset( $version_info->sections ) ) {
$sections = $this->convert_object_to_array( $version_info->sections );
if ( ! empty( $sections['changelog'] ) ) {
echo '<div style="background:#fff;padding:10px;">' . wp_kses_post( $sections['changelog'] ) . '</div>';
}
}
exit;
}
/**
* Gets the current version information from the remote site.
*
* @return array|false
*/
private function get_version_from_remote() {
$api_params = array(
'edd_action' => 'get_version',
'license' => ! empty( $this->api_data['license'] ) ? $this->api_data['license'] : '',
'item_name' => isset( $this->api_data['item_name'] ) ? $this->api_data['item_name'] : false,
'item_id' => isset( $this->api_data['item_id'] ) ? $this->api_data['item_id'] : false,
'version' => isset( $this->api_data['version'] ) ? $this->api_data['version'] : false,
'slug' => $this->slug,
'author' => $this->api_data['author'],
'url' => home_url(),
'beta' => $this->beta,
'php_version' => phpversion(),
'wp_version' => get_bloginfo( 'version' ),
);
/**
* Filters the parameters sent in the API request.
*
* @param array $api_params The array of data sent in the request.
* @param array $this->api_data The array of data set up in the class constructor.
* @param string $this->plugin_file The full path and filename of the file.
*/
$api_params = apply_filters( 'edd_sl_plugin_updater_api_params', $api_params, $this->api_data, $this->plugin_file );
$request = wp_remote_post(
$this->api_url,
array(
'timeout' => 15,
'sslverify' => $this->verify_ssl(),
'body' => $api_params,
)
);
if ( is_wp_error( $request ) || ( 200 !== wp_remote_retrieve_response_code( $request ) ) ) {
$this->log_failed_request();
return false;
}
$request = json_decode( wp_remote_retrieve_body( $request ) );
if ( $request && isset( $request->sections ) ) {
$request->sections = maybe_unserialize( $request->sections );
} else {
$request = false;
}
if ( $request && isset( $request->banners ) ) {
$request->banners = maybe_unserialize( $request->banners );
}
if ( $request && isset( $request->icons ) ) {
$request->icons = maybe_unserialize( $request->icons );
}
if ( ! empty( $request->sections ) ) {
foreach ( $request->sections as $key => $section ) {
$request->$key = (array) $section;
}
}
return $request;
}
/**
* Get the version info from the cache, if it exists.
*
* @param string $cache_key
* @return object
*/
public function get_cached_version_info( $cache_key = '' ) {
if ( empty( $cache_key ) ) {
$cache_key = $this->get_cache_key();
}
$cache = get_option( $cache_key );
// Cache is expired
if ( empty( $cache['timeout'] ) || time() > $cache['timeout'] ) {
return false;
}
// We need to turn the icons into an array, thanks to WP Core forcing these into an object at some point.
$cache['value'] = json_decode( $cache['value'] );
if ( ! empty( $cache['value']->icons ) ) {
$cache['value']->icons = (array) $cache['value']->icons;
}
return $cache['value'];
}
/**
* Adds the plugin version information to the database.
*
* @param string $value
* @param string $cache_key
*/
public function set_version_info_cache( $value = '', $cache_key = '' ) {
if ( empty( $cache_key ) ) {
$cache_key = $this->get_cache_key();
}
$data = array(
'timeout' => strtotime( '+3 hours', time() ),
'value' => wp_json_encode( $value ),
);
update_option( $cache_key, $data, 'no' );
// Delete the duplicate option
delete_option( 'edd_api_request_' . md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) ) );
}
/**
* Returns if the SSL of the store should be verified.
*
* @since 1.6.13
* @return bool
*/
private function verify_ssl() {
return (bool) apply_filters( 'edd_sl_api_request_verify_ssl', true, $this );
}
/**
* Gets the unique key (option name) for a plugin.
*
* @since 1.9.0
* @return string
*/
private function get_cache_key() {
$string = $this->slug . $this->api_data['license'] . $this->beta;
return 'edd_sl_' . md5( serialize( $string ) );
}
}

View File

@@ -0,0 +1,11 @@
{
"autoload": {
"psr-4": {
"Perfmatters\\": "inc/classes"
}
},
"require": {
"sabberworm/php-css-parser": "^8.4",
"deliciousbrains/wp-background-processing": "^1.0"
}
}

122
wp-content/plugins/perfmatters/composer.lock generated Executable file
View File

@@ -0,0 +1,122 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "dd61f4f0f2b9a44a1c5d29a3a78fa7f0",
"packages": [
{
"name": "deliciousbrains/wp-background-processing",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/deliciousbrains/wp-background-processing.git",
"reference": "d5ef95cecba7f792ddca3e3bd70ebfb90dc4996d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/deliciousbrains/wp-background-processing/zipball/d5ef95cecba7f792ddca3e3bd70ebfb90dc4996d",
"reference": "d5ef95cecba7f792ddca3e3bd70ebfb90dc4996d",
"shasum": ""
},
"require": {
"php": ">=5.6"
},
"require-dev": {
"phpcompatibility/phpcompatibility-wp": "*",
"phpunit/phpunit": "^8.0",
"spryker/code-sniffer": "^0.17.18",
"wp-coding-standards/wpcs": "^2.3",
"yoast/phpunit-polyfills": "^1.0"
},
"suggest": {
"coenjacobs/mozart": "Easily wrap this library with your own prefix, to prevent collisions when multiple plugins use this library"
},
"type": "library",
"autoload": {
"classmap": [
"classes/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-2.0-or-later"
],
"authors": [
{
"name": "Delicious Brains",
"email": "nom@deliciousbrains.com"
}
],
"description": "WP Background Processing can be used to fire off non-blocking asynchronous requests or as a background processing tool, allowing you to queue tasks.",
"support": {
"issues": "https://github.com/deliciousbrains/wp-background-processing/issues",
"source": "https://github.com/deliciousbrains/wp-background-processing/tree/1.1.0"
},
"time": "2023-04-18T12:32:25+00:00"
},
{
"name": "sabberworm/php-css-parser",
"version": "8.4.0",
"source": {
"type": "git",
"url": "https://github.com/sabberworm/PHP-CSS-Parser.git",
"reference": "e41d2140031d533348b2192a83f02d8dd8a71d30"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/e41d2140031d533348b2192a83f02d8dd8a71d30",
"reference": "e41d2140031d533348b2192a83f02d8dd8a71d30",
"shasum": ""
},
"require": {
"ext-iconv": "*",
"php": ">=5.6.20"
},
"require-dev": {
"codacy/coverage": "^1.4",
"phpunit/phpunit": "^4.8.36"
},
"suggest": {
"ext-mbstring": "for parsing UTF-8 CSS"
},
"type": "library",
"autoload": {
"psr-4": {
"Sabberworm\\CSS\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Raphael Schweikert"
}
],
"description": "Parser for CSS Files written in PHP",
"homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser",
"keywords": [
"css",
"parser",
"stylesheet"
],
"support": {
"issues": "https://github.com/sabberworm/PHP-CSS-Parser/issues",
"source": "https://github.com/sabberworm/PHP-CSS-Parser/tree/8.4.0"
},
"time": "2021-12-11T13:40:54+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.3.0"
}

View File

@@ -0,0 +1,537 @@
/* wp ui */
.settings_page_perfmatters #wpbody-content {
margin-top: 5px;
}
.settings_page_perfmatters #wpbody-content>.notice {
margin: 15px auto 15px 2px;
max-width: 1200px;
box-sizing: border-box;
}
.settings_page_perfmatters #wpbody-content>.update-nag {
margin-bottom: 0px;
}
#perfmatters-admin-container .notice, #setting-error-perfmatters-hidden-notice {
display: none;
}
#perfmatters-admin-container a {
color: #4A89DD;
}
/* admin wrappers */
#perfmatters-admin {
max-width: 1200px;
margin-top: 15px;
}
#perfmatters-admin-container {
display: flex;
gap: 20px;
}
#perfmatters-admin .perfmatters-admin-block {
position: relative;
background: #fff;
border: 1px solid #e2e4e7;
border-radius: 5px;
padding: 20px;
box-sizing: border-box;
}
/* header */
#perfmatters-admin-header {
margin-bottom: 10px;
width: 227px;
}
#perfmatters-logo-bar {
display: flex;
align-items: center;
}
#perfmatters-logo {
display: flex;
height: 30px;
margin: 10px auto;
overflow: visible;
}
#perfmatters-menu-toggle {
display: none;
text-decoration: none;
}
#perfmatters-menu-toggle .dashicons {
width: 30px;
height: 30px;
font-size: 30px;
}
#perfmatters-menu {
margin-top: 15px;
}
#perfmatters-menu a {
display: flex;
align-items: center;
font-size: 16px;
font-weight: 600;
color: #333;
padding: 10px;
text-decoration: none;
box-shadow: none;
border: 1px solid transparent;
border-radius: 5px;
transition: none;
margin-top: 2px;
}
#perfmatters-menu a:hover {
color: #4A89DD;
}
#perfmatters-menu a.active {
color: #4A89DD;
border-color: #4A89DD;
background: #e8f1ff;
}
#perfmatters-menu a .dashicons {
margin-right: 8px;
font-size: 23px;
width: 23px;
height: 23px;
}
#perfmatters-menu .perfmatters-subnav {
border-top: 1px solid #f2f2f2;
border-bottom: 1px solid #f2f2f2;
padding: 9px 0px;
margin-bottom: 10px;
}
#perfmatters-menu .perfmatters-subnav a {
font-size: 14px;
padding: 8px 10px;
margin: 1px 0px;
font-weight: 500;
}
#perfmatters-menu .perfmatters-subnav a .dashicons {
font-size: 16px;
height: 16px;
}
/* general ui */
.perfmatters-settings-section {
margin-bottom: 20px;
border-bottom: 1px solid #f2f2f2;
padding-bottom: 20px;
}
#perfmatters-admin .section-content {
display: none;
}
#perfmatters-admin .section-content.active {
display: block;
}
#perfmatters-admin h2 {
font-size: 18px;
font-weight: 500;
line-height: normal;
margin: 0px auto 10px auto;
}
#perfmatters-admin .perfmatters-settings-section:first-of-type h2:first-of-type {
display: flex;
align-items: center;
font-size: 22px;
margin: -20px -20px 20px -20px;
padding: 20px;
font-weight: 500;
background: #282E34;
border-radius: 5px 5px 0px 0px;
color: #fff;
}
#perfmatters-admin .perfmatters-settings-section:first-of-type h2:first-of-type .dashicons {
font-size:23px;
width: 23px;
height: 23px;
margin-right: 8px;
color: #fff;
}
#perfmatters-admin .perfmatters-settings-section:last-of-type {
border-bottom: none;
padding-bottom: 0px;
margin-bottom: 0px;
}
#perfmatters-admin .form-table {
margin: 0px;
}
#perfmatters-admin .form-table th {
padding: 15px 10px 15px 0px;
font-size: 13px;
font-weight: normal;
min-width: 200px;
}
#perfmatters-admin .form-table td {
position: relative;
padding: 10px;
}
#perfmatters-admin .perfmatters-title-wrapper {
display: flex;
justify-content: space-between;
}
#perfmatters-admin .form-table th label {
vertical-align: top;
padding-right: 5px;
}
.perfmatters-beta {
background: #ED5464;
color: #ffffff;
padding: 5px;
vertical-align: middle;
font-size: 10px;
margin-left: 3px;
}
/* inputs */
#perfmatters-admin .perfmatters-switch {
position: relative;
display: inline-block;
width: 48px;
height: 28px;
font-size: 1px;
}
#perfmatters-admin .perfmatters-switch input {
display: block;
margin: 0px;
border: none;
outline: none;
box-shadow: none;
}
#perfmatters-admin .perfmatters-slider {
position: absolute;
inset: 0;
background-color: #ccc;
transition: .4s;
border-radius: 50px;
}
#perfmatters-admin .perfmatters-slider:before {
position: absolute;
content: "";
height: 20px;
width: 20px;
left: 4px;
bottom: 4px;
background-color: #fff;
transition: .4s;
border-radius: 50%;
}
#perfmatters-admin input:checked + .perfmatters-slider {
background-color: #4A89DD;
}
#perfmatters-admin input:focus + .perfmatters-slider {
box-shadow: 0 0 1px #4A89DD;
}
#perfmatters-admin input:checked + .perfmatters-slider:before {
transform: translateX(20px);
}
#perfmatters-admin input[type="text"], #perfmatters-admin select, #perfmatters-admin input[type="file"] {
min-width: 300px;
margin: 0px;
border-color: #ccc;
}
#perfmatters-admin input[type="checkbox"] {
border-color: #ccc;
}
#perfmatters-admin input[type="file"] {
margin-bottom: 5px;
}
#perfmatters-admin textarea {
width: 100%;
resize: both;
min-height: 150px;
border-color: #ccc;
}
#perfmatters-admin .CodeMirror {
max-width: 700px;
max-height: 200px;
height: 100px;
}
#perfmatters-admin .CodeMirror.CodeMirror-focused {
height: 200px;
}
#perfmatters-admin ::placeholder {
color: #ccc;
}
/* tooltips */
#perfmatters-admin .perfmatters-tooltip-wrapper {
position: relative;
display: inline-block;
float: right;
}
#perfmatters-admin .perfmatters-tooltip {
display: inline-block;
flex-shrink: 0;
float: right;
margin-right: -10px;
height: 16px;
width: 16px;
vertical-align: top;
text-align: center;
line-height: 16px;
font-size: 12px;
background: #F0F0F1;
color: #777;
border-radius: 50%;
text-decoration: none;
}
#perfmatters-admin .perfmatters-tooltip-text {
display: none;
position: absolute;
z-index: 10;
top: 46px;
left: -32px;
width: 300px;
background-color: #23282D;
color: #fff;
padding: 10px;
border-radius: 3px;
text-align: left;
font-size: 12px;
line-height: 20px;
font-weight: normal;
}
#perfmatters-admin .perfmatters-tooltip-text-am {
font-size: 12px;
font-style: italic;
margin-top: 5px;
display: block;
}
#perfmatters-admin .perfmatters-tooltip-text::after {
content: " ";
position: absolute;
top: -6px;
left: 19px;
border-width: 0px 4.5px 6px;
border-style: solid;
border-color: transparent transparent #23282D transparent;
}
#perfmatters-admin .perfmatters-tooltip-subtext {
display: block;
margin-top: 5px;
text-align: right;
font-style: italic;
font-size: 9px;
line-height: 9px;
color: rgba(255,255,255,0.9);
}
#perfmatters-admin .perfmatters-tooltip-icon {
display: inline-block;
height: 12px;
width: 12px;
vertical-align: baseline;
text-align: center;
line-height: 12px;
font-size: 10px;
background: rgba(255,255,255,0.2);
color: #bbbbbb;
border-radius: 50%;
}
#perfmatters-admin .perfmatters-tooltip-table {
table-layout: fixed;
width: 100%;
margin-top: 3px;
}
#perfmatters-admin .perfmatters-tooltip-table th, #perfmatters-admin .perfmatters-tooltip-table td {
display: table-cell;
padding: 2px 5px;
padding-bottom: 0px;
color: #ffffff;
width: auto;
font-size: 12px;
}
#perfmatters-admin .perfmatters-tooltip-table td {
text-align: center;
}
/* buttons */
#perfmatters-admin-container .button-primary {
padding: 2px 10px;
border-radius: 5px;
background: #4A89DD;
border-color: #4A89DD;
}
#perfmatters-admin-container .button-secondary {
border-color: #4A89DD;
color: #4A89DD;
}
#perfmatters-admin-container p.submit {
margin: 20px 0px 0px 0px;
padding: 0px;
}
#perfmatters-admin-container .perfmatters-button-warning {
background: #ED5464;
border-color: #ED5464;
color: #ffffff;
}
#perfmatters-admin-container .perfmatters-button-warning:hover {
background: #c14552;
border-color: #c14552;
}
/* custom input row ui */
.perfmatters-input-row {
position: relative;
margin-bottom: 10px;
display: flex;
flex-wrap: wrap;
align-items: center;
}
.perfmatters-input-row input[type="text"] {
flex-grow: 1;
}
.perfmatters-input-row label {
margin: 0px 0px 0px 5px;
font-size: 12px;
white-space: nowrap;
}
.perfmatters-input-row input[type="checkbox"] {
margin-right: 0px;
}
#perfmatters-admin .perfmatters-input-row select {
min-width: auto;
}
a.perfmatters-delete-input-row {
border-radius: 4px;
margin-left: 8px;
}
a.perfmatters-delete-input-row span {
color: #ED5464;
text-decoration: none;
}
a.perfmatters-add-input-row {
display: inline-flex;
align-items: center;
white-space: nowrap;
text-decoration: none;
margin-top: 10px;
}
a.perfmatters-add-input-row span.dashicons {
line-height: 30px;
margin-right: 2px;
font-size: 16px;
width: 16px;
height: 16px;
}
.perfmatters-inline-label-input {
position: relative;
display: inline-flex;
align-items: center;
}
.perfmatters-inline-label-input span {
position: absolute;
left: 1px;
background: #F0F0F1;
border-right: 1px solid #ccc;
color: #8c8f94;
height: 28px;
padding: 0px 10px;
display: inline-flex;
align-items: center;
border-radius: 4px 0px 0px 4px;
}
.perfmatters-input-row-hero {
display: flex;
width: 100%;
align-items: center;
}
.perfmatters-input-row-extra {
display: none;
margin-top: 5px;
}
.perfmatters-input-row.perfmatters-opened .perfmatters-input-row-extra {
display: block;
}
.perfmatters-expand-input-row .dashicons {
color: #aaa;
}
.perfmatters-input-row.perfmatters-opened .perfmatters-expand-input-row .dashicons {
color: #4A89DD;
}
.perfmatters-input-row .perfmatters-expand-input-row:focus {
outline: none;
box-shadow: none;
}
/* quick exclusions ui */
.perfmatters-quick-exclusion {
padding: 4px 8px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 3px;
}
.perfmatters-quick-exclusion:last-of-type {
margin-bottom: 0px;
}
.perfmatters-quick-exclusion .dashicons {
font-size: 16px;
line-height: 20px;
color: #555;
}
.perfmatters-quick-exclusion-toggle {
cursor: pointer;
}
.perfmatters-quick-exclusion-items {
display: none;
margin-top: 4px;
border-top: 1px solid #f2f2f2;
padding-top: 4px;
}
.perfmatters-quick-exclusion:not(.perfmatters-opened) .dashicons-minus {
display: none;
}
.perfmatters-quick-exclusion.perfmatters-opened .dashicons-plus {
display: none;
}
.perfmatters-quick-exclusion.perfmatters-opened .perfmatters-quick-exclusion-items {
display: block;
}
/* media queries */
@media all and (max-width: 1024px) {
#perfmatters-admin {
margin-top: 10px;
}
#perfmatters-admin-container {
flex-direction: column;
gap: 10px;
}
#perfmatters-admin-header {
width: 100%;
margin: 0px;
}
#perfmatters-logo {
margin: 0px auto;
}
#perfmatters-menu-toggle {
display: flex;
}
#perfmatters-menu {
display: none;
}
#perfmatters-menu.perfmatters-menu-expanded {
display: block;
}
}
@media all and (max-width: 960px) {
.perfmatters-input-row-extra {
width: 100%;
}
.perfmatters-input-row-extra>* {
width: 100%;
max-width: none !important;
margin: 0px 0px 5px 0px !important;
}
}
@media all and (max-width: 782px) {
.perfmatters-mobile-hide {
display: none !important;
}
#perfmatters-admin .form-table td {
padding: 0px;
}
#perfmatters-admin .perfmatters-tooltip-text {
display: none !important;
}
#perfmatters-admin textarea {
min-width: 0px;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><path fill="#ffffff" d="M9.609 15.601V8.408L15.873 12.01 9.609 15.601z" /><path fill="#212121" fill-opacity="0.8" d="M23.495 6.205C23.213 5.193 22.419 4.399 21.407 4.117 19.537 3.616 12.011 3.616 12.011 3.616 12.011 3.616 4.504 3.606 2.615 4.117 1.603 4.399.809 5.193.527 6.205.173 8.12-.002 10.063.005 12.01-.001 13.95.174 15.886.527 17.793.809 18.805 1.603 19.599 2.615 19.881 4.483 20.383 12.011 20.383 12.011 20.383c0 0 7.506.0 9.396-.5019999999999989C22.419 19.599 23.213 18.805 23.495 17.793 23.841 15.885 24.008 13.949 23.995 12.01 24.009 10.064 23.842 8.12 23.495 6.205zM9.609 15.601V8.408L15.873 12.01 9.609 15.601z" /></g></svg>

After

Width:  |  Height:  |  Size: 701 B

View File

@@ -0,0 +1,84 @@
<?php
namespace Perfmatters;
use WP_CLI;
class CLI {
/**
* Activates a license key.
*
* ## OPTIONS
*
* [<key>]
* : The license key to add and activate.
*
* @subcommand activate-license
*
*/
public function activate_license($args, $assoc_args) {
$network = is_multisite() && empty(WP_CLI::get_config()['url']);
if(!empty($args[0])) {
$network ? update_site_option('perfmatters_edd_license_key', trim($args[0])) : update_option('perfmatters_edd_license_key', trim($args[0]));
}
if(is_multisite()) {
$license_info = perfmatters_check_license($network);
if(empty($license_info->activations_left) || $license_info->activations_left !== 'unlimited') {
WP_CLI::warning(__('Unlimited site license required.', 'perfmatters'));
return;
}
}
if(perfmatters_activate_license($network)) {
WP_CLI::success(__('License activated!', 'perfmatters'));
}
else {
WP_CLI::warning(__('License could not be activated.', 'perfmatters'));
}
}
/**
* Deactivates a license key.
*
* @subcommand deactivate-license
*/
public function deactivate_license() {
$network = is_multisite() && empty(WP_CLI::get_config()['url']);
if(perfmatters_deactivate_license($network)) {
WP_CLI::success(__('License deactivated!', 'perfmatters'));
}
else {
WP_CLI::warning(__('License could not be deactivated.', 'perfmatters'));
}
}
/**
* Deactivates and removes a license key.
*
* @subcommand remove-license
*/
public function remove_license() {
$network = is_multisite() && empty(WP_CLI::get_config()['url']);
if(perfmatters_deactivate_license($network)) {
WP_CLI::success('License deactivated!');
}
$removed = $network ? delete_site_option('perfmatters_edd_license_key') : delete_option('perfmatters_edd_license_key');
if($removed) {
WP_CLI::success(__('License removed!', 'perfmatters'));
}
else {
WP_CLI::warning(__('License could not be removed.', 'perfmatters'));
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,92 @@
<?php
namespace Perfmatters;
class Buffer
{
//initialize buffer class
public static function init()
{
add_action('wp', array('Perfmatters\Buffer', 'queue'));
}
//queue functions
public static function queue()
{
//inital checks
if(is_admin() || perfmatters_is_dynamic_request() || perfmatters_is_page_builder() || isset($_GET['perfmatters']) || isset($_GET['perfmattersoff'])) {
return;
}
//user agent check
if(!empty($_SERVER['HTTP_USER_AGENT'])) {
$excluded_agents = array(
'usercentrics'
);
foreach($excluded_agents as $agent) {
if(stripos($_SERVER['HTTP_USER_AGENT'], $agent) !== false) {
return;
}
}
}
//buffer is allowed
if(!apply_filters('perfmatters_allow_buffer', true)) {
return;
}
//add buffer action
add_action('template_redirect', array('Perfmatters\Buffer', 'start'), -9999);
}
//start buffer
public static function start()
{
if(has_filter('perfmatters_output_buffer_template_redirect')) {
//exclude certain requests
if(is_embed() || is_feed() || is_preview() || is_customize_preview()) {
return;
}
//don't buffer amp
if(function_exists('is_amp_endpoint') && is_amp_endpoint()) {
return;
}
ob_start(function($html) {
//check for valid buffer
if(!self::is_valid_buffer($html)) {
return $html;
}
//run buffer filters
$html = (string) apply_filters('perfmatters_output_buffer_template_redirect', $html);
//return processed html
return $html;
});
}
}
//make sure buffer content is valid
private static function is_valid_buffer($html)
{
//check for valid/invalid tags
if(stripos($html, '<html') === false || stripos($html, '</body>') === false || stripos($html, '<xsl:stylesheet') !== false) {
return false;
}
//check for invalid urls
$current_url = home_url($_SERVER['REQUEST_URI']);
$matches = apply_filters('perfmatters_buffer_excluded_extensions', array('.xml', '.txt', '.php'));
foreach($matches as $match) {
if(stripos($current_url, $match) !== false) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace Perfmatters;
class CDN
{
//initialize cdn
public static function init()
{
add_action('wp', array('Perfmatters\CDN', 'queue'));
}
//queue functions
public static function queue()
{
//add cdn rewrite to the buffer
if(!empty(Config::$options['cdn']['enable_cdn']) && !empty(Config::$options['cdn']['cdn_url'])) {
add_action('perfmatters_output_buffer_template_redirect', array('Perfmatters\CDN', 'rewrite'));
}
}
//rewrite urls in html
public static function rewrite($html)
{
//filter check
if(!apply_filters('perfmatters_cdn', true)) {
return $html;
}
//prep site url
$siteURL = '//' . ((!empty($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : parse_url(home_url(), PHP_URL_HOST));
$escapedSiteURL = quotemeta($siteURL);
$regExURL = 'https?:' . substr($escapedSiteURL, strpos($escapedSiteURL, '//'));
//prep included directories
$directories = 'wp\-content|wp\-includes';
if(!empty(Config::$options['cdn']['cdn_directories'])) {
$directoriesArray = array_map('trim', explode(',', Config::$options['cdn']['cdn_directories']));
if(count($directoriesArray) > 0) {
$directories = implode('|', array_map('quotemeta', array_filter($directoriesArray)));
}
}
//prep included extensions
$extensions_array = apply_filters('perfmatters_cdn_extensions', array(
'avif',
'css',
'gif',
'jpeg',
'jpg',
'js',
'json',
'mp3',
'mp4',
'otf',
'pdf',
'png',
'svg',
'ttf',
'webp',
'woff',
'woff2'
));
$extensions = implode('|', $extensions_array);
//rewrite urls in html
$regEx = '#(?<=[(\"\']|&quot;)(?:' . $regExURL . ')?\/(?:(?:' . $directories . ')[^\"\')]+)\.(' . $extensions . ')[^\"\')]*(?=[\"\')]|&quot;)#';
//base exclusions
$exclusions = array('script-manager.js');
//add user exclusions
if(!empty(Config::$options['cdn']['cdn_exclusions'])) {
$exclusions_user = array_map('trim', explode(',', Config::$options['cdn']['cdn_exclusions']));
$exclusions = array_merge($exclusions, $exclusions_user);
}
//set cdn url
$cdnURL = untrailingslashit(Config::$options['cdn']['cdn_url']);
//replace urls
$html = preg_replace_callback($regEx, function($url) use ($siteURL, $cdnURL, $exclusions) {
//check for exclusions
foreach($exclusions as $exclusion) {
if(!empty($exclusion) && stristr($url[0], $exclusion) != false) {
return $url[0];
}
}
//replace url with no scheme
if(strpos($url[0], '//') === 0) {
return str_replace($siteURL, $cdnURL, $url[0]);
}
//replace non relative site url
if(strstr($url[0], $siteURL)) {
return str_replace(array('http:' . $siteURL, 'https:' . $siteURL), $cdnURL, $url[0]);
}
//replace relative url
return $cdnURL . $url[0];
}, $html);
return $html;
}
}

View File

@@ -0,0 +1,666 @@
<?php
namespace Perfmatters;
use Sabberworm\CSS\CSSList\AtRuleBlockList;
use Sabberworm\CSS\CSSList\CSSBlockList;
use Sabberworm\CSS\CSSList\Document;
use Sabberworm\CSS\OutputFormat;
use Sabberworm\CSS\Parser as CSSParser;
use Sabberworm\CSS\Property\Charset;
use Sabberworm\CSS\RuleSet\DeclarationBlock;
use Sabberworm\CSS\Settings;
use Sabberworm\CSS\Value\URL;
use WP_Admin_Bar;
class CSS
{
private static $used_selectors;
private static $excluded_selectors;
//initialize css functions
public static function init()
{
if(isset($_GET['perfmatterscssoff'])) {
return;
}
if(!empty(Config::$options['assets']['remove_unused_css'])) {
add_action('wp', array('Perfmatters\CSS', 'queue'));
add_action('wp_ajax_perfmatters_clear_post_used_css', array('Perfmatters\CSS', 'clear_post_used_css'));
add_action('admin_bar_menu', array('Perfmatters\CSS', 'admin_bar_menu'));
add_action('admin_notices', array('Perfmatters\CSS', 'admin_notices'));
add_action('admin_post_perfmatters_clear_used_css', array('Perfmatters\CSS', 'admin_bar_clear_used_css'));
}
}
//queue functions
public static function queue()
{
add_action('perfmatters_output_buffer_template_redirect', array('Perfmatters\CSS', 'remove_unused_css'));
}
//remove unused css
public static function remove_unused_css($html)
{
if(empty(apply_filters('perfmatters_remove_unused_css', true))) {
return $html;
}
if(Utilities::get_post_meta('perfmatters_exclude_unused_css')) {
return $html;
}
//only logged out
if(is_user_logged_in()) {
return $html;
}
//skip woocommerce
if(Utilities::is_woocommerce()) {
return $html;
}
//only known url types
$type = self::get_url_type();
if(empty($type)) {
return $html;
}
//setup file variables
$used_css_path = PERFMATTERS_CACHE_DIR . 'css/' . $type . '.used.css';
$used_css_url = PERFMATTERS_CACHE_URL . 'css/' . $type . '.used.css';
$used_css_exists = file_exists($used_css_path);
//match all stylesheets
preg_match_all('#<link\s[^>]*?href=[\'"]([^\'"]+?\.css.*?)[\'"][^>]*?\/?>#i', $html, $stylesheets, PREG_SET_ORDER);
if(!empty($stylesheets)) {
//create our css cache directory
if(!is_dir(PERFMATTERS_CACHE_DIR . 'css/')) {
@mkdir(PERFMATTERS_CACHE_DIR . 'css/', 0755, true);
}
//populate used selectors
self::get_used_selectors($html);
self::get_excluded_selectors();
$used_css_string = '';
//loop through stylesheets
foreach($stylesheets as $key => $stylesheet) {
//stylesheet check
if(!preg_match('#\srel=[\'"]stylesheet[\'"]#is', $stylesheet[0])) {
continue;
}
//ignore google fonts
if(stripos($stylesheet[1], '//fonts.googleapis.com/css') !== false || stripos($stylesheet[1], '.google-fonts.css') !== false) {
continue;
}
//exclude entire stylesheets
$stylesheet_exclusions = array(
'dashicons.min.css', //core
'/uploads/elementor/css/post-', //elementor
'animations.min.css',
'woocommerce-mobile.min.css', //woocommerce
'woocommerce-smallscreen.css',
'/uploads/oxygen/css/', //oxygen
'/uploads/bb-plugin/cache/', //beaver builder
'/uploads/generateblocks/', //generateblocks
'/et-cache/', //divi
'/widget-google-reviews/assets/css/public-main.css' //plugin for google reviews
);
if(!empty(Config::$options['assets']['rucss_excluded_stylesheets'])) {
$stylesheet_exclusions = array_merge($stylesheet_exclusions, Config::$options['assets']['rucss_excluded_stylesheets']);
}
$stylesheet_exclusions = apply_filters('perfmatters_rucss_excluded_stylesheets', $stylesheet_exclusions);
foreach($stylesheet_exclusions as $exclude) {
if(strpos($stylesheet[1], $exclude) !== false) {
unset($stylesheets[$key]);
continue 2;
}
}
//need to generate used css
if(!$used_css_exists) {
//get local stylesheet path
$url = str_replace(trailingslashit(apply_filters('perfmatters_local_stylesheet_url', (!empty(Config::$options['assets']['rucss_cdn_url']) ? Config::$options['assets']['rucss_cdn_url'] : site_url()))), '', explode('?', $stylesheet[1])[0]);
$file = str_replace('/wp-content', '/', WP_CONTENT_DIR) . $url;
//make sure local file exists
if(!file_exists($file)) {
continue;
}
//get used css from stylesheet
$used_css = self::clean_stylesheet($stylesheet[1], @file_get_contents($file));
//add used stylesheet css to total used
$used_css_string.= $used_css;
}
//delay stylesheets
if(empty(Config::$options['assets']['rucss_stylesheet_behavior'])) {
$new_link = preg_replace('#href=([\'"]).+?\1#', 'data-pmdelayedstyle="' . $stylesheet[1] . '"',$stylesheet[0]);
$html = str_replace($stylesheet[0], $new_link, $html);
}
//async stylesheets
elseif(Config::$options['assets']['rucss_stylesheet_behavior'] == 'async') {
$new_link = preg_replace(array('#media=([\'"]).+?\1#', '#onload=([\'"]).+?\1#'), '', $stylesheet[0]);
$new_link = str_replace('<link', '<link media="print" onload="this.media=\'all\';this.onload=null;"', $new_link);
$html = str_replace($stylesheet[0], $new_link, $html);
}
//remove stylesheets
elseif(Config::$options['assets']['rucss_stylesheet_behavior'] == 'remove') {
$html = str_replace($stylesheet[0], '', $html);
}
}
//store used css file
if(!empty($used_css_string)) {
if(file_put_contents($used_css_path, apply_filters('perfmatters_used_css', $used_css_string)) !== false) {
$time = get_option('perfmatters_used_css_time', array());
$time[$type] = time();
//update stored timestamp
update_option('perfmatters_used_css_time', $time);
}
}
//print used css inline after first title tag
$pos = strpos($html, '</title>');
if($pos !== false) {
//print file
if(!empty(Config::$options['assets']['rucss_method']) && Config::$options['assets']['rucss_method'] == 'file') {
//grab stored timestamp for query string
$time = get_option('perfmatters_used_css_time', array());
if(!empty($time[$type])) {
$used_css_url = add_query_arg('ver', $time[$type], $used_css_url);
}
$used_css_output = "<link rel='preload' href='" . $used_css_url . "' as='style' onload=\"this.rel='stylesheet';this.removeAttribute('onload');\">";
$used_css_output.= '<link rel="stylesheet" id="perfmatters-used-css" href="' . $used_css_url . '" media="all" />';
}
//print inline
else {
$used_css_output = '<style id="perfmatters-used-css">' . file_get_contents($used_css_path) . '</style>';
}
$html = substr_replace($html, '</title>' . $used_css_output, $pos, 8);
}
//delay stylesheet script
if(empty(Config::$options['assets']['rucss_stylesheet_behavior'])) {
$delay_check = !empty(apply_filters('perfmatters_delay_js', !empty(Config::$options['assets']['delay_js']))) && !Utilities::get_post_meta('perfmatters_exclude_delay_js');
if(!$delay_check || empty(Config::$options['assets']['delay_js_behavior']) || isset($_GET['perfmattersjsoff'])) {
$script = '<script type="text/javascript" id="perfmatters-delayed-styles-js">!function(){const e=["keydown","mousemove","wheel","touchmove","touchstart","touchend"];function t(){document.querySelectorAll("link[data-pmdelayedstyle]").forEach(function(e){e.setAttribute("href",e.getAttribute("data-pmdelayedstyle"))}),e.forEach(function(e){window.removeEventListener(e,t,{passive:!0})})}e.forEach(function(e){window.addEventListener(e,t,{passive:!0})})}();</script>';
$html = str_replace('</body>', $script . '</body>', $html);
}
}
}
return $html;
}
//get url type
private static function get_url_type()
{
global $wp_query;
$type = '';
if($wp_query->is_page) {
$type = is_front_page() ? 'front' : 'page-' . $wp_query->post->ID;
}
elseif($wp_query->is_home) {
$type = 'home';
}
elseif($wp_query->is_single) {
$type = get_post_type() !== false ? get_post_type() : 'single';
}
elseif($wp_query->is_category) {
$type = 'category';
}
elseif($wp_query->is_tag) {
$type = 'tag';
}
elseif($wp_query->is_tax) {
$type = 'tax';
}
elseif($wp_query->is_archive) {
$type = $wp_query->is_day ? 'day' : ($wp_query->is_month ? 'month' : ($wp_query->is_year ? 'year' : ($wp_query->is_author ? 'author' : 'archive')));
}
elseif($wp_query->is_search) {
$type = 'search';
}
elseif($wp_query->is_404) {
$type = '404';
}
return $type;
}
//get used selectors in html
private static function get_used_selectors($html) {
if(!$html) {
return;
}
//get dom document
$libxml_previous = libxml_use_internal_errors(true);
$dom = new \DOMDocument();
$result = $dom->loadHTML($html);
libxml_clear_errors();
libxml_use_internal_errors($libxml_previous);
if($result) {
$dom->xpath = new \DOMXPath($dom);
//setup used selectors array
self::$used_selectors = array('tags' => array(), 'ids' => array(), 'classes' => array());
//search for used selectors in dom
$classes = array();
foreach($dom->getElementsByTagName('*') as $tag) {
//add tag
self::$used_selectors['tags'][$tag->tagName] = 1;
//add add tag id
if($tag->hasAttribute('id')) {
self::$used_selectors['ids'][$tag->getAttribute('id')] = 1;
}
//store tag classes
if($tag->hasAttribute('class')) {
$class = $tag->getAttribute('class');
$tag_classes = preg_split('/\s+/', $class);
array_push($classes, ...$tag_classes);
}
}
//add classes
$classes = array_filter(array_unique($classes));
if($classes) {
self::$used_selectors['classes'] = array_fill_keys($classes, 1);
}
}
}
//get excluded selectors
private static function get_excluded_selectors() {
self::$excluded_selectors = array(
'.wp-embed-responsive', //core
'.wp-block-embed',
'.wp-block-embed__wrapper',
'.wp-caption',
'#elementor-device-mode', //elementor
'.elementor-nav-menu',
'.elementor-has-item-ratio',
'.elementor-popup-modal',
'.elementor-sticky--active',
'.dialog-type-lightbox',
'.dialog-widget-content',
'.lazyloaded',
'.elementor-motion-effects-container',
'.elementor-motion-effects-layer',
'.ast-header-break-point', //astra
'.dropdown-nav-special-toggle', //kadence
'rs-fw-forcer' //rev slider
);
if(!empty(Config::$options['assets']['rucss_excluded_selectors'])) {
self::$excluded_selectors = array_merge(self::$excluded_selectors, Config::$options['assets']['rucss_excluded_selectors']);
}
self::$excluded_selectors = apply_filters('perfmatters_rucss_excluded_selectors', self::$excluded_selectors);
}
//remove unusde css from stylesheet
private static function clean_stylesheet($url, $css)
{
//https://github.com/sabberworm/PHP-CSS-Parser/issues/150
$css = preg_replace('/^\xEF\xBB\xBF/', '', $css);
//setup css parser
$settings = Settings::create()->withMultibyteSupport(false);
$parser = new CSSParser($css, $settings);
$parsed_css = $parser->parse();
//convert relative urls to full urls
self::fix_relative_urls($url, $parsed_css);
$css_data = self::prep_css_data($parsed_css);
return self::remove_unused_selectors($css_data);
}
//convert relative urls to full urls
private static function fix_relative_urls($stylesheet_url, Document $data)
{
//get base url from stylesheet
$base_url = preg_replace('#[^/]+(\?.*)?$#', '', $stylesheet_url);
//search css for urls
$values = $data->getAllValues();
foreach($values as $value) {
if(!($value instanceof URL)) {
continue;
}
$url = $value->getURL()->getString();
//not relative
if(preg_match('/^(https?|data):/', $url)) {
continue;
}
$parsed_url = parse_url($url);
//final checks
if(!empty($parsed_url['host']) || empty($parsed_url['path']) || $parsed_url['path'][0] === '/') {
continue;
}
//create full url and replace
$new_url = $base_url . $url;
$value->getUrl()->setString($new_url);
}
}
//prep parsed css for cleaning
private static function prep_css_data(CSSBlockList $data)
{
$items = array();
foreach($data->getContents() as $content) {
//remove charset objects since were printing inline
if($content instanceof Charset) {
continue;
}
if($content instanceof AtRuleBlockList) {
$items[] = array(
'rulesets' => self::prep_css_data($content),
'at_rule' => "@{$content->atRuleName()} {$content->atRuleArgs()}",
);
}
else {
$item = array('css' => $content->render(OutputFormat::createCompact()));
if($content instanceof DeclarationBlock) {
$item['selectors'] = self::sort_selectors($content->getSelectors());
}
$items[] = $item;
}
}
return $items;
}
//sort selectors into different categories we need
private static function sort_selectors($selectors)
{
$selectors = array_map(
function($sel) {
return $sel->__toString();
},
$selectors
);
$selectors_data = array();
foreach($selectors as $selector) {
//setup selector data array
$data = array(
'selector' => trim($selector),
'classes' => array(),
'ids' => array(),
'tags' => array(),
'atts' => array()
);
//eliminate false negatives (:not(), pseudo, etc...)
$selector = preg_replace('/(?<!\\\\)::?[a-zA-Z0-9_-]+(\(.+?\))?/', '', $selector);
//atts
$selector = preg_replace_callback(
'/\[([A-Za-z0-9_:-]+)(\W?=[^\]]+)?\]/',
function($matches) use (&$data) {
$data['atts'][] = $matches[1];
return '';
},
$selector
);
//classes
$selector = preg_replace_callback(
'/\.((?:[a-zA-Z0-9_-]+|\\\\.)+)/',
function($matches) use (&$data) {
$data['classes'][] = stripslashes($matches[1]);
return '';
},
$selector
);
//ids
$selector = preg_replace_callback(
'/#([a-zA-Z0-9_-]+)/',
function($matches) use (&$data) {
$data['ids'][] = $matches[1];
return '';
},
$selector
);
//tags
$selector = preg_replace_callback(
'/[a-zA-Z0-9_-]+/',
function($matches) use (&$data) {
$data['tags'][] = $matches[0];
return '';
},
$selector
);
//add selector data to main array
$selectors_data[] = array_filter($data);
}
return array_filter($selectors_data);
}
//remove unused selectors from css data
private static function remove_unused_selectors($data)
{
$rendered = [];
foreach($data as $item) {
//has css
if(isset($item['css'])) {
//need at least one selector match
$should_render = !isset($item['selectors']) || 0 !== count(array_filter($item['selectors'],
function($selector) {
return self::is_selector_used($selector);
}
));
if($should_render) {
$rendered[] = $item['css'];
}
continue;
}
//nested rulesets
if(!empty($item['rulesets'])) {
$child_rulesets = self::remove_unused_selectors($item['rulesets']);
if($child_rulesets) {
$rendered[] = sprintf('%s{%s}', $item['at_rule'], $child_rulesets);
}
}
}
return implode("", $rendered);
}
//check if selector is used
private static function is_selector_used($selector)
{
//:root selector
if($selector['selector'] === ':root') {
return true;
}
//lone attribute selector
if(!empty($selector['atts']) && (empty($selector['classes']) && empty($selector['ids']) && empty($selector['tags']))) {
return true;
}
//search for excluded selector match
if(!empty(self::$excluded_selectors)) {
foreach(self::$excluded_selectors as $key => $value) {
if(preg_match('#(' . preg_quote($value) . ')(?=\s|\.|\:|,|\[|$)#', $selector['selector'])) {
return true;
}
}
}
//is selector used in the dom
foreach(array('classes', 'ids', 'tags') as $type) {
if(!empty($selector[$type])) {
//cast array if needed
$targets = (array)$selector[$type];
foreach($targets as $target) {
//bail if a target doesn't exist
if(!isset(self::$used_selectors[$type][$target])) {
return false;
}
}
}
}
return true;
}
//delete all files in the css cache directory
public static function clear_used_css()
{
$files = glob(PERFMATTERS_CACHE_DIR . 'css/*');
foreach($files as $file) {
if(is_file($file)) {
unlink($file);
}
}
delete_option('perfmatters_used_css_time');
}
//clear used css file for specific post or post type
public static function clear_post_used_css() {
if(empty($_POST['action']) || empty($_POST['nonce']) || empty($_POST['post_id'])) {
return;
}
if($_POST['action'] != 'perfmatters_clear_post_used_css') {
return;
}
if(!wp_verify_nonce($_POST['nonce'], 'perfmatters_clear_post_used_css')) {
return;
}
$post_id = (int)$_POST['post_id'];
$post_type = get_post_type($post_id);
$path = $post_type == 'page' ? 'page-' . $post_id : $post_type;
$file = PERFMATTERS_CACHE_DIR . 'css/' . $path . '.used.css';
if(is_file($file)) {
unlink($file);
}
wp_send_json_success();
exit;
}
//add admin bar menu item
public static function admin_bar_menu(WP_Admin_Bar $wp_admin_bar) {
if(!current_user_can('manage_options') || !perfmatters_network_access()) {
return;
}
$type = !is_admin() ? self::get_url_type() : '';
$menu_item = array(
'parent' => 'perfmatters',
'id' => 'perfmatters-clear-used-css',
'title' => __('Clear Used CSS', 'perfmatters') . ' (' . (!empty($type) ? __('Current', 'perfmatters') : __('All', 'perfmatters')) . ')',
'href' => add_query_arg(array(
'action' => 'perfmatters_clear_used_css',
'_wp_http_referer' => rawurlencode($_SERVER['REQUEST_URI']),
'_wpnonce' => wp_create_nonce('perfmatters_clear_used_css'),
'type' => $type
),
admin_url('admin-post.php'))
);
$wp_admin_bar->add_menu($menu_item);
}
//display admin notices
public static function admin_notices() {
if(get_transient('perfmatters_used_css_cleared') === false) {
return;
}
delete_transient('perfmatters_used_css_cleared');
echo '<div class="notice notice-success is-dismissible"><p><strong>' . __('Used CSS cleared.', 'perfmatters' ) . '</strong></p></div>';
}
//clear used css from admin bar
public static function admin_bar_clear_used_css() {
if(!isset($_GET['_wpnonce']) || !wp_verify_nonce(sanitize_key($_GET['_wpnonce']), 'perfmatters_clear_used_css')) {
wp_nonce_ays('');
}
if(!empty($_GET['type'])) {
//clear specific type
$file = PERFMATTERS_CACHE_DIR . 'css/' . $_GET['type'] . '.used.css';
if(is_file($file)) {
unlink($file);
}
}
else {
//clear all
self::clear_used_css();
if(is_admin()) {
set_transient('perfmatters_used_css_cleared', 1);
}
}
//go back to url where button was pressed
wp_safe_redirect(esc_url_raw(wp_get_referer()));
exit;
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Perfmatters;
use WP_Admin_Bar;
class Config
{
public static $options;
public static $tools;
//initialize config
public static function init()
{
//load plugin options
self::$options = get_option('perfmatters_options');
self::$tools = get_option('perfmatters_tools');
//actions
add_action('admin_bar_menu', array('Perfmatters\Config', 'admin_bar_menu'), 500);
}
//setup admin bar menu
public static function admin_bar_menu(WP_Admin_Bar $wp_admin_bar)
{
if(!current_user_can('manage_options') || !perfmatters_network_access() || !empty(self::$tools['hide_admin_bar_menu'])) {
return;
}
//add top level menu item
$wp_admin_bar->add_menu(array(
'id' => 'perfmatters',
'title' => 'Perfmatters',
'href' => admin_url('options-general.php?page=perfmatters')
));
}
}

View File

@@ -0,0 +1,141 @@
<?php
namespace Perfmatters;
class DatabaseOptimizationProcess extends \WP_Background_Process {
//prefix for process
protected $prefix = 'perfmatters';
//action name for process
protected $action = 'database_optimization';
//totals of removed items
protected $counts = array();
//run on each queue item
protected function task($item) {
global $wpdb;
switch($item) {
case 'post_revisions' :
$query = $wpdb->get_col("SELECT ID FROM $wpdb->posts WHERE post_type = 'revision'");
if($query) {
$count = 0;
foreach($query as $id) {
$count += wp_delete_post_revision(intval($id)) instanceof \WP_Post ? 1 : 0;
}
$this->counts[$item] = $count;
}
break;
case 'post_auto_drafts' :
$query = $wpdb->get_col("SELECT ID FROM $wpdb->posts WHERE post_status = 'auto-draft'");
if($query) {
$count = 0;
foreach($query as $id) {
$count += wp_delete_post(intval($id), true) instanceof \WP_Post ? 1 : 0;
}
$this->counts[$item] = $count;
}
break;
case 'trashed_posts' :
$query = $wpdb->get_col("SELECT ID FROM $wpdb->posts WHERE post_status = 'trash'");
if($query) {
$count = 0;
foreach($query as $id) {
$count += wp_delete_post($id, true) instanceof \WP_Post ? 1 : 0;
}
$this->counts[$item] = $count;
}
break;
case 'spam_comments' :
$query = $wpdb->get_col("SELECT comment_ID FROM $wpdb->comments WHERE comment_approved = 'spam'");
if($query) {
$count = 0;
foreach($query as $id) {
$count += (int) wp_delete_comment(intval($id), true);
}
$this->counts[$item] = $count;
}
break;
case 'trashed_comments' :
$query = $wpdb->get_col("SELECT comment_ID FROM $wpdb->comments WHERE (comment_approved = 'trash' OR comment_approved = 'post-trashed')");
if($query) {
$count = 0;
foreach($query as $id) {
$count += (int) wp_delete_comment(intval($id), true);
}
$this->counts[$item] = $count;
}
break;
case 'expired_transients' :
$time = isset($_SERVER['REQUEST_TIME']) ? (int) $_SERVER['REQUEST_TIME'] : time();
$query = $wpdb->get_col($wpdb->prepare("SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s AND option_value < %d", $wpdb->esc_like('_transient_timeout') . '%', $time));
if($query) {
$count = 0;
foreach($query as $transient) {
$key = str_replace('_transient_timeout_', '', $transient);
$count += (int) delete_transient($key);
}
$this->counts[$item] = $count;
}
break;
case 'all_transients' :
$query = $wpdb->get_col($wpdb->prepare( "SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s OR option_name LIKE %s", $wpdb->esc_like('_transient_') . '%', $wpdb->esc_like('_site_transient_') . '%'));
if($query) {
$count = 0;
foreach($query as $transient) {
if(strpos($transient, '_site_transient_') !== false) {
$count += (int) delete_site_transient(str_replace('_site_transient_', '', $transient));
} else {
$count += (int) delete_transient(str_replace('_transient_', '', $transient));
}
}
$this->counts[$item] = $count;
}
break;
case 'tables' :
$query = $wpdb->get_results("SELECT table_name, data_free FROM information_schema.tables WHERE table_schema = '" . DB_NAME . "' and Engine <> 'InnoDB' and data_free > 0");
if($query) {
$count = 0;
foreach($query as $table) {
$count += (int) $wpdb->query("OPTIMIZE TABLE $table->table_name");
}
$this->counts[$item] = $count;
}
break;
}
return false;
}
//run background process on queue
public function dispatch() {
//set our working transient
set_transient('perfmatters_database_optimization_process', 'working', HOUR_IN_SECONDS);
//run parent dispatch
return parent::dispatch();
}
//run when background process is complete
protected function complete() {
//delete our working transient
delete_transient('perfmatters_database_optimization_process');
//set complete transient
set_transient('perfmatters_database_optimization_process_complete', $this->counts);
//run parent complete
parent::complete();
}
}

View File

@@ -0,0 +1,216 @@
<?php
namespace Perfmatters;
class DatabaseOptimizer {
//declare our optimizer
protected $optimizer;
//actions + filters
public function __construct() {
//initialize optimizer
$this->optimizer = new DatabaseOptimizationProcess();
add_filter('pre_update_option_perfmatters_tools', array($this, 'perfmatters_database_optimization_action'), 10, 2);
add_action('admin_notices', array($this, 'perfmatters_database_optimization_notices'));
add_filter('cron_schedules', array($this, 'perfmatters_add_database_optimization_cron_schedule'));
add_action('init', array($this, 'perfmatters_schedule_database_optimization'));
add_action('perfmatters_database_optimization', array($this, 'perfmatters_run_scheduled_database_optimization'));
}
//run the background process
public function process_handler($items) {
//push the requested items to the queue
array_map(array($this->optimizer, 'push_to_queue'), $items);
//run the process
$this->optimizer->save()->dispatch();
}
//watch and respond to the optimize button
public function perfmatters_database_optimization_action($new_value, $old_value) {
//optimize button was pressed
if(!empty($new_value['database']['optimize_database'])) {
//stop and show error if process is already running
$working = get_transient('perfmatters_database_optimization_process');
if($working !== false) {
add_settings_error('perfmatters', 'perfmatters-database-optimization-running', __('There is already an existing database optimization process running.', 'perfmatters'), 'error');
return $old_value;
}
//get available options array
$optimize_options = array_keys($this->perfmatters_get_database_options());
//build array of requested items
$items = array();
foreach($optimize_options as $item) {
if(!empty($new_value['database'][$item])) {
$items[] = $item;
}
}
//run process handler
if(!empty($items)) {
$this->process_handler($items);
}
//add hidden notice to prevent save message
add_settings_error('perfmatters', 'perfmatters-hidden-notice', '', 'success');
return $old_value;
}
$new_optimize_schedule = isset($new_value['database']['optimize_schedule']) ? $new_value['database']['optimize_schedule'] : '';
$old_optimize_schedule = isset($old_value['database']['optimize_schedule']) ? $old_value['database']['optimize_schedule'] : '';
//optimize schedule was changed
if($new_optimize_schedule !== $old_optimize_schedule) {
if(wp_next_scheduled('perfmatters_database_optimization')) {
wp_clear_scheduled_hook('perfmatters_database_optimization');
}
}
return $new_value;
}
//display notices for database optimization process
public function perfmatters_database_optimization_notices() {
//permissions check
if(!current_user_can('manage_options')) {
return;
}
//make sure were on our settings page
if(empty($_GET['page']) || $_GET['page'] !== 'perfmatters') {
return;
}
//get working transient
$working = get_transient('perfmatters_database_optimization_process');
if($working !== false) {
$notice_type = "info";
$message = __('Database optimization is running in the background.', 'perfmatters');
}
else {
//get completed optimized transient
$completed = get_transient('perfmatters_database_optimization_process_complete');
if($completed === false) {
return;
}
$notice_type = "success";
//get db options array
$database_options = $this->perfmatters_get_database_options();
//build admin notice message
if(!empty($completed)) {
$message = __('Database optimization completed. The following items were removed:', 'perfmatters');
$message.= "<ul style='margin: 0px; padding: 0px 2px;'>";
foreach($completed as $key => $count) {
$message.= "<li>";
$message.= "<strong>" . $database_options[$key] . ':</strong> ' . $count;
$message.= "</li>";
}
$message.= "</ul>";
}
else {
$message = __('Database optimization completed. No optimizations found.', 'perfmatters');
}
//delete our completed transient
delete_transient('perfmatters_database_optimization_process_complete');
}
//display admin notice
if(!empty($message)) {
echo "<div class='notice notice-" . $notice_type . " is-dismissible'>";
echo "<p>" . $message . "</p>";
echo"</div>";
}
}
//add cron schedule
public function perfmatters_add_database_optimization_cron_schedule($schedules) {
$perfmatters_tools = get_option('perfmatters_tools');
if(empty($perfmatters_tools['database']['optimize_schedule']) || $perfmatters_tools['database']['optimize_schedule'] == 'daily') {
return $schedules;
}
switch($perfmatters_tools['database']['optimize_schedule']) {
case 'weekly' :
$schedules['weekly'] = array(
'interval' => 604800,
'display' => __('Once Weekly', 'perfmatters'),
);
break;
case 'monthly' :
$schedules['monthly'] = array(
'interval' => 2592000,
'display' => __('Once Monthly', 'perfmatters'),
);
break;
default :
break;
}
return $schedules;
}
//create database optimization scheduled event
public function perfmatters_schedule_database_optimization() {
$perfmatters_tools = get_option('perfmatters_tools');
if(!empty($perfmatters_tools['database']['optimize_schedule']) && !wp_next_scheduled('perfmatters_database_optimization')) {
wp_schedule_event(time(), $perfmatters_tools['database']['optimize_schedule'], 'perfmatters_database_optimization');
}
}
//scheduled event action
public function perfmatters_run_scheduled_database_optimization() {
$perfmatters_tools = get_option('perfmatters_tools');
$optimize_options = array_keys($this->perfmatters_get_database_options());
//build array of set items
$items = array();
foreach($optimize_options as $item) {
if(!empty($perfmatters_tools['database'][$item])) {
$items[] = $item;
}
}
//run process handler
if(!empty($items)) {
$this->process_handler($items);
}
}
//return array of database options
protected function perfmatters_get_database_options() {
return array(
'post_revisions' => __('Revisions', 'perfmatters'),
'post_auto_drafts' => __('Auto Drafts', 'perfmatters'),
'trashed_posts' => __('Trashed Posts', 'perfmatters'),
'spam_comments' => __('Spam Comments', 'perfmatters'),
'trashed_comments' => __('Trashed Comments', 'perfmatters'),
'expired_transients' => __('Expired transients', 'perfmatters'),
'all_transients' => __('Transients', 'perfmatters'),
'tables' => __('Tables', 'perfmatters')
);
}
}

View File

@@ -0,0 +1,188 @@
<?php
namespace Perfmatters;
use WpOrg\Requests\Requests; //wp 6.2+
if(!defined('REQUESTS_SILENCE_PSR0_DEPRECATIONS')) {
define('REQUESTS_SILENCE_PSR0_DEPRECATIONS', true);
}
use Requests as RequestsOld; //deprecated
class Fonts
{
private static $font_file_cache_url = PERFMATTERS_CACHE_URL;
//initialize fonts
public static function init() {
if(empty(Config::$options['fonts']['disable_google_fonts'])) {
add_action('wp', array('Perfmatters\Fonts', 'queue'));
}
}
//queue functions
public static function queue()
{
//add display swap to the buffer
if(!empty(Config::$options['fonts']['display_swap'])) {
add_action('perfmatters_output_buffer_template_redirect', array('Perfmatters\Fonts', 'display_swap'));
}
//add local google fonts to the buffer
if(!empty(Config::$options['fonts']['local_google_fonts'])) {
add_action('perfmatters_output_buffer_template_redirect', array('Perfmatters\Fonts', 'local_google_fonts'));
}
}
//add display swap to google font files
public static function display_swap($html) {
//find google fonts
preg_match_all('#<link[^>]+?href=(["\'])([^>]*?fonts\.googleapis\.com\/css.*?)\1.*?>#i', $html, $google_fonts, PREG_SET_ORDER);
if(!empty($google_fonts)) {
foreach($google_fonts as $google_font) {
//replace display parameter
$new_href = preg_replace('/&display=(auto|block|fallback|optional|swap)/', '', html_entity_decode($google_font[2]));
$new_href.= '&display=swap';
//create font tag with new href
$new_google_font = str_replace($google_font[2], $new_href, $google_font[0]);
//replace original font tag
$html = str_replace($google_font[0], $new_google_font, $html);
}
}
return $html;
}
//download and host google font files locally
public static function local_google_fonts($html) {
//create our fonts cache directory
if(!is_dir(PERFMATTERS_CACHE_DIR . 'fonts/')) {
@mkdir(PERFMATTERS_CACHE_DIR . 'fonts/', 0755, true);
}
//rewrite cdn url in font file cache url
$cdn_url = !empty(Config::$options['fonts']['cdn_url']) ? Config::$options['fonts']['cdn_url'] : (!empty(Config::$options['cdn']['enable_cdn']) && !empty(Config::$options['cdn']['cdn_url']) ? Config::$options['cdn']['cdn_url'] : '');
if(!empty($cdn_url)) {
self::$font_file_cache_url = str_replace(site_url(), untrailingslashit($cdn_url), PERFMATTERS_CACHE_URL);
}
//remove existing google font preconnect + prefetch links
preg_match_all('#<link(?:[^>]+)?href=(["\'])([^>]*?fonts\.(gstatic|googleapis)\.com.*?)\1.*?>#i', $html, $google_links, PREG_SET_ORDER);
if(!empty($google_links)) {
foreach($google_links as $google_link) {
if(preg_match('#rel=(["\'])(.*?(preconnect|prefetch).*?)\1#i', $google_link[0])) {
$html = str_replace($google_link[0], '', $html);
}
}
}
//find google fonts
preg_match_all('#<link[^>]+?href=(["\'])([^>]*?fonts\.googleapis\.com\/css.*?)\1.*?>#i', $html, $google_fonts, PREG_SET_ORDER);
if(!empty($google_fonts)) {
foreach($google_fonts as $google_font) {
//create unique file details
$file_name = substr(md5($google_font[2]), 0, 12) . ".google-fonts.css";
$file_path = PERFMATTERS_CACHE_DIR . 'fonts/' . $file_name;
$file_url = PERFMATTERS_CACHE_URL . 'fonts/' . $file_name;
//download file if it doesn't exist
if(!file_exists($file_path)) {
if(!self::download_google_font($google_font[2], $file_path)) {
continue;
}
}
//create font tag with new url
$new_google_font = str_replace($google_font[2], $file_url, $google_font[0]);
//async
if(!empty(Config::$options['fonts']['async'])) {
$new_google_font = preg_replace(array('#media=([\'"]).+?\1#', '#onload=([\'"]).+?\1#'), '', $new_google_font);
$new_google_font = str_replace('<link', '<link media="print" onload="this.media=\'all\';this.onload=null;"', $new_google_font);
}
//replace original font tag
$html = str_replace($google_font[0], $new_google_font, $html);
}
}
return $html;
}
//download and save google font css file
private static function download_google_font($url, $file_path)
{
//add https if using relative scheme
if(substr($url, 0, 2) === '//') {
$url = 'https:' . $url;
}
//download css file
$css_response = wp_remote_get(html_entity_decode($url), array('user-agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36'));
//check valid response
if(wp_remote_retrieve_response_code($css_response) !== 200) {
return false;
}
//css content
$css = $css_response['body'];
//find font files inside the css
$regex = '/url\((https:\/\/fonts\.gstatic\.com\/.*?)\)/';
preg_match_all($regex, $css, $matches);
$font_urls = array_unique($matches[1]);
$font_requests = array();
foreach($font_urls as $font_url) {
if(!file_exists(PERFMATTERS_CACHE_DIR . 'fonts/' . basename($font_url))) {
$font_requests[] = array('url' => $font_url, 'type' => 'GET');
}
$cached_font_url = self::$font_file_cache_url . 'fonts/' . basename($font_url);
$css = str_replace($font_url, $cached_font_url, $css);
}
//download new font files to cache directory
if(method_exists(WpOrg\Requests\Requests::class, 'request_multiple')) { //wp 6.2+
$font_responses = WpOrg\Requests\Requests::request_multiple($font_requests);
}
elseif(method_exists(RequestsOld::class, 'request_multiple')) { //deprecated
$font_responses = RequestsOld::request_multiple($font_requests);
}
if(!empty($font_responses)) {
foreach($font_responses as $font_response) {
if(is_a($font_response, 'Requests_Response') || is_a($font_response, 'WpOrg\Requests\Response')) {
$font_path = PERFMATTERS_CACHE_DIR . 'fonts/' . basename($font_response->url);
//save font file
file_put_contents($font_path, $font_response->body);
}
}
}
//save final css file
file_put_contents($file_path, $css);
return true;
}
//delete all files in the fonts cache directory
public static function clear_local_fonts()
{
$files = glob(PERFMATTERS_CACHE_DIR . 'fonts/*');
foreach($files as $file) {
if(is_file($file)) {
unlink($file);
}
}
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace Perfmatters;
class Images
{
//initialize image functions
public static function init()
{
add_action('wp', array('Perfmatters\Images', 'queue'));
}
//queue functions
public static function queue()
{
//image dimensions
if(!empty(Config::$options['lazyload']['image_dimensions']) && !empty(Config::$options['lazyload']['image_dimensions'])) {
add_action('perfmatters_output_buffer_template_redirect', array('Perfmatters\Images', 'image_dimensions'));
}
}
//fix images missing dimensions
public static function image_dimensions($html)
{
//match all img tags without width or height attributes
preg_match_all('#<img((?:[^>](?!(height|width)=[\'\"](?:\S+)[\'\"]))*+)>#is', $html, $images, PREG_SET_ORDER);
if(!empty($images)) {
//remove any duplicate images
$images = array_unique($images, SORT_REGULAR);
//exclude specific images
$image_exclusions = array(
';base64'
);
$image_exclusions = apply_filters('perfmatters_image_dimensions_exclusions', $image_exclusions);
//loop through images
foreach($images as $image) {
//get image attributes array
$image_atts = Utilities::get_atts_array($image[1]);
if(!empty($image_atts['src'])) {
foreach($image_exclusions as $exclude) {
if(strpos($image[1], $exclude) !== false) {
continue 2;
}
}
//get image dimensions
$dimensions = self::get_dimensions_from_url($image_atts['src']);
if(!empty($dimensions)) {
//remove any existing dimension attributes
$new_image = preg_replace('/(height|width)=[\'"](?:\S+)*[\'"]/i', '', $image[0]);
//add dimension attributes to img tag
$new_image = preg_replace('/<\s*img/i', '<img width="' . $dimensions['width'] . '" height="' . $dimensions['height'] . '"', $new_image);
//replace original img tag in html
if(!empty($new_image)) {
$html = str_replace($image[0], $new_image, $html);
}
}
}
}
}
return $html;
}
//return array of dimensions based on image url
private static function get_dimensions_from_url($url)
{
//grab dimensions from file name if available
if(preg_match('/(?:.+)-([0-9]+)x([0-9]+)\.(jpg|jpeg|png|gif|svg)$/', $url, $matches)) {
return array('width' => $matches[1], 'height' => $matches[2]);
}
//get image path
$parsed_url = parse_url($url);
if(empty($parsed_url['path'])) {
return false;
}
$image_path = str_replace('/wp-content', '', WP_CONTENT_DIR) . '/' . $parsed_url['path'];
if(file_exists($image_path)) {
//get dimensions from file
$sizes = getimagesize($image_path);
if(!empty($sizes)) {
return array('width' => $sizes[0], 'height' => $sizes[1]);
}
}
return false;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,725 @@
<?php
namespace Perfmatters;
class LazyLoad
{
//initialize lazyload iframe functions
public static function init_iframes()
{
add_action('wp', array('Perfmatters\LazyLoad', 'queue_iframes'));
}
//queue iframe functions
public static function queue_iframes()
{
//check filters
if(empty(apply_filters('perfmatters_lazyload', !empty(Config::$options['lazyload']['lazy_loading_iframes'])))) {
return;
}
//skip woocommerce
if(Utilities::is_woocommerce()) {
return;
}
//check post meta
if(Utilities::get_post_meta('perfmatters_exclude_lazy_loading')) {
return;
}
add_action('perfmatters_output_buffer_template_redirect', array('Perfmatters\LazyLoad', 'iframes_buffer'));
}
//process iframe buffer
public static function iframes_buffer($html) {
$clean_html = Utilities::clean_html($html);
$html = self::lazyload_iframes($html, $clean_html);
$html = self::lazyload_videos($html, $clean_html);
return $html;
}
//lazy load iframes
public static function lazyload_iframes($html, $buffer) {
//match all iframes
preg_match_all('#<iframe(\s.+)>.*</iframe>#iUs', $buffer, $iframes, PREG_SET_ORDER);
if(!empty($iframes)) {
//remove any duplicates
$iframes = array_unique($iframes, SORT_REGULAR);
foreach($iframes as $iframe) {
//get iframe attributes array
$iframe_atts = Utilities::get_atts_array($iframe[1]);
//dont check excluded if forced attribute was found
if(!self::lazyload_excluded($iframe[1], self::lazyload_forced_atts())) {
//skip if exluded attribute was found
if(self::lazyload_excluded($iframe[1], self::lazyload_excluded_atts())) {
continue;
}
//skip if no-lazy class is found
if(!empty($iframe_atts['class']) && strpos($iframe_atts['class'], 'no-lazy') !== false) {
continue;
}
}
//skip if no src is found
if(empty($iframe_atts['src'])) {
continue;
}
//try rendering youtube preview placeholder if we need to
if(!empty(Config::$options['lazyload']['youtube_preview_thumbnails'])) {
$iframe['src'] = trim($iframe_atts['src']);
$iframe_lazyload = self::lazyload_youtube_iframe($iframe);
}
//default iframe placeholder
if(empty($iframe_lazyload)) {
$iframe_atts['class'] = !empty($iframe_atts['class']) ? $iframe_atts['class'] . ' ' . 'perfmatters-lazy' : 'perfmatters-lazy';
//migrate src
$iframe_atts['data-src'] = $iframe_atts['src'];
unset($iframe_atts['src']);
//replace iframe attributes string
$iframe_lazyload = str_replace($iframe[1], ' ' . Utilities::get_atts_string($iframe_atts), $iframe[0]);
//add noscript original iframe
if(apply_filters('perfmatters_lazyload_noscript', true)) {
$iframe_lazyload.= '<noscript>' . $iframe[0] . '</noscript>';
}
}
//replace iframe with placeholder
$html = str_replace($iframe[0], $iframe_lazyload, $html);
unset($iframe_lazyload);
}
}
return $html;
}
//prep youtube iframe for lazy loading
public static function lazyload_youtube_iframe($iframe) {
if(!$iframe) {
return false;
}
//attempt to get the id based on url
$result = preg_match('#^(?:https?:)?(?://)?(?:www\.)?(?:youtu\.be|youtube\.com|youtube-nocookie\.com)/(?:embed/|v/|watch/?\?v=)?([\w-]{11})#iU', $iframe['src'], $matches);
//return false if there is no usable id
if(!$result || $matches[1] === 'videoseries') {
return false;
}
$youtube_id = $matches[1];
//parse iframe src url
$query = wp_parse_url(htmlspecialchars_decode($iframe['src']), PHP_URL_QUERY);
//clean up the url
$parsed_url = wp_parse_url($iframe['src'], -1);
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '//';
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
$path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
$youtube_url = $scheme . $host . $path;
//thumbnail resolutions
$resolutions = array(
'default' => array(
'width' => 120,
'height' => 90,
),
'mqdefault' => array(
'width' => 320,
'height' => 180,
),
'hqdefault' => array(
'width' => 480,
'height' => 360,
),
'sddefault' => array(
'width' => 640,
'height' => 480,
),
'maxresdefault' => array(
'width' => 1280,
'height' => 720,
)
);
//filter set resolution
$resolution = apply_filters('perfmatters_lazyload_youtube_thumbnail_resolution', 'hqdefault');
//finished youtube lazy output
$youtube_lazyload = '<div class="perfmatters-lazy-youtube" data-src="' . esc_attr($youtube_url) . '" data-id="' . esc_attr($youtube_id) . '" data-query="' . esc_attr($query) . '" onclick="perfmattersLazyLoadYouTube(this);">';
$youtube_lazyload.= '<div>';
$youtube_lazyload.= '<img src="https://i.ytimg.com/vi/' . esc_attr($youtube_id) .'/' . $resolution . '.jpg" alt="YouTube ' . __('video', 'perfmatters') . '" width="' . $resolutions[$resolution]['width'] . '" height="' . $resolutions[$resolution]['height'] . '" data-pin-nopin="true" nopin="nopin">';
$youtube_lazyload.= '<div class="play"></div>';
$youtube_lazyload.= '</div>';
$youtube_lazyload.= '</div>';
//noscript tag
if(apply_filters('perfmatters_lazyload_noscript', true)) {
$youtube_lazyload.= '<noscript>' . $iframe[0] . '</noscript>';
}
return $youtube_lazyload;
}
//lazy load videos
public static function lazyload_videos($html, $buffer) {
//match all videos
preg_match_all('#<video(\s.+)>.*</video>#iUs', $buffer, $videos, PREG_SET_ORDER);
if(!empty($videos)) {
//remove any duplicates
$videos = array_unique($videos, SORT_REGULAR);
foreach($videos as $video) {
//get video attributes array
$video_atts = Utilities::get_atts_array($video[1]);
//dont check excluded if forced attribute was found
if(!self::lazyload_excluded($video[1], self::lazyload_forced_atts())) {
//skip if exluded attribute was found
if(self::lazyload_excluded($video[1], self::lazyload_excluded_atts())) {
continue;
}
//skip if no-lazy class is found
if(!empty($video_atts['class']) && strpos($video_atts['class'], 'no-lazy') !== false) {
continue;
}
}
//skip if no src is found
if(empty($video_atts['src'])) {
continue;
}
//add lazyload class
$video_atts['class'] = !empty($video_atts['class']) ? $video_atts['class'] . ' ' . 'perfmatters-lazy' : 'perfmatters-lazy';
//migrate src
$video_atts['data-src'] = $video_atts['src'];
unset($video_atts['src']);
//replace video attributes string
$video_lazyload = str_replace($video[1], ' ' . Utilities::get_atts_string($video_atts), $video[0]);
//add noscript original video
if(apply_filters('perfmatters_lazyload_noscript', true)) {
$video_lazyload .= '<noscript>' . $video[0] . '</noscript>';
}
//replace video with placeholder
$html = str_replace($video[0], $video_lazyload, $html);
unset($video_lazyload);
}
}
return $html;
}
//initialize lazy loading
public static function init_images()
{
add_action('wp', array('Perfmatters\LazyLoad', 'queue_images'));
}
//queue image functions
public static function queue_images()
{
//check filters
if(empty(apply_filters('perfmatters_lazyload', !empty(Config::$options['lazyload']['lazy_loading']) || !empty(Config::$options['lazyload']['lazy_loading_iframes']) || !empty(Config::$options['lazyload']['css_background_images'])))) {
return;
}
//skip woocommerce
if(Utilities::is_woocommerce()) {
return;
}
//check post meta
if(Utilities::get_post_meta('perfmatters_exclude_lazy_loading')) {
return;
}
//disable wp native lazy loading
add_filter('wp_lazy_loading_enabled', '__return_false');
//actions + filters
add_action('perfmatters_output_buffer_template_redirect', array('Perfmatters\LazyLoad', 'lazyload_buffer'));
add_action('wp_enqueue_scripts', array('Perfmatters\LazyLoad', 'enqueue_scripts'));
add_filter('script_loader_tag', array('Perfmatters\LazyLoad', 'async_script'), 10, 2);
add_action('wp_head', array('Perfmatters\LazyLoad', 'lazyload_css'), PHP_INT_MAX);
//images only
if(!empty(Config::$options['lazyload']['lazy_loading'])) {
add_filter('wp_get_attachment_image_attributes', function($attr) {
unset($attr['loading']);
return $attr;
});
}
}
//lazy load buffer
public static function lazyload_buffer($html) {
$buffer = Utilities::clean_html($html);
//replace image tags
if(!empty(Config::$options['lazyload']['lazy_loading'])) {
$html = self::lazyload_parent_exclusions($html, $buffer);
$html = self::lazyload_pictures($html, $buffer);
$html = self::lazyload_background_images($html, $buffer);
$html = self::lazyload_images($html, $buffer);
}
//replace css background elements
if(!empty(Config::$options['lazyload']['css_background_images'])) {
$html = self::lazyload_css_background_images($html, $buffer);
}
return $html;
}
//enqueue lazy load script
public static function enqueue_scripts() {
wp_register_script('perfmatters-lazy-load', plugins_url('perfmatters/js/lazyload.min.js'), array(), PERFMATTERS_VERSION, true);
wp_enqueue_script('perfmatters-lazy-load');
wp_add_inline_script('perfmatters-lazy-load', self::inline_js(), 'before');
}
//add async tag to enqueued script
public static function async_script($tag, $handle) {
if($handle !== 'perfmatters-lazy-load') {
return $tag;
}
return str_replace(' src', ' async src', $tag);
}
//print lazy load styles
public static function lazyload_css() {
//print noscript styles
if(apply_filters('perfmatters_lazyload_noscript', true)) {
echo '<noscript><style>.perfmatters-lazy[data-src]{display:none !important;}</style></noscript>';
}
$styles = '';
//youtube thumbnails
if(!empty(Config::$options['lazyload']['lazy_loading_iframes']) && !empty(Config::$options['lazyload']['youtube_preview_thumbnails'])) {
$styles.= '.perfmatters-lazy-youtube{position:relative;width:100%;max-width:100%;height:0;padding-bottom:56.23%;overflow:hidden}.perfmatters-lazy-youtube img{position:absolute;top:0;right:0;bottom:0;left:0;display:block;width:100%;max-width:100%;height:auto;margin:auto;border:none;cursor:pointer;transition:.5s all;-webkit-transition:.5s all;-moz-transition:.5s all}.perfmatters-lazy-youtube img:hover{-webkit-filter:brightness(75%)}.perfmatters-lazy-youtube .play{position:absolute;top:50%;left:50%;right:auto;width:68px;height:48px;margin-left:-34px;margin-top:-24px;background:url('.plugins_url('perfmatters/img/youtube.svg').') no-repeat;background-position:center;background-size:cover;pointer-events:none}.perfmatters-lazy-youtube iframe{position:absolute;top:0;left:0;width:100%;height:100%;z-index:99}';
if(current_theme_supports('responsive-embeds')) {
$styles.= '.wp-has-aspect-ratio .wp-block-embed__wrapper{position:relative;}.wp-has-aspect-ratio .perfmatters-lazy-youtube{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;padding-bottom:0}';
}
}
//fade in effect
if(!empty(Config::$options['lazyload']['fade_in'])) {
$styles.= '.perfmatters-lazy.pmloaded,.perfmatters-lazy.pmloaded>img,.perfmatters-lazy>img.pmloaded,.perfmatters-lazy[data-ll-status=entered]{animation:' . apply_filters('perfmatters_fade_in_speed', 500) . 'ms pmFadeIn}@keyframes pmFadeIn{0%{opacity:0}100%{opacity:1}}';
}
//css background images
if(!empty(Config::$options['lazyload']['css_background_images'])) {
$styles.='body .perfmatters-lazy-css-bg:not([data-ll-status=entered]),body .perfmatters-lazy-css-bg:not([data-ll-status=entered]) *,body .perfmatters-lazy-css-bg:not([data-ll-status=entered])::before,body .perfmatters-lazy-css-bg:not([data-ll-status=entered])::after{background-image:none!important;will-change:transform;transition:opacity 0.025s ease-in,transform 0.025s ease-in!important;}';
}
//print styles
if(!empty($styles)) {
echo '<style>' . $styles . '</style>';
}
}
//inline lazy load js
private static function inline_js() {
$threshold = apply_filters('perfmatters_lazyload_threshold', !empty(Config::$options['lazyload']['threshold']) ? Config::$options['lazyload']['threshold'] : '0px');
if(ctype_digit($threshold)) {
$threshold.= 'px';
}
//declare lazy load options
$output = 'window.lazyLoadOptions={elements_selector:"img[data-src],.perfmatters-lazy,.perfmatters-lazy-css-bg",thresholds:"' . $threshold . ' 0px",class_loading:"pmloading",class_loaded:"pmloaded",callback_loaded:function(element){if(element.tagName==="IFRAME"){if(element.classList.contains("pmloaded")){if(typeof window.jQuery!="undefined"){if(jQuery.fn.fitVids){jQuery(element).parent().fitVids()}}}}}};';
//lazy loader initialized
$output.= 'window.addEventListener("LazyLoad::Initialized",function(e){var lazyLoadInstance=e.detail.instance;';
//dom monitoring
if(!empty(Config::$options['lazyload']['lazy_loading_dom_monitoring'])) {
$output.= 'var target=document.querySelector("body");var observer=new MutationObserver(function(mutations){lazyLoadInstance.update()});var config={childList:!0,subtree:!0};observer.observe(target,config);';
}
$output.= '});';
//youtube thumbnails
if(!empty(Config::$options['lazyload']['lazy_loading_iframes']) && !empty(Config::$options['lazyload']['youtube_preview_thumbnails'])) {
$autoplay = apply_filters('perfmatters_lazyload_youtube_autoplay', true);
$output.= 'function perfmattersLazyLoadYouTube(e){var t=document.createElement("iframe"),r="ID?";r+=0===e.dataset.query.length?"":e.dataset.query+"&"' . ($autoplay ? ',r+="autoplay=1"' : '') . ',t.setAttribute("src",r.replace("ID",e.dataset.src)),t.setAttribute("frameborder","0"),t.setAttribute("allowfullscreen","1"),t.setAttribute("allow","accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"),e.replaceChild(t,e.firstChild)}';
}
return $output;
}
//lazy load img tags
private static function lazyload_images($html, $buffer) {
//match all img tags
preg_match_all('#<img([^>]+?)\/?>#is', $buffer, $images, PREG_SET_ORDER);
if(!empty($images)) {
$lazy_image_count = 0;
$exclude_leading_images = apply_filters('perfmatters_exclude_leading_images', Config::$options['lazyload']['exclude_leading_images'] ?? 0);
//remove any duplicate images
$images = array_unique($images, SORT_REGULAR);
//loop through images
foreach($images as $image) {
$lazy_image_count++;
if($lazy_image_count <= $exclude_leading_images) {
continue;
}
//prepare lazy load image
$lazy_image = self::lazyload_image($image);
//replace image in html
$html = str_replace($image[0], $lazy_image, $html);
}
}
return $html;
}
//lazy load picture tags for webp
private static function lazyload_pictures($html, $buffer) {
//match all picture tags
preg_match_all('#<picture(.*)?>(.*)<\/picture>#isU', $buffer, $pictures, PREG_SET_ORDER);
if(!empty($pictures)) {
foreach($pictures as $picture) {
//get picture tag attributes
$picture_atts = Utilities::get_atts_array($picture[1]);
//dont check excluded if forced attribute was found
if(!self::lazyload_excluded($picture[1], self::lazyload_forced_atts())) {
//skip if no-lazy class is found
if((!empty($picture_atts['class']) && strpos($picture_atts['class'], 'no-lazy') !== false) || self::lazyload_excluded($picture[0], self::lazyload_excluded_atts())) {
//mark image for exclusion later
preg_match('#<img([^>]+?)\/?>#is', $picture[0], $image);
if(!empty($image)) {
$image_atts = Utilities::get_atts_array($image[1]);
$image_atts['class'] = (!empty($image_atts['class']) ? $image_atts['class'] . ' ' : '') . 'no-lazy';
$new_image = sprintf('<img %1$s />', Utilities::get_atts_string($image_atts));
$html = str_replace($image[0], $new_image, $html);
}
continue;
}
}
//match all source tags inside the picture
preg_match_all('#<source(\s.+)>#isU', $picture[2], $sources, PREG_SET_ORDER);
if(!empty($sources)) {
//remove any duplicate sources
$sources = array_unique($sources, SORT_REGULAR);
foreach($sources as $source) {
//skip if exluded attribute was found
if(self::lazyload_excluded($source[1], self::lazyload_excluded_atts())) {
continue;
}
//migrate srcet
$new_source = preg_replace('/([\s"\'])srcset/i', '${1}data-srcset', $source[0]);
//migrate sizes
$new_source = preg_replace('/([\s"\'])sizes/i', '${1}data-sizes', $new_source);
//replace source in html
$html = str_replace($source[0], $new_source, $html);
}
}
else {
continue;
}
}
}
return $html;
}
//lazy load background images
private static function lazyload_background_images($html, $buffer) {
//match all elements with inline styles
//preg_match_all('#<(?<tag>div|figure|section|span|li)(\s+[^>]+[\'"\s]?style\s*=\s*[\'"].*?background-image.*?[\'"][^>]*)>#is', $buffer, $elements, PREG_SET_ORDER); //alternate to possibly filter some out that don't have background images???
preg_match_all('#<(?<tag>div|figure|section|span|li|a)(\s+[^>]*[\'"\s]?style\s*=\s*[\'"].*?[\'"][^>]*)>#is', $buffer, $elements, PREG_SET_ORDER);
if(!empty($elements)) {
foreach($elements as $element) {
//get element tag attributes
$element_atts = Utilities::get_atts_array($element[2]);
//dont check excluded if forced attribute was found
if(!self::lazyload_excluded($element[2], self::lazyload_forced_atts())) {
//skip if no-lazy class is found
if(!empty($element_atts['class']) && strpos($element_atts['class'], 'no-lazy') !== false) {
continue;
}
//skip if exluded attribute was found
if(self::lazyload_excluded($element[2], self::lazyload_excluded_atts())) {
continue;
}
}
//skip if no style attribute
if(!isset($element_atts['style'])) {
continue;
}
//match background-image in style string
preg_match('#(([^;\s])*background(-(image|url))?)\s*:\s*(\s*url\s*\((?<url>[^)]+)\))\s*;?#is', $element_atts['style'], $url);
if(!empty($url)) {
$url['url'] = trim($url['url'], '\'" ');
//add lazyload class
$element_atts['class'] = !empty($element_atts['class']) ? $element_atts['class'] . ' ' . 'perfmatters-lazy' : 'perfmatters-lazy';
//remove background image url from inline style attribute
$element_atts['style'] = str_replace($url[0], '', $element_atts['style']);
//migrate src
$element_atts['data-bg'] = esc_url(trim(strip_tags(html_entity_decode($url['url'], ENT_QUOTES|ENT_HTML5)), '\'" '));
if(!empty($url[2])) {
$element_atts['data-bg-var'] = $url[1];
}
//build lazy element
$lazy_element = sprintf('<' . $element['tag'] . ' %1$s >', Utilities::get_atts_string($element_atts));
//replace element with placeholder
$html = str_replace($element[0], $lazy_element, $html);
unset($lazy_element);
}
else {
continue;
}
}
}
return $html;
}
//prep img tag for lazy loading
private static function lazyload_image($image) {
//if there are no attributes, return original match
if(empty($image[1])) {
return $image[0];
}
//get image attributes array
$image_atts = Utilities::get_atts_array($image[1]);
//get new attributes
if(empty($image_atts['src']) || (!self::lazyload_excluded($image[1], self::lazyload_forced_atts()) && ((!empty($image_atts['class']) && strpos($image_atts['class'], 'no-lazy') !== false) || self::lazyload_excluded($image[1], self::lazyload_excluded_atts()) || !empty($image_atts['fetchpriority'])))) {
return $image[0];
}
else {
//add lazyload class
$image_atts['class'] = !empty($image_atts['class']) ? $image_atts['class'] . ' ' . 'perfmatters-lazy' : 'perfmatters-lazy';
//migrate src
$image_atts['data-src'] = $image_atts['src'];
//add placeholder src
$width = !empty($image_atts['width']) ? $image_atts['width'] : 0;
$height = !empty($image_atts['height']) ? $image_atts['height'] : 0;
$image_atts['src'] = "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='" . $width . "'%20height='" . $height . "'%20viewBox='0%200%20" . $width . "%20" . $height . "'%3E%3C/svg%3E";
//migrate srcset
if(!empty($image_atts['srcset'])) {
$image_atts['data-srcset'] = $image_atts['srcset'];
unset($image_atts['srcset']);
}
//migrate sizes
if(!empty($image_atts['sizes'])) {
$image_atts['data-sizes'] = $image_atts['sizes'];
unset($image_atts['sizes']);
}
//unset existing loading attribute
if(isset($image_atts['loading'])) {
unset($image_atts['loading']);
}
}
//replace attributes
$output = sprintf('<img %1$s />', Utilities::get_atts_string($image_atts));
//original noscript image
if(apply_filters('perfmatters_lazyload_noscript', true)) {
$output.= "<noscript>" . $image[0] . "</noscript>";
}
return $output;
}
//lazy load css background images
private static function lazyload_css_background_images($html, $buffer) {
if(!empty(Config::$options['lazyload']['css_background_selectors'])) {
//match all selectors
preg_match_all('#<(?>div|section)(\s[^>]*?(' . implode('|', Config::$options['lazyload']['css_background_selectors']) . ').*?)>#i', $buffer, $selectors, PREG_SET_ORDER);
if(!empty($selectors)) {
foreach($selectors as $selector) {
$selector_atts = Utilities::get_atts_array($selector[1]);
$selector_atts['class'] = !empty($selector_atts['class']) ? $selector_atts['class'] . ' ' . 'perfmatters-lazy-css-bg' : 'perfmatters-lazy-css-bg';
//replace video attributes string
$selector_lazyload = str_replace($selector[1], ' ' . Utilities::get_atts_string($selector_atts), $selector[0]);
//replace video with placeholder
$html = str_replace($selector[0], $selector_lazyload, $html);
unset($selector_lazyload);
}
}
}
return $html;
}
//mark images inside parent exclusions as no-lazy
private static function lazyload_parent_exclusions($html, $buffer) {
if(!empty(Config::$options['lazyload']['lazy_loading_parent_exclusions'])) {
//match all selectors
preg_match_all('#<(div|section|figure)(\s[^>]*?(' . implode('|', Config::$options['lazyload']['lazy_loading_parent_exclusions']) . ').*?)>.*?<img.*?<\/\g1>#is', $buffer, $selectors, PREG_SET_ORDER);
if(!empty($selectors)) {
foreach($selectors as $selector) {
//match all img tags
preg_match_all('#<img([^>]+?)\/?>#is', $selector[0], $images, PREG_SET_ORDER);
if(!empty($images)) {
//remove any duplicate images
$images = array_unique($images, SORT_REGULAR);
//loop through images
foreach($images as $image) {
$image_atts = Utilities::get_atts_array($image[1]);
$image_atts['class'] = !empty($image_atts['class']) ? $image_atts['class'] . ' ' . 'no-lazy' : 'no-lazy';
//replace video attributes string
$new_image = str_replace($image[1], ' ' . Utilities::get_atts_string($image_atts), $image[0]);
//replace video with placeholder
$html = str_replace($image[0], $new_image, $html);
unset($new_image);
}
}
}
}
}
return $html;
}
//get forced attributes
private static function lazyload_forced_atts() {
return apply_filters('perfmatters_lazyload_forced_attributes', array());
}
//get excluded attributes
private static function lazyload_excluded_atts() {
//base exclusions
$attributes = array(
'data-perfmatters-preload',
'gform_ajax_frame',
';base64'
);
//get exclusions added from settings
if(!empty(Config::$options['lazyload']['lazy_loading_exclusions']) && is_array(Config::$options['lazyload']['lazy_loading_exclusions'])) {
$attributes = array_unique(array_merge($attributes, Config::$options['lazyload']['lazy_loading_exclusions']));
}
return apply_filters('perfmatters_lazyload_excluded_attributes', $attributes);
}
//check for excluded attributes in attributes string
private static function lazyload_excluded($string, $excluded) {
if(!is_array($excluded)) {
(array) $excluded;
}
if(empty($excluded)) {
return false;
}
foreach($excluded as $exclude) {
if(strpos($string, $exclude) !== false) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,144 @@
<?php
namespace Perfmatters;
class Meta
{
private static $meta_options;
//initialize meta functions
public static function init()
{
if(!is_admin() || !current_user_can('manage_options')) {
return;
}
global $pagenow;
if(in_array($pagenow, ['post.php', 'new-post.php'])) {
//setup meta options
self::get_meta_options();
//exclude specific woocommerce pages
if(class_exists('WooCommerce') && !empty($_GET['post'])) {
$wc_pages = array_filter(array(
get_option('woocommerce_cart_page_id'),
get_option('woocommerce_checkout_page_id'),
get_option('woocommerce_myaccount_page_id')
));
if(in_array($_GET['post'], $wc_pages)) {
return;
}
}
//meta actions
add_action('add_meta_boxes', array('Perfmatters\Meta', 'add_meta_boxes'), 1);
add_action('save_post', array('Perfmatters\Meta', 'save_meta'), 1, 2);
}
}
//add meta boxes
public static function add_meta_boxes() {
foreach(self::$meta_options as $id => $details) {
if($details['value']) {
//display meta box if at least one value is set
add_meta_box('perfmatters', 'Perfmatters', array('Perfmatters\Meta', 'load_meta_box'), get_post_types(array('public' => true)), 'side', 'high');
break;
}
}
}
//display meta box
public static function load_meta_box() {
global $post;
//noncename needed to verify where the data originated
echo '<input type="hidden" name="perfmatters_meta_noncename" id="perfmatters_meta_noncename" value="' . wp_create_nonce(plugin_basename(__FILE__)) . '" />';
//print inputs
foreach(self::$meta_options as $id => $details) {
//existing meta value
$meta = get_post_meta($post->ID, 'perfmatters_exclude_' . $id, true);
//individual input
echo '<div' . (!$details['value'] ? ' class="hidden"' : '') . '>';
echo '<label for="perfmatters_exclude_' . $id . '">';
echo '<input type="hidden" name="perfmatters_exclude_' . $id . '" value="1" />';
echo '<input type="checkbox" name="perfmatters_exclude_' . $id . '" id="perfmatters_exclude_' . $id . '"' . (!$meta ? " checked" : "") . ' value="" class="widefat" />';
echo $details['name'];
echo '</label>';
echo '</div>';
}
//clear used css
if(!empty(Config::$options['assets']['remove_unused_css'])) {
echo '<style>.perfmatters-clear-post-used-css{display:inline-block;margin-top:10px}.perfmatters-clear-post-used-css .spinner{display:none;float:none}.perfmatters-clear-post-used-css .spinner.is-active{display:inline-block}.perfmatters-clear-post-used-css .dashicons-yes{display:none;margin:2px 10px;color:green;font-size:26px}</style>';
echo '<div class="perfmatters-clear-post-used-css">';
echo '<a class="button button-secondary" id="perfmatters-clear-post-used-css" value="1">' . __('Clear Used CSS', 'perfmatters') . '</a>';
echo '<span class="spinner"></span>';
echo '<span class="dashicons dashicons-yes"></span>';
echo wp_nonce_field('perfmatters_clear_post_used_css', 'perfmatters_clear_post_used_css', false, false);
echo '</div>';
echo '<script>jQuery(document).ready(function(s){s("#perfmatters-clear-post-used-css").click(function(t){if(t.preventDefault(),$button=s(this),$button.hasClass("disabled"))return!1;$button.addClass("disabled"),$button.siblings(".spinner").addClass("is-active");var e={action:"perfmatters_clear_post_used_css",nonce:$button.siblings("#perfmatters_clear_post_used_css").val(),post_id:parseInt(s("#post_ID").val())};s.post(ajaxurl,e,function(s){$button.siblings(".spinner").removeClass("is-active"),$button.siblings(".dashicons-yes").fadeIn().css("display","inline-block"),setTimeout(function(){$button.siblings(".dashicons-yes").fadeOut()},1e3),$button.removeClass("disabled")})})});</script>';
}
}
//save meta box data
public static function save_meta($post_id, $post) {
//verify this came from the our screen and with proper authorization, because save_post can be triggered at other times
if(empty($_POST['perfmatters_meta_noncename']) || !wp_verify_nonce($_POST['perfmatters_meta_noncename'], plugin_basename(__FILE__))) {
return;
}
//dont save for revisions
if($post->post_type == 'revision') {
return;
}
//saved data
$perfmatters_meta = array();
foreach(self::$meta_options as $id => $details) {
$key = 'perfmatters_exclude_' . $id;
if(!empty($_POST[$key]) || get_post_meta($post->ID, $key, true) != false) {
//update option in post meta
update_post_meta($post->ID, $key, $_POST[$key] ?? "");
}
}
}
//populate meta options array for other functions
private static function get_meta_options() {
self::$meta_options = array(
'defer_js' => array(
'name' => __('Defer JavaScript', 'perfmatters'),
'value' => !empty(Config::$options['assets']['defer_js'])
),
'delay_js' => array(
'name' => __('Delay JavaScript', 'perfmatters'),
'value' => !empty(Config::$options['assets']['delay_js'])
),
'unused_css' => array(
'name' => __('Unused CSS', 'perfmatters'),
'value' => !empty(Config::$options['assets']['remove_unused_css'])
),
'lazy_loading' => array(
'name' => __('Lazy Loading', 'perfmatters'),
'value' => !empty(Config::$options['lazyload']['lazy_loading']) || !empty(Config::$options['lazyload']['lazy_loading_iframes'])
),
'instant_page' => array(
'name' => __('Instant Page', 'perfmatters'),
'value' => !empty(Config::$options['preload']['instant_page'])
)
);
}
}

View File

@@ -0,0 +1,359 @@
<?php
namespace Perfmatters;
class Preload
{
private static $fetch_priority;
private static $preloads;
private static $critical_images;
private static $preloads_ready = array();
private static $used_srcs = array();
//initialize preload functions
public static function init()
{
add_action('wp', array('Perfmatters\Preload', 'queue'));
}
//queue functions
public static function queue()
{
//fetch priority
self::$fetch_priority = apply_filters('perfmatters_fetch_priority', Config::$options['preload']['fetch_priority'] ?? array());
if(!empty(self::$fetch_priority)) {
add_action('perfmatters_output_buffer_template_redirect', array('Perfmatters\Preload', 'add_fetch_priority'));
}
//preloads
self::$preloads = apply_filters('perfmatters_preloads', Config::$options['preload']['preload'] ?? array());
self::$critical_images = apply_filters('perfmatters_preload_critical_images', Config::$options['preload']['critical_images'] ?? 0);
if(!empty(self::$preloads) || !empty(self::$critical_images)) {
add_action('perfmatters_output_buffer_template_redirect', array('Perfmatters\Preload', 'add_preloads'));
}
}
//add fetch priority
public static function add_fetch_priority($html) {
$selectors = array();
$parent_selectors = array();
foreach(self::$fetch_priority as $line) {
//device type check
if(!empty($line['device'])) {
$device_type = wp_is_mobile() ? 'mobile' : 'desktop';
if($line['device'] != $device_type) {
continue;
}
}
//location check
if(!empty($line['locations'])) {
if(!self::location_check($line['locations'])) {
continue;
}
}
//add selector
if(!empty($line['parent'])) {
$parent_selectors[] = $line['selector'];
}
else {
$selectors[] = $line['selector'];
}
}
//parent selectors
if(!empty($parent_selectors)) {
//match all selectors
preg_match_all('#<(div|section|figure)(\s[^>]*?(' . implode('|', $parent_selectors) . ').*?)>.*?<(link|img|script).*?<\/\g1>#is', $html, $parents, PREG_SET_ORDER);
if(!empty($parents)) {
foreach($parents as $parent) {
//match all tags
preg_match_all('#<(?>link|img|script)(\s[^>]*?)>#is', $parent[0], $tags, PREG_SET_ORDER);
if(!empty($tags)) {
//loop through images
foreach($tags as $tag) {
$tag_atts = Utilities::get_atts_array($tag[1]);
$key = array_search($parent[2], array_column(self::$fetch_priority, 'selector'));
$tag_atts['fetchpriority'] = self::$fetch_priority[$key]['priority'];;
//replace video attributes string
$new_tag = str_replace($tag[1], ' ' . Utilities::get_atts_string($tag_atts), $tag[0]);
//replace video with placeholder
$html = str_replace($tag[0], $new_tag, $html);
unset($new_tag);
}
}
}
}
}
//selectors
if(!empty($selectors)) {
preg_match_all('#<(?>link|img|script)(\s[^>]*?(' . implode('|', $selectors) . ').*?)>#is', $html, $matches, PREG_SET_ORDER);
if(!empty($matches)) {
foreach($matches as $tag) {
$atts = Utilities::get_atts_array($tag[1]);
$key = array_search($tag[2], array_column(self::$fetch_priority, 'selector'));
$atts['fetchpriority'] = self::$fetch_priority[$key]['priority'];;
//replace video attributes string
$new_tag = str_replace($tag[1], ' ' . Utilities::get_atts_string($atts), $tag[0]);
//replace video with placeholder
$html = str_replace($tag[0], $new_tag, $html);
}
}
}
return $html;
}
//add preloads to html
public static function add_preloads($html) {
if(!empty(self::$critical_images)) {
self::add_critical_image_preloads($html, Utilities::clean_html($html));
}
if(!empty(self::$preloads)) {
$mime_types = array(
'svg' => 'image/svg+xml',
'ttf' => 'font/ttf',
'otf' => 'font/otf',
'woff' => 'font/woff',
'woff2' => 'font/woff2',
'eot' => 'application/vnd.ms-fontobject',
'sfnt' => 'font/sfnt'
);
foreach(self::$preloads as $line) {
//device type check
if(!empty($line['device'])) {
$device_type = wp_is_mobile() ? 'mobile' : 'desktop';
if($line['device'] != $device_type) {
continue;
}
}
//location check
if(!empty($line['locations'])) {
if(!self::location_check($line['locations'])) {
continue;
}
}
$mime_type = "";
if(!empty($line['as']) && $line['as'] == 'font') {
$path_info = pathinfo($line['url']);
$mime_type = !empty($path_info['extension']) && isset($mime_types[$path_info['extension']]) ? $mime_types[$path_info['extension']] : "";
}
//print script/style handle as preload
if(!empty($line['as']) && in_array($line['as'], array('script', 'style'))) {
if(strpos($line['url'], '.') === false) {
global $wp_scripts;
global $wp_styles;
$scripts_arr = $line['as'] == 'script' ? $wp_scripts : $wp_styles;
if(!empty($scripts_arr)) {
$scripts_arr = $scripts_arr->registered;
if(array_key_exists($line['url'], $scripts_arr)) {
$url = $scripts_arr[$line['url']]->src;
$parsed_url = parse_url($scripts_arr[$line['url']]->src);
if(empty($parsed_url['host'])) {
$url = site_url($url);
}
if(strpos($url, '?') === false) {
$ver = $scripts_arr[$line['url']]->ver;
if(empty($ver)) {
$ver = get_bloginfo('version');
}
}
$line['url'] = $url . (!empty($ver) ? '?ver=' . $ver : '');
}
else {
continue;
}
}
else {
continue;
}
}
}
$preload = "<link rel='preload' href='" . $line['url'] . "'" . (!empty($line['as']) ? " as='" . $line['as'] . "'" : "") . (!empty($mime_type) ? " type='" . $mime_type . "'" : "") . (!empty($line['crossorigin']) ? " crossorigin" : "") . (!empty($line['as']) && $line['as'] == 'style' ? " onload=\"this.rel='stylesheet';this.removeAttribute('onload');\"" : "") . ">";
if($line['as'] == 'image') {
array_unshift(self::$preloads_ready, $preload);
}
else {
self::$preloads_ready[] = $preload;
}
}
}
if(!empty(self::$preloads_ready)) {
$preloads_string = "";
foreach(apply_filters('perfmatters_preloads_ready', self::$preloads_ready) as $preload) {
$preloads_string.= $preload;
}
$pos = strpos($html, '</title>');
if($pos !== false) {
$html = substr_replace($html, '</title>' . $preloads_string, $pos, 8);
}
}
return $html;
}
//add critical image preloads
public static function add_critical_image_preloads(&$html, $clean_html) {
//match all image formats
preg_match_all('#(<picture.*?)?<img([^>]+?)\/?>(?><\/picture>)?#is', $clean_html, $matches, PREG_SET_ORDER);
if(!empty($matches)) {
$exclusions = apply_filters('perfmatters_critical_image_exclusions', array(
';base64',
'w3.org'
));
$count = 0;
foreach($matches as $match) {
if($count >= self::$critical_images) {
break;
}
if(strpos($match[0], 'secure.gravatar.com') !== false) {
continue;
}
if(!empty($exclusions) && is_array($exclusions)) {
foreach($exclusions as $exclusion) {
if(strpos($match[0], $exclusion) !== false) {
continue 2;
}
}
}
//picture tag
if(!empty($match[1])) {
preg_match('#<source([^>]+?image\/(webp|avif)[^>]+?)\/?>#is', $match[0], $source);
if(!empty($source)) {
if(self::generate_critical_image_preload($source[1])) {
$new_picture = str_replace('<picture', '<picture data-perfmatters-preload', $match[0]);
$new_picture = str_replace('<img', '<img data-perfmatters-preload', $new_picture);
$html = str_replace($match[0], $new_picture, $html);
$count++;
}
continue;
}
}
//img tag
if(!empty($match[2])) {
if(self::generate_critical_image_preload($match[2])) {
$new_image = str_replace('<img', '<img data-perfmatters-preload', $match[0]);
$html = str_replace($match[0], $new_image, $html);
$count++;
}
}
}
}
if(!empty(self::$preloads_ready)) {
ksort(self::$preloads_ready);
}
}
//generate preload link from att string
private static function generate_critical_image_preload($att_string) {
if(!empty($att_string)) {
$atts = Utilities::get_atts_array($att_string);
$src = $atts['data-src'] ?? $atts['src'] ?? '';
//dont add if src was already used
if(in_array($src, self::$used_srcs)) {
return false;
}
//generate preload
self::$preloads_ready[] = '<link rel="preload" href="' . $src . '" as="image"' . (!empty($atts['srcset']) ? ' imagesrcset="' . $atts['srcset'] . '"' : '') . (!empty($atts['sizes']) ? ' imagesizes="' . $atts['sizes'] . '"' : '') . ' />';
//mark src used and return
if(!empty($src)) {
self::$used_srcs[] = $src;
}
return true;
}
}
private static function location_check($locations) {
$location_match = false;
$exploded_locations = explode(',', $locations);
$trimmed_locations = array_map('trim', $exploded_locations);
//single post exclusion
if(is_singular()) {
global $post;
if(in_array($post->ID, $trimmed_locations)) {
$location_match = true;
}
}
//posts page exclusion
elseif(is_home() && in_array('blog', $trimmed_locations)) {
$location_match = true;
}
elseif(is_archive()) {
//woocommerce shop check
if(function_exists('is_shop') && is_shop()) {
if(in_array(wc_get_page_id('shop'), $trimmed_locations)) {
$location_match = true;
}
}
}
return $location_match;
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace Perfmatters;
class Utilities
{
//get given post meta option for current post
public static function get_post_meta($option) {
global $post;
if(!is_object($post)) {
return false;
}
if(is_home()) {
$post_id = get_queried_object_id();
}
if(is_singular() && isset($post)) {
$post_id = $post->ID;
}
return (isset($post_id)) ? get_post_meta($post_id, $option, true) : false;
}
//remove unecessary bits from html for search
public static function clean_html($html) {
//remove existing script tags
$html = preg_replace('/<script\b(?:[^>]*)>(?:.+)?<\/script>/Umsi', '', $html);
//remove existing noscript tags
$html = preg_replace('#<noscript>(?:.+)</noscript>#Umsi', '', $html);
return $html;
}
//get array of element attributes from attribute string
public static function get_atts_array($atts_string) {
if(!empty($atts_string)) {
$atts_array = array_map(
function(array $attribute) {
return $attribute['value'];
},
wp_kses_hair($atts_string, wp_allowed_protocols())
);
return $atts_array;
}
return false;
}
//get attribute string from array of element attributes
public static function get_atts_string($atts_array) {
if(!empty($atts_array)) {
$assigned_atts_array = array_map(
function($name, $value) {
if($value === '') {
return $name;
}
return sprintf('%s="%s"', $name, esc_attr($value));
},
array_keys($atts_array),
$atts_array
);
$atts_string = implode(' ', $assigned_atts_array);
return $atts_string;
}
return false;
}
//check for specific woocommerce pages
public static function is_woocommerce() {
if(class_exists('WooCommerce') && (is_cart() || is_checkout() || is_account_page())) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,2 @@
<?php
/* Blank Comments Template */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,120 @@
<?php
//register network menu and settings
function perfmatters_network_admin_menu() {
//Add Network Settings Menu Item
$perfmatters_network_settings_page = add_submenu_page('settings.php', 'Perfmatters Network Settings', 'Perfmatters', 'manage_network_options', 'perfmatters', 'perfmatters_admin');
//Add Load Action to Enqueue Scripts
add_action('load-' . $perfmatters_network_settings_page, 'perfmatters_settings_load');
//Create Site Option if Not Found
if(get_site_option('perfmatters_network') == false) {
add_site_option('perfmatters_network', true);
}
//Add Settings Section
add_settings_section('perfmatters_network', 'Network Setup', '__return_false', 'perfmatters_network');
//Add Options Fields
add_settings_field(
'access',
perfmatters_title(__('Network Access', 'perfmatters'), 'access', 'https://perfmatters.io/docs/wordpress-multisite/'),
'perfmatters_network_access_callback',
'perfmatters_network',
'perfmatters_network',
array(
'tooltip' => __('Choose who has access to manage Perfmatters plugin settings.', 'perfmatters')
)
);
add_settings_field(
'default',
perfmatters_title(__('Network Default', 'perfmatters'), 'default', 'https://perfmatters.io/docs/wordpress-multisite/'),
'perfmatters_network_default_callback',
'perfmatters_network',
'perfmatters_network',
array(
'tooltip' => __('Choose a subsite that you want to pull default settings from.', 'perfmatters')
)
);
//Clean Uninstall
add_settings_field(
'clean_uninstall',
perfmatters_title(__('Clean Uninstall', 'perfmatters'), 'clean_uninstall', 'https://perfmatters.io/docs/clean-uninstall/'),
'perfmatters_print_input',
'perfmatters_network',
'perfmatters_network',
array(
'id' => 'clean_uninstall',
'option' => 'perfmatters_network',
'tooltip' => __('When enabled, this will cause all Perfmatters options data to be removed from your database when the plugin is uninstalled.', 'perfmatters')
)
);
//Register Setting
register_setting('perfmatters_network', 'perfmatters_network');
}
add_filter('network_admin_menu', 'perfmatters_network_admin_menu');
//network access callback
function perfmatters_network_access_callback() {
$perfmatters_network = get_site_option('perfmatters_network');
echo "<select name='perfmatters_network[access]' id='access'>";
echo "<option value=''>" . __('Site Admins (Default)', 'perfmatters') . "</option>";
echo "<option value='super' " . ((!empty($perfmatters_network['access']) && $perfmatters_network['access'] == 'super') ? "selected" : "") . ">" . __('Super Admins Only', 'perfmatters') . "</option>";
echo "<select>";
//tooltip
if(!empty($args['tooltip'])) {
perfmatters_tooltip($args['tooltip']);
}
}
//network default callback
function perfmatters_network_default_callback() {
$perfmatters_network = get_site_option('perfmatters_network');
echo "<select name='perfmatters_network[default]' id='default'>";
$sites = array_map('get_object_vars', get_sites(array('deleted' => 0, 'number' => 1000)));
if(is_array($sites) && $sites !== array()) {
echo "<option value=''>" . __('None', 'perfmatters') . "</option>";
foreach($sites as $site) {
echo "<option value='" . $site['blog_id'] . "' " . ((!empty($perfmatters_network['default']) && $perfmatters_network['default'] == $site['blog_id']) ? "selected" : "") . ">" . $site['blog_id'] . ": " . $site['domain'] . $site['path'] . "</option>";
}
}
echo "<select>";
//tooltip
if(!empty($args['tooltip'])) {
perfmatters_tooltip($args['tooltip']);
}
}
//update perfmatters network options
function perfmatters_update_network_options() {
//Verify Post Referring Page
check_admin_referer('perfmatters_network-options');
//Get Registered Options
global $new_whitelist_options;
$options = $new_whitelist_options['perfmatters_network'];
//Loop Through Registered Options
foreach($options as $option) {
if(isset($_POST[$option])) {
//Update Site Uption
update_site_option($option, $_POST[$option]);
}
}
//Redirect to Network Settings Page
wp_redirect(add_query_arg(array('page' => 'perfmatters', 'updated' => 'true'), network_admin_url('settings.php')));
exit;
}
add_action('network_admin_edit_perfmatters_update_network_options', 'perfmatters_update_network_options');

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,162 @@
<?php
//save license key
if(isset($_POST['perfmatters_save_license']) && isset($_POST['perfmatters_edd_license_key'])) {
//save license option
if(is_network_admin()) {
update_site_option('perfmatters_edd_license_key', trim($_POST['perfmatters_edd_license_key']));
}
else {
update_option('perfmatters_edd_license_key', trim($_POST['perfmatters_edd_license_key']));
}
if(is_multisite()) {
//check license info
$license_info = perfmatters_check_license();
if(!empty($license_info->activations_left) && $license_info->activations_left == 'unlimited') {
//activate after save
perfmatters_activate_license();
}
}
else {
//activate after save
perfmatters_activate_license();
}
}
//activate license
if(isset($_POST['perfmatters_edd_license_activate'])) {
perfmatters_activate_license();
}
//deactivate license
if(isset($_POST['perfmatters_edd_license_deactivate'])) {
perfmatters_deactivate_license();
}
//remove license
if(isset($_POST['perfmatters_remove_license'])) {
//deactivate before removing
perfmatters_deactivate_license();
//remove license option
if(is_network_admin()) {
delete_site_option('perfmatters_edd_license_key');
}
else {
delete_option('perfmatters_edd_license_key');
}
}
//get license key
$license = is_network_admin() ? get_site_option('perfmatters_edd_license_key') : get_option('perfmatters_edd_license_key');
//start custom license form
echo "<form method='post' action=''>";
echo '<div class="perfmatters-settings-section">';
//tab header
echo "<h2><span class='dashicons dashicons-admin-network'></span>" . __('License', 'perfmatters') . "</h2>";
echo "<table class='form-table'>";
echo "<tbody>";
//license key
echo "<tr>";
echo "<th>" . perfmatters_title(__('License Key', 'perfmatters'), (empty($license) ? 'perfmatters_edd_license_key' : false), 'https://perfmatters.io/docs/troubleshooting-license-key-activation/') . "</th>";
echo "<td>";
echo "<input id='perfmatters_edd_license_key' name='perfmatters_edd_license_key' type='text' class='regular-text' value='" . (!empty($license) ? substr($license, 0, 4) . '**************************' : '') . "' style='margin-right: 10px;' maxlength='50' />";
if(empty($license)) {
//save license button
echo "<input type='submit' name='perfmatters_save_license' class='button button-primary' value='" . __('Save License', 'perfmatters') . "'>";
}
else {
//remove license button
echo "<input type='submit' class='button perfmatters-button-warning' name='perfmatters_remove_license' value='" . __('Remove License', 'perfmatters') . "' />";
}
perfmatters_tooltip(__('Save or remove your license key.', 'perfmatters'));
echo "</td>";
echo "</tr>";
if(!empty($license)) {
//force disable styles on license input
echo "<style>
input[name=\"perfmatters_edd_license_key\"] {
background: rgba(255,255,255,.5);
border-color: rgba(222,222,222,.75);
box-shadow: inset 0 1px 2px rgba(0,0,0,.04);
color: rgba(51,51,51,.5);
pointer-events: none;
}
</style>";
//check license info
$license_info = perfmatters_check_license();
if(!empty($license_info)) {
//activate/deactivate license
if(!empty($license_info->license) && $license_info->license != 'invalid') {
echo "<tr>";
echo "<th>" . __('Activate License', 'perfmatters') . "</th>";
echo "<td>";
if($license_info->license == 'valid') {
echo "<input type='submit' class='button-secondary' name='perfmatters_edd_license_deactivate' value='" . __('Deactivate License', 'perfmatters') . "' style='margin-right: 10px;' />";
echo "<span style='color:green;line-height:30px;'><span class='dashicons dashicons-cloud'style='line-height:30px;'></span> " . __('License is activated.', 'novashare') . "</span>";
}
elseif(!is_multisite() || (!empty($license_info->activations_left) && $license_info->activations_left == 'unlimited')) {
echo "<input type='submit' class='button-secondary' name='perfmatters_edd_license_activate' value='" . __('Activate License', 'perfmatters') . "' style='margin-right: 10px;' />";
echo "<span style='color:red;line-height:30px;'><span class='dashicons dashicons-warning'style='line-height:30px;'></span> " . __('License is not activated.', 'novashare') . "</span>";
}
else {
echo "<span style='color:red; display: block;'>" . __('Unlimited License needed for use in a multisite environment. Please contact support to upgrade.', 'perfmatters') . "</span>";
}
echo "</td>";
echo "</tr>";
}
//license status (active/expired)
if(!empty($license_info->license)) {
echo "<tr>";
echo "<th>" . __('License Status', 'perfmatters') . "</th>";
echo "<td" . ($license_info->license == "expired" ? " style='color: red;'" : "") . ">";
echo ucfirst($license_info->license);
if($license_info->license == "expired") {
echo "<br />";
echo "<a href='https://perfmatters.io/checkout/?edd_license_key=" . $license . "&download_id=696' class='button-primary' style='margin-top: 10px;' target='_blank'>" . __('Renew Your License for Updates + Support!', 'perfmatters') . "</a>";
}
echo "</td>";
echo "</tr>";
}
//licenses used
if(!empty($license_info->site_count) && !empty($license_info->license_limit) && !is_network_admin()) {
echo "<tr>";
echo "<th>" . __('Licenses Used', 'perfmatters') . "</th>";
echo "<td>" . $license_info->site_count . "/" . $license_info->license_limit . "</td>";
echo "</tr>";
}
//expiration date
if(!empty($license_info->expires)) {
echo "<tr>";
echo "<th>" . __('Expiration Date', 'perfmatters') . "</th>";
echo "<td>" . ($license_info->expires != 'lifetime' ? date("F d, Y", strtotime($license_info->expires)) : __('Lifetime', 'perfmatters')) . "</td>";
echo "</tr>";
}
}
}
echo "</tbody>";
echo "</table>";
echo '</div>';
echo "</form>";

View File

@@ -0,0 +1,162 @@
<?php
if(isset($_POST['perfmatters_apply_defaults'])) {
check_admin_referer('perfmatters-network-apply');
if(isset($_POST['perfmatters_network_apply_blog']) && is_numeric($_POST['perfmatters_network_apply_blog'])) {
$blog = get_blog_details($_POST['perfmatters_network_apply_blog']);
if($blog) {
$perfmatters_network = get_site_option('perfmatters_network');
if(!empty($perfmatters_network['default'])) {
if($blog->blog_id != $perfmatters_network['default']) {
$option_names = array(
'perfmatters_options',
'perfmatters_tools'
);
foreach($option_names as $option_name) {
//clear selected blog previous option
delete_blog_option($blog->blog_id, $option_name);
//grab new option from default blog
$new_option = get_blog_option($perfmatters_network['default'], $option_name);
//remove options we don't want to copy
if($option_name == 'perfmatters_option') {
unset($new_option['cdn']['cdn_url']);
}
//update selected blog with default option
update_blog_option($blog->blog_id, $option_name, $new_option);
}
//Default Settings Updated Notice
echo "<div class='notice updated is-dismissible'><p>" . __('Default settings applied!', 'perfmatters') . "</p></div>";
}
else {
//Can't Apply to Network Default
echo "<div class='notice error is-dismissible'><p>" . __('Select a site that is not already the Network Default.', 'perfmatters') . "</p></div>";
}
}
else {
//Network Default Not Set
echo "<div class='notice error is-dismissible'><p>" . __('Network Default not set.', 'perfmatters') . "</p></div>";
}
}
else {
//Blog Not Found Notice
echo "<div class='notice error is-dismissible'><p>" . __('Error: Blog Not Found.', 'perfmatters') . "</p></div>";
}
}
}
elseif(isset($_POST['perfmatters_apply_defaults_all'])) {
check_admin_referer('perfmatters-network-apply');
$perfmatters_network = get_site_option('perfmatters_network');
if(!empty($perfmatters_network['default'])) {
$sites = array_map('get_object_vars', get_sites(array('deleted' => 0, 'number' => 1000)));
if(is_array($sites) && $sites !== array()) {
$update_count = 0;
foreach($sites as $site) {
$apply = perfmatters_apply_defaults_to_blog($site['blog_id'], $perfmatters_network['default']);
if($apply) {
$update_count++;
}
}
if($update_count > 0) {
//default settings applied
echo "<div class='notice updated is-dismissible'><p>" . __('Default settings applied!', 'perfmatters') . "</p></div>";
}
}
else {
//no sites available
echo "<div class='notice error is-dismissible'><p>" . __('No available sites found.', 'perfmatters') . "</p></div>";
}
}
else {
//network default not set
echo "<div class='notice error is-dismissible'><p>" . __('Network Default not set.', 'perfmatters') . "</p></div>";
}
}
//Options Updated
if(isset($_GET['updated'])) {
echo "<div class='notice updated is-dismissible'><p>" . __('Options saved.', 'perfmatters') . "</p></div>";
}
echo "<form method='POST' action='edit.php?action=perfmatters_update_network_options'>";
settings_fields('perfmatters_network');
perfmatters_settings_section('perfmatters_network', 'perfmatters_network', 'dashicons-networking');
submit_button();
echo "</form>";
echo "<form method='POST' style='margin: 30px auto 10px;'>";
echo '<div>';
echo "<h2>" . __('Apply Default Settings', 'perfmatters') . "</h2>";
wp_nonce_field('perfmatters-network-apply', '_wpnonce', true, true);
echo "<p>" . __('Select a site from the dropdown and click to apply the settings from your network default (above).', 'perfmatters') . "</p>";
echo "<select name='perfmatters_network_apply_blog' style='margin-right: 10px;'>";
$sites = array_map('get_object_vars', get_sites(array('deleted' => 0, 'number' => 1000)));
if(is_array($sites) && $sites !== array()) {
echo "<option value=''>" . __('Select a Site', 'perfmatters') . "</option>";
foreach($sites as $site) {
echo "<option value='" . $site['blog_id'] . "'>" . $site['blog_id'] . ": " . $site['domain'] . $site['path'] . "</option>";
}
}
echo "</select>";
echo "<input type='submit' name='perfmatters_apply_defaults' value='" . __('Apply Default Settings', 'perfmatters') . "' class='button' />";
echo "<br />";
echo "<p>" . __('Apply the settings from your network default to all sites. Depending on the amount, this may take a while.', 'perfmatters') . "</p>";
echo "<input type='submit' name='perfmatters_apply_defaults_all' value='" . __('Apply Default Settings to All Sites', 'perfmatters') . "' class='button' onclick='return confirm(\"" . __('Are you sure? This will permanently overwrite all Perfmatters options for all subsites.', 'perfmatters') . "\");' />";
echo '</div>';
echo "</form>";
//apply defaults to blog
function perfmatters_apply_defaults_to_blog($blog_id, $network_default) {
$blog = get_blog_details($blog_id);
if($blog && !empty($network_default)) {
if($blog->blog_id != $network_default) {
$option_names = array(
'perfmatters_options',
'perfmatters_tools'
);
foreach($option_names as $option_name) {
//clear selected blog previous option
delete_blog_option($blog->blog_id, $option_name);
//grab new option from default blog
$new_option = get_blog_option($network_default, $option_name);
//remove options we don't want to copy
if($option_name == 'perfmatters_options') {
unset($new_option['cdn']['cdn_url']);
}
//update selected blog with default option
update_blog_option($blog->blog_id, $option_name, $new_option);
}
return true;
}
}
return false;
}

View File

@@ -0,0 +1,591 @@
<?php
/*
Plugin Name: Perfmatters MU
Plugin URI: https://perfmatters.io/
Description: Perfmatters is a lightweight performance plugin developed to speed up your WordPress site.
Version: 2.1.4
Author: forgemedia
Author URI: https://forgemedia.io/
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: perfmatters
Domain Path: /languages
*/
add_filter('option_active_plugins', 'perfmatters_mu_disable_plugins', 1);
function perfmatters_mu_disable_plugins($plugins) {
//admin check
if(is_admin()) {
return $plugins;
}
//only filter GET requests
if((!isset($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] !== 'GET') && !isset($_GET['perfmatters'])) {
return $plugins;
}
//dont filter if its a rest or ajax request
if((defined('REST_REQUEST') && REST_REQUEST) || (defined('WP_CLI') && WP_CLI) || (function_exists('wp_is_json_request') && wp_is_json_request()) || wp_doing_ajax() || wp_doing_cron()) {
return $plugins;
}
//manual wp-json check
if(stripos(trailingslashit($_SERVER['REQUEST_URI']), '/wp-json/') !== false) {
return $plugins;
}
//manual ajax check
if(!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
return $plugins;
}
//base plugin active check
if(is_array($plugins) && !in_array('perfmatters/perfmatters.php', $plugins)) {
return $plugins;
}
//make sure script manager is enabled
$perfmatters_options = get_option('perfmatters_options');
if(empty($perfmatters_options['assets']['script_manager'])) {
return $plugins;
}
//make sure MU is enabled
$pmsm_settings = get_option('perfmatters_script_manager_settings');
if(empty($pmsm_settings['mu_mode'])) {
return $plugins;
}
//wp login check
$perfmatters_options = get_option('perfmatters_options');
if((!empty($GLOBALS['pagenow']) && $GLOBALS['pagenow'] == 'wp-login.php') || (!empty($perfmatters_options['login_url']) && !empty($GLOBALS['_SERVER']['REQUEST_URI']) && trim($GLOBALS['_SERVER']['REQUEST_URI'], '/') == $perfmatters_options['login_url'])) {
return $plugins;
}
//script manager is being viewed
if(isset($_GET['perfmatters'])) {
//store active plugins for script manager UI in case they get disabled completely
global $pmsm_active_plugins;
$pmsm_active_plugins = $plugins;
//don't filter plugins if script manager is up
return $plugins;
}
if(isset($_GET['perfmattersoff'])) {
return $plugins;
}
//testing mode check
if(!empty($pmsm_settings['testing_mode'])) {
require_once(wp_normalize_path(ABSPATH) . 'wp-includes/pluggable.php');
if(!function_exists('wp_get_current_user') || !current_user_can('manage_options')) {
return $plugins;
}
}
//check for manual override
if(!empty($_GET['mu_mode']) && $_GET['mu_mode'] == 'off') {
return $plugins;
}
//make sure mu hasn't run already
global $mu_run_flag;
global $mu_plugins;
if($mu_run_flag) {
return $mu_plugins;
}
//get script manager configuration
$pmsm = get_option('perfmatters_script_manager');
//we have some plugins that are disabled
if(!empty($pmsm['disabled']['plugins'])) {
//attempt to get post id
$currentID = perfmatters_mu_get_current_ID();
//echo $currentID;
//assign disable/enable arrays
$disabled = $pmsm['disabled']['plugins'];
$enabled = !empty($pmsm['enabled']['plugins']) ? $pmsm['enabled']['plugins'] : '';
//loop through disabled plugins
foreach($disabled as $handle => $data) {
//current plugin disable is set
if(!empty($data['everywhere'])
|| (!empty($data['current']) && in_array($currentID, $data['current']))
|| pmsm_mu_check_post_types($data, $currentID)
|| pmsm_mu_check_user_status($data)
|| pmsm_mu_check_device_type($data)
|| (!empty($data['regex']) && preg_match($data['regex'], home_url(add_query_arg(array(), $_SERVER['REQUEST_URI']))))
) {
if(!empty($enabled[$handle])) {
//enable current url check
if(!empty($enabled[$handle]['current']) && in_array($currentID, $enabled[$handle]['current'])) {
continue;
}
//user status check
if(pmsm_mu_check_user_status($enabled[$handle])) {
continue;
}
//device type check
if(pmsm_mu_check_device_type($enabled[$handle])) {
continue;
}
//enable regex
if(!empty($enabled[$handle]['regex'])) {
if(preg_match($enabled[$handle]['regex'], home_url(add_query_arg(array(), $_SERVER['REQUEST_URI'])))) {
continue;
}
}
//home page as post type
if(pmsm_mu_check_post_types($enabled[$handle], $currentID)) {
continue;
}
}
//remove plugin from list
$m_array = preg_grep('/^' . $handle . '.*/', $plugins);
$single_array = array();
if(!empty($m_array) && is_array($m_array)) {
if(count($m_array) > 1) {
$single_array = preg_grep('/' . $handle . '\.php/', $m_array);
}
if(!empty($single_array)) {
unset($plugins[key($single_array)]);
}
else {
unset($plugins[key($m_array)]);
}
}
}
}
}
$mu_run_flag = true;
$mu_plugins = $plugins;
return $plugins;
}
//remove our filter after plugins have loaded
function perfmatters_mu_remove_filters() {
remove_filter('option_active_plugins', 'perfmatters_mu_disable_plugins', 1, 1);
}
add_action('plugins_loaded', 'perfmatters_mu_remove_filters', 1);
//attempt to get the current id for mu mode
function perfmatters_mu_get_current_ID() {
//load necessary parts for url_to_postid
if(!defined('LOGGED_IN_COOKIE')) {
wp_cookie_constants();
}
require_once(wp_normalize_path(ABSPATH) . 'wp-includes/pluggable.php');
global $wp_rewrite;
global $wp;
$wp_rewrite = new WP_Rewrite();
$wp = new WP();
//attempt to get post id from url
$currentID = (int) perfmatters_url_to_postid(home_url($_SERVER['REQUEST_URI']));
//id wasn't found
if($currentID === 0) {
//check for home url match
//$request = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . strtok($_SERVER['REQUEST_URI']);
$request = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
if(trailingslashit(home_url()) !== trailingslashit($request)) {
$currentID = '';
}
}
//clean up
unset($wp_rewrite);
unset($wp);
return $currentID;
}
//check if current post type is set in option
function pmsm_mu_check_post_types($option, $currentID = '') {
if($currentID === 0) {
if(get_option('show_on_front') == 'page' && !empty($option['post_types']) && in_array('page', $option['post_types'])) {
return true;
}
}
elseif(!empty($currentID)) {
//grab post details
$post = get_post($currentID);
//post type enable check
if(!empty($post->post_type) && !empty($option['post_types']) && in_array($post->post_type, $option['post_types'])) {
return true;
}
}
return false;
}
//check if current user status is set
function pmsm_mu_check_user_status($option) {
if(!empty($option['user_status'])) {
$status = is_user_logged_in();
if(($status && $option['user_status'] == 'loggedin') || (!$status && $option['user_status'] == 'loggedout')) {
return true;
}
}
return false;
}
//check if current device type is set
function pmsm_mu_check_device_type($option) {
if(!empty($option['device_type'])) {
$mobile = wp_is_mobile();
if(($mobile && $option['device_type'] == 'mobile') || (!$mobile && $option['device_type'] == 'desktop')) {
return true;
}
}
return false;
}
//custom url_to_postid() replacement - modified from https://gist.github.com/Webcreations907/ce5b77565dfb9a208738
function perfmatters_url_to_postid($url) {
if ( isset( $_GET['post'] ) && ! empty( $_GET['post'] ) && is_numeric( $_GET['post'] ) ) {
return $_GET['post'];
}
// First, check to see if there is a 'p=N' or 'page_id=N' to match against
// added post to match, even though it's above
if ( preg_match( '#[?&](p|post|page_id|attachment_id)=(\d+)#', $url, $values ) ) {
$id = absint( $values[2] );
if ( $id ) {
return $id;
}
}
// Check to see if we are using rewrite rules
$rewrite = get_option('rewrite_rules');
global $wp_rewrite;
//$rewrite = $wp_rewrite->wp_rewrite_rules();
// Not using rewrite rules, and 'p=N' and 'page_id=N' methods failed, so we're out of options
if ( empty( $rewrite ) ) {
if ( isset( $_GET ) && ! empty( $_GET ) ) {
/************************************************************************
* ADDED: Trys checks URL for ?posttype=postname
*************************************************************************/
// Assign $url to $tempURL just incase. :)
$tempUrl = $url;
// Get rid of the #anchor
$url_split = explode( '#', $tempUrl );
$tempUrl = $url_split[0];
// Get rid of URL ?query=string
$url_query = explode( '&', $tempUrl );
$tempUrl = $url_query[0];
// Get rid of ? mark
$url_query = explode( '?', $tempUrl);
if(isset($url_query[1]) && !empty($url_query[1]) && strpos( $url_query[1], '=' )){
$url_query = explode( '=', $url_query[1] );
if(isset($url_query[0]) && isset($url_query[1])){
$args = array(
'name' => $url_query[1],
'post_type' => $url_query[0],
'showposts' => 1,
);
if ( $post = get_posts( $args ) ) {
return $post[0]->ID;
}
}
}
/************************************************************************
* END ADD
*************************************************************************/
// Add custom rules for non-rewrite URLs
foreach ( $GLOBALS['wp_post_types'] as $key => $value ) {
if ( isset( $_GET[ $key ] ) && ! empty( $_GET[ $key ] ) ) {
$args = array(
'name' => $_GET[ $key ],
'post_type' => $key,
'showposts' => 1,
);
if ( $post = get_posts( $args ) ) {
return $post[0]->ID;
}
}
}
}
}
// Get rid of the #anchor
$url_split = explode( '#', $url );
$url = $url_split[0];
// Get rid of URL ?query=string
$url_query = explode( '?', $url );
$url = $url_query[0];
// Add 'www.' if it is absent and should be there
if ( false !== strpos( home_url(), '://www.' ) && false === strpos( $url, '://www.' ) ) {
$url = str_replace( '://', '://www.', $url );
}
// Strip 'www.' if it is present and shouldn't be
if ( false === strpos( home_url(), '://www.' ) ) {
$url = str_replace( '://www.', '://', $url );
}
// Strip 'index.php/' if we're not using path info permalinks
if ( isset( $wp_rewrite ) && ! $wp_rewrite->using_index_permalinks() ) {
$url = str_replace( 'index.php/', '', $url );
}
if ( false !== strpos( $url, home_url() ) ) {
// Chop off http://domain.com
$url = str_replace( home_url(), '', $url );
} else {
// Chop off /path/to/blog
$home_path = parse_url( home_url() );
$home_path = isset( $home_path['path'] ) ? $home_path['path'] : '';
$url = str_replace( $home_path, '', $url );
}
// Trim leading and lagging slashes
$url = trim( $url, '/' );
$request = $url;
if ( empty( $request ) && ( ! isset( $_GET ) || empty( $_GET ) ) ) {
return get_option( 'page_on_front' );
}
// Look for matches.
$request_match = $request;
foreach ( (array) $rewrite as $match => $query ) {
// If the requesting file is the anchor of the match, prepend it
// to the path info.
if ( ! empty( $url ) && ( $url != $request ) && ( strpos( $match, $url ) === 0 ) ) {
$request_match = $url . '/' . $request;
}
if ( preg_match( "!^$match!", $request_match, $matches ) ) {
if ( $wp_rewrite->use_verbose_page_rules && preg_match( '/pagename=\$matches\[([0-9]+)\]/', $query, $varmatch ) ) {
// This is a verbose page match, let's check to be sure about it.
$page = get_page_by_path( $matches[ $varmatch[1] ] );
if ( ! $page ) {
continue;
}
$post_status_obj = get_post_status_object( $page->post_status );
if (is_object($post_status_obj) && ! $post_status_obj->public && ! $post_status_obj->protected
&& ! $post_status_obj->private && $post_status_obj->exclude_from_search ) {
continue;
}
}
// Got a match.
// Trim the query of everything up to the '?'.
$query = preg_replace( "!^.+\?!", '', $query );
// Substitute the substring matches into the query.
$query = addslashes( WP_MatchesMapRegex::apply( $query, $matches ) );
// Filter out non-public query vars
global $wp;
parse_str( $query, $query_vars );
$query = array();
foreach ( (array) $query_vars as $key => $value ) {
if ( in_array( $key, $wp->public_query_vars ) ) {
$query[ $key ] = $value;
}
}
/************************************************************************
* ADDED: $GLOBALS['wp_post_types'] doesn't seem to have custom postypes
* Trying below to find posttypes in $rewrite rules
*************************************************************************/
// PostType Array
$custom_post_type = false;
$post_types = array();
foreach ($rewrite as $key => $value) {
if(!is_string($value)) {
continue;
}
if(preg_match('/post_type=([^&]+)/i', $value, $matched)){
if(isset($matched[1]) && !in_array($matched[1], $post_types)){
$post_types[] = $matched[1];
}
}
}
foreach ((array) $query_vars as $key => $value) {
if(in_array($key, $post_types)){
$custom_post_type = true;
$query['post_type'] = $key;
$query['postname'] = $value;
}
}
/************************************************************************
* END ADD
*************************************************************************/
// Taken from class-wp.php
if(!empty($GLOBALS['wp_post_types'])) {
foreach ( $GLOBALS['wp_post_types'] as $post_type => $t ) {
if ( $t->query_var ) {
$post_type_query_vars[ $t->query_var ] = $post_type;
}
}
}
foreach ( $wp->public_query_vars as $wpvar ) {
if ( isset( $wp->extra_query_vars[ $wpvar ] ) ) {
$query[ $wpvar ] = $wp->extra_query_vars[ $wpvar ];
} elseif ( isset( $_POST[ $wpvar ] ) ) {
$query[ $wpvar ] = $_POST[ $wpvar ];
} elseif ( isset( $_GET[ $wpvar ] ) ) {
$query[ $wpvar ] = $_GET[ $wpvar ];
} elseif ( isset( $query_vars[ $wpvar ] ) ) {
$query[ $wpvar ] = $query_vars[ $wpvar ];
}
if ( ! empty( $query[ $wpvar ] ) ) {
if ( ! is_array( $query[ $wpvar ] ) ) {
$query[ $wpvar ] = (string) $query[ $wpvar ];
} else {
foreach ( $query[ $wpvar ] as $vkey => $v ) {
if ( ! is_object( $v ) ) {
$query[ $wpvar ][ $vkey ] = (string) $v;
}
}
}
if ( isset( $post_type_query_vars[ $wpvar ] ) ) {
$query['post_type'] = $post_type_query_vars[ $wpvar ];
$query['name'] = $query[ $wpvar ];
}
}
}
// Do the query
if ( isset( $query['pagename'] ) && ! empty( $query['pagename'] ) ) {
$args = array(
'name' => $query['pagename'],
'post_type' => array('post','page'), // Added post for custom permalink eg postname
'showposts' => 1,
);
if ( $post = get_posts( $args ) ) {
return $post[0]->ID;
}
}
$query = new WP_Query( $query );
if ( ! empty( $query->posts ) && $query->is_singular ) {
return $query->post->ID;
} else {
/************************************************************************
* Added:
* $query->is_singular seems to not be set on custom post types, trying
* below. Made it this far without return, worth a try? :)
*************************************************************************/
if(!empty( $query->posts ) && isset($query->post->ID) && $custom_post_type == true){
return $query->post->ID;
}
/************************************************************************
* Will match posttype when query_var => true, will not work with
* custom query_var set ie query_var => 'acme_books'.
*************************************************************************/
if(isset($post_types)) {
foreach($rewrite as $key => $value) {
if(!is_string($value)) {
continue;
}
if(preg_match('/\?([^&]+)=([^&]+)/i', $value, $matched)) {
if(isset($matched[1]) && !in_array($matched[1], $post_types) && array_key_exists($matched[1], $query_vars)) {
$post_types[] = $matched[1];
$args = array(
'name' => $query_vars[$matched[1]],
'post_type' => $matched[1],
'showposts' => 1,
);
if($post = get_posts($args)) {
return $post[0]->ID;
}
}
elseif(isset($matched[1]) && in_array($matched[1], $post_types) && !empty($query_vars['name'])) {
$args = array(
'name' => $query_vars['name'],
'post_type' => $matched[1],
'showposts' => 1,
);
if($post = get_posts($args)) {
return $post[0]->ID;
}
}
}
}
}
/************************************************************************
* END ADD
*************************************************************************/
return 0;
}
}
}
return 0;
}

View File

@@ -0,0 +1,443 @@
<?php
//security check
if(!function_exists('wp_get_current_user') || !current_user_can('manage_options') || is_admin() || !isset($_GET['perfmatters']) || !perfmatters_network_access() || perfmatters_is_page_builder()) {
return;
}
global $pmsm_print_flag;
//script manager already printed
if($pmsm_print_flag) {
return;
}
$pmsm_print_flag = true;
//set variables
global $perfmatters_tools;
global $wp;
global $wp_scripts;
global $wp_styles;
global $perfmatters_options;
global $currentID;
$currentID = perfmatters_get_current_ID();
$pmsm_tab = !empty($_POST['tab']) ? $_POST['tab'] : 'main';
//filter language locale for script manager ui
switch_to_locale(apply_filters('perfmatters_script_manager_locale', ''));
//process settings form
if(isset($_POST['pmsm_save_settings'])) {
//validate settings nonce
if(!isset($_POST['perfmatters_script_manager_settings_nonce']) || !wp_verify_nonce($_POST['perfmatters_script_manager_settings_nonce'], 'perfmatter_script_manager_save_settings')) {
print 'Sorry, your nonce did not verify.';
exit;
}
else {
//update settings
update_option('perfmatters_script_manager_settings', (!empty($_POST['perfmatters_script_manager_settings']) ? $_POST['perfmatters_script_manager_settings'] : ''));
}
}
//manually closed disclaimer
if(isset($_POST['pmsm_disclaimer_close'])) {
if(isset($_POST['pmsm_disclaimer_close_nonce']) && wp_verify_nonce($_POST['pmsm_disclaimer_close_nonce'], 'pmsm_disclaimer_close')) {
$settings = get_option('perfmatters_script_manager_settings');
if(empty($settings) || !is_array($settings)) {
$settings = array();
}
$settings['hide_disclaimer'] = 1;
update_option('perfmatters_script_manager_settings', $settings);
}
}
//process reset form
if(isset($_POST['perfmatters_script_manager_settings_reset'])) {
delete_option('perfmatters_script_manager');
delete_option('perfmatters_script_manager_settings');
}
//global trash
if(isset($_POST['pmsm_global_trash'])) {
$trash = explode("|", $_POST['pmsm_global_trash']);
if(count($trash) == 4) {
list($category, $type, $script, $detail) = $trash;
$options = get_option('perfmatters_script_manager');
unset($options[$category][$type][$script][$detail]);
if($category == 'disabled' && $detail == 'everywhere') {
unset($options['enabled'][$type][$script]);
}
//clean up the options array before saving
perfmatters_script_manager_filter_options($options);
update_option('perfmatters_script_manager', $options);
}
}
//global refresh
if(isset($_POST['pmsm_global_refresh'])) {
$refresh = explode("|", $_POST['pmsm_global_refresh']);
if(count($refresh) == 3) {
list($category, $type, $script) = $refresh;
$options = get_option('perfmatters_script_manager');
$current = $options[$category][$type][$script]['current'] ?? array();
foreach($current as $key => $post_id) {
if(!get_post_status($post_id)) {
unset($options[$category][$type][$script]['current'][$key]);
}
}
//clean up the options array before saving
perfmatters_script_manager_filter_options($options);
update_option('perfmatters_script_manager', $options);
}
}
//load script manager settings
global $perfmatters_script_manager_settings;
$perfmatters_script_manager_settings = get_option('perfmatters_script_manager_settings');
//build array of existing plugin disables
global $perfmatters_disables;
$perfmatters_disables = array();
if(!empty($perfmatters_options['disable_google_maps'])) {
$perfmatters_disables[] = 'maps.google.com';
$perfmatters_disables[] = 'maps.googleapis.com';
$perfmatters_disables[] = 'maps.gstatic.com';
}
if(!empty($perfmatters_options['disable_google_fonts'])) {
$perfmatters_disables[] = 'fonts.googleapis.com';
}
//setup filters array
global $perfmatters_filters;
$perfmatters_filters = array(
"js" => array (
"title" => "JS",
"scripts" => $wp_scripts
),
"css" => array(
"title" => "CSS",
"scripts" => $wp_styles
)
);
//load script manager options
global $perfmatters_script_manager_options;
$perfmatters_script_manager_options = get_option('perfmatters_script_manager');
//load styles
include('script_manager_css.php');
//disable shortcodes
remove_all_shortcodes();
//wrapper
echo "<div id='perfmatters-script-manager-wrapper'>";
//header
echo "<div id='perfmatters-script-manager-header'>";
echo "<div id='pmsm-header-hero'>";
//menu toggle
echo "<span id='pmsm-menu-toggle'><span class='dashicons dashicons-menu'></span></span>";
//logo
echo "<img src='" . plugins_url('img/logo.svg', dirname(__FILE__)) . "' title='Perfmatters' id='perfmatters-logo' />";
echo "</div>";
//main navigation
echo "<form method='POST'>";
echo "<div id='perfmatters-script-manager-tabs'>";
echo "<button name='tab' value=''" . ($pmsm_tab == 'main' ? " class='active'" : "") . " title='" . __('Script Manager', 'perfmatters') . "'><span class='dashicons dashicons-admin-settings'></span>" . __('Script Manager', 'perfmatters') . "</button>";
echo "<button name='tab' value='global'" . ($pmsm_tab == 'global' ? " class='active'" : "") . " title='" . __('Global View', 'perfmatters') . "'><span class='dashicons dashicons-admin-site'></span>" . __('Global View', 'perfmatters') . "</button>";
echo "<button name='tab' value='settings'" . ($pmsm_tab == 'settings' ? " class='active'" : "") . " title='" . __('Settings', 'perfmatters') . "'><span class='dashicons dashicons-admin-generic'></span>" . __('Settings', 'perfmatters') . "</button>";
echo "</div>";
echo "</form>";
echo "</div>";
//main container
echo "<div id='perfmatters-script-manager'>";
//visible container
echo "<div id='pmsm-viewport'>";
echo '<div id="pmsm-notices">';
//disclaimer
if(empty($perfmatters_script_manager_settings['hide_disclaimer'])) {
echo '<div class="pmsm-notice">';
echo '<form method="POST">';
echo $pmsm_tab != 'main' ? '<input type="hidden" name="tab" value="' . $pmsm_tab . '" />' : '';
wp_nonce_field('pmsm_disclaimer_close', 'pmsm_disclaimer_close_nonce');
echo '<button type="submit" id="pmsm-disclaimer-close" name="pmsm_disclaimer_close"/><span class="dashicons dashicons-dismiss"></span></button>';
echo '</form>';
_e('We recommend testing Script Manager changes on a staging/dev site first, as you could break your site\'s appearance.', 'perfmatters');
echo ' <a href="https://perfmatters.io/docs/disable-scripts-per-post-page/" target="_blank">' . __('View Documentation', 'perfmatters') . '</a>';
echo '</div>';
}
//testing mode
if(!empty($perfmatters_script_manager_settings['testing_mode'])) {
echo '<div class="pmsm-notice pmsm-notice-warning">' . __('You are in Testing Mode. Changes will only be visible to logged-in admins.') . '</div>';
}
echo '</div>';
//universal form
echo "<form method='POST' id='pmsm-" . $pmsm_tab . "-form'>";
//content container
echo "<div id='perfmatters-script-manager-container'>";
//main tab
if($pmsm_tab == 'main') {
//title bar
echo "<div class='perfmatters-script-manager-title-bar'>";
echo "<h1>" . __('Script Manager', 'perfmatters') . "</h1>";
echo "<p>" . __('Manage scripts loading on the current page.', 'perfmatters') . "</p>";
echo "</div>";
//load master array
global $master_array;
$master_array = perfmatters_script_manager_load_master_array();
//print scripts
foreach($master_array['resources'] as $category => $groups) {
echo '<div class="pmsm-category-container">';
if(!empty($groups)) {
echo "<h3>" . $category . "</h3>";
if($category != "misc") {
echo "<div style='background: #ffffff; padding: 10px;'>";
foreach($groups as $group => $details) {
echo "<div class='perfmatters-script-manager-group'>";
echo "<div class='pmsm-group-heading'>";
echo "<h4>" . (!empty($details['name']) ? $details['name'] : "") . "</h4>";
//Status
echo "<div class='perfmatters-script-manager-status' style='display: flex; align-items: center; white-space: nowrap; margin-left: 10px;'>";
if(!empty($details['size'])) {
echo "<span class='pmsm-group-tag pmsm-group-size'>" . __('Total size', 'perfmatters') . ": " . round($details['size'] / 1024, 1) . " KB</span>";
}
perfmatters_script_manager_print_status($category, $group);
echo "</div>";
echo "</div>";
$assets = !empty($details['assets']) ? $details['assets'] : false;
perfmatters_script_manager_print_section($category, $group, $assets);
echo "</div>";
}
echo "</div>";
}
else {
if(!empty($groups['assets'])) {
perfmatters_script_manager_print_section($category, $category, $groups['assets']);
}
}
}
echo '</div>';
}
//loading wrapper
echo "<div id='pmsm-loading-wrapper'>";
if(function_exists('is_amp_endpoint') && is_amp_endpoint()) {
echo "<span class='pmsm-loading-text'>" . __('The Script Manager does not support AMP pages.', 'perfmatters') . "</span>";
}
else {
echo "<span class='pmsm-loading-text'>" . __('Loading Scripts', 'perfmatters') . "<span class='pmsm-spinner'></span></span>";
}
echo "</div>";
}
//global view tab
elseif($pmsm_tab == 'global') {
include('script_manager_global.php');
}
//settings tab
elseif($pmsm_tab == 'settings') {
//title bar
echo "<div class='perfmatters-script-manager-title-bar'>";
echo "<h1>" . __('Settings', 'perfmatters') . "</h1>";
echo "<p>" . __('View and manage all of your Script Manager settings.', 'perfmatters') . "</p>";
echo "</div>";
//settings container
echo "<div id='script-manager-settings' class='perfmatters-script-manager-section'>";
echo "<input type='hidden' name='tab' value='settings' />";
echo "<table>";
echo "<tbody>";
echo "<tr>";
echo "<th>" . perfmatters_title(__('Display Archives', 'perfmatters'), 'separate_archives') . "</th>";
echo "<td>";
$args = array(
'id' => 'separate_archives',
'option' => 'perfmatters_script_manager_settings'
);
perfmatters_print_input($args);
echo "<div>" . __('Add WordPress archives to your Script Manager selection options. Archive posts will no longer be grouped with their post type.', 'perfmatters') . "</div>";
echo "</td>";
echo "</tr>";
echo "<tr>";
echo "<th>" . perfmatters_title(__('Display Dependencies', 'perfmatters'), 'separate_archives') . "</th>";
echo "<td>";
$args = array(
'id' => 'dependencies',
'option' => 'perfmatters_script_manager_settings'
);
perfmatters_print_input($args);
echo "<div>" . __('Show dependencies for each script.', 'perfmatters') . "</div>";
echo "</td>";
echo "</tr>";
echo "<tr>";
echo "<th>" . perfmatters_title(__('Testing Mode', 'perfmatters'), 'testing_mode') . "</th>";
echo "<td>";
$args = array(
'id' => 'testing_mode',
'option' => 'perfmatters_script_manager_settings'
);
perfmatters_print_input($args);
echo "<div>" . __('Restrict your Script Manager configuration to logged-in admins only.', 'perfmatters') . ' <a href="https://perfmatters.io/docs/testing-mode/" target="_blank">' . __('View Documentation', 'perfmatters') . '</a>' . "</div>";
echo "</td>";
echo "</tr>";
echo "<tr>";
echo "<th>" . perfmatters_title(__('MU Mode', 'perfmatters'), 'mu_mode') . "</th>";
echo "<td>";
$args = array(
'id' => 'mu_mode',
'option' => 'perfmatters_script_manager_settings'
);
perfmatters_print_input($args);
echo "<div>" . __('Must-use (MU) mode requires elevated permissions and a file to be copied into the mu-plugins directory. This gives you more control and the ability to disable plugin queries, inline CSS, etc.', 'perfmatters') . ' <a href="https://perfmatters.io/docs/mu-mode/" target="_blank">' . __('View Documentation', 'perfmatters') . '</a>' . "</div>";
echo "<div style='background: #faf3c4; padding: 10px; margin-top: 7px;'><strong>" . __('Warning', 'perfmatters') . ":</strong> " . __('Any previous plugin-level disables will now disable the entire plugin. Please review your existing Script Manager configuration before enabling this option.', 'perfmatters') . "</div>";
//mu plugin file check
if(!empty($perfmatters_script_manager_settings['mu_mode'])) {
if(file_exists(WPMU_PLUGIN_DIR . "/perfmatters_mu.php")) {
//$mu_plugins = get_mu_plugins();
if(!function_exists('get_plugin_data')) {
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
}
//get plugin data
$mu_plugin_data = get_plugin_data(WPMU_PLUGIN_DIR . "/perfmatters_mu.php");
if(empty($mu_plugin_data['Version']) || !defined('PERFMATTERS_VERSION') || ($mu_plugin_data['Version'] != PERFMATTERS_VERSION)) {
$mu_message = __("MU plugin version mismatch.", 'perfmatters');
$mu_class = "pmsm-mu-mismatch";
}
else {
$mu_message = __("MU plugin installed.", 'perfmatters');
$mu_class = "pmsm-mu-found";
}
}
else {
$mu_message = __("MU plugin file not found.", 'perfmatters');
$mu_class = "pmsm-mu-missing";
}
echo "<div class='" . $mu_class . "'>" . $mu_message . "</div>";
}
echo "</td>";
echo "</tr>";
echo "<tr>";
echo "<th>" . perfmatters_title(__('Hide Disclaimer', 'perfmatters'), 'hide_disclaimer') . "</th>";
echo "<td>";
$args = array(
'id' => 'hide_disclaimer',
'option' => 'perfmatters_script_manager_settings'
);
perfmatters_print_input($args);
echo "<div>" . __('Hide the disclaimer message box across all Script Manager views.', 'perfmatters') . "</div>";
echo "</td>";
echo "</tr>";
echo "<tr>";
echo "<th>" . perfmatters_title(__('Reset Script Manager', 'perfmatters'), 'reset_script_manager') . "</th>";
echo "<td>";
//Reset Form
echo "<div>";
echo "<input type='submit' name='pmsm-reset' id='pmsm-reset' class='pmsm-reset' value='" . __('Reset Script Manager', 'perfmatters') . "' />";
echo "</div>";
echo "<div>";
echo "<span class='perfmatters-tooltip-text'>" . __('Remove and reset all of your existing Script Manager settings.', 'perfmatters') . "</span>";
echo "</div>";
echo "</td>";
echo "</tr>";
echo "</tbody>";
echo "</table>";
//Nonce
wp_nonce_field('perfmatter_script_manager_save_settings', 'perfmatters_script_manager_settings_nonce');
echo "</div>";
}
echo "</div>";
//toolbar
echo "<div class='perfmatters-script-manager-toolbar'>";
echo "<div class='perfmatters-script-manager-toolbar-wrapper'>";
echo "<div class='perfmatters-script-manager-toolbar-container'>";
//save button
echo "<div id='pmsm-save'>";
if($pmsm_tab != 'global') {
echo "<input type='submit' name='pmsm_save_" . $pmsm_tab . "' value='" . __('Save', 'perfmatters') . "' />";
echo "<span class='pmsm-spinner'></span>";
}
echo "</div>";
//copyright
echo "<div class='pmsm-copyright'>© " . date("Y") . " Perfmatters</div>";
echo "</div>";
//message
echo "<div id='pmsm-message' class='pmsm-message'></div>";
echo "</div>";
echo "</div>";
echo "</form>";
echo "</div>";
//hidden reset form
if($pmsm_tab == 'settings') {
echo "<form method='POST' id='pmsm-reset-form' pmsm-confirm=\"" . __('Are you sure? This will remove and reset all of your existing Script Manager settings and cannot be undone!') . "\">";
echo "<input type='hidden' name='tab' value='settings' />";
echo "<input type='hidden' name='perfmatters_script_manager_settings_reset' class='pmsm-reset' value='submit' />";
echo "</form>";
}
echo "</div>";
echo "</div>";

View File

@@ -0,0 +1,846 @@
<?php
echo "<style>
html, body {
overflow: hidden !important;
}
#wpadminbar, #wpadminbar .ab-sub-wrapper, #wpadminbar ul, #wpadminbar ul li {
z-index: 2147483647;
}
#perfmatters-script-manager-wrapper {
display: flex;
position: fixed;
z-index: 2147483646;
top: 32px;
bottom: 0px;
left: 0px;
right: 0px;
background: rgba(0,0,0,0.5);
overflow-y: auto;
}
#perfmatters-script-manager {
display: flex;
flex-grow: 1;
position: relative;
background: #EEF2F5;
font-size: 14px;
line-height: 1.5em;
color: #4a545a;
min-height: 100%;
box-sizing: border-box;
overflow: hidden;
}
#perfmatters-script-manager-wrapper * {
font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
}
#perfmatters-script-manager-wrapper .dashicons {
font-family: dashicons;
}
#perfmatters-script-manager a {
color: #4A89DD;
text-decoration: none;
border: none;
}
#perfmatters-script-manager label {
float: none;
opacity: 1;
}
#perfmatters-script-manager-header {
width: 270px;
min-width: 270px;
background: #282E34;
}
#pmsm-header-hero {
display: flex;
}
#pmsm-menu-toggle {
cursor: pointer;
}
#pmsm-header-hero .dashicons-menu {
margin: 20px;
color: #ffffff;
}
#pmsm-header-hero #perfmatters-logo {
display: block;
width: 150px;
}
#perfmatters-script-manager-header h2 {
font-size: 24px;
margin: 0px 0px 10px 0px;
color: #4a545a;
font-weight: bold;
}
#perfmatters-script-manager-header h2 span {
background: #ED5464;
color: #ffffff;
padding: 5px;
vertical-align: middle;
font-size: 10px;
margin-left: 5px;
}
#perfmatters-script-manager-header p {
font-size: 14px;
color: #4a545a;
font-style: italic;
margin: 0px auto 15px auto;
}
#perfmatters-script-manager-close {
position: absolute;
top: 0px;
right: 0px;
height: 26px;
width: 26px;
}
#perfmatters-script-manager-close img {
height: 26px;
width: 26px;
}
#perfmatters-script-manager-tabs button {
display: flex;
align-items: center;
float: left;
padding: 15px 20px;
width: 100%;
font-size: 17px;
line-height: normal;
text-align: left;
background: transparent;
color: #ffffff;
font-weight: normal;
border: none;
cursor: pointer;
border-radius: 0px;
height: 60px;
text-transform: none;
text-decoration: none;
outline: none;
}
#perfmatters-script-manager-tabs {
overflow: hidden;
}
#perfmatters-script-manager-tabs button span {
font: 400 20px/1 dashicons;
margin-right: 20px;
}
#perfmatters-script-manager-tabs button.active {
background: #4A89DD;
color: #ffffff;
}
#perfmatters-script-manager-tabs button:hover {
background: #2f373f;
color: #ffffff;
}
#perfmatters-script-manager-tabs button.active:hover {
background: #4A89DD;
color: #ffffff;
}
#perfmatters-script-manager-header.pmsm-header-minimal {
width: 60px;
min-width: 60px;
}
#perfmatters-script-manager-header.pmsm-header-minimal #perfmatters-logo {
display: none;
}
#perfmatters-script-manager-header.pmsm-header-expanded {
height: auto;
}
#pmsm-viewport {
width: 100%;
overflow-y: scroll;
padding: 20px 20px 0px 20px;
}
.pmsm-notice {
background: #ffffff;
padding: 10px;
margin: 0px 0px 10px 0px;
border-radius: 3px;
border-left: 4px solid #4A89DD;
}
.pmsm-notice-warning {
border-color: #FFC14E;
}
#pmsm-disclaimer-close {
float: right;
padding: 0px;
width: 30px;
height: 30px;
line-height: 30px;
font-weight: bold;
background: none;
color: #787c82
}
#pmsm-disclaimer-close:hover {
color: #d63638;
}
#pmsm-notices .pmsm-notice:last-child {
margin-bottom: 20px;
}
#perfmatters-script-manager-container {
position: relative;
max-width: 1000px;
margin: 0px auto;
}
#perfmatters-script-manager-container .perfmatters-script-manager-title-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
#perfmatters-script-manager-container .perfmatters-script-manager-title-bar h1 {
font-size: 24px;
line-height: normal;
font-weight: 400;
margin: 0px;
color: #282E34;
}
#perfmatters-script-manager-container .perfmatters-script-manager-title-bar p {
margin: 0px;
color: #282E34;
font-size: 13px;
}
.pmsm-category-container {
border-radius: 5px;
margin-top: 20px;
overflow: hidden;
}
#perfmatters-script-manager h3 {
padding: 10px;
margin: 0px;
font-size: 16px;
background: #282E34;
color: #ffffff;
text-transform: capitalize;
font-weight: 400;
}
.perfmatters-script-manager-group {
box-shadow: 0 1px 2px 0 rgba(40,46,52,.1);
margin: 0px 0px 10px 0px;
}
.perfmatters-script-manager-group:last-of-type {
margin-bottom: 0px;
}
.pmsm-group-heading {
display: flex;
justify-content: space-between;
align-items: center;
/*height: 40px;*/
padding: 10px;
background: #edf3f9;
color: #282E34;
font-weight: 700;
box-sizing: content-box;
}
.perfmatters-script-manager-group h4 {
display: inline-block;
margin: 0px;
padding: 0px;
font-size: 18px;
line-height: normal;
font-weight: 700;
color: #282E34;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.perfmatters-script-manager-section {
padding: 0px 10px;
background: #ffffff;
margin: 0px 0px 0px 0px;
}
.perfmatters-script-manager-assets-disabled {
padding: 0px 0px 10px 0px;
}
#perfmatters-script-manager table {
table-layout: fixed;
width: 100%;
margin: 0px;
padding: 0px;
border: none;
text-align: left;
font-size: 14px;
border-collapse: collapse;
}
#perfmatters-script-manager table thead {
background: none;
color: #282E34;
font-weight: bold;
border: none;
}
#perfmatters-script-manager table thead th {
font-size: 12px;
padding: 5px;
vertical-align: middle;
border: none;
background: #ffffff;
color: #4a545a;
width: auto;
opacity: 0.75;
}
#perfmatters-script-manager .pmsm-column-status {
width: 80px;
}
#perfmatters-script-manager .pmsm-column-type, #perfmatters-script-manager .pmsm-column-size {
width: 100px;
text-align: center;
}
#perfmatters-script-manager table tr {
border: none;
border-bottom: 1px solid #EDF3F9;
background: #ffffff;
box-sizing: content-box;
}
#perfmatters-script-manager table tbody tr:last-child {
border-bottom: 0px;
}
#perfmatters-script-manager table td {
padding: 8px 5px;
border: none;
vertical-align: top;
font-size: 14px;
background: #ffffff;
color: #4a545a;
width: auto;
}
#perfmatters-script-manager table td.perfmatters-script-manager-type {
font-size: 14px;
text-align: center;
padding-top: 16px;
text-transform: uppercase;
}
#perfmatters-script-manager .pmsm-script-type-js .pmsm-tag {
background: #FFC14E;
color: #222;
}
#perfmatters-script-manager .pmsm-script-type-css .pmsm-tag {
background: #536AD4;
color: #fff;
}
#perfmatters-script-manager table td.perfmatters-script-manager-size {
font-size: 14px;
text-align: center;
padding-top: 16px;
}
#perfmatters-script-manager table td.perfmatters-script-manager-script a {
white-space: nowrap;
}
#perfmatters-script-manager .perfmatters-script-manager-script .pmsm-script-handle {
display: block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
font-size: 14px;
font-weight: bold;
margin-bottom: 3px;
}
#perfmatters-script-manager .perfmatters-script-manager-script a {
display: block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
font-size: 10px;
color: #4A89DD;
line-height: normal;
}
#perfmatters-script-manager .perfmatters-script-manager-disable, #perfmatters-script-manager .perfmatters-script-manager-enable {
margin: 10px 0px 0px 0px;
}
#perfmatters-script-manager table .perfmatters-script-manager-disable *:after, #perfmatters-script-manager table .perfmatters-script-manager-disable *:before {
display: none;
}
#perfmatters-script-manager select {
display: inline-block;
position: relative;
height: auto;
width: auto;
background: #ffffff;
background-color: #ffffff;
padding: 7px 10px;
margin: 0px;
font-size: 14px;
appearance: menulist;
}
#perfmatters-script-manager select.perfmatters-disable-select, #perfmatters-script-manager select.perfmatters-status-select {
border: 2px solid #27ae60;
}
#perfmatters-script-manager select.perfmatters-disable-select.everywhere, #perfmatters-script-manager select.perfmatters-status-select.disabled {
border: 2px solid #ED5464;
}
#perfmatters-script-manager select.perfmatters-disable-select.current {
border: 2px solid #f1c40f;
}
#perfmatters-script-manager select.perfmatters-disable-select.hide {
display: none;
}
#perfmatters-script-manager .perfmatters-script-manager-enable-placeholder {
color: #bbbbbb;
font-style: italic;
font-size: 14px;
}
#perfmatters-script-manager input[type='radio'] {
position: static;
display: inline-block;
visibility: visible;
margin: 0px 3px 0px 0px;
vertical-align: middle;
opacity: 1;
z-index: 0;
appearance: radio;
vertical-align: baseline;
height: auto;
width: auto;
font-size: 16px;
}
#perfmatters-script-manager input[type='radio']:after {
display: none;
}
#perfmatters-script-manager .perfmatters-script-manager-controls .pmsm-checkbox-container {
display: inline;
}
#perfmatters-script-manager input[type='checkbox'] {
position: static;
display: inline-block;
visibility: visible;
margin: 0px 3px 0px 0px;
vertical-align: middle;
opacity: 1;
z-index: 0;
appearance: checkbox;
vertical-align: baseline;
height: auto;
width: auto;
font-size: 16px;
outline: none;
transform: none;
}
#perfmatters-script-manager input[type='checkbox']:before, #perfmatters-script-manager input[type='checkbox']:after {
display: none;
}
#perfmatters-script-manager .perfmatters-script-manager-controls {
text-align: left;
}
#perfmatters-script-manager .pmsm-input-group {
display: flex;
padding: 3px 0px;
}
#perfmatters-script-manager .pmsm-input-group-label {
min-width: 70px;
font-size: 10px;
font-weight: bold;
margin: 0px 5px 0px 0px;
white-space: nowrap;
color: #282E34;
}
#perfmatters-script-manager .perfmatters-script-manager-controls label {
display: inline-flex;
align-items: center;
margin: 0px 10px 0px 0px;
width: auto;
font-size: 12px;
color: #282E34;
white-space: nowrap;
}
#perfmatters-script-manager .perfmatters-script-manager-controls input[type='text'], #perfmatters-script-manager .perfmatters-script-manager-controls select {
padding: 2px;
width: 100%;
background: #fff;
font-size: 12px;
outline: none;
border: 1px solid #ccc;
margin: 0px;
height: 30px;
color: #282E34;
border-radius: unset;
text-transform: none;
}
#perfmatters-script-manager .pmsm-dependencies {
font-size: 10px;
line-height: 14px;
margin-top: 6px;
}
#perfmatters-script-manager .pmsm-dependencies span {
font-weight: bold;
}
#perfmatters-script-manager .pmsm-reqs span {
color: #ED5464;
}
#perfmatters-script-manager .pmsm-group-tag {
display: inline-block;
vertical-align: top;
font-size: 12px;
background: #fff;
padding: 2px 6px;
height: 20px;
line-height: 20px;
margin: 0px 8px 0px 0px;
border-radius: 3px;
}
#perfmatters-script-manager .pmsm-tag {
display: inline-block;
font-size: 12px;
background: #fff;
padding: 2px 6px;
border-radius: 3px;
}
#perfmatters-script-manager .pmsm-mu-mode-badge {
background: #282E34;
color: #ffffff;
}
#perfmatters-script-manager .perfmatters-script-manager-toolbar {
position: sticky;
bottom: 0px;
left: 0px;
right: 0px;
padding: 0px 20px;
background: #EEF2F5;
box-sizing: content-box;
z-index: 2
}
#perfmatters-script-manager .perfmatters-script-manager-toolbar-wrapper {
position: relative;
max-width: 1000px;
margin: 0px auto;
}
#perfmatters-script-manager .perfmatters-script-manager-toolbar-container {
position: relative;
background: #EEF2F5;
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 0px;
z-index: 2;
}
#perfmatters-script-manager input[type='submit'] {
background: #4a89dd;
color: #ffffff;
cursor: pointer;
border: none;
font-size: 14px;
margin: 0px;
padding: 0px 20px;
height: 35px;
line-height: 35px;
font-weight: 700;
width: auto;
border-radius: 0px;
text-transform: none;
outline: none;
border-radius: 5px;
}
#perfmatters-script-manager input[type='submit']:hover {
background: #5A93E0;
}
#perfmatters-script-manager input[type='submit']:disabled {
opacity: 0.5;
cursor: default;
}
#script-manager-settings input[type='submit'] {
float: left;
}
#perfmatters-script-manager input[type='submit'].pmsm-reset {
float: none;
background: #ED5464;
height: 35px;
line-height: 35px;
padding: 0px 10px;
font-size: 12px;
margin-bottom: 5px;
}
#perfmatters-script-manager input[type='submit'].pmsm-reset:hover {
background: #c14552;
}
#perfmatters-script-manager .pmsm-message {
display: block;
visibility: hidden;
opacity: 0;
position: absolute;
bottom: 00px;
right: 10px;
background: #282E34;
color: #ffffff;
padding: 10px;
border-radius: 3px;
z-index: 1;
transition: all 500ms ease;
}
#perfmatters-script-manager .pmsm-message.pmsm-fade {
visibility: visible;
opacity: 1;
bottom: 80px;
}
/* On/Off Toggle Switch */
#perfmatters-script-manager .perfmatters-script-manager-switch {
position: relative;
display: inline-block;
width: 48px;
height: 28px;
font-size: 1px;
margin: 0px;
}
#perfmatters-script-manager .perfmatters-script-manager-switch input[type='checkbox'] {
display: block;
margin: 0px;
border: none;
outline: none;
box-shadow: none;
appearance: none;
height: 0px;
width: 0px;
}
#perfmatters-script-manager .perfmatters-script-manager-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #27ae60;
transition: .4s;
border-radius: 50px;
}
#perfmatters-script-manager .perfmatters-script-manager-slider:before {
position: absolute;
content: '';
width: 20px;
top: 4px;
right: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
#perfmatters-script-manager .perfmatters-script-manager-switch input:checked + .perfmatters-script-manager-slider {
background-color: #ED5464;
}
#perfmatters-script-manager .perfmatters-script-manager-switch input:focus + .perfmatters-script-manager-slider {
box-shadow: 0 0 1px #ED5464;
}
#perfmatters-script-manager .perfmatters-script-manager-switch input:checked + .perfmatters-script-manager-slider:before {
transform: translateX(-20px);
}
/*#perfmatters-script-manager .perfmatters-script-manager-slider:after {
content:'" . __('ON', 'perfmatters') . "';
color: white;
display: block;
position: absolute;
transform: translate(-50%,-50%);
top: 50%;
left: 27%;
font-size: 9px;
font-weight: normal;
font-family: Verdana, sans-serif;
}
#perfmatters-script-manager .perfmatters-script-manager-switch input:checked + .perfmatters-script-manager-slider:after {
left: unset;
right: 0%;
content:'" . __('OFF', 'perfmatters') . "';
}*/
#perfmatters-script-manager .perfmatters-script-manager-assets-disabled p {
margin: 20px 0px 0px 0px;
text-align: center;
font-size: 12px;
padding: 10px 0px 0px 0px;
border-top: 1px solid #f8f8f8;
}
/*Global View*/
#pmsm-global-form .perfmatters-script-manager-section tbody tr:hover td {
background: #EDF3F9;
}
.pmsm-action-button {
visibility: hidden;
}
#pmsm-global-form .perfmatters-script-manager-section tbody tr:hover .pmsm-action-button {
visibility: visible;
}
.pmsm-action-button, .pmsm-action-button:hover, .pmsm-action-button:focus, .pmsm-action-button:active {
background: none;
border: none;
padding: 0px;
margin: 0px;
height: auto;
width: auto;
line-height: normal;
}
.pmsm-action-button .dashicons-trash {
color: #ED5464;
}
.pmsm-action-button:hover .dashicons-trash {
color: #c14552;
}
.pmsm-action-button .dashicons-update {
color: orange;
}
.pmsm-action-button:hover .dashicons-update {
color: darkorange;
}
#pmsm-global-form table td {
word-wrap: break-word;
}
/*Settings View*/
#script-manager-settings table th {
width: 200px;
vertical-align: top;
padding: 8px 5px;
border: none;
}
#script-manager-settings .perfmatters-switch {
position: relative;
display: inline-block;
width: 48px;
height: 28px;
font-size: 1px;
margin-bottom: 5px;
}
#script-manager-settings .perfmatters-switch input {
display: block;
margin: 0px;
border: none;
outline: none;
box-shadow: none;
appearance: none;
height: 0px;
width: 0px;
}
#script-manager-settings .perfmatters-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 50px;
}
#script-manager-settings .perfmatters-slider:before {
position: absolute;
content: '';
height: 20px;
width: 20px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
#script-manager-settings input:checked + .perfmatters-slider {
background-color: #2196F3;
}
#script-manager-settings input:focus + .perfmatters-slider {
box-shadow: 0 0 1px #2196F3;
}
#script-manager-settings input:checked + .perfmatters-slider:before {
transform: translateX(20px);
}
.perfmatters-beta {
background: #ED5464;
color: #ffffff;
padding: 5px;
vertical-align: middle;
font-size: 10px;
margin-left: 3px;
}
#script-manager-settings .pmsm-mu-found, #script-manager-settings .pmsm-mu-missing, #script-manager-settings .pmsm-mu-mismatch {
margin-top: 7px;
font-weight: bold;
}
#script-manager-settings .pmsm-mu-found {
color: #27ae60;
}
#script-manager-settings .pmsm-mu-missing {
color: #ED5464;
}
#script-manager-settings .pmsm-mu-mismatch {
color: #F6BF27;
}
#jquery-message {
font-size: 12px;
font-style: italic;
color: #27ae60;
margin-top: 5px;
}
@media (max-width: 1000px) {
#perfmatters-script-manager-header:not(.pmsm-header-minimal) {
width: 60px;
min-width: 60px;
}
#perfmatters-script-manager-header:not(.pmsm-header-minimal) #perfmatters-logo {
display: none;
}
}
@media (max-width: 782px) {
#perfmatters-script-manager-wrapper {
top: 46px;
padding-top: 60px;
}
#perfmatters-script-manager-header {
width: 100% !important;
min-width: 100% !important;
position: absolute;
top: 0px;
z-index: 9999;
height: 60px;
overflow: hidden;
}
#pmsm-header-hero {
justify-content: space-between;
}
#perfmatters-logo {
display: block !important;
margin-right: 20px;
}
#perfmatters-script-manager-container .perfmatters-script-manager-title-bar {
flex-direction: column;
}
#perfmatters-script-manager-container .perfmatters-script-manager-title-bar h1 {
margin-bottom: 5px;
}
#perfmatters-script-manager .pmsm-column-status {
width: 90px;
}
#perfmatters-script-manager .pmsm-column-type, #perfmatters-script-manager .pmsm-column-size {
width: 70px;
}
}
#pmsm-loading-wrapper {
position: absolute;
top: 48px;
bottom: 0;
padding-top: 200px;
width: 100%;
background: rgba(255,255,255,0.75);
text-align: center;
z-index: 1;
}
#pmsm-loading-wrapper .pmsm-loading-text {
font-size: 24px;
}
.pmsm-spinner {
background: url(/wp-admin/images/wpspin_light-2x.gif) no-repeat;
background-size: 16px 16px;
display: none;
vertical-align: middle;
width: 16px;
height: 16px;
margin: 0px 0px 0px 5px;
}
#pmsm-loading-wrapper .pmsm-spinner {
display: inline-block;
background-size: 32px 32px;
width: 32px;
height: 32px;
}
#perfmatters-script-manager .pmsm-hide {
display: none;
}
.pmsm-locked {
opacity: 0.5;
}
</style>";

View File

@@ -0,0 +1,131 @@
<?php
echo '<input type="hidden" name="tab" value="global" />';
//title bar
echo '<div class="perfmatters-script-manager-title-bar">';
echo '<h1>' . __('Global View', 'perfmatters') . '</h1>';
echo '<p>' . __('This is a visual representation of the Script Manager configuration across your entire site.', 'perfmatters') . '</p>';
echo '</div>';
$options = get_option('perfmatters_script_manager');
//global scripts display
if(!empty($options)) {
foreach($options as $category => $types) {
//category header
echo '<h3>' . $category . '</h3>';
if(!empty($types)) {
$type_names = array(
'js' => 'JavaScript',
'css' => 'CSS',
'plugins' => 'Plugins'
);
echo '<div style="background: #ffffff; padding: 10px;">';
foreach($types as $type => $scripts) {
echo '<div class="perfmatters-script-manager-group">';
echo '<div class="pmsm-group-heading">';
echo '<h4>' . $type_names[$type] . '</h4>';
echo '</div>';
echo '<div class="perfmatters-script-manager-section">';
echo '<table>';
echo '<thead>';
echo '<tr>';
echo '<th>' . __('Handle', 'perfmatters') . '</th>';
echo '<th>' . __('Setting', 'perfmatters') . '</th>';
echo '<th style="width: 40px;"></th>';
echo '</tr>';
echo '</thead>';
echo '<tbody>';
if(!empty($scripts)) {
foreach($scripts as $script => $details) {
if(!empty($details)) {
foreach($details as $detail => $values) {
$contains_junk = false;
echo '<tr>';
echo '<td>' . $script . '</td>';
echo '<td>';
echo '' . $detail . '';
if($detail == 'current' || $detail == 'post_types' || $detail == 'archives') {
if(!empty($values)) {
echo ' (';
$valueString = '';
foreach($values as $key => $value) {
if($detail == 'current') {
if((int)$value !== 0) {
if($value == 'pmsm-404') {
$valueString.= '404, ';
}
else {
$permalink = get_permalink($value);
if($permalink) {
$valueString.= '<a href="' . $permalink . '" title="' . get_the_title($value) . '" target="_blank">' . $value . '</a>, ';
}
else {
$valueString.= '<a style="color: orange; text-decoration: line-through;">' . $value . '</a>, ';
$contains_junk = true;
}
}
}
else {
$valueString.= '<a href="' . get_home_url() . '" target="_blank">homepage</a>, ';
}
}
else {
$valueString.= $value . ', ';
}
}
echo rtrim($valueString, ", ");
echo ')';
}
}
elseif($detail !== 'everywhere') {
echo ' (' . $values . ')';
}
echo '</td>';
echo '<td style="text-align: right;">';
//refresh button
if($contains_junk) {
echo '<button class="pmsm-action-button" name="pmsm_global_refresh" title="Refresh" value="' . $category . '|' . $type . '|' . $script . '">';
echo '<span class="dashicons dashicons-update"></span>';
echo '</button>';
}
//trash button
echo '<button class="pmsm-action-button" name="pmsm_global_trash" title="Delete" value="' . $category . '|' . $type . '|' . $script . '|' . $detail . '" onClick="return confirm(\'Are you sure you want to delete this option?\');">';
echo '<span class="dashicons dashicons-trash"></span>';
echo '</button>';
echo '</td>';
echo '</tr>';
}
}
}
}
echo '</tbody>';
echo '</table>';
echo '</div>';
echo '</div>';
}
echo '</div>';
}
}
}
else {
echo '<div class="perfmatters-script-manager-section">';
echo '<p style="padding: 20px; text-align: center;">' . __("You don't have any scripts disabled yet.", 'perfmatters') . '</p>';
echo '</div>';
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,45 @@
<?php
echo '<div class="perfmatters-settings-section">';
//page title
echo '<h2><span class="dashicons dashicons-editor-help"></span>' . __("Support", "novashare") . '</h2>';
echo '<div style="margin-bottom: 20px;"></div>'; //spacer
//documentation
echo '<h2>' . __('Documentation', 'perfmatters') . '</h2>';
echo '<div class="form-table">';
echo '<div style="margin: 1em auto;">' . __('Need help? Check out our in-depth documentation. Every feature has a step-by-step walkthrough.', 'perfmatters') . '</div>';
echo '<a class="button-secondary" href="https://perfmatters.io/docs/?utm_source=perfmatters&utm_medium=support-page&utm_campaign=documentation-cta" target="_blank">' . __('Documentation', 'perfmatters') . '</a>';
echo '</div>';
echo '</div>';
//contact us
echo '<div class="perfmatters-settings-section">';
echo '<h2>' . __('Contact Us', 'perfmatters') . '</h2>';
echo '<div class="form-table">';
echo '<div style="margin: 1em auto;">' . __('If you have questions or problems, please send us a message. Well get back to you as soon as possible.', 'perfmatters') . '</div>';
echo '<a class="button-secondary" href="https://perfmatters.io/contact/?utm_source=perfmatters&utm_medium=support-page&utm_campaign=contact-us-cta" target="_blank">' . __('Contact Us', 'perfmatters') . '</a>';
echo '</div>';
echo '</div>';
//faq
echo '<div class="perfmatters-settings-section">';
echo '<h2>' . __('Frequently Asked Questions', 'perfmatters') . '</h2>';
echo '<div class="form-table" style="display: inline-flex; flex-wrap: wrap;">';
$faq_utm = '?utm_source=perfmatters&utm_medium=support-page&utm_campaign=faq';
echo '<ul style="margin-right: 40px;">';
echo '<li><a href="https://perfmatters.io/docs/how-to-install-perfmatters/' . $faq_utm . '" target="_blank">' . __('How do I license activate the plugin?', 'perfmatters') . '</a></li>';
echo '<li><a href="https://perfmatters.io/docs/update-perfmatters-plugin/' . $faq_utm . '" target="_blank">' . __('How do I update the plugin?', 'perfmatters') . '</a></li>';
echo '<li><a href="https://perfmatters.io/docs/upgrade-license/' . $faq_utm . '" target="_blank">' . __('How do I upgrade my license?', 'perfmatters') . '</a></li>';
echo '<li><a href="https://perfmatters.io/docs/changelog/' . $faq_utm . '" target="_blank">' . __('Where can I view the changelog?', 'perfmatters') . '</a></li>';
echo '<li><a href="https://perfmatters.io/affiliate-program/' . $faq_utm . '" target="_blank">' . __('Where can I sign up for the affiliate program?', 'perfmatters') . '</a></li>';
echo '</ul>';
echo '<ul>';
echo '<li><a href="https://perfmatters.io/docs/disable-scripts-per-post-page/' . $faq_utm . '" target="_blank">' . __('How do I disable scripts on a per post/page basis?', 'perfmatters') . '</a></li>';
echo '<li><a href="https://perfmatters.io/docs/delay-javascript/' . $faq_utm . '" target="_blank">' . __('How do I delay JavaScript?', 'perfmatters') . '</a></li>';
echo '<li><a href="https://perfmatters.io/docs/remove-unused-css/' . $faq_utm . '" target="_blank">' . __('How do I remove unused CSS?', 'perfmatters') . '</a></li>';
echo '<li><a href="https://perfmatters.io/docs/lazy-load-wordpress/' . $faq_utm . '" target="_blank">' . __('How do I lazy load images and videos?', 'perfmatters') . '</a></li>';
echo '<li><a href="https://perfmatters.io/docs/local-analytics/' . $faq_utm . '" target="_blank">' . __('How do I host Google Analytics locally?', 'perfmatters') . '</a></li>';
echo '</ul>';
echo '</div>';
echo '</div>';

View File

@@ -0,0 +1 @@
enScroll=!1;const lStor=localStorage,sStor=sessionStorage,doc=document,docEl=document.documentElement,docBody=document.body,docLoc=document.location,w=window,s=screen,nav=navigator||{};function a(){const k=window.pmGAID,t=()=>Math.floor(Math.random()*1e9)+1,n=()=>Math.floor(Date.now()/1e3),y=()=>(sStor._p||(sStor._p=t()),sStor._p),v=()=>t()+"."+n(),p=()=>(lStor.cid_v4||(lStor.cid_v4=v()),lStor.cid_v4),m=lStor.getItem("cid_v4"),u=()=>m?void 0:enScroll==!0?void 0:"1",l=()=>(sStor.sid||(sStor.sid=n()),sStor.sid),d=()=>{if(!sStor._ss)return sStor._ss="1",sStor._ss;if(sStor.getItem("_ss")=="1")return void 0},r="1",h=()=>{if(sStor.sct)if(enScroll==!0)return sStor.sct;else x=+sStor.getItem("sct")+ +r,sStor.sct=x;else sStor.sct=r;return sStor.sct},e=docLoc.search,f=new URLSearchParams(e),a=["q","s","search","query","keyword"],g=a.some(t=>e.includes("&"+t+"=")||e.includes("?"+t+"=")),i=()=>g==!0?"view_search_results":enScroll==!0?"scroll":"page_view",b=()=>enScroll==!0?"90":void 0,j=()=>{if(i()=="view_search_results"){for(let e of f)if(a.includes(e[0]))return e[1]}else return void 0},o=encodeURIComponent,_=e=>{let t=[];for(let n in e)e.hasOwnProperty(n)&&e[n]!==void 0&&t.push(o(n)+"="+o(e[n]));return t.join("&")},O=!1,C="https://www.google-analytics.com/g/collect",E=_({v:"2",tid:k,_p:y(),sr:(s.width*w.devicePixelRatio+"x"+s.height*w.devicePixelRatio).toString(),ul:(nav.language||void 0).toLowerCase(),cid:p(),_fv:u(),_s:"1",dl:docLoc.origin+docLoc.pathname+e,dt:doc.title||void 0,dr:doc.referrer||void 0,sid:l(),sct:h(),seg:"1",en:i(),"epn.percent_scrolled":b(),"ep.search_term":j(),_ss:d(),_dbg:O?1:void 0}),c=C+"?"+E;if(nav.sendBeacon)nav.sendBeacon(c);else{let e=new XMLHttpRequest;e.open("POST",c,!0)}}a();function sPr(){return(docEl.scrollTop||docBody.scrollTop)/((docEl.scrollHeight||docBody.scrollHeight)-docEl.clientHeight)*100}doc.addEventListener("scroll",sEv,{passive:!0});function sEv(){const e=sPr();if(e<90)return;enScroll=!0,a(),doc.removeEventListener("scroll",sEv,{passive:!0})}

View File

@@ -0,0 +1 @@
(function(a,b,c){var d=a.history,e=document,f=navigator||{},g=localStorage,h=encodeURIComponent,i=d.pushState,k=function(){return Math.random().toString(36)},l=function(){return g.cid||(g.cid=k()),g.cid},m=function(r){var s=[];for(var t in r)r.hasOwnProperty(t)&&void 0!==r[t]&&s.push(h(t)+"="+h(r[t]));return s.join("&")},n=function(r,s,t,u,v,w,x){var z="https://www.google-analytics.com/collect",A=m({v:"1",ds:"web",aip:c.anonymizeIp?1:void 0,tid:b,cid:l(),t:r||"pageview",sd:c.colorDepth&&screen.colorDepth?screen.colorDepth+"-bits":void 0,dr:e.referrer||void 0,dt:e.title,dl:e.location.origin+e.location.pathname+e.location.search,ul:c.language?(f.language||"").toLowerCase():void 0,de:c.characterSet?e.characterSet:void 0,sr:c.screenSize?(a.screen||{}).width+"x"+(a.screen||{}).height:void 0,vp:c.screenSize&&a.visualViewport?(a.visualViewport||{}).width+"x"+(a.visualViewport||{}).height:void 0,ec:s||void 0,ea:t||void 0,el:u||void 0,ev:v||void 0,exd:w||void 0,exf:"undefined"!=typeof x&&!1==!!x?0:void 0});if(f.sendBeacon)f.sendBeacon(z,A);else{var y=new XMLHttpRequest;y.open("POST",z,!0),y.send(A)}};d.pushState=function(r){return"function"==typeof d.onpushstate&&d.onpushstate({state:r}),setTimeout(n,c.delay||10),i.apply(d,arguments)},n(),a.ma={trackEvent:function o(r,s,t,u){return n("event",r,s,t,u)},trackException:function q(r,s){return n("exception",null,null,null,null,r,s)}}})(window,window.pmGAID,{anonymizeIp:window.pmGAAIP,colorDepth:!0,characterSet:!0,screenSize:!0,language:!0});

View File

@@ -0,0 +1,100 @@
(function(){/*
Copyright The Closure Library Authors.
SPDX-License-Identifier: Apache-2.0
*/
var aa=this||self,l=function(a,b){a=a.split(".");var c=aa;a[0]in c||"undefined"==typeof c.execScript||c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)a.length||void 0===b?c=c[d]&&c[d]!==Object.prototype[d]?c[d]:c[d]={}:c[d]=b};var m=function(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c])},q=function(a){for(var b in a)if(a.hasOwnProperty(b))return!0;return!1};var r=/^(?:(?:https?|mailto|ftp):|[^:/?#]*(?:[/?#]|$))/i;/*
SPDX-License-Identifier: Apache-2.0
*/
var t=window,u=window.history,v=document,w=navigator,x=function(a,b){v.addEventListener?v.addEventListener(a,b,!1):v.attachEvent&&v.attachEvent("on"+a,b)};var y={},z=function(){y.TAGGING=y.TAGGING||[];y.TAGGING[1]=!0};var A=/:[0-9]+$/,B=function(a,b,c){a=a.split("&");for(var d=0;d<a.length;d++){var e=a[d].split("=");if(decodeURIComponent(e[0]).replace(/\+/g," ")===b)return b=e.slice(1).join("="),c?b:decodeURIComponent(b).replace(/\+/g," ")}},E=function(a,b){b&&(b=String(b).toLowerCase());if("protocol"===b||"port"===b)a.protocol=C(a.protocol)||C(t.location.protocol);"port"===b?a.port=String(Number(a.hostname?a.port:t.location.port)||("http"==a.protocol?80:"https"==a.protocol?443:"")):"host"===b&&(a.hostname=(a.hostname||
t.location.hostname).replace(A,"").toLowerCase());return D(a,b,void 0,void 0,void 0)},D=function(a,b,c,d,e){var f=C(a.protocol);b&&(b=String(b).toLowerCase());switch(b){case "url_no_fragment":d="";a&&a.href&&(d=a.href.indexOf("#"),d=0>d?a.href:a.href.substr(0,d));a=d;break;case "protocol":a=f;break;case "host":a=a.hostname.replace(A,"").toLowerCase();c&&(d=/^www\d*\./.exec(a))&&d[0]&&(a=a.substr(d[0].length));break;case "port":a=String(Number(a.port)||("http"==f?80:"https"==f?443:""));break;case "path":a.pathname||
a.hostname||z();a="/"==a.pathname.substr(0,1)?a.pathname:"/"+a.pathname;a=a.split("/");0<=(d||[]).indexOf(a[a.length-1])&&(a[a.length-1]="");a=a.join("/");break;case "query":a=a.search.replace("?","");e&&(a=B(a,e,void 0));break;case "extension":a=a.pathname.split(".");a=1<a.length?a[a.length-1]:"";a=a.split("/")[0];break;case "fragment":a=a.hash.replace("#","");break;default:a=a&&a.href}return a},C=function(a){return a?a.replace(":","").toLowerCase():""},F=function(a){var b=v.createElement("a");a&&
(b.href=a);var c=b.pathname;"/"!==c[0]&&(a||z(),c="/"+c);a=b.hostname.replace(A,"");return{href:b.href,protocol:b.protocol,host:b.host,hostname:a,pathname:c,search:b.search,hash:b.hash,port:b.port}};function G(){for(var a=H,b={},c=0;c<a.length;++c)b[a[c]]=c;return b}function I(){var a="ABCDEFGHIJKLMNOPQRSTUVWXYZ";a+=a.toLowerCase()+"0123456789-_";return a+"."}var H,J;function K(a){H=H||I();J=J||G();for(var b=[],c=0;c<a.length;c+=3){var d=c+1<a.length,e=c+2<a.length,f=a.charCodeAt(c),g=d?a.charCodeAt(c+1):0,h=e?a.charCodeAt(c+2):0,k=f>>2;f=(f&3)<<4|g>>4;g=(g&15)<<2|h>>6;h&=63;e||(h=64,d||(g=64));b.push(H[k],H[f],H[g],H[h])}return b.join("")}
function ba(a){function b(k){for(;d<a.length;){var n=a.charAt(d++),p=J[n];if(null!=p)return p;if(!/^[\s\xa0]*$/.test(n))throw Error("Unknown base64 encoding at char: "+n);}return k}H=H||I();J=J||G();for(var c="",d=0;;){var e=b(-1),f=b(0),g=b(64),h=b(64);if(64===h&&-1===e)return c;c+=String.fromCharCode(e<<2|f>>4);64!=g&&(c+=String.fromCharCode(f<<4&240|g>>2),64!=h&&(c+=String.fromCharCode(g<<6&192|h)))}};var L;var M=void 0,O=function(){var a=ca,b=da,c=N(),d=function(g){a(g.target||g.srcElement||{})},e=function(g){b(g.target||g.srcElement||{})};if(!c.init){x("mousedown",d);x("keyup",d);x("submit",e);var f=HTMLFormElement.prototype.submit;HTMLFormElement.prototype.submit=function(){b(this);f.call(this)};c.init=!0}},P=function(a,b,c,d,e){a={callback:a,domains:b,fragment:2===c,placement:c,forms:d,sameHost:e};N().decorators.push(a)},Q=function(a,b,c){for(var d=N().decorators,e={},f=0;f<d.length;++f){var g=d[f],
h;if(h=!c||g.forms)a:{h=g.domains;var k=a,n=!!g.sameHost;if(h&&(n||k!==v.location.hostname))for(var p=0;p<h.length;p++)if(h[p]instanceof RegExp){if(h[p].test(k)){h=!0;break a}}else if(0<=k.indexOf(h[p])||n&&0<=h[p].indexOf(k)){h=!0;break a}h=!1}h&&(h=g.placement,void 0==h&&(h=g.fragment?2:1),h===b&&m(e,g.callback()))}return e};function N(){var a={};var b=t.google_tag_data;t.google_tag_data=void 0===b?a:b;a=t.google_tag_data;b=a.gl;b&&b.decorators||(b={decorators:[]},a.gl=b);return b};var ea=/(.*?)\*(.*?)\*(.*)/,fa=/([^?#]+)(\?[^#]*)?(#.*)?/;function R(a){return new RegExp("(.*?)(^|&)"+a+"=([^&]*)&?(.*)")}var T=function(a,b){var c=[],d;for(d in a)if(a.hasOwnProperty(d)){var e=a[d];void 0!==e&&e===e&&null!==e&&"[object Object]"!==e.toString()&&(c.push(d),c.push(K(String(e))))}a=c.join("*");void 0!==b&&(c="xp_"+b,b=ha[b](a),a=a+"*"+[c,K(String(b))].join("*"));return["1",S(a),a].join("*")};
function S(a,b){a=[t.navigator.userAgent,(new Date).getTimezoneOffset(),w.userLanguage||w.language,Math.floor((new Date(Date.now())).getTime()/60/1E3)-(void 0===b?0:b),a].join("*");if(!(b=L)){b=Array(256);for(var c=0;256>c;c++){for(var d=c,e=0;8>e;e++)d=d&1?d>>>1^3988292384:d>>>1;b[c]=d}}L=b;b=4294967295;for(c=0;c<a.length;c++)b=b>>>8^L[(b^a.charCodeAt(c))&255];return((b^-1)>>>0).toString(36)}var U={},ha=(U[1]=ia,U[2]=ja,U[3]=ka,U);function ia(){return"CHECKSUM_EXP_DISABLED"}
function ja(){return"CHECKSUM_EXP_DISABLED"}function la(){w.userAgentData&&w.userAgentData.getHighEntropyValues(["architecture","model","bitness","platformVersion","uaFullVersion"]).then(function(){})}function ka(){return"CHECKSUM_EXP_DISABLED"}function ma(a){return function(b){var c=F(t.location.href),d=c.search.replace("?","");var e=B(d,"_gl",!0);b.query=V(e||"")||{};e=E(c,"fragment");var f=e.match(R("_gl"));b.fragment=V(f&&f[3]||"")||{};a&&na(c,d,e)}}
function W(a,b){if(a=R(a).exec(b)){var c=a[2],d=a[4];b=a[1];d&&(b=b+c+d)}return b}function na(a,b,c){function d(f,g){f=W("_gl",f);f.length&&(f=g+f);return f}if(u&&u.replaceState){var e=R("_gl");if(e.test(b)||e.test(c))a=E(a,"path"),b=d(b,"?"),c=d(c,"#"),u.replaceState({},void 0,""+a+b+c)}}
var V=function(a){var b=void 0===b?3:b;try{if(a){a:{for(var c=0;3>c;++c){var d=ea.exec(a);if(d){var e=d;break a}a=decodeURIComponent(a)}e=void 0}if(e&&"1"===e[1]){var f=e[2],g=e[3];a:{for(e=0;e<b;++e)if(f===S(g,e)){var h=!0;break a}h=!1}if(h){b={};var k=g?g.split("*"):[];for(g=0;g<k.length;g+=2)b[k[g]]=ba(k[g+1]);return b}}}}catch(n){}};
function X(a,b,c,d){function e(k){k=W(a,k);var n=k.charAt(k.length-1);k&&"&"!==n&&(k+="&");return k+h}d=void 0===d?!1:d;var f=fa.exec(c);if(!f)return"";c=f[1];var g=f[2]||"";f=f[3]||"";var h=a+"="+b;d?f="#"+e(f.substring(1)):g="?"+e(g.substring(1));return""+c+g+f}function Y(a,b){var c="FORM"===(a.tagName||"").toUpperCase(),d=Q(b,1,c),e=Q(b,2,c);b=Q(b,3,c);var f=M;q(d)&&(d=T(d,f),c?oa("_gl",d,a):Z("_gl",d,a,!1));!c&&q(e)&&(c=T(e),Z("_gl",c,a,!0));for(var g in b)b.hasOwnProperty(g)&&pa(g,b[g],a)}
function pa(a,b,c,d){if(c.tagName){if("a"===c.tagName.toLowerCase())return Z(a,b,c,d);if("form"===c.tagName.toLowerCase())return oa(a,b,c)}if("string"==typeof c)return X(a,b,c,d)}function Z(a,b,c,d){c.href&&(a=X(a,b,c.href,void 0===d?!1:d),r.test(a)&&(c.href=a))}
function oa(a,b,c){if(c&&c.action){var d=(c.method||"").toLowerCase();if("get"===d){d=c.childNodes||[];for(var e=!1,f=0;f<d.length;f++){var g=d[f];if(g.name===a){g.setAttribute("value",b);e=!0;break}}e||(d=v.createElement("input"),d.setAttribute("type","hidden"),d.setAttribute("name",a),d.setAttribute("value",b),c.appendChild(d))}else"post"===d&&(a=X(a,b,c.action),r.test(a)&&(c.action=a))}}
function ca(a){try{a:{for(var b=100;a&&0<b;){if(a.href&&a.nodeName.match(/^a(?:rea)?$/i)){var c=a;break a}a=a.parentNode;b--}c=null}if(c){var d=c.protocol;"http:"!==d&&"https:"!==d||Y(c,c.hostname)}}catch(e){}}function da(a){try{if(a.action){var b=E(F(a.action),"host");Y(a,b)}}catch(c){}};l("google_tag_data.glBridge.auto",function(a,b,c,d,e){O();void 0!==e&&(M=e);3===e&&la();P(a,b,"fragment"===c?2:1,!!d,!1)});l("google_tag_data.glBridge.passthrough",function(a,b,c,d){O();void 0!==d&&(M=d);P(a,[D(t.location,"host",!0)],b,!!c,!0)});l("google_tag_data.glBridge.decorate",function(a,b,c){a=T(a);return pa("_gl",a,b,!!c)});l("google_tag_data.glBridge.generate",T);
l("google_tag_data.glBridge.get",function(a,b){var c=ma(!!b);b=N();b.data||(b.data={query:{},fragment:{}},c(b.data));c={};if(b=b.data)m(c,b.query),a&&m(c,b.fragment);return c});})(window);
(function(){function La(a){var b=1,c;if(a)for(b=0,c=a.length-1;0<=c;c--){var d=a.charCodeAt(c);b=(b<<6&268435455)+d+(d<<14);d=b&266338304;b=0!=d?b^d>>21:b}return b};/*
Copyright The Closure Library Authors.
SPDX-License-Identifier: Apache-2.0
*/
var $c=function(a){this.C=a||[]};$c.prototype.set=function(a){this.C[a]=!0};$c.prototype.encode=function(){for(var a=[],b=0;b<this.C.length;b++)this.C[b]&&(a[Math.floor(b/6)]^=1<<b%6);for(b=0;b<a.length;b++)a[b]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".charAt(a[b]||0);return a.join("")+"~"};var ha=window.GoogleAnalyticsObject,wa;if(wa=void 0!=ha)wa=-1<(ha.constructor+"").indexOf("String");var ya;if(ya=wa){var fc=window.GoogleAnalyticsObject;ya=fc?fc.replace(/^[\s\xa0]+|[\s\xa0]+$/g,""):""}var gb=ya||"ga",jd=/^(?:utma\.)?\d+\.\d+$/,kd=/^amp-[\w.-]{22,64}$/,Ba=!1;var vd=new $c;function J(a){vd.set(a)}var Td=function(a){a=Dd(a);a=new $c(a);for(var b=vd.C.slice(),c=0;c<a.C.length;c++)b[c]=b[c]||a.C[c];return(new $c(b)).encode()},Dd=function(a){a=a.get(Gd);ka(a)||(a=[]);return a};var ea=function(a){return"function"==typeof a},ka=function(a){return"[object Array]"==Object.prototype.toString.call(Object(a))},qa=function(a){return void 0!=a&&-1<(a.constructor+"").indexOf("String")},D=function(a,b){return 0==a.indexOf(b)},sa=function(a){return a?a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,""):""},ra=function(){for(var a=O.navigator.userAgent+(M.cookie?M.cookie:"")+(M.referrer?M.referrer:""),b=a.length,c=O.history.length;0<c;)a+=c--^b++;return[hd()^La(a)&2147483647,Math.round((new Date).getTime()/
1E3)].join(".")},ta=function(a){var b=M.createElement("img");b.width=1;b.height=1;b.src=a;return b},ua=function(){},K=function(a){if(encodeURIComponent instanceof Function)return encodeURIComponent(a);J(28);return a},L=function(a,b,c,d){try{a.addEventListener?a.addEventListener(b,c,!!d):a.attachEvent&&a.attachEvent("on"+b,c)}catch(e){J(27)}},f=/^[\w\-:/.?=&%!\[\]]+$/,Nd=/^[\w+/_-]+[=]{0,2}$/,ff=null,Id=function(a,b,c,d,e){if(!ff){ff={createScriptURL:function(ca){return ca},createHTML:function(ca){return ca}};
try{ff=window.trustedTypes.createPolicy("google-analytics",ff)}catch(ca){}}if(a){var g=M.querySelector&&M.querySelector("script[nonce]")||null;g=g?g.nonce||g.getAttribute&&g.getAttribute("nonce")||"":"";c?(e=d="",b&&f.test(b)&&(d=' id="'+b+'"'),g&&Nd.test(g)&&(e=' nonce="'+g+'"'),f.test(a)&&M.write(ff.createHTML("<script"+d+e+' src="'+a+'">\x3c/script>'))):(c=M.createElement("script"),c.type="text/javascript",c.async=!0,c.src=ff.createScriptURL(a),d&&(c.onload=d),e&&(c.onerror=e),b&&(c.id=b),g&&c.setAttribute("nonce",
g),a=M.getElementsByTagName("script")[0],a.parentNode.insertBefore(c,a))}},be=function(a,b){return E(M.location[b?"href":"search"],a)},E=function(a,b){return(a=a.match("(?:&|#|\\?)"+K(b).replace(/([.*+?^=!:${}()|\[\]\/\\])/g,"\\$1")+"=([^&#]*)"))&&2==a.length?a[1]:""},xa=function(){var a=""+M.location.hostname;return 0==a.indexOf("www.")?a.substring(4):a},de=function(a,b){var c=a.indexOf(b);if(5==c||6==c)if(a=a.charAt(c+b.length),"/"==a||"?"==a||""==a||":"==a)return!0;return!1},za=function(a,b){if(1==
b.length&&null!=b[0]&&"object"===typeof b[0])return b[0];for(var c={},d=Math.min(a.length+1,b.length),e=0;e<d;e++)if("object"===typeof b[e]){for(var g in b[e])b[e].hasOwnProperty(g)&&(c[g]=b[e][g]);break}else e<a.length&&(c[a[e]]=b[e]);return c},Ee=function(a,b){for(var c=0;c<a.length;c++)if(b==a[c])return!0;return!1};var ee=function(){this.oa=[];this.ea={};this.m={}};ee.prototype.set=function(a,b,c){this.oa.push(a);c?this.m[":"+a]=b:this.ea[":"+a]=b};ee.prototype.get=function(a){return this.m.hasOwnProperty(":"+a)?this.m[":"+a]:this.ea[":"+a]};ee.prototype.map=function(a){for(var b=0;b<this.oa.length;b++){var c=this.oa[b],d=this.get(c);d&&a(c,d)}};var O=window,M=document,jf=document.currentScript?document.currentScript.src:"",va=function(a,b){return setTimeout(a,b)};var Qa=window,Za=document,G=function(a){var b=Qa._gaUserPrefs;if(b&&b.ioo&&b.ioo()||a&&!0===Qa["ga-disable-"+a])return!0;try{var c=Qa.external;if(c&&c._gaUserPrefs&&"oo"==c._gaUserPrefs)return!0}catch(g){}a=[];b=String(Za.cookie).split(";");for(c=0;c<b.length;c++){var d=b[c].split("="),e=d[0].replace(/^\s*|\s*$/g,"");e&&"AMP_TOKEN"==e&&((d=d.slice(1).join("=").replace(/^\s*|\s*$/g,""))&&(d=decodeURIComponent(d)),a.push(d))}for(b=0;b<a.length;b++)if("$OPT_OUT"==a[b])return!0;return Za.getElementById("__gaOptOutExtension")?
!0:!1};var Ca=function(a){var b=[],c=M.cookie.split(";");a=new RegExp("^\\s*"+a+"=\\s*(.*?)\\s*$");for(var d=0;d<c.length;d++){var e=c[d].match(a);e&&b.push(e[1])}return b},zc=function(a,b,c,d,e,g,ca){e=G(e)?!1:eb.test(M.location.hostname)||"/"==c&&vc.test(d)?!1:!0;if(!e)return!1;b&&1200<b.length&&(b=b.substring(0,1200));c=a+"="+b+"; path="+c+"; ";g&&(c+="expires="+(new Date((new Date).getTime()+g)).toGMTString()+"; ");d&&"none"!==d&&(c+="domain="+d+";");ca&&(c+=ca+";");d=M.cookie;M.cookie=c;if(!(d=d!=M.cookie))a:{a=
Ca(a);for(d=0;d<a.length;d++)if(b==a[d]){d=!0;break a}d=!1}return d},Cc=function(a){return encodeURIComponent?encodeURIComponent(a).replace(/\(/g,"%28").replace(/\)/g,"%29"):a},vc=/^(www\.)?google(\.com?)?(\.[a-z]{2})?$/,eb=/(^|\.)doubleclick\.net$/i;var Oe=function(a){var b=[],c=M.cookie.split(";");a=new RegExp("^\\s*"+(a||"_gac")+"_(UA-\\d+-\\d+)=\\s*(.+?)\\s*$");for(var d=0;d<c.length;d++){var e=c[d].match(a);e&&b.push({ja:e[1],value:e[2],timestamp:Number(e[2].split(".")[1])||0})}b.sort(function(g,ca){return ca.timestamp-g.timestamp});return b};
function df(a,b,c){b=Oe(b);var d={};if(!b||!b.length)return d;for(var e=0;e<b.length;e++){var g=b[e].value.split(".");if("1"!==g[0]||c&&3>g.length||!c&&3!==g.length)a&&(a.na=!0);else if(Number(g[1])){d[b[e].ja]?a&&(a.pa=!0):d[b[e].ja]=[];var ca={version:g[0],timestamp:1E3*Number(g[1]),qa:g[2]};c&&3<g.length&&(ca.labels=g.slice(3));d[b[e].ja].push(ca)}}return d};var Fa,Ga,fb,Ab,ja=/^https?:\/\/[^/]*cdn\.ampproject\.org\//,Ue=/^(?:www\.|m\.|amp\.)+/,Ub=[],da=function(a){if(ye(a[Kd])){if(void 0===Ab){var b;if(b=(b=De.get())&&b._ga||void 0)Ab=b,J(81)}if(void 0!==Ab)return a[Q]||(a[Q]=Ab),!1}if(a[Kd]){J(67);if(a[ac]&&"cookie"!=a[ac])return!1;if(void 0!==Ab)a[Q]||(a[Q]=Ab);else{a:{b=String(a[W]||xa());var c=String(a[Yb]||"/"),d=Ca(String(a[U]||"_ga"));b=na(d,b,c);if(!b||jd.test(b))b=!0;else if(b=Ca("AMP_TOKEN"),0==b.length)b=!0;else{if(1==b.length&&(b=decodeURIComponent(b[0]),
"$RETRIEVING"==b||"$OPT_OUT"==b||"$ERROR"==b||"$NOT_FOUND"==b)){b=!0;break a}b=!1}}if(b&&tc(ic,String(a[Na])))return!0}}return!1},ic=function(){Z.D([ua])},tc=function(a,b){var c=Ca("AMP_TOKEN");if(1<c.length)return J(55),!1;c=decodeURIComponent(c[0]||"");if("$OPT_OUT"==c||"$ERROR"==c||G(b))return J(62),!1;if(!ja.test(M.referrer)&&"$NOT_FOUND"==c)return J(68),!1;if(void 0!==Ab)return J(56),va(function(){a(Ab)},0),!0;if(Fa)return Ub.push(a),!0;if("$RETRIEVING"==c)return J(57),va(function(){tc(a,b)},
1E4),!0;Fa=!0;c&&"$"!=c[0]||(xc("$RETRIEVING",3E4),setTimeout(Mc,3E4),c="");return Pc(c,b)?(Ub.push(a),!0):!1},Pc=function(a,b,c){if(!window.JSON)return J(58),!1;var d=O.XMLHttpRequest;if(!d)return J(59),!1;var e=new d;if(!("withCredentials"in e))return J(60),!1;e.open("POST",(c||"https://ampcid.google.com/v1/publisher:getClientId")+"?key=AIzaSyA65lEHUEizIsNtlbNo-l2K18dT680nsaM",!0);e.withCredentials=!0;e.setRequestHeader("Content-Type","text/plain");e.onload=function(){Fa=!1;if(4==e.readyState){try{200!=
e.status&&(J(61),Qc("","$ERROR",3E4));var g=JSON.parse(e.responseText);g.optOut?(J(63),Qc("","$OPT_OUT",31536E6)):g.clientId?Qc(g.clientId,g.securityToken,31536E6):!c&&g.alternateUrl?(Ga&&clearTimeout(Ga),Fa=!0,Pc(a,b,g.alternateUrl)):(J(64),Qc("","$NOT_FOUND",36E5))}catch(ca){J(65),Qc("","$ERROR",3E4)}e=null}};d={originScope:"AMP_ECID_GOOGLE"};a&&(d.securityToken=a);e.send(JSON.stringify(d));Ga=va(function(){J(66);Qc("","$ERROR",3E4)},1E4);return!0},Mc=function(){Fa=!1},xc=function(a,b){if(void 0===
fb){fb="";for(var c=id(),d=0;d<c.length;d++){var e=c[d];if(zc("AMP_TOKEN",encodeURIComponent(a),"/",e,"",b)){fb=e;return}}}zc("AMP_TOKEN",encodeURIComponent(a),"/",fb,"",b)},Qc=function(a,b,c){Ga&&clearTimeout(Ga);b&&xc(b,c);Ab=a;b=Ub;Ub=[];for(c=0;c<b.length;c++)b[c](a)},ye=function(a){a:{if(ja.test(M.referrer)){var b=M.location.hostname.replace(Ue,"");b:{var c=M.referrer;c=c.replace(/^https?:\/\//,"");var d=c.replace(/^[^/]+/,"").split("/"),e=d[2];d=(d="s"==e?d[3]:e)?decodeURIComponent(d):d;if(!d){if(0==
c.indexOf("xn--")){c="";break b}(c=c.match(/(.*)\.cdn\.ampproject\.org\/?$/))&&2==c.length&&(d=c[1].replace(/-/g,".").replace(/\.\./g,"-"))}c=d?d.replace(Ue,""):""}(d=b===c)||(c="."+c,d=b.substring(b.length-c.length,b.length)===c);if(d){b=!0;break a}else J(78)}b=!1}return b&&!1!==a};var bd=function(a){return(a?"https:":Ba||"https:"==M.location.protocol?"https:":"http:")+"//www.google-analytics.com"},Ge=function(a){switch(a){default:case 1:return"https://www.google-analytics.com/gtm/js?id=";case 2:return"https://www.googletagmanager.com/gtag/js?id="}},Da=function(a){this.name="len";this.message=a+"-8192"},ba=function(a,b,c){c=c||ua;if(2036>=b.length)wc(a,b,c);else if(8192>=b.length)x(a,b,c)||wd(a,b,c)||wc(a,b,c);else throw ge("len",b.length),new Da(b.length);},pe=function(a,b,
c,d){d=d||ua;wd(a+"?"+b,"",d,c)},wc=function(a,b,c){var d=ta(a+"?"+b);d.onload=d.onerror=function(){d.onload=null;d.onerror=null;c()}},wd=function(a,b,c,d){var e=O.XMLHttpRequest;if(!e)return!1;var g=new e;if(!("withCredentials"in g))return!1;a=a.replace(/^http:/,"https:");g.open("POST",a,!0);g.withCredentials=!0;g.setRequestHeader("Content-Type","text/plain");g.onreadystatechange=function(){if(4==g.readyState){if(d&&"text/plain"===g.getResponseHeader("Content-Type"))try{Ea(d,g.responseText,c)}catch(ca){ge("xhr",
"rsp"),c()}else c();g=null}};g.send(b);return!0},Ea=function(a,b,c){if(1>b.length)ge("xhr","ver","0"),c();else if(3<a.count++)ge("xhr","tmr",""+a.count),c();else{var d=b.charAt(0);if("1"===d)oc(a,b.substring(1),c);else if(a.V&&"2"===d){var e=b.substring(1).split(","),g=0;b=function(){++g===e.length&&c()};for(d=0;d<e.length;d++)oc(a,e[d],b)}else ge("xhr","ver",String(b.length)),c()}},oc=function(a,b,c){if(0===b.length)c();else{var d=b.charAt(0);switch(d){case "d":pe("https://stats.g.doubleclick.net/j/collect",
a.U,a,c);break;case "g":wc("https://www.google.com/ads/ga-audiences",a.google,c);(b=b.substring(1))&&(/^[a-z.]{1,6}$/.test(b)?wc("https://www.google.%/ads/ga-audiences".replace("%",b),a.google,ua):ge("tld","bcc",b));break;case "G":if(a.V){a.V("G-"+b.substring(1));c();break}case "x":if(a.V){a.V();c();break}default:ge("xhr","brc",d),c()}}},x=function(a,b,c){return O.navigator.sendBeacon?O.navigator.sendBeacon(a,b)?(c(),!0):!1:!1},ge=function(a,b,c){1<=100*Math.random()||G("?")||(a=["t=error","_e="+
a,"_v=j96","sr=1"],b&&a.push("_f="+b),c&&a.push("_m="+K(c.substring(0,100))),a.push("aip=1"),a.push("z="+hd()),wc(bd(!0)+"/u/d",a.join("&"),ua))};var qc=function(){return O.gaData=O.gaData||{}},h=function(a){var b=qc();return b[a]=b[a]||{}};var Ha=function(){this.M=[]};Ha.prototype.add=function(a){this.M.push(a)};Ha.prototype.D=function(a){try{for(var b=0;b<this.M.length;b++){var c=a.get(this.M[b]);c&&ea(c)&&c.call(O,a)}}catch(d){}b=a.get(Ia);b!=ua&&ea(b)&&(a.set(Ia,ua,!0),setTimeout(b,10))};function Ja(a){if(100!=a.get(Ka)&&La(P(a,Q))%1E4>=100*R(a,Ka))throw"abort";}function Ma(a){if(G(P(a,Na)))throw"abort";}function Oa(){var a=M.location.protocol;if("http:"!=a&&"https:"!=a)throw"abort";}
function Pa(a){try{O.navigator.sendBeacon?J(42):O.XMLHttpRequest&&"withCredentials"in new O.XMLHttpRequest&&J(40)}catch(c){}a.set(ld,Td(a),!0);a.set(Ac,R(a,Ac)+1);var b=[];ue.map(function(c,d){d.F&&(c=a.get(c),void 0!=c&&c!=d.defaultValue&&("boolean"==typeof c&&(c*=1),b.push(d.F+"="+K(""+c))))});!1===a.get(xe)&&b.push("npa=1");b.push("z="+Bd());a.set(Ra,b.join("&"),!0)}
function Sa(a){var b=P(a,fa);!b&&a.get(Vd)&&(b="beacon");var c=P(a,gd),d=P(a,oe),e=c||(d||bd(!1)+"")+"/collect";switch(P(a,ad)){case "d":e=c||(d||bd(!1)+"")+"/j/collect";b=a.get(qe)||void 0;pe(e,P(a,Ra),b,a.Z(Ia));break;default:b?(c=P(a,Ra),d=(d=a.Z(Ia))||ua,"image"==b?wc(e,c,d):"xhr"==b&&wd(e,c,d)||"beacon"==b&&x(e,c,d)||ba(e,c,d)):ba(e,P(a,Ra),a.Z(Ia))}e=P(a,Na);e=h(e);b=e.hitcount;e.hitcount=b?b+1:1;e.first_hit||(e.first_hit=(new Date).getTime());e=P(a,Na);delete h(e).pending_experiments;a.set(Ia,
ua,!0)}function Hc(a){qc().expId&&a.set(Nc,qc().expId);qc().expVar&&a.set(Oc,qc().expVar);var b=P(a,Na);if(b=h(b).pending_experiments){var c=[];for(d in b)b.hasOwnProperty(d)&&b[d]&&c.push(encodeURIComponent(d)+"."+encodeURIComponent(b[d]));var d=c.join("!")}else d=void 0;d&&((b=a.get(m))&&(d=b+"!"+d),a.set(m,d,!0))}function cd(){if(O.navigator&&"preview"==O.navigator.loadPurpose)throw"abort";}
function yd(a){var b=O.gaDevIds||[];if(ka(b)){var c=a.get("&did");qa(c)&&0<c.length&&(b=b.concat(c.split(",")));c=[];for(var d=0;d<b.length;d++)Ee(c,b[d])||c.push(b[d]);0!=c.length&&a.set("&did",c.join(","),!0)}}function vb(a){if(!a.get(Na))throw"abort";}
function Pe(a){try{if(!a.get(Qe)&&(a.set(Qe,!0),!a.get("&gtm"))){var b=void 0;lf(be("gtm_debug"))&&(b=2);!b&&D(M.referrer,"https://tagassistant.google.com/")&&(b=3);!b&&Ee(M.cookie.split("; "),"__TAG_ASSISTANT=x")&&(b=4);if(!b){var c=M.documentElement.getAttribute("data-tag-assistant-present");lf(c)&&(b=5)}if(b){O["google.tagmanager.debugui2.queue"]||(O["google.tagmanager.debugui2.queue"]=[],Id("https://www.google-analytics.com/debug/bootstrap?id="+a.get(Na)+"&src=LEGACY&cond="+b));var d=M.currentScript;
O["google.tagmanager.debugui2.queue"].push({messageType:"LEGACY_CONTAINER_STARTING",data:{id:a.get(Na),scriptSource:d&&d.src||""}})}}}catch(e){}}function lf(a){if(null==a||0===a.length)return!1;a=Number(a);var b=Date.now();return a<b+3E5&&a>b-9E5};var hd=function(){return Math.round(2147483647*Math.random())},Bd=function(){try{var a=new Uint32Array(1);O.crypto.getRandomValues(a);return a[0]&2147483647}catch(b){return hd()}};function Ta(a){var b=R(a,Ua);500<=b&&J(15);var c=P(a,Va);if("transaction"!=c&&"item"!=c){c=R(a,Wa);var d=(new Date).getTime(),e=R(a,Xa);0==e&&a.set(Xa,d);e=Math.round(2*(d-e)/1E3);0<e&&(c=Math.min(c+e,20),a.set(Xa,d));if(0>=c)throw"abort";a.set(Wa,--c)}a.set(Ua,++b)};var Ya=function(){this.data=new ee};Ya.prototype.get=function(a){var b=$a(a),c=this.data.get(a);b&&void 0==c&&(c=ea(b.defaultValue)?b.defaultValue():b.defaultValue);return b&&b.Z?b.Z(this,a,c):c};var P=function(a,b){a=a.get(b);return void 0==a?"":""+a},R=function(a,b){a=a.get(b);return void 0==a||""===a?0:Number(a)};Ya.prototype.Z=function(a){return(a=this.get(a))&&ea(a)?a:ua};
Ya.prototype.set=function(a,b,c){if(a)if("object"==typeof a)for(var d in a)a.hasOwnProperty(d)&&ab(this,d,a[d],c);else ab(this,a,b,c)};var ab=function(a,b,c,d){if(void 0!=c)switch(b){case Na:wb.test(c)}var e=$a(b);e&&e.o?e.o(a,b,c,d):a.data.set(b,c,d)};var ue=new ee,ve=[],bb=function(a,b,c,d,e){this.name=a;this.F=b;this.Z=d;this.o=e;this.defaultValue=c},$a=function(a){var b=ue.get(a);if(!b)for(var c=0;c<ve.length;c++){var d=ve[c],e=d[0].exec(a);if(e){b=d[1](e);ue.set(b.name,b);break}}return b},yc=function(a){var b;ue.map(function(c,d){d.F==a&&(b=d)});return b&&b.name},S=function(a,b,c,d,e){a=new bb(a,b,c,d,e);ue.set(a.name,a);return a.name},cb=function(a,b){ve.push([new RegExp("^"+a+"$"),b])},T=function(a,b,c){return S(a,b,c,void 0,db)},db=function(){};var hb=T("apiVersion","v"),ib=T("clientVersion","_v");S("anonymizeIp","aip");var jb=S("adSenseId","a"),Va=S("hitType","t"),Ia=S("hitCallback"),Ra=S("hitPayload");S("nonInteraction","ni");S("currencyCode","cu");S("dataSource","ds");var Vd=S("useBeacon",void 0,!1),fa=S("transport");S("sessionControl","sc","");S("sessionGroup","sg");S("queueTime","qt");var Ac=S("_s","_s");S("screenName","cd");var kb=S("location","dl",""),lb=S("referrer","dr"),mb=S("page","dp","");S("hostname","dh");
var nb=S("language","ul"),ob=S("encoding","de");S("title","dt",function(){return M.title||void 0});cb("contentGroup([0-9]+)",function(a){return new bb(a[0],"cg"+a[1])});var pb=S("screenColors","sd"),qb=S("screenResolution","sr"),rb=S("viewportSize","vp"),sb=S("javaEnabled","je"),tb=S("flashVersion","fl");S("campaignId","ci");S("campaignName","cn");S("campaignSource","cs");S("campaignMedium","cm");S("campaignKeyword","ck");S("campaignContent","cc");
var ub=S("eventCategory","ec"),xb=S("eventAction","ea"),yb=S("eventLabel","el"),zb=S("eventValue","ev"),Bb=S("socialNetwork","sn"),Cb=S("socialAction","sa"),Db=S("socialTarget","st"),Eb=S("l1","plt"),Fb=S("l2","pdt"),Gb=S("l3","dns"),Hb=S("l4","rrt"),Ib=S("l5","srt"),Jb=S("l6","tcp"),Kb=S("l7","dit"),Lb=S("l8","clt"),Ve=S("l9","_gst"),We=S("l10","_gbt"),Xe=S("l11","_cst"),Ye=S("l12","_cbt"),Mb=S("timingCategory","utc"),Nb=S("timingVar","utv"),Ob=S("timingLabel","utl"),Pb=S("timingValue","utt");
S("appName","an");S("appVersion","av","");S("appId","aid","");S("appInstallerId","aiid","");S("exDescription","exd");S("exFatal","exf");var Nc=S("expId","xid"),Oc=S("expVar","xvar"),m=S("exp","exp"),Rc=S("_utma","_utma"),Sc=S("_utmz","_utmz"),Tc=S("_utmht","_utmht"),Ua=S("_hc",void 0,0),Xa=S("_ti",void 0,0),Wa=S("_to",void 0,20);cb("dimension([0-9]+)",function(a){return new bb(a[0],"cd"+a[1])});cb("metric([0-9]+)",function(a){return new bb(a[0],"cm"+a[1])});S("linkerParam",void 0,void 0,Bc,db);
var Ze=T("_cd2l",void 0,!1),ld=S("usage","_u"),Gd=S("_um");S("forceSSL",void 0,void 0,function(){return Ba},function(a,b,c){J(34);Ba=!!c});var ed=S("_j1","jid"),ia=S("_j2","gjid");cb("\\&(.*)",function(a){var b=new bb(a[0],a[1]),c=yc(a[0].substring(1));c&&(b.Z=function(d){return d.get(c)},b.o=function(d,e,g,ca){d.set(c,g,ca)},b.F=void 0);return b});
var Qb=T("_oot"),dd=S("previewTask"),Rb=S("checkProtocolTask"),md=S("validationTask"),Sb=S("checkStorageTask"),Uc=S("historyImportTask"),Tb=S("samplerTask"),Vb=S("_rlt"),Wb=S("buildHitTask"),Xb=S("sendHitTask"),Vc=S("ceTask"),zd=S("devIdTask"),Cd=S("timingTask"),Ld=S("displayFeaturesTask"),oa=S("customTask"),ze=S("fpsCrossDomainTask"),Re=T("_cta"),V=T("name"),Q=T("clientId","cid"),n=T("clientIdTime"),xd=T("storedClientId"),Ad=S("userId","uid"),Na=T("trackingId","tid"),U=T("cookieName",void 0,"_ga"),
W=T("cookieDomain"),Yb=T("cookiePath",void 0,"/"),Zb=T("cookieExpires",void 0,63072E3),Hd=T("cookieUpdate",void 0,!0),Be=T("cookieFlags",void 0,""),$b=T("legacyCookieDomain"),Wc=T("legacyHistoryImport",void 0,!0),ac=T("storage",void 0,"cookie"),bc=T("allowLinker",void 0,!1),cc=T("allowAnchor",void 0,!0),Ka=T("sampleRate","sf",100),dc=T("siteSpeedSampleRate",void 0,1),ec=T("alwaysSendReferrer",void 0,!1),I=T("_gid","_gid"),la=T("_gcn"),Kd=T("useAmpClientId"),ce=T("_gclid"),fe=T("_gt"),he=T("_ge",void 0,
7776E6),ie=T("_gclsrc"),je=T("storeGac",void 0,!0),oe=S("_x_19"),Ae=S("_fplc","_fplc"),F=T("_cs"),Je=T("_useUp",void 0,!1),Le=S("up","up"),Qe=S("_tac",void 0,!1),Se=T("_gbraid"),Te=T("_gbt"),bf=T("_gbe",void 0,7776E6),gd=S("transportUrl"),Md=S("_r","_r"),Od=S("_slc","_slc"),qe=S("_dp"),ad=S("_jt",void 0,"n"),Ud=S("allowAdFeatures",void 0,!0),xe=S("allowAdPersonalizationSignals",void 0,!0);
function X(a,b,c,d){b[a]=function(){try{return d&&J(d),c.apply(this,arguments)}catch(e){throw ge("exc",a,e&&e.name),e;}}};var Ed=function(a){if("cookie"==a.get(ac))return a=Ca("FPLC"),0<a.length?a[0]:void 0},Fe=function(a){var b;if(b=P(a,oe)&&a.get(Ze))b=De.get(a.get(cc)),b=!(b&&b._fplc);b&&!Ed(a)&&a.set(Ae,"0")};var aa=function(a){var b=Math.min(R(a,dc),100);return La(P(a,Q))%100>=b?!1:!0},gc=function(a){var b={};if(Ec(b)||Fc(b)){var c=b[Eb];void 0==c||Infinity==c||isNaN(c)||(0<c?(Y(b,Gb),Y(b,Jb),Y(b,Ib),Y(b,Fb),Y(b,Hb),Y(b,Kb),Y(b,Lb),Y(b,Ve),Y(b,We),Y(b,Xe),Y(b,Ye),va(function(){a(b)},10)):L(O,"load",function(){gc(a)},!1))}},Ec=function(a){var b=O.performance||O.webkitPerformance;b=b&&b.timing;if(!b)return!1;var c=b.navigationStart;if(0==c)return!1;a[Eb]=b.loadEventStart-c;a[Gb]=b.domainLookupEnd-b.domainLookupStart;
a[Jb]=b.connectEnd-b.connectStart;a[Ib]=b.responseStart-b.requestStart;a[Fb]=b.responseEnd-b.responseStart;a[Hb]=b.fetchStart-c;a[Kb]=b.domInteractive-c;a[Lb]=b.domContentLoadedEventStart-c;a[Ve]=N.L-c;a[We]=N.ya-c;O.google_tag_manager&&O.google_tag_manager._li&&(b=O.google_tag_manager._li,a[Xe]=b.cst,a[Ye]=b.cbt);return!0},Fc=function(a){if(O.top!=O)return!1;var b=O.external,c=b&&b.onloadT;b&&!b.isValidLoadTime&&(c=void 0);2147483648<c&&(c=void 0);0<c&&b.setPageReadyTime();if(void 0==c)return!1;
a[Eb]=c;return!0},Y=function(a,b){var c=a[b];if(isNaN(c)||Infinity==c||0>c)a[b]=void 0},Fd=function(a){return function(b){if("pageview"==b.get(Va)&&!a.I){a.I=!0;var c=aa(b),d=0<E(P(b,kb),"gclid").length,e=0<E(P(b,kb),"wbraid").length;(c||d||e)&&gc(function(g){c&&a.send("timing",g);(d||e)&&a.send("adtiming",g)})}}};var hc=!1,mc=function(a){if("cookie"==P(a,ac)){if(a.get(Hd)||P(a,xd)!=P(a,Q)){var b=1E3*R(a,Zb);ma(a,Q,U,b);a.data.set(xd,P(a,Q))}(a.get(Hd)||uc(a)!=P(a,I))&&ma(a,I,la,864E5);if(a.get(je)){if(b=P(a,ce)){var c=Math.min(R(a,he),1E3*R(a,Zb));c=0===c?0:Math.min(c,1E3*R(a,fe)+c-(new Date).getTime());a.data.set(he,c);var d={},e=P(a,fe),g=P(a,ie),ca=kc(P(a,Yb)),l=lc(P(a,W)),k=P(a,Na),w=P(a,Be);g&&"aw.ds"!=g?d&&(d.ua=!0):(b=["1",e,Cc(b)].join("."),0<=c&&(d&&(d.ta=!0),zc("_gac_"+Cc(k),b,ca,l,k,c,w)));le(d)}}else J(75);
a.get(je)&&(b=P(a,Se))&&(c=Math.min(R(a,bf),1E3*R(a,Zb)),c=0===c?0:Math.min(c,1E3*R(a,Te)+c-(new Date).getTime()),a.data.set(bf,c),d={},w=P(a,Te),ca=kc(P(a,Yb)),l=lc(P(a,W)),k=P(a,Na),a=P(a,Be),b=["1",w,Cc(b)].join("."),0<=c&&(d&&(d.ta=!0),zc("_gac_gb_"+Cc(k),b,ca,l,k,c,a)),ef(d))}},ma=function(a,b,c,d){var e=nd(a,b);if(e){c=P(a,c);var g=kc(P(a,Yb)),ca=lc(P(a,W)),l=P(a,Be),k=P(a,Na);if("auto"!=ca)zc(c,e,g,ca,k,d,l)&&(hc=!0);else{J(32);for(var w=id(),Ce=0;Ce<w.length;Ce++)if(ca=w[Ce],a.data.set(W,
ca),e=nd(a,b),zc(c,e,g,ca,k,d,l)){hc=!0;return}a.data.set(W,"auto")}}},uc=function(a){var b=Ca(P(a,la));return Xd(a,b)},nc=function(a){if("cookie"==P(a,ac)&&!hc&&(mc(a),!hc))throw"abort";},Yc=function(a){if(a.get(Wc)){var b=P(a,W),c=P(a,$b)||xa(),d=Xc("__utma",c,b);d&&(J(19),a.set(Tc,(new Date).getTime(),!0),a.set(Rc,d.R),(b=Xc("__utmz",c,b))&&d.hash==b.hash&&a.set(Sc,b.R))}},nd=function(a,b){b=Cc(P(a,b));var c=lc(P(a,W)).split(".").length;a=jc(P(a,Yb));1<a&&(c+="-"+a);return b?["GA1",c,b].join("."):
""},Xd=function(a,b){return na(b,P(a,W),P(a,Yb))},na=function(a,b,c){if(!a||1>a.length)J(12);else{for(var d=[],e=0;e<a.length;e++){var g=a[e];var ca=g.split(".");var l=ca.shift();("GA1"==l||"1"==l)&&1<ca.length?(g=ca.shift().split("-"),1==g.length&&(g[1]="1"),g[0]*=1,g[1]*=1,ca={H:g,s:ca.join(".")}):ca=kd.test(g)?{H:[0,0],s:g}:void 0;ca&&d.push(ca)}if(1==d.length)return J(13),d[0].s;if(0==d.length)J(12);else{J(14);d=Gc(d,lc(b).split(".").length,0);if(1==d.length)return d[0].s;d=Gc(d,jc(c),1);1<d.length&&
J(41);return d[0]&&d[0].s}}},Gc=function(a,b,c){for(var d=[],e=[],g,ca=0;ca<a.length;ca++){var l=a[ca];l.H[c]==b?d.push(l):void 0==g||l.H[c]<g?(e=[l],g=l.H[c]):l.H[c]==g&&e.push(l)}return 0<d.length?d:e},lc=function(a){return 0==a.indexOf(".")?a.substr(1):a},id=function(){var a=[],b=xa().split(".");if(4==b.length){var c=b[b.length-1];if(parseInt(c,10)==c)return["none"]}for(c=b.length-2;0<=c;c--)a.push(b.slice(c).join("."));b=M.location.hostname;eb.test(b)||vc.test(b)||a.push("none");return a},kc=
function(a){if(!a)return"/";1<a.length&&a.lastIndexOf("/")==a.length-1&&(a=a.substr(0,a.length-1));0!=a.indexOf("/")&&(a="/"+a);return a},jc=function(a){a=kc(a);return"/"==a?1:a.split("/").length},le=function(a){a.ta&&J(77);a.na&&J(74);a.pa&&J(73);a.ua&&J(69)},ef=function(a){a.ta&&J(85);a.na&&J(86);a.pa&&J(87)};function Xc(a,b,c){"none"==b&&(b="");var d=[],e=Ca(a);a="__utma"==a?6:2;for(var g=0;g<e.length;g++){var ca=(""+e[g]).split(".");ca.length>=a&&d.push({hash:ca[0],R:e[g],O:ca})}if(0!=d.length)return 1==d.length?d[0]:Zc(b,d)||Zc(c,d)||Zc(null,d)||d[0]}function Zc(a,b){if(null==a)var c=a=1;else c=La(a),a=La(D(a,".")?a.substring(1):"."+a);for(var d=0;d<b.length;d++)if(b[d].hash==c||b[d].hash==a)return b[d]};var Jc=new RegExp(/^https?:\/\/([^\/:]+)/),De=O.google_tag_data.glBridge,Kc=RegExp("(.*)([?&#])(?:_ga=[^&#]*)(?:&?)(.*)"),od=RegExp("(.*)([?&#])(?:_gac=[^&#]*)(?:&?)(.*)");function Bc(a){if(a.get(Ze))return J(35),De.generate($e(a));var b=P(a,Q),c=P(a,I)||"";b="_ga=2."+K(pa(c+b,0)+"."+c+"-"+b);(a=af(a))?(J(44),a="&_gac=1."+K([pa(a.qa,0),a.timestamp,a.qa].join("."))):a="";return b+a}
function Ic(a,b){var c=new Date,d=O.navigator,e=d.plugins||[];a=[a,d.userAgent,c.getTimezoneOffset(),c.getYear(),c.getDate(),c.getHours(),c.getMinutes()+b];for(b=0;b<e.length;++b)a.push(e[b].description);return La(a.join("."))}function pa(a,b){var c=new Date,d=O.navigator,e=c.getHours()+Math.floor((c.getMinutes()+b)/60);return La([a,d.userAgent,d.language||"",c.getTimezoneOffset(),c.getYear(),c.getDate()+Math.floor(e/24),(24+e)%24,(60+c.getMinutes()+b)%60].join("."))}
var Dc=function(a){J(48);this.target=a;this.T=!1};Dc.prototype.ca=function(a,b){if(a){if(this.target.get(Ze))return De.decorate($e(this.target),a,b);if(a.tagName){if("a"==a.tagName.toLowerCase()){a.href&&(a.href=qd(this,a.href,b));return}if("form"==a.tagName.toLowerCase())return rd(this,a)}if("string"==typeof a)return qd(this,a,b)}};
var qd=function(a,b,c){var d=Kc.exec(b);d&&3<=d.length&&(b=d[1]+(d[3]?d[2]+d[3]:""));(d=od.exec(b))&&3<=d.length&&(b=d[1]+(d[3]?d[2]+d[3]:""));a=a.target.get("linkerParam");d=b.indexOf("?");var e=b.indexOf("#");b=c?b+((-1==e?"#":"&")+a):-1==e?b+((-1===d?"?":"&")+a):b.substring(0,e)+(-1===d||d>e?"?":"&")+a+b.substring(e);b=b.replace(/&+_ga=/,"&_ga=");return b=b.replace(RegExp("&+_gac="),"&_gac=")},rd=function(a,b){if(b&&b.action)if("get"==b.method.toLowerCase()){a=a.target.get("linkerParam").split("&");
for(var c=0;c<a.length;c++){var d=a[c].split("="),e=d[1];d=d[0];for(var g=b.childNodes||[],ca=!1,l=0;l<g.length;l++)if(g[l].name==d){g[l].setAttribute("value",e);ca=!0;break}ca||(g=M.createElement("input"),g.setAttribute("type","hidden"),g.setAttribute("name",d),g.setAttribute("value",e),b.appendChild(g))}}else"post"==b.method.toLowerCase()&&(b.action=qd(a,b.action))};
Dc.prototype.S=function(a,b,c){function d(g){try{g=g||O.event;a:{var ca=g.target||g.srcElement;for(g=100;ca&&0<g;){if(ca.href&&ca.nodeName.match(/^a(?:rea)?$/i)){var l=ca;break a}ca=ca.parentNode;g--}l={}}("http:"==l.protocol||"https:"==l.protocol)&&sd(a,l.hostname||"")&&l.href&&(l.href=qd(e,l.href,b))}catch(k){J(26)}}var e=this;this.target.get(Ze)?De.auto(function(){return $e(e.target)},a,b?"fragment":"",c):(this.T||(this.T=!0,L(M,"mousedown",d,!1),L(M,"keyup",d,!1)),c&&L(M,"submit",function(g){g=
g||O.event;if((g=g.target||g.srcElement)&&g.action){var ca=g.action.match(Jc);ca&&sd(a,ca[1])&&rd(e,g)}}))};Dc.prototype.$=function(a){if(a){var b=this,c=b.target.get(F);void 0!==c&&De.passthrough(function(){if(c("analytics_storage"))return{};var d={};return d._ga=b.target.get(Q),d._up="1",d},1,!0)}};function sd(a,b){if(b==M.location.hostname)return!1;for(var c=0;c<a.length;c++)if(a[c]instanceof RegExp){if(a[c].test(b))return!0}else if(0<=b.indexOf(a[c]))return!0;return!1}
function ke(a,b){return b!=Ic(a,0)&&b!=Ic(a,-1)&&b!=Ic(a,-2)&&b!=pa(a,0)&&b!=pa(a,-1)&&b!=pa(a,-2)}function $e(a){var b=af(a),c={};c._ga=a.get(Q);c._gid=a.get(I)||void 0;c._gac=b?[b.qa,b.timestamp].join("."):void 0;b=a.get(Ae);a=Ed(a);return c._fplc=b&&"0"!==b?b:a,c}function af(a){function b(e){return void 0==e||""===e?0:Number(e)}var c=a.get(ce);if(c&&a.get(je)){var d=b(a.get(fe));if(1E3*d+b(a.get(he))<=(new Date).getTime())J(76);else return{timestamp:d,qa:c}}};var p=/^(GTM|OPT)-[A-Z0-9]+$/,Ie=/^G-[A-Z0-9]+$/,q=/;_gaexp=[^;]*/g,r=/;((__utma=)|([^;=]+=GAX?\d+\.))[^;]*/g,Aa=/^https?:\/\/[\w\-.]+\.google.com(:\d+)?\/optimize\/opt-launch\.html\?.*$/,t=function(a){function b(d,e){e&&(c+="&"+d+"="+K(e))}var c=Ge(a.type)+K(a.id);"dataLayer"!=a.B&&b("l",a.B);b("cx",a.context);b("t",a.target);b("cid",a.clientId);b("cidt",a.ka);b("gac",a.la);b("aip",a.ia);a.sync&&b("m","sync");b("cycle",a.G);a.qa&&b("gclid",a.qa);Aa.test(M.referrer)&&b("cb",String(hd()));return c},
He=function(a,b){var c=(new Date).getTime();O[a.B]=O[a.B]||[];c={"gtm.start":c};a.sync||(c.event="gtm.js");O[a.B].push(c);2===a.type&&function(d,e,g){O[a.B].push(arguments)}("config",a.id,b)},Ke=function(a,b,c,d){c=c||{};var e=1;Ie.test(b)&&(e=2);var g={id:b,type:e,B:c.dataLayer||"dataLayer",G:!1},ca=void 0;a.get("&gtm")==b&&(g.G=!0);1===e?(g.ia=!!a.get("anonymizeIp"),g.sync=d,b=String(a.get("name")),"t0"!=b&&(g.target=b),G(String(a.get("trackingId")))||(g.clientId=String(a.get(Q)),g.ka=Number(a.get(n)),
c=c.palindrome?r:q,c=(c=M.cookie.replace(/^|(; +)/g,";").match(c))?c.sort().join("").substring(1):void 0,g.la=c,g.qa=E(P(a,kb),"gclid"))):2===e&&(g.context="c",ca={allow_google_signals:a.get(Ud),allow_ad_personalization_signals:a.get(xe)});He(g,ca);return t(g)};var H={},Jd=function(a,b){b||(b=(b=P(a,V))&&"t0"!=b?Wd.test(b)?"_gat_"+Cc(P(a,Na)):"_gat_"+Cc(b):"_gat");this.Y=b},Rd=function(a,b){var c=b.get(Wb);b.set(Wb,function(e){Pd(a,e,ed);Pd(a,e,ia);var g=c(e);Qd(a,e);return g});var d=b.get(Xb);b.set(Xb,function(e){var g=d(e);if(se(e)){J(80);var ca={U:re(e,1),google:re(e,2),count:0};pe("https://stats.g.doubleclick.net/j/collect",ca.U,ca);e.set(ed,"",!0)}return g})},Pd=function(a,b,c){!1===b.get(Ud)||b.get(c)||("1"==Ca(a.Y)[0]?b.set(c,"",!0):b.set(c,""+hd(),
!0))},Qd=function(a,b){se(b)&&zc(a.Y,"1",P(b,Yb),P(b,W),P(b,Na),6E4,P(b,Be))},se=function(a){return!!a.get(ed)&&!1!==a.get(Ud)},Ne=function(a){return!H[P(a,Na)]&&void 0===a.get("&gtm")&&void 0===a.get(fa)&&void 0===a.get(gd)&&void 0===a.get(oe)},re=function(a,b){var c=new ee,d=function(g){$a(g).F&&c.set($a(g).F,a.get(g))};d(hb);d(ib);d(Na);d(Q);d(ed);1==b&&(d(Ad),d(ia),d(I));!1===a.get(xe)&&c.set("npa","1");c.set($a(ld).F,Td(a));var e="";c.map(function(g,ca){e+=K(g)+"=";e+=K(""+ca)+"&"});e+="z="+
hd();1==b?e="t=dc&aip=1&_r=3&"+e:2==b&&(e="t=sr&aip=1&_r=4&slf_rd=1&"+e);return e},Me=function(a){if(Ne(a))return H[P(a,Na)]=!0,function(b){if(b&&!H[b]){var c=Ke(a,b);Id(c);H[b]=!0}}},Wd=/^gtm\d+$/;var fd=function(a,b){a=a.model;if(!a.get("dcLoaded")){var c=new $c(Dd(a));c.set(29);a.set(Gd,c.C);b=b||{};var d;b[U]&&(d=Cc(b[U]));b=new Jd(a,d);Rd(b,a);a.set("dcLoaded",!0)}};var Sd=function(a){if(!a.get("dcLoaded")&&"cookie"==a.get(ac)){var b=new Jd(a);Pd(b,a,ed);Pd(b,a,ia);Qd(b,a);b=se(a);var c=Ne(a);b&&a.set(Md,1,!0);c&&a.set(Od,1,!0);if(b||c)a.set(ad,"d",!0),J(79),a.set(qe,{U:re(a,1),google:re(a,2),V:Me(a),count:0},!0)}};var Lc=function(){var a=O.gaGlobal=O.gaGlobal||{};return a.hid=a.hid||hd()};var wb=/^(UA|YT|MO|GP)-(\d+)-(\d+)$/,pc=function(a){function b(e,g){d.model.data.set(e,g)}function c(e,g){b(e,g);d.filters.add(e)}var d=this;this.model=new Ya;this.filters=new Ha;b(V,a[V]);b(Na,sa(a[Na]));b(U,a[U]);b(W,a[W]||xa());b(Yb,a[Yb]);b(Zb,a[Zb]);b(Hd,a[Hd]);b(Be,a[Be]);b($b,a[$b]);b(Wc,a[Wc]);b(bc,a[bc]);b(cc,a[cc]);b(Ka,a[Ka]);b(dc,a[dc]);b(ec,a[ec]);b(ac,a[ac]);b(Ad,a[Ad]);b(n,a[n]);b(Kd,a[Kd]);b(je,a[je]);b(Ze,a[Ze]);b(oe,a[oe]);b(Je,a[Je]);b(F,a[F]);b(hb,1);b(ib,"j96");c(Re,Pe);c(Qb,
Ma);c(oa,ua);c(dd,cd);c(Rb,Oa);c(md,vb);c(Sb,nc);c(Uc,Yc);c(Tb,Ja);c(Vb,Ta);c(Vc,Hc);c(zd,yd);c(Ld,Sd);c(ze,Fe);c(Wb,Pa);c(Xb,Sa);c(Cd,Fd(this));pd(this.model);td(this.model,a[Q]);this.model.set(jb,Lc())};pc.prototype.get=function(a){return this.model.get(a)};pc.prototype.set=function(a,b){this.model.set(a,b)};
pc.prototype.send=function(a){if(!(1>arguments.length)){if("string"===typeof arguments[0]){var b=arguments[0];var c=[].slice.call(arguments,1)}else b=arguments[0]&&arguments[0][Va],c=arguments;b&&(c=za(me[b]||[],c),c[Va]=b,this.model.set(c,void 0,!0),this.filters.D(this.model),this.model.data.m={})}};pc.prototype.ma=function(a,b){var c=this;u(a,c,b)||(v(a,function(){u(a,c,b)}),y(String(c.get(V)),a,void 0,b,!0))};
var td=function(a,b){var c=P(a,U);a.data.set(la,"_ga"==c?"_gid":c+"_gid");if("cookie"==P(a,ac)){hc=!1;c=Ca(P(a,U));c=Xd(a,c);if(!c){c=P(a,W);var d=P(a,$b)||xa();c=Xc("__utma",d,c);void 0!=c?(J(10),c=c.O[1]+"."+c.O[2]):c=void 0}c&&(hc=!0);if(d=c&&!a.get(Hd))if(d=c.split("."),2!=d.length)d=!1;else if(d=Number(d[1])){var e=R(a,Zb);d=d+e<(new Date).getTime()/1E3}else d=!1;d&&(c=void 0);c&&(a.data.set(xd,c),a.data.set(Q,c),(c=uc(a))&&a.data.set(I,c));a.get(je)&&(c=a.get(ce),d=a.get(ie),!c||d&&"aw.ds"!=
d)&&(c={},d=(M?df(c):{})[P(a,Na)],le(c),d&&0!=d.length&&(c=d[0],a.data.set(fe,c.timestamp/1E3),a.data.set(ce,c.qa)));a.get(je)&&(c=a.get(Se),d={},e=(M?df(d,"_gac_gb",!0):{})[P(a,Na)],ef(d),e&&0!=e.length&&(d=e[0],e=d.qa,c&&c!==e||(d.labels&&d.labels.length&&(e+="."+d.labels.join(".")),a.data.set(Te,d.timestamp/1E3),a.data.set(Se,e))))}if(a.get(Hd)){c=be("_ga",!!a.get(cc));var g=be("_gl",!!a.get(cc));d=De.get(a.get(cc));e=d._ga;g&&0<g.indexOf("_ga*")&&!e&&J(30);if(b||!a.get(Je))g=!1;else if(g=a.get(F),
void 0===g||g("analytics_storage"))g=!1;else{J(84);a.data.set(Le,1);if(g=d._up)if(g=Jc.exec(M.referrer)){g=g[1];var ca=M.location.hostname;g=ca===g||0<=ca.indexOf("."+g)||0<=g.indexOf("."+ca)?!0:!1}else g=!1;g=g?!0:!1}ca=d.gclid;var l=d._gac;if(c||e||ca||l)if(c&&e&&J(36),a.get(bc)||ye(a.get(Kd))||g){if(e&&(J(38),a.data.set(Q,e),d._gid&&(J(51),a.data.set(I,d._gid))),ca?(J(82),a.data.set(ce,ca),d.gclsrc&&a.data.set(ie,d.gclsrc)):l&&(e=l.split("."))&&2===e.length&&(J(37),a.data.set(ce,e[0]),a.data.set(fe,
e[1])),(d=d._fplc)&&P(a,oe)&&(J(83),a.data.set(Ae,d)),c)b:if(d=c.indexOf("."),-1==d)J(22);else{e=c.substring(0,d);g=c.substring(d+1);d=g.indexOf(".");c=g.substring(0,d);g=g.substring(d+1);if("1"==e){if(d=g,ke(d,c)){J(23);break b}}else if("2"==e){d=g.indexOf("-");e="";0<d?(e=g.substring(0,d),d=g.substring(d+1)):d=g.substring(1);if(ke(e+d,c)){J(53);break b}e&&(J(2),a.data.set(I,e))}else{J(22);break b}J(11);a.data.set(Q,d);if(c=be("_gac",!!a.get(cc)))c=c.split("."),"1"!=c[0]||4!=c.length?J(72):ke(c[3],
c[1])?J(71):(a.data.set(ce,c[3]),a.data.set(fe,c[2]),J(70))}}else J(21)}b&&(J(9),a.data.set(Q,K(b)));a.get(Q)||(b=(b=O.gaGlobal)&&b.from_cookie&&"cookie"!==P(a,ac)?void 0:(b=b&&b.vid)&&-1!==b.search(jd)?b:void 0,b?(J(17),a.data.set(Q,b)):(J(8),a.data.set(Q,ra())));a.get(I)||(J(3),a.data.set(I,ra()));mc(a);b=O.gaGlobal=O.gaGlobal||{};c=P(a,Q);a=c===P(a,xd);if(void 0==b.vid||a&&!b.from_cookie)b.vid=c,b.from_cookie=a},pd=function(a){var b=O.navigator,c=O.screen,d=M.location,e=a.set;a:{var g=!!a.get(ec),
ca=!!a.get(Kd);var l=M.referrer;if(/^(https?|android-app):\/\//i.test(l)){if(g)break a;g="//"+M.location.hostname;if(!de(l,g)){if(ca&&(ca=g.replace(/\./g,"-")+".cdn.ampproject.org",de(l,ca))){l=void 0;break a}break a}}l=void 0}e.call(a,lb,l);d&&(e=d.pathname||"","/"!=e.charAt(0)&&(J(31),e="/"+e),a.set(kb,d.protocol+"//"+d.hostname+e+d.search));c&&a.set(qb,c.width+"x"+c.height);c&&a.set(pb,c.colorDepth+"-bit");c=M.documentElement;l=(e=M.body)&&e.clientWidth&&e.clientHeight;ca=[];c&&c.clientWidth&&
c.clientHeight&&("CSS1Compat"===M.compatMode||!l)?ca=[c.clientWidth,c.clientHeight]:l&&(ca=[e.clientWidth,e.clientHeight]);c=0>=ca[0]||0>=ca[1]?"":ca.join("x");a.set(rb,c);c=a.set;var k;if((e=(e=O.navigator)?e.plugins:null)&&e.length)for(l=0;l<e.length&&!k;l++)ca=e[l],-1<ca.name.indexOf("Shockwave Flash")&&(k=ca.description);if(!k)try{var w=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");k=w.GetVariable("$version")}catch(Ce){}if(!k)try{w=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6"),k=
"WIN 6,0,21,0",w.AllowScriptAccess="always",k=w.GetVariable("$version")}catch(Ce){}if(!k)try{w=new ActiveXObject("ShockwaveFlash.ShockwaveFlash"),k=w.GetVariable("$version")}catch(Ce){}k&&(w=k.match(/[\d]+/g))&&3<=w.length&&(k=w[0]+"."+w[1]+" r"+w[2]);c.call(a,tb,k||void 0);a.set(ob,M.characterSet||M.charset);a.set(sb,b&&"function"===typeof b.javaEnabled&&b.javaEnabled()||!1);a.set(nb,(b&&(b.language||b.browserLanguage)||"").toLowerCase());a.data.set(ce,be("gclid",!0));a.data.set(ie,be("gclsrc",!0));
a.data.set(fe,Math.round((new Date).getTime()/1E3));a.get(ce)||(a.data.set(Se,be("wbraid",!0)),a.data.set(Te,Math.round((new Date).getTime()/1E3)));if(d&&a.get(cc)&&(b=M.location.hash)){b=b.split(/[?&#]+/);d=[];for(k=0;k<b.length;++k)(D(b[k],"utm_id")||D(b[k],"utm_campaign")||D(b[k],"utm_source")||D(b[k],"utm_medium")||D(b[k],"utm_term")||D(b[k],"utm_content")||D(b[k],"gclid")||D(b[k],"dclid")||D(b[k],"gclsrc")||D(b[k],"wbraid"))&&d.push(b[k]);0<d.length&&(b="#"+d.join("&"),a.set(kb,a.get(kb)+b))}},
me={pageview:[mb],event:[ub,xb,yb,zb],social:[Bb,Cb,Db],timing:[Mb,Nb,Pb,Ob]};var rc=function(a){if("prerender"==M.visibilityState)return!1;a();return!0},z=function(a){if(!rc(a)){J(16);var b=!1,c=function(){if(!b&&rc(a)){b=!0;var d=c,e=M;e.removeEventListener?e.removeEventListener("visibilitychange",d,!1):e.detachEvent&&e.detachEvent("onvisibilitychange",d)}};L(M,"visibilitychange",c)}};var te=/^(?:(\w+)\.)?(?:(\w+):)?(\w+)$/,sc=function(a){if(ea(a[0]))this.u=a[0];else{var b=te.exec(a[0]);null!=b&&4==b.length&&(this.da=b[1]||"t0",this.K=b[2]||"",this.methodName=b[3],this.aa=[].slice.call(a,1),this.K||(this.A="create"==this.methodName,this.i="require"==this.methodName,this.g="provide"==this.methodName,this.ba="remove"==this.methodName),this.i&&(3<=this.aa.length?(this.X=this.aa[1],this.W=this.aa[2]):this.aa[1]&&(qa(this.aa[1])?this.X=this.aa[1]:this.W=this.aa[1])));b=a[1];a=a[2];
if(!this.methodName)throw"abort";if(this.i&&(!qa(b)||""==b))throw"abort";if(this.g&&(!qa(b)||""==b||!ea(a)))throw"abort";if(ud(this.da)||ud(this.K))throw"abort";if(this.g&&"t0"!=this.da)throw"abort";}};function ud(a){return 0<=a.indexOf(".")||0<=a.indexOf(":")};var Yd,Zd,$d,A;Yd=new ee;$d=new ee;A=new ee;Zd={ec:45,ecommerce:46,linkid:47};
var u=function(a,b,c){b==N||b.get(V);var d=Yd.get(a);if(!ea(d))return!1;b.plugins_=b.plugins_||new ee;if(b.plugins_.get(a))return!0;b.plugins_.set(a,new d(b,c||{}));return!0},y=function(a,b,c,d,e){if(!ea(Yd.get(b))&&!$d.get(b)){Zd.hasOwnProperty(b)&&J(Zd[b]);var g=void 0;if(p.test(b)){J(52);a=N.j(a);if(!a)return!0;c=Ke(a.model,b,d,e);g=function(){Z.D(["provide",b,function(){}]);var l=O[d&&d.dataLayer||"dataLayer"];l&&l.hide&&ea(l.hide.end)&&l.hide[b]&&(l.hide.end(),l.hide.end=void 0)}}!c&&Zd.hasOwnProperty(b)?
(J(39),c=b+".js"):J(43);if(c){var ca;d&&(ca=d[oe]);qa(ca)||(ca=void 0);a=ae(cf(c,ca));!ca||ne(a.protocol)&&B(a)||(a=ae(cf(c)));ne(a.protocol)&&B(a)&&(Id(a.url,void 0,e,void 0,g),$d.set(b,!0))}}},v=function(a,b){var c=A.get(a)||[];c.push(b);A.set(a,c)},C=function(a,b){Yd.set(a,b);b=A.get(a)||[];for(var c=0;c<b.length;c++)b[c]();A.set(a,[])},B=function(a){var b=ae(M.location.href);if(D(a.url,Ge(1))||D(a.url,Ge(2)))return!0;if(a.query||0<=a.url.indexOf("?")||0<=a.path.indexOf("://"))return!1;if(a.host==
b.host&&a.port==b.port||jf&&(b=M.createElement("a"),b.href=jf,b=kf(b),a.host===b[0]&&a.port===b[1]))return!0;b="http:"==a.protocol?80:443;return"www.google-analytics.com"==a.host&&(a.port||b)==b&&D(a.path,"/plugins/")?!0:!1},ne=function(a){var b=M.location.protocol;return"https:"==a||a==b?!0:"http:"!=a?!1:"http:"==b},kf=function(a){var b=a.hostname||"",c=0<=b.indexOf("]");b=b.split(c?"]":":")[0].toLowerCase();c&&(b+="]");c=(a.protocol||"").toLowerCase();c=1*a.port||("http:"==c?80:"https:"==c?443:
"");a=a.pathname||"";D(a,"/")||(a="/"+a);return[b,""+c,a]},ae=function(a){var b=M.createElement("a");b.href=M.location.href;var c=(b.protocol||"").toLowerCase(),d=kf(b),e=b.search||"",g=c+"//"+d[0]+(d[1]?":"+d[1]:"");D(a,"//")?a=c+a:D(a,"/")?a=g+a:!a||D(a,"?")?a=g+d[2]+(a||e):0>a.split("/")[0].indexOf(":")&&(a=g+d[2].substring(0,d[2].lastIndexOf("/"))+"/"+a);b.href=a;c=kf(b);return{protocol:(b.protocol||"").toLowerCase(),host:c[0],port:c[1],path:c[2],query:b.search||"",url:a||""}},cf=function(a,b){return a&&
0<=a.indexOf("/")?a:(b||bd(!1))+"/plugins/ua/"+a};var Z={ga:function(){Z.fa=[]}};Z.ga();Z.D=function(a){var b=Z.J.apply(Z,arguments);b=Z.fa.concat(b);for(Z.fa=[];0<b.length&&!Z.v(b[0])&&!(b.shift(),0<Z.fa.length););Z.fa=Z.fa.concat(b)};Z.ra=function(a){N.q&&(300===N.q.length&&(N.q.shift(),N.qd++),N.q.push(a))};Z.J=function(a){for(var b=[],c=0;c<arguments.length;c++)try{var d=new sc(arguments[c]);d.g?C(d.aa[0],d.aa[1]):(d.i&&(d.ha=y(d.da,d.aa[0],d.X,d.W)),b.push(d));Z.ra(arguments[c])}catch(e){}return b};
Z.v=function(a){try{if(a.u)a.u.call(O,N.j("t0"));else{var b=a.da==gb?N:N.j(a.da);if(a.A){if("t0"==a.da&&(b=N.create.apply(N,a.aa),null===b))return!0}else if(a.ba)N.remove(a.da);else if(b)if(a.i){if(a.ha&&(a.ha=y(a.da,a.aa[0],a.X,a.W)),!u(a.aa[0],b,a.W))return!0}else if(a.K){var c=a.methodName,d=a.aa,e=b.plugins_.get(a.K);e[c].apply(e,d)}else b[a.methodName].apply(b,a.aa)}}catch(g){}};var N=function(a){J(1);Z.D.apply(Z,[arguments])};N.h={};N.P=[];N.L=0;N.ya=0;N.answer=42;var we=[Na,W,V];N.create=function(a){var b=za(we,[].slice.call(arguments));b[V]||(b[V]="t0");var c=""+b[V];if(N.h[c])return N.h[c];if(da(b))return null;b=new pc(b);N.h[c]=b;N.P.push(b);c=qc().tracker_created;if(ea(c))try{c(b)}catch(d){}return b};N.remove=function(a){for(var b=0;b<N.P.length;b++)if(N.P[b].get(V)==a){N.P.splice(b,1);N.h[a]=null;break}};N.j=function(a){return N.h[a]};N.getAll=function(){return N.P.slice(0)};
N.N=function(){"ga"!=gb&&J(49);var a=O[gb];if(!a||42!=a.answer){N.L=a&&a.l;N.ya=1*new Date;N.loaded=!0;var b=a&&a.q,c=ka(b);a=[];c?a=b.slice(0):J(50);N.q=c?b:[];N.q.splice(0);N.qd=0;b=O[gb]=N;X("create",b,b.create);X("remove",b,b.remove);X("getByName",b,b.j,5);X("getAll",b,b.getAll,6);b=pc.prototype;X("get",b,b.get,7);X("set",b,b.set,4);X("send",b,b.send);X("requireSync",b,b.ma);b=Ya.prototype;X("get",b,b.get);X("set",b,b.set);if("https:"!=M.location.protocol&&!Ba){a:{b=M.getElementsByTagName("script");
for(c=0;c<b.length&&100>c;c++){var d=b[c].src;if(d&&0==d.indexOf(bd(!0)+"/analytics")){b=!0;break a}}b=!1}b&&(Ba=!0)}(O.gaplugins=O.gaplugins||{}).Linker=Dc;b=Dc.prototype;C("linker",Dc);X("decorate",b,b.ca,20);X("autoLink",b,b.S,25);X("passthrough",b,b.$,25);C("displayfeatures",fd);C("adfeatures",fd);Z.D.apply(N,a)}};var gf=N.N,hf=O[gb];hf&&hf.r?gf():z(gf);z(function(){Z.D(["provide","render",ua])});})(window);

View File

@@ -0,0 +1,481 @@
//assign variables
//const pmDelayTimer = setTimeout(pmTriggerDOMListener, 10000); //set inline before main minified script for filterable timeout variable
const pmUserInteractions =["keydown","mousedown","mousemove","wheel","touchmove","touchstart","touchend"];
const pmDelayedScripts = {normal: [], defer: [], async: []};
const jQueriesArray = [];
const pmInterceptedClicks = [];
//const pmTrash = [];
var pmDOMLoaded = false;
var pmClickTarget = '';
window.pmIsClickPending = false;
//add pageshow listener
window.addEventListener("pageshow", (e) => {
window.pmPersisted = e.persisted;
});
//add user interaction event listeners
pmUserInteractions.forEach(function(event) {
window.addEventListener(event, pmTriggerDOMListener, {passive:true});
});
//add click handling listeners
if(pmDelayClick) {
window.addEventListener("touchstart", pmTouchStartHandler, {passive: true});
window.addEventListener("mousedown", pmTouchStartHandler);
}
//add visibility change listener
document.addEventListener("visibilitychange", pmTriggerDOMListener);
//add dom listener and trigger scripts
function pmTriggerDOMListener() {
//clear existing timeout
if(typeof pmDelayTimer !== 'undefined') {
clearTimeout(pmDelayTimer);
}
//remove existing user interaction event listeners
pmUserInteractions.forEach(function(event) {
window.removeEventListener(event, pmTriggerDOMListener, {passive:true});
});
//remove visibility change listener
document.removeEventListener("visibilitychange", pmTriggerDOMListener);
//add dom listner if page is still loading
if(document.readyState === 'loading') {
document.addEventListener("DOMContentLoaded", pmTriggerDelayedScripts);
}
else {
//trigger delayed script process
pmTriggerDelayedScripts();
}
}
//main delayed script process
async function pmTriggerDelayedScripts() {
//prep
pmDelayEventListeners();
pmDelayJQueryReady();
pmProcessDocumentWrite();
pmSortDelayedScripts();
pmPreloadDelayedScripts();
//load scripts
await pmLoadDelayedScripts(pmDelayedScripts.normal);
await pmLoadDelayedScripts(pmDelayedScripts.defer);
await pmLoadDelayedScripts(pmDelayedScripts.async);
//trigger delayed DOM events
await pmTriggerEventListeners();
//load delayed styles
document.querySelectorAll("link[data-pmdelayedstyle]").forEach(function (e) {
e.setAttribute("href", e.getAttribute("data-pmdelayedstyle"));
});
//start click replay event
window.dispatchEvent(new Event("perfmatters-allScriptsLoaded")),
pmWaitForPendingClicks().then(() => {
pmReplayClicks();
});
//pmEmptyTrash();
//pmReplayClicks();
}
//delay original page event listeners
function pmDelayEventListeners() {
//create event listeners array
let eventListeners = {};
//delay dom event
function delayDOMEvent(object, event) {
//rewrites event name to trigger later
function rewriteEventName(eventName) {
return eventListeners[object].delayedEvents.indexOf(eventName) >= 0 ? "perfmatters-" + eventName : eventName;
}
//make sure we haven't added this object yet
if(!eventListeners[object]) {
//setup object in eventlisteners array
eventListeners[object] = {
originalFunctions: {
add: object.addEventListener,
remove: object.removeEventListener
},
delayedEvents: []
}
//swap delayed events with originals
object.addEventListener = function () {
arguments[0] = rewriteEventName(arguments[0]);
eventListeners[object].originalFunctions.add.apply(object, arguments);
}
object.removeEventListener = function () {
arguments[0] = rewriteEventName(arguments[0]);
eventListeners[object].originalFunctions.remove.apply(object, arguments);
}
}
//add event to delayed events array for object
eventListeners[object].delayedEvents.push(event);
}
//delay dom event trigger
function delayDOMEventTrigger(object, event) {
const originalEvent = object[event];
Object.defineProperty(object, event, {
get: !originalEvent ? function () {} : originalEvent,
set: function(n) {
object["perfmatters" + event] = n;
},
});
}
//delay dom events
delayDOMEvent(document, "DOMContentLoaded");
delayDOMEvent(window, "DOMContentLoaded");
delayDOMEvent(window, "load");
delayDOMEvent(window, "pageshow");
delayDOMEvent(document, "readystatechange");
//delay dom event triggers
delayDOMEventTrigger(document, "onreadystatechange");
delayDOMEventTrigger(window, "onload");
delayDOMEventTrigger(window, "onpageshow");
}
//delay jquery ready
function pmDelayJQueryReady() {
//store original jquery
let originalJQuery = window.jQuery;
//modify original jquery object
Object.defineProperty(window, "jQuery", {
//return original when accessed directly
get() {
return originalJQuery;
},
//modify value when jquery is set
set(newJQuery) {
//make sure it's valid and we haven't modified it already
if(newJQuery && newJQuery.fn && !jQueriesArray.includes(newJQuery)) {
//modify new jquery ready event
newJQuery.fn.ready = newJQuery.fn.init.prototype.ready = function(originalJQuery) {
//dom loaded, go ahead
if(pmDOMLoaded) {
originalJQuery.bind(document)(newJQuery);
}
//dom not loaded, so wait for listener
else {
document.addEventListener("perfmatters-DOMContentLoaded", function() {
originalJQuery.bind(document)(newJQuery);
});
}
};
//store on event
const newJQueryOn = newJQuery.fn.on;
//modify new jquery on event
newJQuery.fn.on = newJQuery.fn.init.prototype.on = function() {
if(this[0] === window) {
//rewrite event name
function rewriteEventName(eventName) {
eventName = eventName.split(" ");
eventName = eventName.map(function(name) {
if(name === "load" || name.indexOf("load.") === 0) {
return "perfmatters-jquery-load";
}
else {
return name;
}
});
eventName = eventName.join(" ");
return eventName;
}
//rewrite event name/s
if(typeof arguments[0] == "string" || arguments[0] instanceof String) {
arguments[0] = rewriteEventName(arguments[0]);
}
else if(typeof arguments[0] == "object") {
Object.keys(arguments[0]).forEach(function(argument) {
delete Object.assign(arguments[0], {[rewriteEventName(argument)]: arguments[0][argument]})[argument];
});
}
}
return newJQueryOn.apply(this, arguments), this;
};
//add modified jquery to storage array
jQueriesArray.push(newJQuery);
}
//replace original jquery with modified version
originalJQuery = newJQuery;
}
});
}
//print document write values directly after their parent script
function pmProcessDocumentWrite() {
//create map to store scripts
const map = new Map();
//modify document.write functions
document.write = document.writeln = function(value) {
//prep
var script = document.currentScript;
var range = document.createRange();
//make sure script isn't in map yet
let mapScript = map.get(script);
if(mapScript === void 0) {
//add script's next sibling to map
mapScript = script.nextSibling;
map.set(script, mapScript);
}
//insert value before script's next sibling
var fragment = document.createDocumentFragment();
range.setStart(fragment, 0);
fragment.appendChild(range.createContextualFragment(value));
script.parentElement.insertBefore(fragment, mapScript);
};
}
//find all delayed scripts and sort them by load order
function pmSortDelayedScripts() {
document.querySelectorAll("script[type=pmdelayedscript]").forEach(function(event) {
if(event.hasAttribute("src")) {
if(event.hasAttribute("defer") && event.defer !== false) {
pmDelayedScripts.defer.push(event);
}
else if(event.hasAttribute("async") && event.async !== false) {
pmDelayedScripts.async.push(event);
}
else {
pmDelayedScripts.normal.push(event);
}
}
else {
pmDelayedScripts.normal.push(event);
}
});
}
//add block of preloads for delayed scripts that have src URLs
function pmPreloadDelayedScripts() {
var preloadFragment = document.createDocumentFragment();
[...pmDelayedScripts.normal, ...pmDelayedScripts.defer, ...pmDelayedScripts.async].forEach(function(script) {
var src = script.getAttribute("src");
if(src) {
var link = document.createElement("link");
link.href = src;
link.rel = "preload";
link.as = "script";
preloadFragment.appendChild(link);
}
});
document.head.appendChild(preloadFragment);
}
//load array of delayed scripts one at a time
async function pmLoadDelayedScripts(scripts) {
//grab first script in array
var script = scripts.shift();
//replace script and move to the next one
if(script) {
await pmReplaceScript(script);
return pmLoadDelayedScripts(scripts);
}
//resolve when all scripts have been swapped
return Promise.resolve();
}
//replace delayed script in document
async function pmReplaceScript(script) {
//wait
await pmNextFrame();
//create new script and replace
return new Promise(function(replaceScript) {
//prep
const newscript = document.createElement("script");
//loop through script attributes
[...script.attributes].forEach(function(attribute) {
let attributeName = attribute.nodeName;
if(attributeName !== "type") {
//swap data-type if needed
if(attributeName === "data-type") {
attributeName = "type";
}
//add attribute to newscript
newscript.setAttribute(attributeName, attribute.nodeValue);
}
});
//src script
if(script.hasAttribute("src")) {
newscript.addEventListener("load", replaceScript);
newscript.addEventListener("error", replaceScript);
}
//inline script
else {
newscript.text = script.text;
replaceScript();
}
//replace original script with final
script.parentNode.replaceChild(newscript, script);
});
}
//trigger delayed event listeners after scripts have loaded
async function pmTriggerEventListeners() {
//set flag
pmDOMLoaded = true;
await pmNextFrame();
//trigger events
document.dispatchEvent(new Event("perfmatters-DOMContentLoaded"));
await pmNextFrame();
window.dispatchEvent(new Event("perfmatters-DOMContentLoaded"));
await pmNextFrame();
document.dispatchEvent(new Event("perfmatters-readystatechange"));
await pmNextFrame();
if(document.perfmattersonreadystatechange) {
document.perfmattersonreadystatechange();
}
await pmNextFrame();
window.dispatchEvent(new Event("perfmatters-load"));
await pmNextFrame();
if(window.perfmattersonload) {
window.perfmattersonload();
}
await pmNextFrame();
jQueriesArray.forEach(function(singleJQuery) {
singleJQuery(window).trigger("perfmatters-jquery-load")
});
const pmPageShowEvent = new Event("perfmatters-pageshow");
pmPageShowEvent.persisted = window.pmPersisted;
window.dispatchEvent(pmPageShowEvent);
await pmNextFrame();
if(window.perfmattersonpageshow) {
window.perfmattersonpageshow({ persisted: window.pmPersisted });
}
}
//wait for next frame before proceeding
async function pmNextFrame() {
return new Promise(function(e) {
requestAnimationFrame(e);
});
}
/*function pmEmptyTrash() {
window.pmTrash.forEach((t) => t.remove());
}*/
function pmReplayClicks() {
window.removeEventListener("touchstart", pmTouchStartHandler, {passive: true});
window.removeEventListener("mousedown", pmTouchStartHandler);
pmInterceptedClicks.forEach((e) => {
if(e.target.outerHTML === pmClickTarget) {
e.target.dispatchEvent(new MouseEvent("click", {view: e.view, bubbles: true, cancelable: true}));
}
});
}
function pmWaitForPendingClicks() {
return new Promise((t) => {
window.pmIsClickPending ? (pmPendingClickFinished = t) : t();
});
}
function pmPndingClickStarted() {
window.pmIsClickPending = true;
}
function pmPendingClickFinished() {
window.pmIsClickPending = false;
}
function pmClickHandler(e) {
e.target.removeEventListener("click", pmClickHandler);
pmRenameDOMAttribute(e.target, "pm-onclick", "onclick");
pmInterceptedClicks.push(e), e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
pmPendingClickFinished();
}
function pmTouchStartHandler(e) {
if(e.target.tagName !== "HTML") {
if(!pmClickTarget) {
pmClickTarget = e.target.outerHTML;
}
window.addEventListener("touchend", pmTouchEndHandler);
window.addEventListener("mouseup", pmTouchEndHandler);
window.addEventListener("touchmove", pmTouchMoveHandler, {passive: true});
window.addEventListener("mousemove", pmTouchMoveHandler);
e.target.addEventListener("click", pmClickHandler);
pmRenameDOMAttribute(e.target, "onclick", "pm-onclick");
pmPendingClickStarted();
}
}
function pmTouchMoveHandler(e) {
window.removeEventListener("touchend", pmTouchEndHandler);
window.removeEventListener("mouseup", pmTouchEndHandler);
window.removeEventListener("touchmove", pmTouchMoveHandler, {passive: true});
window.removeEventListener("mousemove", pmTouchMoveHandler);
e.target.removeEventListener("click", pmClickHandler);
pmRenameDOMAttribute(e.target, "pm-onclick", "onclick");
pmPendingClickFinished();
}
function pmTouchEndHandler(e) {
window.removeEventListener("touchend", pmTouchEndHandler);
window.removeEventListener("mouseup", pmTouchEndHandler);
window.removeEventListener("touchmove", pmTouchMoveHandler, {passive: true});
window.removeEventListener("mousemove", pmTouchMoveHandler);
}
function pmRenameDOMAttribute(e, t, n) {
if(e.hasAttribute && e.hasAttribute(t)) {
event.target.setAttribute(n, event.target.getAttribute(t));
event.target.removeAttribute(t);
}
}

View File

View File

@@ -0,0 +1,872 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.LazyLoad = factory());
}(this, (function () { 'use strict';
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
var runningOnBrowser = typeof window !== "undefined";
var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro)bot|crawl|spider/i.test(navigator.userAgent);
var supportsIntersectionObserver = runningOnBrowser && "IntersectionObserver" in window;
var supportsClassList = runningOnBrowser && "classList" in document.createElement("p");
var isHiDpi = runningOnBrowser && window.devicePixelRatio > 1;
var defaultSettings = {
elements_selector: ".lazy",
container: isBot || runningOnBrowser ? document : null,
threshold: 300,
thresholds: null,
data_src: "src",
data_srcset: "srcset",
data_sizes: "sizes",
data_bg: "bg",
data_bg_hidpi: "bg-hidpi",
data_bg_multi: "bg-multi",
data_bg_multi_hidpi: "bg-multi-hidpi",
data_bg_set: "bg-set",
data_poster: "poster",
class_applied: "applied",
class_loading: "loading",
class_loaded: "loaded",
class_error: "error",
class_entered: "entered",
class_exited: "exited",
unobserve_completed: true,
unobserve_entered: false,
cancel_on_exit: true,
callback_enter: null,
callback_exit: null,
callback_applied: null,
callback_loading: null,
callback_loaded: null,
callback_error: null,
callback_finish: null,
callback_cancel: null,
use_native: false,
restore_on_error: false
};
var getExtendedSettings = function getExtendedSettings(customSettings) {
return _extends({}, defaultSettings, customSettings);
};
/* Creates instance and notifies it through the window element */
var createInstance = function createInstance(classObj, options) {
var event;
var eventString = "LazyLoad::Initialized";
var instance = new classObj(options);
try {
// Works in modern browsers
event = new CustomEvent(eventString, {
detail: {
instance: instance
}
});
} catch (err) {
// Works in Internet Explorer (all versions)
event = document.createEvent("CustomEvent");
event.initCustomEvent(eventString, false, false, {
instance: instance
});
}
window.dispatchEvent(event);
};
/* Auto initialization of one or more instances of lazyload, depending on the
options passed in (plain object or an array) */
var autoInitialize = function autoInitialize(classObj, options) {
if (!options) {
return;
}
if (!options.length) {
// Plain object
createInstance(classObj, options);
} else {
// Array of objects
for (var i = 0, optionsItem; optionsItem = options[i]; i += 1) {
createInstance(classObj, optionsItem);
}
}
};
var SRC = "src";
var SRCSET = "srcset";
var SIZES = "sizes";
var POSTER = "poster";
var ORIGINALS = "llOriginalAttrs";
var DATA = "data";
var statusLoading = "loading";
var statusLoaded = "loaded";
var statusApplied = "applied";
var statusEntered = "entered";
var statusError = "error";
var statusNative = "native";
var dataPrefix = "data-";
var statusDataName = "ll-status";
var getData = function getData(element, attribute) {
return element.getAttribute(dataPrefix + attribute);
};
var setData = function setData(element, attribute, value) {
var attrName = dataPrefix + attribute;
if (value === null) {
element.removeAttribute(attrName);
return;
}
element.setAttribute(attrName, value);
};
var getStatus = function getStatus(element) {
return getData(element, statusDataName);
};
var setStatus = function setStatus(element, status) {
return setData(element, statusDataName, status);
};
var resetStatus = function resetStatus(element) {
return setStatus(element, null);
};
var hasEmptyStatus = function hasEmptyStatus(element) {
return getStatus(element) === null;
};
var hasStatusLoading = function hasStatusLoading(element) {
return getStatus(element) === statusLoading;
};
var hasStatusError = function hasStatusError(element) {
return getStatus(element) === statusError;
};
var hasStatusNative = function hasStatusNative(element) {
return getStatus(element) === statusNative;
};
var statusesAfterLoading = [statusLoading, statusLoaded, statusApplied, statusError];
var hadStartedLoading = function hadStartedLoading(element) {
return statusesAfterLoading.indexOf(getStatus(element)) >= 0;
};
var safeCallback = function safeCallback(callback, arg1, arg2, arg3) {
if (!callback) {
return;
}
if (arg3 !== undefined) {
callback(arg1, arg2, arg3);
return;
}
if (arg2 !== undefined) {
callback(arg1, arg2);
return;
}
callback(arg1);
};
var addClass = function addClass(element, className) {
if (supportsClassList) {
element.classList.add(className);
return;
}
element.className += (element.className ? " " : "") + className;
};
var removeClass = function removeClass(element, className) {
if (supportsClassList) {
element.classList.remove(className);
return;
}
element.className = element.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), " ").replace(/^\s+/, "").replace(/\s+$/, "");
};
var addTempImage = function addTempImage(element) {
element.llTempImage = document.createElement("IMG");
};
var deleteTempImage = function deleteTempImage(element) {
delete element.llTempImage;
};
var getTempImage = function getTempImage(element) {
return element.llTempImage;
};
var unobserve = function unobserve(element, instance) {
if (!instance) return;
var observer = instance._observer;
if (!observer) return;
observer.unobserve(element);
};
var resetObserver = function resetObserver(observer) {
observer.disconnect();
};
var unobserveEntered = function unobserveEntered(element, settings, instance) {
if (settings.unobserve_entered) unobserve(element, instance);
};
var updateLoadingCount = function updateLoadingCount(instance, delta) {
if (!instance) return;
instance.loadingCount += delta;
};
var decreaseToLoadCount = function decreaseToLoadCount(instance) {
if (!instance) return;
instance.toLoadCount -= 1;
};
var setToLoadCount = function setToLoadCount(instance, value) {
if (!instance) return;
instance.toLoadCount = value;
};
var isSomethingLoading = function isSomethingLoading(instance) {
return instance.loadingCount > 0;
};
var haveElementsToLoad = function haveElementsToLoad(instance) {
return instance.toLoadCount > 0;
};
var getSourceTags = function getSourceTags(parentTag) {
var sourceTags = [];
for (var i = 0, childTag; childTag = parentTag.children[i]; i += 1) {
if (childTag.tagName === "SOURCE") {
sourceTags.push(childTag);
}
}
return sourceTags;
};
var forEachPictureSource = function forEachPictureSource(element, fn) {
var parent = element.parentNode;
if (!parent || parent.tagName !== "PICTURE") {
return;
}
var sourceTags = getSourceTags(parent);
sourceTags.forEach(fn);
};
var forEachVideoSource = function forEachVideoSource(element, fn) {
var sourceTags = getSourceTags(element);
sourceTags.forEach(fn);
};
var attrsSrc = [SRC];
var attrsSrcPoster = [SRC, POSTER];
var attrsSrcSrcsetSizes = [SRC, SRCSET, SIZES];
var attrsData = [DATA];
var hasOriginalAttrs = function hasOriginalAttrs(element) {
return !!element[ORIGINALS];
};
var getOriginalAttrs = function getOriginalAttrs(element) {
return element[ORIGINALS];
};
var deleteOriginalAttrs = function deleteOriginalAttrs(element) {
return delete element[ORIGINALS];
}; // ## SAVE ##
var setOriginalsObject = function setOriginalsObject(element, attributes) {
if (hasOriginalAttrs(element)) {
return;
}
var originals = {};
attributes.forEach(function (attribute) {
originals[attribute] = element.getAttribute(attribute);
});
element[ORIGINALS] = originals;
};
var saveOriginalBackgroundStyle = function saveOriginalBackgroundStyle(element) {
if (hasOriginalAttrs(element)) {
return;
}
element[ORIGINALS] = {
backgroundImage: element.style.backgroundImage
};
}; // ## RESTORE ##
var setOrResetAttribute = function setOrResetAttribute(element, attrName, value) {
if (!value) {
element.removeAttribute(attrName);
return;
}
element.setAttribute(attrName, value);
};
var restoreOriginalAttrs = function restoreOriginalAttrs(element, attributes) {
if (!hasOriginalAttrs(element)) {
return;
}
var originals = getOriginalAttrs(element);
attributes.forEach(function (attribute) {
setOrResetAttribute(element, attribute, originals[attribute]);
});
};
var restoreOriginalBgImage = function restoreOriginalBgImage(element) {
if (!hasOriginalAttrs(element)) {
return;
}
var originals = getOriginalAttrs(element);
element.style.backgroundImage = originals.backgroundImage;
};
var manageApplied = function manageApplied(element, settings, instance) {
addClass(element, settings.class_applied);
setStatus(element, statusApplied); // Instance is not provided when loading is called from static class
if (!instance) return;
if (settings.unobserve_completed) {
// Unobserve now because we can't do it on load
unobserve(element, settings);
}
safeCallback(settings.callback_applied, element, instance);
};
var manageLoading = function manageLoading(element, settings, instance) {
addClass(element, settings.class_loading);
setStatus(element, statusLoading); // Instance is not provided when loading is called from static class
if (!instance) return;
updateLoadingCount(instance, +1);
safeCallback(settings.callback_loading, element, instance);
};
var setAttributeIfValue = function setAttributeIfValue(element, attrName, value) {
if (!value) {
return;
}
element.setAttribute(attrName, value);
};
var setImageAttributes = function setImageAttributes(element, settings) {
setAttributeIfValue(element, SIZES, getData(element, settings.data_sizes));
setAttributeIfValue(element, SRCSET, getData(element, settings.data_srcset));
setAttributeIfValue(element, SRC, getData(element, settings.data_src));
};
var setSourcesImg = function setSourcesImg(imgEl, settings) {
forEachPictureSource(imgEl, function (sourceTag) {
setOriginalsObject(sourceTag, attrsSrcSrcsetSizes);
setImageAttributes(sourceTag, settings);
});
setOriginalsObject(imgEl, attrsSrcSrcsetSizes);
setImageAttributes(imgEl, settings);
};
var setSourcesIframe = function setSourcesIframe(iframe, settings) {
setOriginalsObject(iframe, attrsSrc);
setAttributeIfValue(iframe, SRC, getData(iframe, settings.data_src));
};
var setSourcesVideo = function setSourcesVideo(videoEl, settings) {
forEachVideoSource(videoEl, function (sourceEl) {
setOriginalsObject(sourceEl, attrsSrc);
setAttributeIfValue(sourceEl, SRC, getData(sourceEl, settings.data_src));
});
setOriginalsObject(videoEl, attrsSrcPoster);
setAttributeIfValue(videoEl, POSTER, getData(videoEl, settings.data_poster));
setAttributeIfValue(videoEl, SRC, getData(videoEl, settings.data_src));
videoEl.load();
};
var setSourcesObject = function setSourcesObject(object, settings) {
setOriginalsObject(object, attrsData);
setAttributeIfValue(object, DATA, getData(object, settings.data_src));
};
var setBackground = function setBackground(element, settings, instance) {
var bg1xValue = getData(element, settings.data_bg);
var bgHiDpiValue = getData(element, settings.data_bg_hidpi);
var bgDataValue = isHiDpi && bgHiDpiValue ? bgHiDpiValue : bg1xValue;
if (!bgDataValue) return;
//perfmatters adjustment to account for css variables like --background-image
var bgVarValue = getData(element, 'bg-var');
if(bgVarValue) {
element.style.setProperty(bgVarValue, "url(\"".concat(bgDataValue, "\")"));
}
else {
element.style.backgroundImage = "url(\"".concat(bgDataValue, "\")");
}
getTempImage(element).setAttribute(SRC, bgDataValue);
manageLoading(element, settings, instance);
}; // NOTE: THE TEMP IMAGE TRICK CANNOT BE DONE WITH data-multi-bg
// BECAUSE INSIDE ITS VALUES MUST BE WRAPPED WITH URL() AND ONE OF THEM
// COULD BE A GRADIENT BACKGROUND IMAGE
var setMultiBackground = function setMultiBackground(element, settings, instance) {
var bg1xValue = getData(element, settings.data_bg_multi);
var bgHiDpiValue = getData(element, settings.data_bg_multi_hidpi);
var bgDataValue = isHiDpi && bgHiDpiValue ? bgHiDpiValue : bg1xValue;
if (!bgDataValue) {
return;
}
element.style.backgroundImage = bgDataValue;
manageApplied(element, settings, instance);
};
var setImgsetBackground = function setImgsetBackground(element, settings, instance) {
var bgImgSetDataValue = getData(element, settings.data_bg_set);
if (!bgImgSetDataValue) {
return;
}
var imgSetValues = bgImgSetDataValue.split("|");
var bgImageValues = imgSetValues.map(function (value) {
return "image-set(".concat(value, ")");
});
element.style.backgroundImage = bgImageValues.join(); // Temporary fix for Chromeium with the -webkit- prefix
if (element.style.backgroundImage === '') {
bgImageValues = imgSetValues.map(function (value) {
return "-webkit-image-set(".concat(value, ")");
});
element.style.backgroundImage = bgImageValues.join();
}
manageApplied(element, settings, instance);
};
var setSourcesFunctions = {
IMG: setSourcesImg,
IFRAME: setSourcesIframe,
VIDEO: setSourcesVideo,
OBJECT: setSourcesObject
};
var setSourcesNative = function setSourcesNative(element, settings) {
var setSourcesFunction = setSourcesFunctions[element.tagName];
if (!setSourcesFunction) {
return;
}
setSourcesFunction(element, settings);
};
var setSources = function setSources(element, settings, instance) {
var setSourcesFunction = setSourcesFunctions[element.tagName];
if (!setSourcesFunction) {
return;
}
setSourcesFunction(element, settings);
manageLoading(element, settings, instance);
};
var elementsWithLoadEvent = ["IMG", "IFRAME", "VIDEO", "OBJECT"];
var hasLoadEvent = function hasLoadEvent(element) {
return elementsWithLoadEvent.indexOf(element.tagName) > -1;
};
var checkFinish = function checkFinish(settings, instance) {
if (instance && !isSomethingLoading(instance) && !haveElementsToLoad(instance)) {
safeCallback(settings.callback_finish, instance);
}
};
var addEventListener = function addEventListener(element, eventName, handler) {
element.addEventListener(eventName, handler);
element.llEvLisnrs[eventName] = handler;
};
var removeEventListener = function removeEventListener(element, eventName, handler) {
element.removeEventListener(eventName, handler);
};
var hasEventListeners = function hasEventListeners(element) {
return !!element.llEvLisnrs;
};
var addEventListeners = function addEventListeners(element, loadHandler, errorHandler) {
if (!hasEventListeners(element)) element.llEvLisnrs = {};
var loadEventName = element.tagName === "VIDEO" ? "loadeddata" : "load";
addEventListener(element, loadEventName, loadHandler);
addEventListener(element, "error", errorHandler);
};
var removeEventListeners = function removeEventListeners(element) {
if (!hasEventListeners(element)) {
return;
}
var eventListeners = element.llEvLisnrs;
for (var eventName in eventListeners) {
var handler = eventListeners[eventName];
removeEventListener(element, eventName, handler);
}
delete element.llEvLisnrs;
};
var doneHandler = function doneHandler(element, settings, instance) {
deleteTempImage(element);
updateLoadingCount(instance, -1);
decreaseToLoadCount(instance);
removeClass(element, settings.class_loading);
if (settings.unobserve_completed) {
unobserve(element, instance);
}
};
var loadHandler = function loadHandler(event, element, settings, instance) {
var goingNative = hasStatusNative(element);
doneHandler(element, settings, instance);
addClass(element, settings.class_loaded);
setStatus(element, statusLoaded);
safeCallback(settings.callback_loaded, element, instance);
if (!goingNative) checkFinish(settings, instance);
};
var errorHandler = function errorHandler(event, element, settings, instance) {
var goingNative = hasStatusNative(element);
doneHandler(element, settings, instance);
addClass(element, settings.class_error);
setStatus(element, statusError);
safeCallback(settings.callback_error, element, instance);
if (settings.restore_on_error) restoreOriginalAttrs(element, attrsSrcSrcsetSizes);
if (!goingNative) checkFinish(settings, instance);
};
var addOneShotEventListeners = function addOneShotEventListeners(element, settings, instance) {
var elementToListenTo = getTempImage(element) || element;
if (hasEventListeners(elementToListenTo)) {
// This happens when loading is retried twice
return;
}
var _loadHandler = function _loadHandler(event) {
loadHandler(event, element, settings, instance);
removeEventListeners(elementToListenTo);
};
var _errorHandler = function _errorHandler(event) {
errorHandler(event, element, settings, instance);
removeEventListeners(elementToListenTo);
};
addEventListeners(elementToListenTo, _loadHandler, _errorHandler);
};
var loadBackground = function loadBackground(element, settings, instance) {
addTempImage(element);
addOneShotEventListeners(element, settings, instance);
saveOriginalBackgroundStyle(element);
setBackground(element, settings, instance);
setMultiBackground(element, settings, instance);
setImgsetBackground(element, settings, instance);
};
var loadRegular = function loadRegular(element, settings, instance) {
addOneShotEventListeners(element, settings, instance);
setSources(element, settings, instance);
};
var load = function load(element, settings, instance) {
if (hasLoadEvent(element)) {
loadRegular(element, settings, instance);
} else {
loadBackground(element, settings, instance);
}
};
var loadNative = function loadNative(element, settings, instance) {
element.setAttribute("loading", "lazy");
addOneShotEventListeners(element, settings, instance);
setSourcesNative(element, settings);
setStatus(element, statusNative);
};
var removeImageAttributes = function removeImageAttributes(element) {
element.removeAttribute(SRC);
element.removeAttribute(SRCSET);
element.removeAttribute(SIZES);
};
var resetSourcesImg = function resetSourcesImg(element) {
forEachPictureSource(element, function (sourceTag) {
removeImageAttributes(sourceTag);
});
removeImageAttributes(element);
};
var restoreImg = function restoreImg(imgEl) {
forEachPictureSource(imgEl, function (sourceEl) {
restoreOriginalAttrs(sourceEl, attrsSrcSrcsetSizes);
});
restoreOriginalAttrs(imgEl, attrsSrcSrcsetSizes);
};
var restoreVideo = function restoreVideo(videoEl) {
forEachVideoSource(videoEl, function (sourceEl) {
restoreOriginalAttrs(sourceEl, attrsSrc);
});
restoreOriginalAttrs(videoEl, attrsSrcPoster);
videoEl.load();
};
var restoreIframe = function restoreIframe(iframeEl) {
restoreOriginalAttrs(iframeEl, attrsSrc);
};
var restoreObject = function restoreObject(objectEl) {
restoreOriginalAttrs(objectEl, attrsData);
};
var restoreFunctions = {
IMG: restoreImg,
IFRAME: restoreIframe,
VIDEO: restoreVideo,
OBJECT: restoreObject
};
var restoreAttributes = function restoreAttributes(element) {
var restoreFunction = restoreFunctions[element.tagName];
if (!restoreFunction) {
restoreOriginalBgImage(element);
return;
}
restoreFunction(element);
};
var resetClasses = function resetClasses(element, settings) {
if (hasEmptyStatus(element) || hasStatusNative(element)) {
return;
}
removeClass(element, settings.class_entered);
removeClass(element, settings.class_exited);
removeClass(element, settings.class_applied);
removeClass(element, settings.class_loading);
removeClass(element, settings.class_loaded);
removeClass(element, settings.class_error);
};
var restore = function restore(element, settings) {
restoreAttributes(element);
resetClasses(element, settings);
resetStatus(element);
deleteOriginalAttrs(element);
};
var cancelLoading = function cancelLoading(element, entry, settings, instance) {
if (!settings.cancel_on_exit) return;
if (!hasStatusLoading(element)) return;
if (element.tagName !== "IMG") return; //Works only on images
removeEventListeners(element);
resetSourcesImg(element);
restoreImg(element);
removeClass(element, settings.class_loading);
updateLoadingCount(instance, -1);
resetStatus(element);
safeCallback(settings.callback_cancel, element, entry, instance);
};
var onEnter = function onEnter(element, entry, settings, instance) {
var dontLoad = hadStartedLoading(element);
/* Save status
before setting it, to prevent loading it again. Fixes #526. */
setStatus(element, statusEntered);
addClass(element, settings.class_entered);
removeClass(element, settings.class_exited);
unobserveEntered(element, settings, instance);
safeCallback(settings.callback_enter, element, entry, instance);
if (dontLoad) return;
load(element, settings, instance);
};
var onExit = function onExit(element, entry, settings, instance) {
if (hasEmptyStatus(element)) return; //Ignore the first pass, at landing
addClass(element, settings.class_exited);
cancelLoading(element, entry, settings, instance);
safeCallback(settings.callback_exit, element, entry, instance);
};
var tagsWithNativeLazy = ["IMG", "IFRAME", "VIDEO"];
var shouldUseNative = function shouldUseNative(settings) {
return settings.use_native && "loading" in HTMLImageElement.prototype;
};
var loadAllNative = function loadAllNative(elements, settings, instance) {
elements.forEach(function (element) {
if (tagsWithNativeLazy.indexOf(element.tagName) === -1) {
return;
}
loadNative(element, settings, instance);
});
setToLoadCount(instance, 0);
};
var isIntersecting = function isIntersecting(entry) {
return entry.isIntersecting || entry.intersectionRatio > 0;
};
var getObserverSettings = function getObserverSettings(settings) {
return {
root: settings.container === document ? null : settings.container,
rootMargin: settings.thresholds || settings.threshold + "px"
};
};
var intersectionHandler = function intersectionHandler(entries, settings, instance) {
entries.forEach(function (entry) {
return isIntersecting(entry) ? onEnter(entry.target, entry, settings, instance) : onExit(entry.target, entry, settings, instance);
});
};
var observeElements = function observeElements(observer, elements) {
elements.forEach(function (element) {
observer.observe(element);
});
};
var updateObserver = function updateObserver(observer, elementsToObserve) {
resetObserver(observer);
observeElements(observer, elementsToObserve);
};
var setObserver = function setObserver(settings, instance) {
if (!supportsIntersectionObserver || shouldUseNative(settings)) {
return;
}
instance._observer = new IntersectionObserver(function (entries) {
intersectionHandler(entries, settings, instance);
}, getObserverSettings(settings));
};
var toArray = function toArray(nodeSet) {
return Array.prototype.slice.call(nodeSet);
};
var queryElements = function queryElements(settings) {
return settings.container.querySelectorAll(settings.elements_selector);
};
var excludeManagedElements = function excludeManagedElements(elements) {
return toArray(elements).filter(hasEmptyStatus);
};
var hasError = function hasError(element) {
return hasStatusError(element);
};
var filterErrorElements = function filterErrorElements(elements) {
return toArray(elements).filter(hasError);
};
var getElementsToLoad = function getElementsToLoad(elements, settings) {
return excludeManagedElements(elements || queryElements(settings));
};
var retryLazyLoad = function retryLazyLoad(settings, instance) {
var errorElements = filterErrorElements(queryElements(settings));
errorElements.forEach(function (element) {
removeClass(element, settings.class_error);
resetStatus(element);
});
instance.update();
};
var setOnlineCheck = function setOnlineCheck(settings, instance) {
if (!runningOnBrowser) {
return;
}
instance._onlineHandler = function () {
retryLazyLoad(settings, instance);
};
window.addEventListener("online", instance._onlineHandler);
};
var resetOnlineCheck = function resetOnlineCheck(instance) {
if (!runningOnBrowser) {
return;
}
window.removeEventListener("online", instance._onlineHandler);
};
var LazyLoad = function LazyLoad(customSettings, elements) {
var settings = getExtendedSettings(customSettings);
this._settings = settings;
this.loadingCount = 0;
setObserver(settings, this);
setOnlineCheck(settings, this);
this.update(elements);
};
LazyLoad.prototype = {
update: function update(givenNodeset) {
var settings = this._settings;
var elementsToLoad = getElementsToLoad(givenNodeset, settings);
setToLoadCount(this, elementsToLoad.length);
if (isBot || !supportsIntersectionObserver) {
this.loadAll(elementsToLoad);
return;
}
if (shouldUseNative(settings)) {
loadAllNative(elementsToLoad, settings, this);
return;
}
updateObserver(this._observer, elementsToLoad);
},
destroy: function destroy() {
// Observer
if (this._observer) {
this._observer.disconnect();
} // Clean handlers
resetOnlineCheck(this); // Clean custom attributes on elements
queryElements(this._settings).forEach(function (element) {
deleteOriginalAttrs(element);
}); // Delete all internal props
delete this._observer;
delete this._settings;
delete this._onlineHandler;
delete this.loadingCount;
delete this.toLoadCount;
},
loadAll: function loadAll(elements) {
var _this = this;
var settings = this._settings;
var elementsToLoad = getElementsToLoad(elements, settings);
elementsToLoad.forEach(function (element) {
unobserve(element, _this);
load(element, settings, _this);
});
},
restoreAll: function restoreAll() {
var settings = this._settings;
queryElements(settings).forEach(function (element) {
restore(element, settings);
});
}
};
LazyLoad.load = function (element, customSettings) {
var settings = getExtendedSettings(customSettings);
load(element, settings);
};
LazyLoad.resetStatus = function (element) {
resetStatus(element);
}; // Automatic instances creation if required (useful for async script loading)
if (runningOnBrowser) {
autoInitialize(LazyLoad, window.lazyLoadOptions);
}
return LazyLoad;
})));

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,227 @@
jQuery(document).ready(function($) {
//tab-content display
$('.perfmatters-subnav > a').click(function(e) {
e.preventDefault();
//deactivate current tab + hide content
var active_tab = $(this).closest('.perfmatters-subnav').find('a.active');
active_tab.removeClass('active');
$(active_tab.attr('href')).removeClass('active');
//add 'active' css into clicked navigation
$(this).addClass('active');
$($(this).attr('href')).addClass('active');
$('#perfmatters-options-form').attr('action', "options.php" + "#" + $(this).attr('rel'));
$('#perfmatters-admin .CodeMirror').each(function(i, el) {
el.CodeMirror.refresh();
});
});
//menu toggle
var menuToggle = document.getElementById('perfmatters-menu-toggle');
if(menuToggle) {
menuToggle.addEventListener('click', function(e) {
e.preventDefault();
var header = document.getElementById('perfmatters-menu');
if(!header.classList.contains('perfmatters-menu-expanded')) {
header.classList.add('perfmatters-menu-expanded');
}
else {
header.classList.remove('perfmatters-menu-expanded');
}
});
}
//tooltip display
$(".perfmatters-tooltip").hover(function() {
$(this).closest("tr").find(".perfmatters-tooltip-text").fadeIn(100);
},function(){
$(this).closest("tr").find(".perfmatters-tooltip-text").fadeOut(100);
});
//add input row
$('.perfmatters-add-input-row').on('click', function(ev) {
ev.preventDefault();
var rowCount = $(this).prop('rel');
if(rowCount < 1) {
$(this).closest('.perfmatters-input-row-wrapper').find('.perfmatters-input-row').addClass('perfmatters-opened').show();
}
else {
var $container = $(this).closest('.perfmatters-input-row-wrapper').find('.perfmatters-input-row-container');
var $clonedRow = $container.find('.perfmatters-input-row').last().clone();
$clonedRow.addClass('perfmatters-opened');
$clonedRow.find(':text, select').val('');
$clonedRow.find(':checkbox').prop('checked', false);
perfmattersUpdateRowCount($clonedRow, rowCount);
$container.append($clonedRow);
}
rowCount++;
$(this).prop('rel', rowCount);
});
//delete input row
$('.perfmatters-input-row-wrapper').on('click', '.perfmatters-delete-input-row', function(ev) {
ev.preventDefault();
var siblings = $(this).closest('.perfmatters-input-row').siblings();
var $addButton = $(this).closest('.perfmatters-input-row-wrapper').find('.perfmatters-add-input-row');
if($addButton.prop('rel') == 1) {
$row = $(this).closest('.perfmatters-input-row');
$row.find(':text, select').val('');
$row.find(':checkbox').prop("checked", false);
$row.hide();
}
else {
$(this).closest('.perfmatters-input-row').remove();
}
$addButton.prop('rel', $addButton.prop('rel') - 1);
siblings.each(function(i) {
perfmattersUpdateRowCount(this, i);
});
});
//expand input row
$('.perfmatters-input-row-wrapper').on('click', '.perfmatters-expand-input-row', function(ev) {
ev.preventDefault();
$row = $(this).closest('.perfmatters-input-row');
if($row.hasClass('perfmatters-opened')) {
$row.removeClass('perfmatters-opened');
}
else {
$row.addClass('perfmatters-opened');
}
});
//quick exclusions
$(".perfmatters-quick-exclusion-title-bar").click(function(e) {
var clicked = $(this).closest(".perfmatters-quick-exclusion");
if(clicked.hasClass("perfmatters-opened")) {
clicked.removeClass("perfmatters-opened");
}
else {
clicked.addClass("perfmatters-opened");
}
});
//input display control
$('.perfmatters-input-controller input, .perfmatters-input-controller select').change(function() {
var controller = $(this);
var inputID = $(this).attr('id');
var nestedControllers = [];
$('.' + inputID).each(function() {
var skipFlag = true;
var forceHide = false;
var forceShow = false;
var optionSelected = false;
if($(this).hasClass('perfmatters-input-controller')) {
nestedControllers.push($(this).find('input, select').attr('id'));
}
var currentInputContainer = this;
$.each(nestedControllers, function(index, value) {
var currentController = $('#' + value);
if(currentController.is('input')) {
var controlChecked = $('#' + value).is(':checked');
var controlReverse = $('#' + value).closest('.perfmatters-input-controller').hasClass('perfmatters-input-controller-reverse');
if($(currentInputContainer).hasClass(value) && (controlChecked == controlReverse)) {
skipFlag = false;
return false;
}
}
else if(currentController.is('select')) {
var classNames = currentInputContainer.className.match(/perfmatters-select-control-([^\s]*)/g);
if(classNames) {
var foundClass = ($.inArray('perfmatters-select-control-' + $('#' + value).val(), classNames)) >= 0;
if(!foundClass) {
forceHide = true;
}
}
}
});
if(controller.is('select')) {
var classNames = this.className.match(/perfmatters-select-control-([^\s]*)/g);
var foundClass = ($.inArray('perfmatters-select-control-' + controller.val(), classNames)) >= 0;
if(classNames && (foundClass != $(this).hasClass('perfmatters-control-reverse'))) {
forceShow = true;
}
else {
forceHide = true;
}
}
if(skipFlag) {
if(($(this).hasClass('hidden') || forceShow) && !forceHide) {
$(this).removeClass('hidden');
}
else {
$(this).addClass('hidden');
}
}
});
});
//validate input
$("#perfmatters-admin [perfmatters_validate]").keypress(function(e) {
//grab input and pattern
var code = e.which;
var character = String.fromCharCode(code);
var pattern = $(this).attr('perfmatters_validate');
//prevent input if character is invalid
if(!character.match(pattern)) {
e.preventDefault();
}
});
//initialize codemirror textareas
var $codemirror = $('.perfmatters-codemirror');
if($codemirror.length) {
$codemirror.each(function() {
wp.codeEditor.initialize(this, cm_settings);
});
}
});
//update row count for given input row attributes
function perfmattersUpdateRowCount(row, rowCount) {
jQuery(row).find('input, select, label').each(function() {
if(jQuery(this).attr('id')) {
jQuery(this).attr('id', jQuery(this).attr('id').replace(/[0-9]+/g, rowCount));
}
if(jQuery(this).attr('name')) {
jQuery(this).attr('name', jQuery(this).attr('name').replace(/[0-9]+/g, rowCount));
}
if(jQuery(this).attr('for')) {
jQuery(this).attr('for', jQuery(this).attr('for').replace(/[0-9]+/g, rowCount));
}
});
}

View File

@@ -0,0 +1,324 @@
document.addEventListener("DOMContentLoaded", function() {
//hide loader
var loader = document.getElementById('pmsm-loading-wrapper');
if(loader) {
loader.style.display = "none";
}
//group heading containers
var groupHeadings = document.querySelectorAll(".pmsm-group-heading");
groupHeadings.forEach(function(groupHeading) {
groupHeading.addEventListener('change', function(e) {
var elem = e.target;
//group status toggle/select
if(elem.classList.contains('perfmatters-status-toggle') || elem.classList.contains('perfmatters-status-select')) {
var group = elem.closest('.perfmatters-script-manager-group');
var table = group.querySelector('.perfmatters-script-manager-section table');
var disabled = group.querySelector('.perfmatters-script-manager-section .perfmatters-script-manager-assets-disabled');
var muBadge = group.querySelector('.pmsm-mu-mode-badge');
if((elem.type == 'checkbox' && elem.checked) || (elem.type == 'select-one' && elem.value == 'disabled')) {
elem.classList.add('disabled');
if(table) {
table.style.display = "none";
}
if(disabled) {
disabled.style.display = "block";
}
if(muBadge) {
muBadge.style.display = "inline-block";
}
}
else {
elem.classList.remove('disabled');
if(table) {
table.style.display = "table";
}
if(disabled) {
disabled.style.display = "none";
}
if(muBadge) {
muBadge.style.display = "none";
}
}
}
});
});
//section containers
var sections = document.querySelectorAll(".perfmatters-script-manager-section");
sections.forEach(function(section) {
section.addEventListener('change', function(e) {
var elem = e.target;
//script status toggle/select
if(elem.classList.contains('perfmatters-status-toggle') || elem.classList.contains('perfmatters-status-select')) {
var tr = elem.closest('tr');
var controls = tr.querySelector('.perfmatters-script-manager-controls');
if((elem.type == 'checkbox' && elem.checked) || (elem.type == 'select-one' && elem.value == 'disabled')) {
elem.classList.add('disabled');
controls.style.display = "block";
}
else {
elem.classList.remove('disabled');
controls.style.display = "none";
}
}
//disables
if(elem.classList.contains('pmsm-disable-everywhere')) {
var controls = elem.closest('.perfmatters-script-manager-controls');
var enable = controls.querySelector('.perfmatters-script-manager-enable');
var hideMatches = controls.querySelectorAll('.pmsm-everywhere-hide');
enable.style.display = (elem.checked ? "block" : "none");
hideMatches.forEach(function(hide) {
if(elem.checked) {
hide.classList.add("pmsm-hide");
}
else {
hide.classList.remove("pmsm-hide");
}
});
}
});
});
//set changed status of selected inputs
var inputs = document.querySelectorAll("#pmsm-main-form input, #pmsm-main-form select");
inputs.forEach(function(input) {
input.addEventListener('change', function(e) {
var elem = e.target;
elem.classList.add('pmsm-changed');
if(elem.type == 'checkbox') {
var checkboxContainer = elem.closest('.pmsm-checkbox-container');
var checkboxes = checkboxContainer.querySelectorAll('input');
checkboxes.forEach(function(checkbox) {
checkbox.classList.add('pmsm-changed');
});
}
});
});
var mainForm = document.getElementById("pmsm-main-form");
//submit main script manager form
if(mainForm) {
mainForm.addEventListener('submit', function(e) {
//prevent server side submission
e.preventDefault();
//disable any inputs that weren't touched
var inputs = e.target.querySelectorAll('input:not(.pmsm-changed), select:not(.pmsm-changed)');
inputs.forEach(function(input) {
input.disabled = true;
});
//save button feedback
var saveButton = document.querySelector('#pmsm-save input');
var saveSpinner = document.querySelector('#pmsm-save .pmsm-spinner');
saveButton.value = pmsm.messages.buttonSaving;
saveSpinner.style.display = "inline-block";
//get form data
const formData = new FormData(e.target);
const formDataString = new URLSearchParams(formData).toString();
//ajax request
var request = new XMLHttpRequest();
request.open('POST', pmsm.ajaxURL, true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
request.onload = function() {
//successful request
if(this.status >= 200 && this.status < 400) {
//setup message variable
var message;
if(this.response == 'update_success') {
message = pmsm.messages.updateSuccess;
//if script status was toggled back on, clear child input values
var changedScriptStatusToggles = e.target.querySelectorAll('.perfmatters-script-manager-section .perfmatters-status-toggle.pmsm-changed');
changedScriptStatusToggles.forEach(function(toggle) {
if(!toggle.checked) {
var toggleRow = toggle.closest('tr');
toggleRow.querySelector('.perfmatters-script-manager-enable').style.display = "none";
var rowInputs = toggleRow.querySelectorAll('input');
rowInputs.forEach(function(input) {
if(input.type == "checkbox" || input.type == "radio") {
input.checked = false;
}
else if(input.type == "text") {
input.value = "";
}
});
}
});
//if group status was toggled back on, clear child input values
var changedGroupStatusToggles = e.target.querySelectorAll('.pmsm-group-heading .perfmatters-status-toggle.pmsm-changed');
changedGroupStatusToggles.forEach(function(toggle) {
if(!toggle.checked) {
var toggleGroup = toggle.closest('.perfmatters-script-manager-group');
var toggleGroupAssets = toggleGroup.querySelector('.perfmatters-script-manager-assets-disabled');
toggleGroupAssets.querySelector('.perfmatters-script-manager-enable').style.display = "none";
var groupInputs = toggleGroupAssets.querySelectorAll('input');
groupInputs.forEach(function(input) {
if(input.type == "checkbox" || input.type == "radio") {
input.checked = false;
}
else if(input.type == "text") {
input.value = "";
}
});
}
});
//reset changed inputs
var changedInputs = e.target.querySelectorAll('.pmsm-changed');
changedInputs.forEach(function(input) {
input.classList.remove('pmsm-changed');
});
}
else if(this.response == 'update_failure') {
message = pmsm.messages.updateFailure;
}
else if(this.response == 'update_nooption') {
message = pmsm.messages.updateNoOption;
}
else if(this.response == 'update_nochange') {
message = pmsm.messages.updateNoChange;
}
//display message
if(message) {
pmsmPopupMessage(message);
}
//reenable form inputs
inputs.forEach(function(input) {
input.disabled = false;
});
saveButton.value = pmsm.messages.buttonSave;
saveSpinner.style.display = "none";
}
else {
//failed request response
console.log(this.response);
}
};
request.onerror = function() {
//connection error
};
//send request
request.send('action=pmsm_save&current_id=' + pmsm.currentID + '&pmsm_data=' + encodeURIComponent(formDataString));
});
}
//reset button
var resetButton = document.getElementById('pmsm-reset');
if(resetButton) {
resetButton.addEventListener('click', function(ev) {
ev.preventDefault();
var resetForm = document.getElementById('pmsm-reset-form');
var confirmCheck = confirm(resetForm.getAttribute('pmsm-confirm'));
if(confirmCheck) {
resetForm.submit();
}
});
}
//menu toggle
var menuToggle = document.getElementById('pmsm-menu-toggle');
if(menuToggle) {
menuToggle.addEventListener('click', function(ev) {
ev.preventDefault();
var header = document.getElementById('perfmatters-script-manager-header');
if(window.innerWidth > 782) {
if(!header.classList.contains('pmsm-header-minimal')) {
header.classList.add('pmsm-header-minimal');
}
else {
header.classList.remove('pmsm-header-minimal');
}
}
else {
if(!header.classList.contains('pmsm-header-expanded')) {
header.classList.add('pmsm-header-expanded');
}
else {
header.classList.remove('pmsm-header-expanded');
}
}
});
window.addEventListener('click', function(e) {
var header = document.getElementById('perfmatters-script-manager-header');
if(!header.contains(e.target)) {
header.classList.remove('pmsm-header-expanded');
}
});
}
});
//popup message after submit
function pmsmPopupMessage(message) {
if(message) {
var messageContainer = document.getElementById('pmsm-message');
messageContainer.innerHTML = message;
messageContainer.classList.add('pmsm-fade');
setTimeout(function() {
messageContainer.classList.remove('pmsm-fade');
}, 2000);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,500 @@
<?php
/*
Plugin Name: Perfmatters
Plugin URI: https://perfmatters.io/
Description: Perfmatters is a lightweight performance plugin developed to speed up your WordPress site.
Version: 2.1.4
Author: forgemedia
Author URI: https://forgemedia.io/
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: perfmatters
Domain Path: /languages
*/
/*****************************************************************************************
* EDD License
*****************************************************************************************/
define('PERFMATTERS_STORE_URL', 'https://perfmatters.io/');
define('PERFMATTERS_ITEM_ID', 696);
define('PERFMATTERS_ITEM_NAME', 'perfmatters');
define('PERFMATTERS_VERSION', '2.1.4');
update_option('perfmatters_edd_license_key', '***********************');
update_option('perfmatters_edd_license_status', 'valid');
function perfmatters_plugins_loaded() {
//setup cache constants
$perfmatters_cache_path = apply_filters('perfmatters_cache_path', 'cache');
$parsed_url = parse_url(get_site_url());
$host = $parsed_url['host'] . ($parsed_url['path'] ?? '');
if(!defined('PERFMATTERS_CACHE_DIR')) {
define('PERFMATTERS_CACHE_DIR', WP_CONTENT_DIR . '/' . $perfmatters_cache_path . "/perfmatters/$host/");
}
if(!defined('PERFMATTERS_CACHE_URL')) {
define('PERFMATTERS_CACHE_URL', str_replace('http:', 'https:', content_url('/')) . $perfmatters_cache_path . "/perfmatters/$host/");
}
//load translations
load_plugin_textdomain('perfmatters', false, dirname(plugin_basename( __FILE__)) . '/languages/');
//initialize plugin classes
Perfmatters\Config::init();
Perfmatters\Meta::init();
//initialize classes that filter the buffer
Perfmatters\Fonts::init();
Perfmatters\Images::init();
Perfmatters\CSS::init();
Perfmatters\LazyLoad::init_iframes();
Perfmatters\Preload::init();
Perfmatters\LazyLoad::init_images();
Perfmatters\CDN::init();
Perfmatters\JS::init();
Perfmatters\Buffer::init();
//initialize db optimizer
new Perfmatters\DatabaseOptimizer();
}
add_action('plugins_loaded', 'perfmatters_plugins_loaded');
//setup cli commands
if(defined('WP_CLI' ) && WP_CLI) {
require_once plugin_dir_path(__FILE__) . 'inc/CLI.php';
function perfmatters_cli_register_commands() {
WP_CLI::add_command('perfmatters', 'Perfmatters\CLI');
}
add_action('cli_init', 'perfmatters_cli_register_commands');
}
//initialize the updater
function perfmatters_edd_plugin_updater() {
//to support auto-updates, this needs to run during the wp_version_check cron job for privileged users
$doing_cron = defined('DOING_CRON') && DOING_CRON;
if(!current_user_can('manage_options') && !$doing_cron && !defined('WP_CLI')) {
return;
}
//retrieve our license key from the DB
$license_key = is_multisite() ? trim(get_site_option('perfmatters_edd_license_key')) : trim(get_option('perfmatters_edd_license_key'));
//setup the updater
$edd_updater = new Perfmatters_Plugin_Updater(PERFMATTERS_STORE_URL, __FILE__, array(
'version' => PERFMATTERS_VERSION,
'license' => $license_key,
'item_id' => PERFMATTERS_ITEM_ID,
'author' => 'forgemedia',
'beta' => false
)
);
}
add_action('init', 'perfmatters_edd_plugin_updater', 0);
//add our admin menus
if(is_admin()) {
add_action('admin_menu', 'perfmatters_menu', 9);
}
global $perfmatters_settings_page;
//admin menu
function perfmatters_menu() {
if(perfmatters_network_access()) {
global $perfmatters_settings_page;
$perfmatters_settings_page = add_options_page('perfmatters', 'Perfmatters', 'manage_options', 'perfmatters', 'perfmatters_admin');
add_action('load-' . $perfmatters_settings_page, 'perfmatters_settings_load');
}
}
//admin settings page
function perfmatters_admin() {
include plugin_dir_path(__FILE__) . '/inc/admin.php';
}
//admin settings page load hook
function perfmatters_settings_load() {
add_action('admin_enqueue_scripts', 'perfmatters_admin_scripts');
}
//plugin admin scripts
function perfmatters_admin_scripts() {
if(perfmatters_network_access()) {
wp_register_style('perfmatters-styles', plugins_url('/css/style.css', __FILE__), array(), PERFMATTERS_VERSION);
wp_enqueue_style('perfmatters-styles');
wp_register_script('perfmatters-js', plugins_url('/js/perfmatters.js', __FILE__), array(), PERFMATTERS_VERSION);
wp_enqueue_script('perfmatters-js');
if(empty($_GET['tab']) || $_GET['tab'] == 'options') {
$cm_settings['codeEditor'] = wp_enqueue_code_editor(array('type' => 'text/html'));
wp_localize_script('jquery', 'cm_settings', $cm_settings);
wp_enqueue_script('wp-theme-plugin-editor');
wp_enqueue_style('wp-codemirror');
}
}
}
//check multisite and verify access
function perfmatters_network_access() {
if(is_multisite()) {
$perfmatters_network = get_site_option('perfmatters_network');
if((!empty($perfmatters_network['access']) && $perfmatters_network['access'] == 'super') && !is_super_admin()) {
return false;
}
}
return true;
}
//license messages in plugins table
function perfmatters_meta_links($links, $file) {
if(strpos($file, 'perfmatters.php') !== false) {
//support link
$perfmatters_links = array('<a href="https://perfmatters.io/docs/" target="_blank">' . __('Support', 'perfmatters') . '</a>');
$links = array_merge($links, $perfmatters_links);
}
return $links;
}
add_filter('plugin_row_meta', 'perfmatters_meta_links', 10, 2);
//settings link in plugins table
function perfmatters_action_links($actions, $plugin_file)
{
if(plugin_basename(__FILE__) == $plugin_file) {
$settings_url = is_network_admin() ? network_admin_url('settings.php?page=perfmatters') : admin_url('options-general.php?page=perfmatters');
$settings_link = array('settings' => '<a href="' . $settings_url . '">' . __('Settings', 'perfmatters') . '</a>');
$actions = array_merge($settings_link, $actions);
}
return $actions;
}
add_filter('plugin_action_links', 'perfmatters_action_links', 10, 5);
//display message with plugin update if theres no valid license
function perfmatters_plugin_update_message() {
$license_status = is_multisite() ? get_site_option('perfmatters_edd_license_status') : get_option('perfmatters_edd_license_status');
if(empty($license_status) || $license_status !== 'valid') {
echo ' <strong><a href="' . esc_url(admin_url('options-general.php?page=perfmatters&tab=license')) . '">' . __('Enter valid license key for automatic updates.', 'perfmatters') . '</a></strong>';
}
}
add_action('in_plugin_update_message-perfmatters/perfmatters.php', 'perfmatters_plugin_update_message', 10, 2);
function perfmatters_activate() {
//enable local analytics scheduled event
$perfmatters_options = get_option('perfmatters_options');
if(!empty($perfmatters_options['analytics']['enable_local_ga'])) {
if(!wp_next_scheduled('perfmatters_update_ga')) {
wp_schedule_event(time(), 'daily', 'perfmatters_update_ga');
}
}
//check if we need to copy mu plugin file
$pmsm_settings = get_option('perfmatters_script_manager_settings');
if(!empty($pmsm_settings['mu_mode']) && !file_exists(WPMU_PLUGIN_DIR . "/perfmatters_mu.php")) {
if(file_exists(plugin_dir_path(__FILE__) . "/inc/perfmatters_mu.php")) {
@copy(plugin_dir_path(__FILE__) . "/inc/perfmatters_mu.php", WPMU_PLUGIN_DIR . "/perfmatters_mu.php");
}
}
}
register_activation_hook(__FILE__, 'perfmatters_activate');
//register a license deactivation
function perfmatters_deactivate() {
//remove scheduled events
foreach(array('perfmatters_update_ga', 'perfmatters_database_optimization') as $hook) {
if(wp_next_scheduled($hook)) {
wp_clear_scheduled_hook($hook);
}
}
}
register_deactivation_hook(__FILE__, 'perfmatters_deactivate');
//install plugin data
function perfmatters_install() {
if(!function_exists('get_plugin_data')) {
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
}
//mu plugin file check
if(file_exists(WPMU_PLUGIN_DIR . "/perfmatters_mu.php")) {
//get plugin data
$mu_plugin_data = get_plugin_data(WPMU_PLUGIN_DIR . "/perfmatters_mu.php");
if(!empty($mu_plugin_data['Version']) && $mu_plugin_data['Version'] != PERFMATTERS_VERSION) {
@unlink(WPMU_PLUGIN_DIR . "/perfmatters_mu.php");
if(file_exists(plugin_dir_path(__FILE__) . "/inc/perfmatters_mu.php")) {
@copy(plugin_dir_path(__FILE__) . "/inc/perfmatters_mu.php", WPMU_PLUGIN_DIR . "/perfmatters_mu.php");
}
}
}
$perfmatters_version = get_option('perfmatters_version');
//migrate data to new locations
if($perfmatters_version < '1.7.5') {
//migration fields array
$migration_fields = array(
'perfmatters_options' => array(
'perfmatters_options' => array(
'lazy_loading' => 'lazyload',
'lazy_loading_iframes' => 'lazyload',
'youtube_preview_thumbnails' => 'lazyload',
'lazy_loading_exclusions' => 'lazyload',
'lazy_loading_dom_monitoring' => 'lazyload',
'disable_google_fonts' => 'fonts'
)
),
'perfmatters_cdn' => array(
'perfmatters_options' => array(
'enable_cdn' => 'cdn',
'cdn_url' => 'cdn',
'cdn_directories' => 'cdn',
'cdn_exclusions' => 'cdn'
)
),
'perfmatters_ga' => array(
'perfmatters_options' => array(
'enable_local_ga' => 'analytics',
'tracking_id' => 'analytics',
'tracking_code_position' => 'analytics',
'script_type' => 'analytics',
'disable_display_features' => 'analytics',
'anonymize_ip' => 'analytics',
'track_admins' => 'analytics',
'adjusted_bounce_rate' => 'analytics',
'cdn_url' => 'analytics',
'use_monster_insights' => 'analytics',
'enable_amp' => 'analytics'
)
),
'perfmatters_extras' => array(
'perfmatters_options' => array(
'blank_favicon' => '',
'script_manager' => 'assets',
'defer_js' => 'assets',
'defer_jquery' => 'assets',
'js_exclusions' => 'assets',
'delay_js' => 'assets',
'delay_timeout' => 'assets',
'header_code' => 'assets',
'body_code' => 'assets',
'footer_code' => 'assets',
'instant_page' => 'preload',
'preload' => 'preload',
'preconnect' => 'preload',
'dns_prefetch' => 'preload'
),
'perfmatters_tools' => array(
'clean_uninstall' => '',
'accessibility_mode' => '',
'post_revisions' => 'database',
'post_auto_drafts' => 'database',
'trashed_posts' => 'database',
'spam_comments' => 'database',
'trashed_comments' => 'database',
'expired_transients' => 'database',
'all_transients' => 'database',
'tables' => 'database',
'optimize_schedule' => 'database'
)
)
);
//loop through and migrate old data to new options
foreach($migration_fields as $old_option_id => $new_options) {
//old option
$old_option_array = get_option($old_option_id, array());
foreach($new_options as $new_option_id => $fields) {
//new option
$new_option_array = get_option($new_option_id, array());
foreach($fields as $id => $section) {
if(!empty($old_option_array[$id])) {
if(empty($section)) {
$new_option_array[$id] = $old_option_array[$id];
}
else {
$new_option_array[$section][$id] = $old_option_array[$id];
}
}
}
//save new option
update_option($new_option_id, $new_option_array);
}
}
}
if($perfmatters_version < '1.7.6') {
$update_flag = false;
$perfmatters_options = get_option('perfmatters_options');
if(!empty($perfmatters_options['assets']['delay_js'])) {
$perfmatters_options['assets']['delay_js_inclusions'] = $perfmatters_options['assets']['delay_js'];
$perfmatters_options['assets']['delay_js'] = '1';
$update_flag = true;
}
if($update_flag) {
update_option('perfmatters_options', $perfmatters_options);
}
}
if($perfmatters_version < '2.1.1') {
$perfmatters_options = get_option('perfmatters_options');
$perfmatters_tools = get_option('perfmatters_tools');
if(!empty($perfmatters_options['assets']['defer_jquery']) && empty($perfmatters_tools['show_advanced'])) {
$perfmatters_tools['show_advanced'] = '1';
update_option('perfmatters_tools', $perfmatters_tools);
}
}
//update version
if($perfmatters_version != PERFMATTERS_VERSION) {
update_option('perfmatters_version', PERFMATTERS_VERSION, false);
}
//update network version if needed
if(is_multisite()) {
if(get_site_option('perfmatters_version') != PERFMATTERS_VERSION) {
update_site_option('perfmatters_version', PERFMATTERS_VERSION, false);
}
}
}
//check version for update
function perfmatters_version_check() {
$install_flag = false;
if(is_multisite()) {
if(get_site_option('perfmatters_version') != PERFMATTERS_VERSION) {
$install_flag = true;
}
}
if(get_option('perfmatters_version') != PERFMATTERS_VERSION) {
$install_flag = true;
}
if($install_flag) {
perfmatters_install();
}
}
add_action('plugins_loaded', 'perfmatters_version_check');
//uninstall plugin + delete options
function perfmatters_uninstall() {
//deactivate license if needed
perfmatters_deactivate_license();
//plugin options
$perfmatters_options = array(
'perfmatters_options',
'perfmatters_cdn', //deprecated
'perfmatters_ga', //deprecated
'perfmatters_extras', //deprecated
'perfmatters_tools',
'perfmatters_used_css_time',
'perfmatters_script_manager',
'perfmatters_script_manager_settings',
'perfmatters_edd_license_key',
'perfmatters_edd_license_status',
'perfmatters_version'
);
//meta options
$perfmatters_meta_options = array(
'perfmatters_exclude_defer_js',
'perfmatters_exclude_lazy_loading',
'perfmatters_exclude_instant_page'
);
if(is_multisite()) {
$perfmatters_network = get_site_option('perfmatters_network');
if(!empty($perfmatters_network['clean_uninstall']) && $perfmatters_network['clean_uninstall'] == 1) {
global $wpdb;
//remove network option
delete_site_option('perfmatters_network');
$sites = array_map('get_object_vars', get_sites(array('deleted' => 0)));
if(is_array($sites) && $sites !== array()) {
foreach($sites as $site) {
//remove options
foreach($perfmatters_options as $option) {
delete_blog_option($site['blog_id'], $option);
}
//remove meta options
foreach($perfmatters_meta_options as $option) {
$wpdb->delete($wpdb->get_blog_prefix($site['blog_id']) . 'postmeta', array('meta_key' => $option));
}
}
}
//remove stored version
delete_site_option('perfmatters_version');
}
}
else {
$perfmatters_tools = get_option('perfmatters_tools');
if(!empty($perfmatters_tools['clean_uninstall']) && $perfmatters_tools['clean_uninstall'] == 1) {
global $wpdb;
//remove options
foreach($perfmatters_options as $option) {
delete_option($option);
}
//remove meta options
foreach($perfmatters_meta_options as $option) {
$wpdb->delete($wpdb->prefix . 'postmeta', array('meta_key' => $option));
}
//remove stored version
delete_option('perfmatters_version');
}
}
//remove cache directory if needed
require_once(ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php');
require_once(ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php');
if(class_exists('WP_Filesystem_Direct')) {
$fileSystemDirect = new WP_Filesystem_Direct(false);
$cache_dir = ABSPATH . 'wp-content/cache/perfmatters';
if($fileSystemDirect->is_dir($cache_dir)) {
$fileSystemDirect->rmdir($cache_dir, true);
}
}
//remove mu plugin file if needed
if(file_exists(WPMU_PLUGIN_DIR . "/perfmatters_mu.php")) {
@unlink(WPMU_PLUGIN_DIR . "/perfmatters_mu.php");
}
}
register_uninstall_hook(__FILE__, 'perfmatters_uninstall');
//main file includes
require_once plugin_dir_path(__FILE__) . 'EDD_SL_Plugin_Updater.php';
require_once plugin_dir_path(__FILE__) . 'inc/settings.php';
require_once plugin_dir_path(__FILE__) . 'inc/functions.php';
require_once plugin_dir_path(__FILE__) . 'inc/functions_script_manager.php';
require_once plugin_dir_path(__FILE__) . 'inc/functions_network.php';
//composer autoloader
require_once plugin_dir_path(__FILE__) . 'vendor/autoload.php';

View File

@@ -0,0 +1,701 @@
=== Perfmatters ===
Contributors:
Donate link: https://perfmatters.io
Tags: perfmatters
Requires at least: 5.5
Requires PHP: 7.0
Tested up to: 6.3
Stable tag: 2.1.4
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Perfmatters is a lightweight performance plugin developed to speed up your WordPress site.
== Description ==
[Perfmatters](https://perfmatters.io/) is a lightweight web performance plugin designed to help increase Google Core Web Vitals scores and fine-tune how assets load on your site.
= Features =
* Easy quick toggle options to turn off resources that shouldn't be loading.
* Disable scripts and plugins on a per post/page or sitewide basis with the Script Manager.
* Defer and delay JavaScript, including third-party scripts.
* Automatically remove unused CSS.
* Preload resources, critical images, and prefetch links for quicker load times.
* Lazy load images and enable click-to-play thumbnails on videos.
* Host Google Analytics and Google Fonts locally.
* Change your WordPress login URL.
* Disable and limit WordPress revisions.
* Add code to your header, body, and footer.
* Optimize your database.
= Documentation =
Check out our [documentation](https://perfmatters.io/docs/) for more information on how to use Perfmatters.
== Changelog ==
= 2.1.4 - 08.08.2023 =
* Added new preload option to add the Fetch Priority attribute to different resources on the site to help improve LCP.
* Added built-in lazy loading exclusion for fetchpriority attribute.
* Added Delay JS quick exclusion for Termageddon + Usercentrics.
* Switched indvidual JS delay to use the same inline script as delay all to take advantage of delayed triggering of event listeners.
* Fixed an issue where an empty notice was appearing when a database optimization process completed.
* Fixed an issue with critical image preloads where an image with an empty src attribute would prevent other similar ones from being added on the same URL.
* UI improvements to input row sections.
= 2.1.3 - 07.02.2023 =
* Fixed an issue that was preventing existing Script Manager settings from showing up in certain instances.
* Translation updates.
= 2.1.2 - 06.29.2023 =
* Added new lazy loading advanced option to Exclude Images by Parent Selector.
* Added built-in exclusion to Delay JS for jqueryParams inline script to prevent load order issues.
* Added additional built-in exclusions to Remove Unused CSS for better compatibility with Elementor.
* Added HTTPS check to PERFMATTERS_CACHE_URL definition.
* Updated Script Manager UI to sort plugins alphabetically by plugin name as well as assets inside each individual section alphabetically by script handle.
* Fixed an issue where plugins without any enqueued scripts would not always show up in the Script Manager (MU Mode) after visiting the global view.
* Updated background processing library to version 1.1.0.
* Translation updates.
= 2.1.1 - 05.31.2023 =
* Added WP-CLI support for managing plugin license key activation.
* Changed behavior of Disable Cart Fragments toggle to only load cart fragmentation script when there are items in the cart.
* Added default array for critical image preload exclusions that are always needed.
* Added additional Delay JS quick exclusions for Bricks Slider and WP Armour.
* Added additional built-in exclusions for Remove Unused CSS for better compatibility with Elementor and Google Reviews Widget.
* Updated lazy loading fade-in effect to use CSS animation property instead of transition for better compatibility with existing element transitions.
* Added requirement for advanced options to be turned on to be able to defer jQuery.
* Added WP-CLI request exclusion to MU plugin functions.
* Fixed a PHP warning that could sometimes be generated if an image was not able to be parsed for missing dimensions.
* Updated instant.page library to version 5.2.0.
* Translation updates.
= 2.1.0 - 05.01.2023 =
* Added new delay JS option for Quick Exclusions that will show up when certain popular plugins and themes are activated.
* Made some updates to the Script Manager UI to match recent changes to the main plugin settings.
* Cleared out some code for the previous settings admin header that was no longer needed.
* Made an adjustment to CDN URL function to work even if a trailing slash was entered.
* Rearranged our local and Google font options to give frequently used options more priority.
* Fixed a bug where multiple settings sections were displaying at the same time after saving from the database tab.
* Fixed an issue where accessibility mode tooltips were not getting styled properly in the plugin UI.
* Fixed a styling issue where link and button colors were getting applied outside of the main Perfmatters admin container.
* Fixed an issue in MU Mode where the global filtered plugin list would not always return correctly.
* Translation updates.
= 2.0.9 - 03.30.2023 =
* Updated Request library functions used to download local font files to fix a compatibility issue with WordPress 6.2.
* Added new perfmatters_preloads_ready filter.
* Fixed a styling issue in Safari where the settings UI logo was getting clipped.
= 2.0.8 - 03.29.2023 =
* Updated plugin settings UI. Completely overhauled admin header and navigation. Made additional improvements to various elements (icons, buttons, toggles, etc.).
* Added additional checks to allow PERFMATTERS_CACHE_DIR and PERFMATTERS_CACHE_URL to be manually set in wp-config.php.
* Updated user agent for local font remote request.
* Fixed an issue where multiple preload tags for the same resource could be printed if the resource was matched more than once in the DOM.
* Fixed an issue where an individually delayed script would fail to load if it matched more than one delayed script entry.
* Fixed an issue where FastClick script could still load even if Delay JS was turned off.
* Translation updates.
= 2.0.7 - 03.10.2023 =
* Fixed an issue that was introduced in the last update that was causing certain images that had their HTML modified by another tool not to lazy load correctly.
* Translation updates.
= 2.0.6 - 03.02.2023 =
* Added new Minimal v4 script type option in local analytics.
* Added support for ::after pseudo element when lazy loading CSS background images.
* Added support for AVIF images in a source tag to preload critical images feature.
* Added new perfmatters_preload_critical_images filter.
* Added new perfmatters_image_dimensions_exclusions filter.
* Added notice to plugin update row if there is not an active license key.
* Added async attribute to Instant Page script tag.
* Added async attribute to all relevant local analytics script tags.
* Reworked preload class to allow managing preloads entirely with perfmatters_preloads filter if needed.
* Fixed an issue in MU Mode where plugins would not always disable correctly when helper plugins with similar directories were also active.
* Fixed a couple of PHP warnings in MU plugin that would show up when certain variables were not declared.
* Fixed an issue where our lazy loading script was attempting to load in images that had been prepped by another active lazy loader.
* Fixed an issue where base64 encoded images were being picked up by missing image dimensions feature.
* Removed BETA tag from preload critical images option.
= 2.0.5 - 02.02.2023 =
* Added new perfmatters_exclude_leading_images filter.
* Fixed an issue that was affecting lazy loaded inline background images in certain formats.
* Fixed a PHP warning related to Fastclick and the built-in exclusion for WooCommerce pages.
* Updated license key field to prevent it from getting auto-filled by browser extensions.
= 2.0.4 - 01.27.2023 =
* Fixed an issue that was causing the Perfmatters admin bar menu and meta options to not show up in the admin.
* Added additional nopin attribute for Pinterest to YouTube preview thumbnails.
* Translation updates.
= 2.0.3 - 01.26.2023 =
* Added new local Google fonts advanced option to Load Asynchronously.
* Added user agent check before running output buffer with initial exclusion for Usercentrics scanner.
* Added support for CSS variables when they are being used for lazy loaded inline background images.
* Added new perfmatters_lazyload_youtube_autoplay filter.
* Improved delay all script handling of jQuery load event.
* Changed all WooCommerce checks to use class_exists for better compatibility.
* Adjusted the order of preloads in the buffer to make sure they print above used CSS.
* Moved buffer class initialization to wp action hook to improve filtering possibilities.
* Moved WooCommerce built-in exclusions to apply to select individual features instead of the entire buffer.
* Slight modification to previous MU Mode addition to fix an issue.
* Fixed an issue where custom heartbeat interval was not being applied correctly when editing certain custom post types.
* Fixed an issue with the local stylesheet CDN URL when advanced options were turned on but no URL was set.
* Fixed an issue where delay script was printing out more than once if multiple closing body tags were present in the DOM.
= 2.0.2 - 12.15.2022 =
* Fixed an issue that was preventing Removed Unused CSS from running correctly in certain cases when Advanced Options were toggled on.
* Translation updates.
= 2.0.1 - 12.14.2022 =
* Added new toggle to Show Advanced Options in the Perfmatters UI.
* Added new advanced option to Disable Click Delay in JavaScript section.
* Added new advanced option to Enable FastClick in JavaScript section.
* Added new advanced option to specify a CDN URL in CSS section.
* Added new Local Redirect option to existing login URL disabled behavior selection.
* Added new perfmatters_buffer_excluded_extensions filter.
* Added new perfmatters_rucss_excluded_stylesheets filter.
* Added additional built-in exclusions for Remove Unused CSS for better compatibility with Elementor, Divi, Slider Revolution, OptimizePress, and WordPress core.
* Added additional logic in MU Mode to more reliably retrieve the ID for certain custom post types.
* Moved lazyload functions to new class structure to be more inline with current codebase.
* Modified regex for lazy loading inline background images to support additional formats.
* Integrated lazyload functions into the main output buffer to allow interaction with other existing features.
* Fixed an issue where dynamic preloads were not recognizing existing query strings in some cases.
* Fixed a PHP warning that would show up in some cases by adding additional string check when looping through rewrite array.
* Fixed an issue with MU Mode where sometimes the wrong plugin would get disabled if there were multiple plugins using similar directory paths.
* Fixed an issue where images inside script tags were being picked up by the Preload Critical Images function.
* Translation updates.
= 2.0.0 - 10.18.2022 =
* Added new system for query string timestamps for Used CSS file method to help see changes quicker in environments with caching.
* Added support for ?perfmattersoff query string which gives the ability to quickly prevent the majority of Perfmatters features from running on the front end for testing purposes.
* Added additional support for updating the plugin via WP-CLI.
* Made some changes to admin bar menu item. There is now a Perfmatters top-level admin bar menu item that links to our plugin settings page. The Script Manager and Clear Used CSS function can be accessed by hovering over that main menu item if those features are enabled.
* Added new toggle in tools to Hide Admin Bar Menu.
* Disabled certain features from running on WooCommerce cart, checkout, and account pages for better compatibility.
* Increased site limit in dropdowns on Multisite network settings page.
* Added additional compatibility styles to the Script Manager.
* Added additional built-in exclusions for Remove Unused CSS for better compatibility with Elementor, Astra, Kadence, and GenerateBlocks.
* Added new perfmatters_login_url filter.
* Added new perfmatters_lazyload_noscript filter.
* Fixed an issue where YouTube preview thumbnails were generating a preload warning in certain instances.
* Fixed an issue that was causing analytics.js not to be served over HTTPS in instances where an SSL migration had been done previously on the site.
* Fixed an issue where delayed style attribute was applied to preloaded stylesheets that already existed in the DOM.
* Fixed an issue where some features were being allowed to run on XML sitemap URLs in certain cases.
* Fixed an issue where theme and plugin files were not falling back to a WordPress version query string when present in a dynamic preload.
= 1.9.9 - 09.05.2022 =
* Added additional autosave interval options.
* Added WPBakery query string parameter to excluded page builders array.
* Changed certain lazy loading classes to be more specific to prevent conflicts.
* Adjusted lazy loading image attribute filter to not run unless images specifically are meant to be lazy loaded by Perfmatters.
* Added an additional function_exists check in the JS class to prevent an error from being thrown in some cases.
= 1.9.8 - 08.31.2022 =
* Made adjustments to the CSS Background Image styles to work with some changes in the latest version of our lazy loading library.
* Fixed an issue that was preventing quotations from being stripped from background image URLs when prepping an inline background image for lazy loading.
* Fixed an issue where delayed CSS was not loading properly when using individual JS delay.
* Fixed an error that was being logged in some cases when checking for an active plugin in the JS class.
= 1.9.7 - 08.30.2022 =
* Made an adjustment to how inline background images are prepped to work with some changes in the latest version of our lazy loading library.
= 1.9.6 - 08.30.2022 =
* Added new perfmatters_delay_js_delay_click filter.
* Added new perfmatters_local_stylesheet_url filter.
* Made some performance improvements to the way the lazy loading script and inline code are loaded.
* Added additional compatibility for Elementor animations when using Delay JS.
* Added additional details in the Script Manager global view for individual stored settings.
* Added the ability to identify and clear outdated post IDs set in the Script Manager options from the global view.
* Script Manager global view organization and style improvements.
* Updated lazy loading library to version 17.8.
* Updated instant.page library to version 5.1.1.
* Added Bricks query string parameter to excluded page builders array.
* Fixed an issue that was causing the cache directory to not create unique subsite paths for specific multisite setups.
* Fixed an issue where delayed stylesheets were not being loaded if Delay JS was toggled off in the post meta options.
= 1.9.5 - 07.17.2022 =
* Added additional logic to Delay JS script to make sure the initial interaction is processed.
* Added additional styles to CSS Background Image feature to work with background images set on ::before selectors.
* Added additional logic on custom login URL admin_url filter to fix certain scenarios where login URL was not being replaced correctly.
* Added additional default tags to various dropdowns in plugin settings for better clarification.
* Added default arrays for stylesheet and selector exclusions that are always needed.
* Adjusted perfmatters_cdn filter location for compatibility.
* Made some adjustments to CDN Rewrite Regex to fix some issues where unwanted strings were getting picked up as URLs in some cases.
* Translation updates.
= 1.9.4 - 06.21.2022 =
* Updated EDD plugin updater class to version 1.9.2.
* Added default exclusion to REST API option for compatibility.
= 1.9.3 - 06.17.2022 =
* Remove Used CSS filter adjustment to fix an issue where certain WordPress post functions wouldn't be available when trying to selectively disable the feature.
* Rolled back minor plugin UI JavaScript addition, as it was interfering with entering data on multiple lines in certain input fields.
= 1.9.2 - 06.16.2022 =
* Added new perfmatters_used_css filter.
* Added new perfmatters_allow_buffer filter.
* Added a notice in the Script Manager when Testing Mode is enabled.
* Improved reliability of CSS Background Image function when child elements with additional background images are present.
* Script Manager style compatibility fixes.
* Fixed an issue where some post specific meta options were not being respected when determining if a feature should run.
* Fixed an issue where pressing enter on the main plugin settings page would trigger a specific form action instead of save settings.
* Changed CSS class initialization hook to be in the correct order with other output buffer functions.
* Made an adjustment to how we generate the local used stylesheet URL for better compatibility.
* Fixed an issue where loading attribute was still getting applied to images that were excluded from lazy loading.
* Fixed an issue where images inside an excluded picture element were not also getting excluded.
* Fixed an issue in the Script Manager where archives were not being grouped together with their respective post type.
* Additions to plugin UI JavaScript to allow for disabled sections to be hidden even when nested controllers are present.
* Moved background process library to composer autoloader.
* Removed BETA tag from Remove Unused CSS option.
= 1.9.1 - 05.23.2022 =
* Added new option to lazy load CSS Background Images.
* Added new option for Dual Tracking when using gtag.js in local analytics.
* Added new perfmatters_rest_api_exceptions filter.
* Fixed an issue where individually delayed local scripts would not get correctly rewritten to load from the CDN.
* Fixed an issue where lazy loading would run into an error if no px or % was specified with the threshold value.
* Fixed an issue with buffer validation that was conflicting with certain caching setups.
* Fixed an issue where existing font preconnect and prefetch tags were not being detected properly when using Local Fonts.
* Fixed an error related to cookie constants when running MU Mode in certain environments.
* Fixed multiple AMP validation errors and added additional checks to prevent certain functions from running on AMP URLs.
* Minor adjustment to CDN rewrite regex pattern to work with encoded quotation characters.
* Changed toggle CSS selectors to be more specific to prevent conflicts.
* Moved plugin settings header output to in_admin_header action hook for compatibility.
* Moved JS optimization functions to new class structure to be more inline with current codebase.
* Improvements to critical image preloading allowed for a move to a singular output buffer.
= 1.9.0 - 04.15.2022 =
* Fixed an issue that was causing excluded selectors to not be recognized properly after Used CSS was cleared.
* Minor adjustments to the new plugin UI.
= 1.8.9 - 04.13.2022 =
* Updated plugin settings UI.
* Added new post meta option to Clear Used CSS for an individual page or post type.
* Added new perfmatters_rucss_excluded_selectors filter.
* Fixed a lazy loading issue that was preventing some images from loading properly in Safari.
* Migrated Delay JS Timeout dropdown to a simpler on/off toggle that will default to 10 seconds. Our filter is also still available to set a custom timeout value.
* Fixed an issue with MU plugin that was interfering with rewrite rules in some instances.
* Added additional excluded page builder parameter for Flatsome UX.
* Moved restore default functionality to a separate option on the tools page.
* Code refactoring.
* Translation updates.
= 1.8.8 - 03.23.2022 =
* Changed default setting for Used CSS Method from file to inline, as we think this will be the more compatible solution for most users going forward. If you were previously using the file method, you may need to save that option again.
* Added width and height parameters to placeholder SVGs to prevent warnings for a ratio mismatch that would happen for some images.
* Fixed an issue where the noscript tags were getting malformed for some images inside picture tags after lazy loading.
* Removed placeholder SVGs on source tags since the image tag will already have one.
* Changed settings export file name date format to be easier to organize when managing multiples.
* Updated tooltip for Blank Favicon option to be more clear.
= 1.8.7 - 03.14.2022 =
* Added new Used CSS Method option to choose whether to load used CSS from a file or inline.
* Added new perfmatters_cache_path filter.
* Updated metabox functions to restrict metabox display to administrators only.
* Made some adjustments to custom login URL function to better support 3rd party tools using WP CLI.
* Added Fusion Builder query string parameters to excluded page builders array.
* Adjusted Unused CSS regex to be more consistent when stylesheets are placed in between other link tags.
* Changes to instances where ABSPATH was used to determine a directory location for better compatibility with certain hosts.
* Fixed an issue with Remove Global Styles option where duotone SVGs were not being removed on WordPress 5.9.2.
* Fixed an issue where WooCommerce block stylesheets were not getting correctly dequeued when Disable Scripts option was set.
* Fixed an issue that was causing the CSS Parser library not to get included correctly in certain cases.
* Translation updates.
= 1.8.6 - 02.10.2022 =
* Added new option to Remove Global Styles related to duotone filters.
* Added new perfmatters_script_manager_locale filter.
* Added new perfmatters_disable_woocommerce_scripts filter.
* Added new perfmatters_page_builders filter.
* Added new perfmatters_delay_js_behavior filter.
* Fixed an issue with the unused CSS parser that was incorrectly rewriting relative URLs if there was no query string present on the original stylesheet src.
* Added additional parameter to page builders array for compatibility.
* Fixed an issue that was causing the login URL disabled 404 behavior to result in an error if a 404 template was not found.
* Added some additional checks before creating cache directories for local fonts and used CSS.
* Fixed an issue that was causing the fade-in effect to conflict with child images inside a lazy loaded container.
* Fixed an undefined index warning coming from unused CSS settings update function.
* Added a default delay JS exclusion for admin only inline customize-support script.
* Refactored entire meta.php code to be more efficient (38% smaller) and in line with current structure.
* Translation updates.
= 1.8.5 - 01.19.2022 =
* Added new feature to Remove Unused CSS (BETA).
* Added new perfmatters_remove_unused_css filter.
* Adjusted CDN Rewrite buffer priority for better compatibility with other features.
* Made an improvement to the Disable XML-RPC function to return a 403 error when xmlrpc.php is accessed directly.
* Script Manager stylesheet updates for better compatibility.
* Fixed an issue in the Script Manager where the input controls were sometimes not displaying after toggling a script off.
* Added additional style for YouTube preview thumbnail play button to fix an alignment issue with certain setups.
* Buffer adjustments for compatibility.
= 1.8.4 - 12.19.2021 =
* Fixed an issue that was interfering with sitemap display in certain configurations.
* Added <a> element support for lazy loading inline background images.
= 1.8.3 - 12.13.2021 =
* Added new perfmatters_fade_in_speed filter.
* Fixed an issue that was preventing lazy loading fade in from working correctly with certain background images.
* Fixed an issue that was interfering with the display of certain inline SVG elements.
* Adjusted local analytics hook priority for better compatibility.
* Script Manager style updates for better compatibility.
* Translation updates.
= 1.8.2 - 12.08.2021 =
* New Lazy Loading option to Exclude Leading Images.
* New Lazy Loading option to add a Fade In effect.
* New option to Preload Critical Images (BETA).
* Expanded Disable XML-RPC function to also remove pingback link tag if it is present in the document.
* Added new Delay JavaScript checkbox to meta options in the post editor.
* Added additional integration with perfmatters_delay_js filter.
* Moved YouTube autoplay parameter placement on lazy loaded iframes for better compatibility with existing query strings.
* Optimizations to lazy loading inline CSS functions.
* Various optimizations and improvements to the output buffer.
* Migrated manual preload functionality to use the output buffer which will allow for easier integration with new features.
* Made some adjustments to MU plugin functions to more reliably detect post IDs when using specific permalink setups.
* Fixed an issue where some Current URL links in the Script Manager's Global View were not pointing to the right posts.
* Fixed an issue with a certain endpoint that was redirecting to the custom login URL.
* Fixed a PHP notice that was sometimes appearing when refreshing local fonts.
* Removed BETA tag from Delay All JS option.
= 1.8.1 - 10.26.2021 =
* Updated Local Google Font function to more effectively remove existing font preconnect and prefetch tags.
* Updated Local Google Font function for better compatibility with sites that still have remnants from a previous http to https migration.
* Fixed an issue in the Script Manager where the home page was being treated as a post if set to display the blog feed.
= 1.8.0 - 10.22.2021 =
* Fixed an issue with Delay All JS that was preventing certain async scripts from fully loading.
= 1.7.9 - 10.19.2021 =
* Added new options to the Script Manager to disable assets directly by post type, archive, user status, and device type.
* Added support for dynamic preloading by handle for enqueued scripts and styles.
* Added new perfmatters_lazyload filter.
* Added new perfmatters_cdn filter.
* Added new perfmatters_delay_js_timeout filter.
* Fix to Delay All JS script for better compatibility with certain page builder animations.
* Updated class initialization for better compatibility.
* Fixed an issue where the Script Manager was interpreting certain array keys as shortcodes if they were identical.
* Added an additional check to prevent the Script Manager from being able to load on top of a page builder.
* Fixed a PHP notice coming from the MU plugin.
* Made some changes to our plugin updater function that should help with auto-updates in a multisite environment.
* Translation updates.
= 1.7.8 - 09.16.2021 =
* Added new option to Add Missing Image Dimensions.
* Added the ability to delete individual Script Manager options from the Global View.
* Added new perfmatters_delay_js filter.
* Updated EDD plugin updater class to version 1.9.0.
* Translation updates.
= 1.7.7 - 08.25.2021 =
* Fixed a PHP warning related to JavaScript deferral for specific configurations.
* Fixed an issue with lazy loading exclusions not being loaded correctly in some cases.
= 1.7.6 - 08.24.2021 =
* Added new Delay Behavior dropdown with a new option to Delay All Scripts.
* Added new Lazy Loading Threshold option and adjusted the default value if not set to improve performance.
* Added confirmation message when manually running the database optimization tool.
* Updated disable emoji function to get rid of a PHP notice.
* Added additional check to MU Mode to only filter GET requests.
* Added new perfmatters_defer_js filter.
* Fixed an issue where Instant Page was attempting to run on the new widgets screen in WordPress 5.8.
* Fixed an issue with Local Google Fonts where certain invalid font URLs would still attempt to be downloaded and served.
* Removed BETA tag from fonts section.
* Delay JavaScript compatibility improvements.
* Added additional input validation functionality to plugin settings page.
* Translation updates.
= 1.7.5 - 07.13.2021 =
* Added new custom login URL options to change the Disabled Behavior and set a custom Message.
* Migrated CDN, Analytics, and Extras tab data to separate sections in the Options tab for better organization and easier access.
* CDN rewrite improvements to better handle sites with multiple domain URLs.
* Regex adjustments to Local Fonts function for better reliability.
* Added exclusion checks to individual <source> tags when using WebP images.
* Added function to disable capital_P_dangit filter.
* Fixed a lazy loading warning that was showing in Microsoft Edge.
* Removed loading attribute that was getting applied to <picture> tags in some cases when using WebP images.
* Plugin UI navigation performance improvements.
* Plugin UI style fixes.
* Added a conditional check to only show WooCommerce options when WooCommerce is installed and activated.
* Fixed an MU Mode issue where the Home URL did not trigger a match if a query string was present.
* Fixed an issue where the Customizer was getting certain optimizations applied.
* Fixed an issue where the Disable Embeds toggle was interfering with responsive video styles.
* Script Manager UI fixes.
* Updated uninstall function to remove Perfmatters cache folder.
* Added readme.txt file.
= 1.7.4 06.08.2021 =
* Re-enabled Local Google Fonts functionality.
* Refactoring of buffer-related code and various functions that were already using our main buffer filter.
* Translation updates.
= 1.7.3 06.03.2021 =
* Rolled back the latest changes related to the new universal buffer class and Local Google Fonts while we do some more in-depth testing. Well be working to release this feature next week using an alternative method.
= 1.7.2 06.02.2021 =
* Added new Fonts section inside of the main Options tab.
* Added new option to use Display Swap for Google fonts.
* Added new Local Google Fonts option which will attempt to download any Google Font files and serve them from your local server or CDN.
* Integrated new universal HTML buffer library to help going forward with plugin features that manipulate DOM elements.
* Migrated CDN Rewrite feature to the universal buffer class.
* Added new perfmatters_delayed_scripts filter to modify the Delay JavaScript input array before any scripts are delayed.
* Added new perfmatters_preload filter to modify the Preloads data array before anything is printed.
* Made some compatibility improvements to the inline lazy loading JavaScript.
* Added attributes to delayed scripts to exclude them from being picked up by Litespeed Cache.
* Added exclusion for SiteGround Optimizer to the main Script Manager JavaScript file.
* Added CodeMirror support to all code text area inputs in plugin settings.
* Removed license activation check and corresponding links from the plugins page to improve back-end performance.
= 1.7.1 05.06.2021 =
* Added expiration date row to license tab in plugin settings.
* Added support for WooCommerce shop page when setting a preload location by post ID.
* Fixed an issue with device exceptions not working correctly in MU Mode.
* Fixed a query string encoding issue that was affecting some email templates when using a custom login URL.
= 1.7.0 04.26.2021 =
* Fixed an issue where Preload tags were still being printed on archive pages even if a location was set.
* Fixed a compatibility issue with older WordPress versions when using certain functions that check for a JSON request.
* Translation updates.
= 1.6.9 04.22.2021 =
* New additions to preload feature, allowing specification for device type and location.
* Script Manager improvements to allow for Regex disable to be used alongside Current URL disables for the same script.
* Added new Script Manager exception for device type.
* Add new Delay Timeout option when delaying JavaScript.
* Added new wheel event to user interaction script for delay function.
* Added new multisite network administration tool to apply default site settings to all subsites.
* Multiple improvements to WooCommerce disable scripts toggle for increased effectiveness.
* Added additional exclusions for JSON and REST requests to all asset optimization functions.
* Fixed an undefined index warning coming from local analytics function.
* Fixed an issue where YouTube preview thumbnails were getting a layout shift warning when using a theme with responsive embed support.
* Fixed a Script Manager bug that was not fully clearing exceptions when changing disable away from everywhere.
* Script Manager styling compatibility fixes.
* Translation updates.
= 1.6.8 03.10.2021 =
* Compatibility fixes for local analytics when using MonsterInsights.
* Local analytics improvements for multisite.
* Added alt tag to YouTube preview thumbnail images.
* Fixed a PHP undefined index notice coming from functions.php.
* Translation file updates.
= 1.6.7 03.02.2021 =
* Added new tool to Purge Perfmatters Meta Options.
* Added new Exclude Post IDs input for existing Disable Google Maps option.
* Added new gtag.js option to local analytics script type selection.
* Added new CDN URL input to local analytics options when using gtag.js.
* Added new option to Enable AMP Support to local analytics.
* Moved Use MonsterInsights option to gtag.js script type and updated script replacement hook. Important: If you were previously using analytics.js with MonsterInsights, please move to the gtag.js option.
* Added onload function to style preloads to prevent duplicate preloads from occurring.
* Added exception for WP Rocket script deferral to our lazy load script.
* Added exception for site health tool to disable heartbeat function.
* Fixed an issue where background images werent being lazy loaded if the style attribute was the first attribute declared on the element.
* Script Manager styling fixes.
* Fixed a PHP warning coming from settings.php.
* Translation file updates.
= 1.6.6 01.13.2021 =
* Added new Script Manager exception to select logged in or logged out users.
* Added new option in Script Manager settings to Display Dependencies.
* Added total plugin sizes in the Script Manager.
* Added new perfmatters_lazyload_threshold filter to adjust the distance at which lazy elements are loaded.
* Multiple Script Manager style and UI improvements.
* Fixed an issue where MU mode script was attempting to run on wp-login.php.
* Multiple page builder compatibility fixes.
* Made an adjustment to prevent YouTube preview thumbnails from getting picked up by Pinterest image hover tools.
* Removed deprecated plugin option to Remove Query Strings. Make sure to double-check your preloads as Google needs the exact URL when preloading.
* PHP 8 compatibility testing.
* Minor adjustments to lazy load inline scripts to fix invalid markup warnings.
= 1.6.5 12.04.2020 =
* Added new option to Delay JavaScript from loading until user interaction.
* Added new gtag.js v4 option to local analytics.
* Added new built-in option to Exclude from Lazy Loading which can be used in addition to the existing filter.
* Add new perfmatters_lazyload_youtube_thumbnail_resolution filter to adjust YouTube preview thumbnail quality.
* Optimized analytics updater function.
* Updated EDD plugin updater class which will now allow for WordPress auto-update support.
* Removed option to Defer Inline JavaScript which is now being replaced by the new Delay JavaScript option.
* Adjusted Script Manager hook priority for better compatibility.
* Compatibility fix to the DOM Monitoring lazy load option.
* Added compatibility fix for jQuery fitVids to lazy loading function.
* Fixed an issue where lazy loading was attempting to run on AMP pages.
= 1.6.4 10.29.2020 =
* Fixed an issue that was causing the Reset Script Manager button to not work correctly.
* Fixed an issue where the Perfmatters meta box wouldnt display if only using Lazy Loading.
* Adjusted Script Manager hook priority for better compatibility.
* Added additional checks to MU Mode plugin file to prevent it from interfering with certain REST API requests. (Fixes a bug when running the Yoast SEO data indexer.)
* Added additional checks to confirm user functions are available before verifying admin status.
* Updated translation files.
= 1.6.3 10.22.2020 =
* Added new Testing Mode option to the Script Manager settings.
* Rewrote script-manager.js entirely using vanilla JavaScript to get rid of the jQuery dependency on the back-end.
* Added additional MU Mode check to help prevent certain configurations from interfering with AJAX requests.
* Improved Script Manager form handling.
* Adjusted Script Manager disclaimer text and added a close button.
* Moved the Script Manager print function from the wp_footer hook to shutdown for better compatibility.
* Fixed an undefined index warning in the Lazy Load function.
* Added a Lazy Load exclusion for Gravity Forms iframes.
* Added a Rocket Loader exclusion to the Instant Page JS file.
* Added an exclusion to the CDN Rewrite for script-manager.js.
* Script Manager styling fixes for better compatibility.
= 1.6.2 09.24.2020 =
* Updated placeholder text in Preload UI.
* Fixed an issue where the Password Strength Meter script was getting disabled in the admin.
* Small tweak to JS Deferral buffer to make sure HTML is being filtered correctly.
* Translation updates.
= 1.6.1 09.23.2020 =
* New Local Analytics Script Type toggle with new Minimal Analytics options.
* New JavaScript Deferral options in Extras → Assets.
* New meta option to exclude JavaScript deferral, Instant Page, and Lazy Load per individual post/page.
* Updates to Cart Fragments and Password Strength Meter toggles to improve effectiveness.
* Multiple updates to Instant Page functionality for better compatibility.
* Multiple plugin admin UI updates and improvements.
* Script Manager style updates for better compatibility.
* MU Mode improvements for increased stability.
* Fixed an issue causing Preload and Preconnect settings to not save correctly in some cases.
= 1.6.0 08.17.2020 =
* Added a filter to disable WordPress native lazy loading when Perfmatters lazy loading is active.
* Adjusted Script Manager styles to more effectively overlay the entire page while still allowing admin bar functions to be fully available.
* Fixed an undefined index notice that was appearing on specific lazy loading and script manager functions.
* Updated translation files.
= 1.5.9 08.12.2020 =
* Added new Preloading section in the Extras tab, with new options for Instant Page and Preload.
* Added new perfmatters_lazyload_forced_attributes filter to allow for matched elements to be skipped when checking for exclusions.
* Added support for WooCommerce Shop page to show up as a Current URL option in the Script Manager.
* Added exclusions for REST and AJAX requests to MU Mode function.
* Fixed a bug that was causing the MU Mode function to still run even if the Script Manager was disabled.
* Fixed an issue where images were being prepped for lazy loading on feed URLs.
* Fixed an issue where lazy loading was breaking images in embeds from the same site.
* Compatibility fixes for lazy load script with Autoptimize and Litespeed Cache.
= 1.5.8 07.20.2020 =
* Added support for lazy loading background images, iframes, and videos.
* Added new lazy loading option to enable YouTube Preview Thumbnails.
* Removed native lazy loading in preparation for WordPress 5.5.
* Added multiple page builder exclusions to our lazy load functions.
* Added proper support for 404 templates in the Script Manager (non-MU).
* Fixed some minor styling issues in the Script Manager UI.
* Fixed an undefined index in the database optimizer class.
* Removed customer email row from the license tab.
= 1.5.7 06.22.2020 =
* Added new Database Optimization section in the Extras tab.
* Added new DOM Monitoring option to complement our existing lazy load settings.
* Added additional input styles in the Script Manager for better compatibility
* Made some changes to the Script Manager file include process for better compatibility.
* Fixed multiple undefined index notices.
* Updated translation files.
= 1.5.6 06.02.2020 =
* Plugin UI improvements, new tooltip styles.
* Licensing workflow improvements. Simpler UI, license no longer deactivated on plugin deactivation, license auto-activates on input.
* Moved Script Manager javascript back to a separate plugin file for better compatibility.
* Added Remove Query Strings exemption to the Script Manager javascript file.
* Code refactoring.
= 1.5.5 05.27.2020 =
* Added a new modified function to the MU plugin file which should be able to get the current post ID more effectively for certain types of URLs (custom post types, blog page, etc…).
* Made some improvements to the MU plugin file detection and update process.
= 1.5.4 05.26.2020 =
* Added additional tooltip warning text to the MU Mode toggle.
* Added mu_mode=off URL parameter to force the page to load with MU Mode settings disabled.
* Added an additional check to make sure MU Mode settings dont run if the base Perfmatters plugin is not activated.
= 1.5.3 05.25.2020 =
* Added new MU Mode (BETA) feature in the Script Manager which can be used to disable plugins per page.
* Reworked main Script Manager update function to dynamically save settings via AJAX to prevent having to reload the page every time options are saved.
* Moved Script Manager javascript inline to better support further updates.
* Fixed an issue in the Script Manager where a Current URL disable would not function correctly for an individual script if the plugins scripts were disabled globally on a different Current URL.
* Changed hooks for Disable Google Maps and Disable Google Fonts toggles to prevent a conflict with the Block Editor (Gutenberg).
* Added an exclusion attribute to our LazyLoad script to prevent it from conflicting with WP Rockets JS deferral feature.
* Updated EDD Plugin Updater Class to version 1.7.1.
* Updated various translation files.
= 1.5.2 04.22.2020 =
* Added new options in Extras → Tools to Import and Export Plugin Settings.
* Updated Script Manager form input names to be more specific to prevent conflicts when saving Script Manager settings.
* Added compatibility fix for Beaver Builder to the Script Manager dequeue function.
* Updated French and German translation files.
= 1.5.1 04.02.2020 =
* Adjusted the Script Manager styles for better compatibility with other admin bar tools when the Script Manager UI is being displayed.
* Fixed an issue in the Script Manager that was causing individual script settings to not work correctly when the parent group had previously been disabled.
* Updated Russian (ru_RU) translation files.
* Updated plugin description.
= 1.5.0 03.20.2020 =
* Fixed a bug that was causing the Script Manager dequeue function to interfere with the query loop in certain cases.
= 1.4.9 03.18.2020 =
* Performance update to Script Manager form submission function which should help dramatically reduce the footprint when saving script configurations.
* Removed the Current URL option in the Script Manager when loaded on URLs without a valid post ID. (ex. dynamically generated archive templates)
* Added plugin settings page header with links to Contact and Support.
* Minor styling fixes in plugin settings UI.
* Updated Russian (ru_RU) translation files.
= 1.4.8 03.03.2020 =
* Added new Body Code box in the Extras tab to go along with our existing header + footer boxes to give some more control there.
* Added some limits to the Script Manager action links in WP Admin to ensure they are only showing up for public post types.
* Fixed a bug that was causing the admin stylesheet not to load on the network settings page when running on a multisite.
* Added Russian (ru_RU) translation files. (credit: Sergey Shljahov)
= 1.4.7 02.04.2020 =
* Added an exception for Gravity Forms to the Disable Heartbeat function.
* Added an exception for Contact Form 7 to the Disable REST API function.
* Added updated German (de_DE) translation files. (credit: Daniel Luttermann)
= 1.4.6 01.21.2020 =
* Added a specific and more generous threshold for lazy loading. This ensures butter-smooth loading of images while visitors scroll down the page. While raw performance is the objective, perceived performance (how quick a user thinks the site is) is also important.
* Added some additional dequeues to the Disable WooCommerce function to target inline CSS and JS.
= 1.4.5 12.08.2019 =
* Updated Disable Google Maps and Disable Google Fonts toggles to not run in WP Admin.
* Turned off native lazy loading by default and added new option to Use Native.
* Added perfmatters_lazyload_excluded_attributes filter which allows for an array of attribute strings to be given that if found will exclude the matched image/s from lazy loading.
* Made some compatibility improvements to the Script Manager function that gets the ID of the current post.
* Added perfmatters_get_current_ID filter which allows the user to extend or modify the functionality of the Script Managers current ID function.
= 1.4.4 10.20.2019 =
* Fixed undefined index PHP Notice coming from the Preconnect settings display function.
* Added additional compatibility with Elementor when using the Script Manager to disable certain Elementor scripts + styles.
* Added a ignore flag class to all Lazy Load functions. Simply add the no-lazy class to any image element you want to be exempt from lazy loading.
* Added validation filter to Login URL input to prevent incompatible characters from being entered.
= 1.4.3 10.02.2019 =
* Fixed an issue with the Lazy Load function that was causing an error with some older PHP versions.
= 1.4.2 09.30.2019 =
* Added new option for Lazy Loading images (BETA).
= 1.4.1 08.18.2019 =
* New addition to the Preconnect option, you can now choose to whether or not to add the crossorigin property for each Preconnect URL.
* Optimization to the loading of Perfmatters admin scripts + styles.
* Added additional Script Manager styles for better compatibility.
* Added an additional function for the Custom Login URL to help rewrite certain wp-admin links in specific multisite setups.
* Reorganized plugin action links in the plugins table.
= 1.4.0 07.16.2019 =
* Fixed an issue where the Current URL Exceptions were not loading correctly after saving in the Script Manager.
= 1.3.9 07.14.2019 =
* Added new Extra options to Add Header Code and Add Footer Code.
* Added missing blank defaults for DNS Prefetch and Preconnect options.
* Added functionality to force the Admin Bar to display when the Script Manager is loaded.
* Script Manager styling adjustments.
* Added success message on save when the Script Manager options are updated.
* Added support for 404 pages when trying to disable or enable on the Current URL.
= 1.3.8 06.17.2019 =
* Added new option to Disable Comments.
* Updated a section of the Script Manager to better reflect the Current URL when determining if it is a match for the given regex pattern.
= 1.3.7 05.29.2019 =
* Added links to the Script Manager from the posts list page and post edit page which will take you to the front end and load the Script Manager for the corresponding post.
* Added warning notices for both WP_POST_REVISIONS and AUTOSAVE_INTERVAL if they are set in Perfmatters while also defined elsewhere.