first
This commit is contained in:
353
wp-content/plugins/debloat/inc/admin/admin.php
Normal file
353
wp-content/plugins/debloat/inc/admin/admin.php
Normal file
@ -0,0 +1,353 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat;
|
||||
|
||||
use Sphere\Debloat\Admin\Cache;
|
||||
use Sphere\Debloat\Admin\OptionsData;
|
||||
|
||||
/**
|
||||
* Admin initialization.
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Admin
|
||||
{
|
||||
/**
|
||||
* @var Sphere\Debloat\Admin\Cache
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* Setup hooks
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->cache = new Cache;
|
||||
$this->cache->init();
|
||||
|
||||
add_action('cmb2_admin_init', [$this, 'setup_options']);
|
||||
|
||||
// Enqueue at a lower priority to be after CMB2.
|
||||
add_action('admin_enqueue_scripts', [$this, 'register_assets'], 99);
|
||||
|
||||
// Page to delete cache.
|
||||
add_action('admin_menu', function() {
|
||||
add_submenu_page(
|
||||
'',
|
||||
'Delete Cache',
|
||||
'Delete Cache',
|
||||
'manage_options',
|
||||
'debloat-delete-cache',
|
||||
[$this, 'delete_cache']
|
||||
);
|
||||
});
|
||||
|
||||
// Empty cache on save.
|
||||
add_action('cmb2_save_options-page_fields', [$this, '_delete_cache']);
|
||||
|
||||
/**
|
||||
* Fix: CMB2 doesn't save unchecked making default => true impossible.
|
||||
*/
|
||||
add_filter('cmb2_sanitize_checkbox', function($override, $value) {
|
||||
return is_null($value) ? '0' : $value;
|
||||
}, 20, 2);
|
||||
|
||||
// Custom CMB2 field for manual callback
|
||||
add_action('cmb2_render_manual', function($field) {
|
||||
|
||||
// Add attributes to an empty span for cmb2-conditional
|
||||
if (!empty($field->args['attributes'])) {
|
||||
printf('<meta name="%s" %s />',
|
||||
$field->args('id'),
|
||||
\CMB2_Utils::concat_attrs($field->args('attributes'))
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($field->args['render_html']) && is_callable($field->args['render_html'])) {
|
||||
call_user_func($field->args['render_html'], $field);
|
||||
}
|
||||
|
||||
if (!empty($field->args['desc'])) {
|
||||
echo '<p class="cmb2-metabox-description">' . esc_html($field->args['desc']) . '</p>';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register admin assets
|
||||
*/
|
||||
public function register_assets()
|
||||
{
|
||||
// Specific assets for option pages only
|
||||
if (!empty($_GET['page']) && strpos($_GET['page'], 'debloat_options') !== false) {
|
||||
|
||||
wp_enqueue_script(
|
||||
'debloat-cmb2-conditionals',
|
||||
Plugin::get_instance()->dir_url . 'js/admin/cmb2-conditionals.js',
|
||||
['jquery'],
|
||||
Plugin::VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'debloat-options',
|
||||
Plugin::get_instance()->dir_url . 'js/admin/options.js',
|
||||
['jquery', 'debloat-cmb2-conditionals'],
|
||||
Plugin::VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_style(
|
||||
'debloat-admin-cmb2',
|
||||
Plugin::get_instance()->dir_url . 'css/admin/cmb2.css',
|
||||
['cmb2-styles'],
|
||||
Plugin::VERSION
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Cache page.
|
||||
*/
|
||||
public function delete_cache()
|
||||
{
|
||||
check_admin_referer('debloat_delete_cache');
|
||||
$this->_delete_cache();
|
||||
|
||||
echo '
|
||||
<h2>Clearing Cache</h2>
|
||||
<p>Caches cleared. You may also have to clear your cache plugins.</p>
|
||||
<a href="' . esc_url(admin_url('admin.php?page=debloat_options')) . '">Back to Options</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback: Delete the cache.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
public function _delete_cache()
|
||||
{
|
||||
$this->cache->empty();
|
||||
|
||||
/**
|
||||
* Hook after deleting cache.
|
||||
*/
|
||||
do_action('debloat/after_delete_cache');
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup admin options with CMB2
|
||||
*/
|
||||
public function setup_options()
|
||||
{
|
||||
// Configure admin options
|
||||
$options = new_cmb2_box([
|
||||
'id' => 'debloat_options',
|
||||
'title' => esc_html__('Debloat Plugin Settings', 'debloat'),
|
||||
'object_types' => ['options-page'],
|
||||
'option_key' => 'debloat_options',
|
||||
'parent_slug' => 'options-general.php',
|
||||
'menu_title' => esc_html__('Debloat: Optimize', 'debloat'),
|
||||
'tab_group' => 'debloat_options',
|
||||
'tab_title' => esc_html__('Optimize CSS', 'debloat'),
|
||||
'classes' => 'sphere-cmb2-wrap',
|
||||
'display_cb' => [$this, 'render_options_page'],
|
||||
]);
|
||||
|
||||
$this->add_options(
|
||||
OptionsData::get_css(),
|
||||
$options
|
||||
);
|
||||
|
||||
// Configure admin options
|
||||
$js_options = new_cmb2_box([
|
||||
'id' => 'debloat_options_js',
|
||||
'title' => esc_html__('Optimize JS', 'debloat'),
|
||||
'object_types' => ['options-page'],
|
||||
'option_key' => 'debloat_options_js',
|
||||
'parent_slug' => 'debloat_options',
|
||||
'menu_title' => esc_html__('Optimize JS', 'debloat'),
|
||||
'tab_group' => 'debloat_options',
|
||||
'tab_title' => esc_html__('Optimize JS', 'debloat'),
|
||||
'classes' => 'sphere-cmb2-wrap',
|
||||
'display_cb' => [$this, 'render_options_page'],
|
||||
]);
|
||||
|
||||
$this->add_options(
|
||||
OptionsData::get_js(),
|
||||
$js_options
|
||||
);
|
||||
|
||||
// Configure admin options
|
||||
$general_options = new_cmb2_box([
|
||||
'id' => 'debloat_options_general',
|
||||
'title' => esc_html__('General Settings', 'debloat'),
|
||||
'object_types' => ['options-page'],
|
||||
'option_key' => 'debloat_options_general',
|
||||
'parent_slug' => 'debloat_options',
|
||||
'menu_title' => esc_html__('General Settings', 'debloat'),
|
||||
'tab_group' => 'debloat_options',
|
||||
'tab_title' => esc_html__('General Settings', 'debloat'),
|
||||
'display_cb' => [$this, 'render_options_page'],
|
||||
'classes' => 'sphere-cmb2-wrap'
|
||||
]);
|
||||
|
||||
$this->add_options(
|
||||
OptionsData::get_general(),
|
||||
$general_options
|
||||
);
|
||||
|
||||
do_action('debloat/admin/after_options', $options);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add options to CMB2 array.
|
||||
*
|
||||
* @param array $options
|
||||
* @param \CMB2 $object
|
||||
* @return void
|
||||
*/
|
||||
protected function add_options($options, $object)
|
||||
{
|
||||
return array_map(
|
||||
function($option) use ($object) {
|
||||
if (isset($option['attributes']['data-conditional-id'])) {
|
||||
$condition = &$option['attributes']['data-conditional-id'];
|
||||
if (is_array($condition)) {
|
||||
$condition = json_encode($condition);
|
||||
}
|
||||
}
|
||||
|
||||
$field_id = $object->add_field($option);
|
||||
|
||||
if ($option['type'] === 'group') {
|
||||
$this->add_option_group($option, $field_id, $object);
|
||||
}
|
||||
|
||||
},
|
||||
$options
|
||||
);
|
||||
}
|
||||
|
||||
protected function add_option_group($option, $group_id, $object)
|
||||
{
|
||||
if ($option['id'] === 'allow_conditionals_data') {
|
||||
$object->add_group_field($group_id, [
|
||||
'id' => 'type',
|
||||
'name' => esc_html__('Condition Type', 'debloat'),
|
||||
'type' => 'radio',
|
||||
'default' => 'class',
|
||||
'options' => [
|
||||
'class' => esc_html__('Class - If a class (in "condition match") exists in HTML, keep classes matching "selector match".', 'debloat'),
|
||||
'prefix' => esc_html__('Prefix - Condition matches the first class and keeps all the used child classes. Example: .s-dark will keep .s-dark .site-header.', 'debloat'),
|
||||
],
|
||||
]);
|
||||
|
||||
$object->add_group_field($group_id, [
|
||||
'id' => 'match',
|
||||
'name' => esc_html__('Condition Match', 'debloat'),
|
||||
'desc' => esc_html__('Required. Usually a single class, example:', 'debloat') . '<code>.my-class</code>',
|
||||
'type' => 'text',
|
||||
'default' => '',
|
||||
]);
|
||||
|
||||
$object->add_group_field($group_id, [
|
||||
'id' => 'search',
|
||||
'name' => esc_html__('Selectors Match', 'debloat'),
|
||||
'desc' => esc_html__('Enter one per line. See example matchings in "Always Keep Selectors" above.', 'debloat'),
|
||||
'type' => 'textarea_small',
|
||||
'default' => '',
|
||||
'attributes' => [
|
||||
'data-conditional-id' => json_encode([$group_id, 'type']),
|
||||
'data-conditional-value' => 'class'
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function render_options_page($hookup)
|
||||
{
|
||||
?>
|
||||
<div class="cmb2-options-page debloat-options option-<?php echo esc_attr( sanitize_html_class( $hookup->option_key ) ); ?>">
|
||||
<div class="wrap">
|
||||
<?php if ( $hookup->cmb->prop( 'title' ) ) : ?>
|
||||
<h2><?php echo wp_kses_post( $hookup->cmb->prop( 'title' ) ); ?></h2>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="wrap"><?php $hookup->options_page_tab_nav_output(); ?></div>
|
||||
|
||||
<div class="debloat-inner-wrap">
|
||||
<form class="cmb-form debloat-options-form" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" method="POST" id="<?php echo esc_attr($hookup->cmb->cmb_id); ?>" enctype="multipart/form-data" encoding="multipart/form-data">
|
||||
<input type="hidden" name="action" value="<?php echo esc_attr( $hookup->option_key ); ?>">
|
||||
|
||||
<div class="sphere-cmb2-wrap debloat-intro-info">
|
||||
<div class="cmb2-wrap cmb2-metabox">
|
||||
<div class="cmb-row">
|
||||
<h3>Important: Debloat Plugin</h3>
|
||||
<p>
|
||||
This plugin is for advanced users. The features "Remove Unused CSS" and "Delay JS" are especially for advanced users only.
|
||||
</p>
|
||||
<ol>
|
||||
<li>Use a cache plugin like W3 Total Cache, WP Super Cache, etc. <strong>Required</strong> for Remove Unused CSS feature.</li>
|
||||
<li>Do <strong>NOT</strong> enable minification, CSS, or JS optimization via another plugin.</li>
|
||||
<li>If your theme doesn't have it built-in, use a Lazyload plugin for images.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php $hookup->options_page_metabox(); ?>
|
||||
<?php submit_button( esc_attr( $hookup->cmb->prop( 'save_button' ) ), 'primary', 'submit-cmb' ); ?>
|
||||
</form>
|
||||
<div class="debloat-sidebar">
|
||||
<?php $this->cache_info(); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function cache_info()
|
||||
{
|
||||
$js_cache = Plugin::file_cache()->get_stats('js');
|
||||
$css_cache = Plugin::file_cache()->get_stats('css');
|
||||
|
||||
// Number of css sheets in cache.
|
||||
$css_sheets = count($this->cache->get_transients());
|
||||
?>
|
||||
|
||||
<div class="sphere-cmb2-wrap debloat-cache-info">
|
||||
<div class="cmb2-wrap cmb2-metabox">
|
||||
<div class="cmb-row cmb-type-title">
|
||||
<div class="cmb-td">
|
||||
<h3 class="cmb2-metabox-title">
|
||||
<?php esc_html_e('Cache Stats', 'debloat'); ?>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cmb-row">
|
||||
<?php if (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG): ?>
|
||||
<p><strong>Minification Disabled</strong>: SCRIPT_DEBUG is enabled (likely in wp-config.php).</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="cache-stats">
|
||||
<p><?php printf(esc_html__('Minified CSS files: %d', 'debloat'), $css_cache); ?></p>
|
||||
<p><?php printf(esc_html__('Minified JS files: %d', 'debloat'), $js_cache); ?></p>
|
||||
<p><?php printf(esc_html__('Processed CSS Sheets: %d', 'debloat'), $css_sheets); ?></p>
|
||||
</div>
|
||||
|
||||
<a href="<?php echo wp_nonce_url(admin_url('admin.php?page=debloat-delete-cache'), 'debloat_delete_cache'); ?>"
|
||||
class="button button-secondary" style="margin-top: 10px;">
|
||||
<?php echo esc_html('Empty All Cache', 'debloat'); ?>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
}
|
||||
}
|
183
wp-content/plugins/debloat/inc/admin/cache.php
Normal file
183
wp-content/plugins/debloat/inc/admin/cache.php
Normal file
@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat\Admin;
|
||||
use Sphere\Debloat\Plugin;
|
||||
|
||||
/**
|
||||
* Cache clear, stats and similar for admin area.
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Cache
|
||||
{
|
||||
protected $deleting = false;
|
||||
|
||||
public function init()
|
||||
{
|
||||
$this->register_clear_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register hooks to clear cache.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_clear_hooks()
|
||||
{
|
||||
/**
|
||||
* Use this hook to clear caches externally.
|
||||
*/
|
||||
add_action('debloat/empty_caches', [$this, 'empty']);
|
||||
|
||||
/**
|
||||
* Other plugin hooks to empty cache on.
|
||||
*/
|
||||
$hooks = [
|
||||
// WP Rocket.
|
||||
'after_rocket_clean_domain',
|
||||
|
||||
// W3 Total Cache.
|
||||
'w3tc_flush_all',
|
||||
|
||||
// SGF plugin font cache delete.
|
||||
'sgf/after_delete_cache',
|
||||
];
|
||||
|
||||
foreach ($hooks as $hook) {
|
||||
add_action($hook, [$this, 'empty']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the debloat cache transients.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_transients()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
return (array) $wpdb->get_results(
|
||||
"SELECT `option_name` FROM {$wpdb->options} WHERE `option_name` LIKE '_transient_debloat_sheet_cache_%'",
|
||||
ARRAY_A
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the debloat cache transients.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function delete_transients()
|
||||
{
|
||||
foreach ($this->get_transients() as $transient) {
|
||||
$transient = str_replace('_transient_', '', $transient['option_name']);
|
||||
\delete_transient($transient);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all types of caches.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function empty()
|
||||
{
|
||||
// Already clearing the caches. Needed as we hook into plugins clear methods,
|
||||
// but also call them ourselves on cache clear.
|
||||
if ($this->deleting) {
|
||||
return;
|
||||
}
|
||||
|
||||
// One of the hooks set via register_clear_hooks() may fire.
|
||||
$this->deleting = true;
|
||||
|
||||
$this->delete_transients();
|
||||
|
||||
// Delete files cache.
|
||||
Plugin::file_cache()->delete_cache('js');
|
||||
Plugin::file_cache()->delete_cache('css');
|
||||
|
||||
// Clear cache plugins.
|
||||
$this->empty_cache_plugins();
|
||||
|
||||
$this->deleting = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty external caches from plugins and hosts.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function empty_cache_plugins()
|
||||
{
|
||||
// WP Super Cache.
|
||||
if (function_exists('wp_cache_clear_cache')) {
|
||||
wp_cache_clear_cache(
|
||||
is_multisite() ? get_current_blog_id() : 0
|
||||
);
|
||||
}
|
||||
|
||||
// W3 Total Cache.
|
||||
if (function_exists('w3tc_pgcache_flush')) {
|
||||
w3tc_pgcache_flush();
|
||||
}
|
||||
|
||||
// WP Rocket.
|
||||
if (function_exists('rocket_clean_domain')) {
|
||||
rocket_clean_domain();
|
||||
}
|
||||
|
||||
// WP Fastest Cache.
|
||||
if (function_exists('wpfc_clear_all_cache')) {
|
||||
wpfc_clear_all_cache();
|
||||
}
|
||||
|
||||
// Swift Performance plugin.
|
||||
if (class_exists('\Swift_Performance_Cache') && is_callable(['\Swift_Performance_Cache', 'clear_all_cache'])) {
|
||||
\Swift_Performance_Cache::clear_all_cache();
|
||||
}
|
||||
|
||||
// LiteSpeed Cache.
|
||||
if (class_exists('\LiteSpeed_Cache_API') && is_callable(['\LiteSpeed_Cache_API', 'purge_all'])) {
|
||||
\LiteSpeed_Cache_API::purge_all();
|
||||
}
|
||||
|
||||
// Cache Enabler.
|
||||
if (class_exists('\Cache_Enabler') && is_callable(['\Cache_Enabler', 'clear_total_cache'])) {
|
||||
\Cache_Enabler::clear_total_cache();
|
||||
}
|
||||
|
||||
// Comet cache.
|
||||
if (class_exists('\comet_cache') && is_callable(['\comet_cache', 'clear'])) {
|
||||
\comet_cache::clear();
|
||||
}
|
||||
|
||||
// RT Nginx Helper plugin.
|
||||
if (defined('NGINX_HELPER_BASENAME')) {
|
||||
do_action('rt_nginx_helper_purge_all');
|
||||
}
|
||||
|
||||
// Hummingbird
|
||||
if (class_exists('\Hummingbird\WP_Hummingbird') && is_callable(['\Hummingbird\WP_Hummingbird', 'flush_cache'])) {
|
||||
\Hummingbird\WP_Hummingbird::flush_cache();
|
||||
}
|
||||
|
||||
// Pagely.
|
||||
if (class_exists('\PagelyCachePurge') && is_callable(['\PagelyCachePurge', 'purgeAll'])) {
|
||||
\PagelyCachePurge::purgeAll();
|
||||
}
|
||||
|
||||
// WPEngine
|
||||
if (class_exists('\WpeCommon')) {
|
||||
is_callable(['\WpeCommon', 'purge_memcached']) && \WpeCommon::purge_memcached();
|
||||
is_callable(['\WpeCommon', 'purge_varnish_cache']) && \WpeCommon::purge_varnish_cache();
|
||||
}
|
||||
|
||||
// SiteGround.
|
||||
if (function_exists('sg_cachepress_purge_cache')) {
|
||||
sg_cachepress_purge_cache();
|
||||
}
|
||||
}
|
||||
}
|
446
wp-content/plugins/debloat/inc/admin/options-data.php
Normal file
446
wp-content/plugins/debloat/inc/admin/options-data.php
Normal file
@ -0,0 +1,446 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat\Admin;
|
||||
|
||||
/**
|
||||
* Options data.
|
||||
*/
|
||||
class OptionsData
|
||||
{
|
||||
/**
|
||||
* Common shared data and options.
|
||||
*
|
||||
* @param string $key
|
||||
* @return array
|
||||
*/
|
||||
public static function get_common($key = '')
|
||||
{
|
||||
$_common = [];
|
||||
$_common['enable_on'] = [
|
||||
'all' => esc_html__('All Pages', 'debloat'),
|
||||
'single' => esc_html__('Single Post/Article', 'debloat'),
|
||||
'pages' => esc_html__('Pages', 'delobat'),
|
||||
'home' => esc_html__('Homepage', 'delobat'),
|
||||
'archives' => esc_html__('Archives', 'delobat'),
|
||||
'categories' => esc_html__('Categories', 'delobat'),
|
||||
'search' => esc_html__('Search', 'delobat'),
|
||||
];
|
||||
|
||||
return $key ? $_common[$key] : $_common;
|
||||
}
|
||||
|
||||
public static function get_css()
|
||||
{
|
||||
$options = [];
|
||||
$options[] = [
|
||||
'name' => esc_html__('Optimize CSS', 'debloat'),
|
||||
// 'description' => 'foo',
|
||||
'type' => 'title',
|
||||
'id' => '_optimize_css',
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'optimize_css',
|
||||
'name' => esc_html__('Fix Render-Blocking CSS', 'debloat'),
|
||||
'desc' => esc_html__('Enable CSS Optimizations to fix Render-blocking CSS.', 'debloat'),
|
||||
'type' => 'checkbox',
|
||||
'default' => 0,
|
||||
];
|
||||
$options[] = [
|
||||
'id' => 'optimize_css_to_inline',
|
||||
'name' => esc_html__('Inline Optimized CSS', 'debloat'),
|
||||
'desc' => esc_html__('Inline the CSS to prevent flash of unstyled content. Highly recommended.', 'debloat'),
|
||||
'type' => 'checkbox',
|
||||
'default' => 1,
|
||||
'attributes' => ['data-conditional-id' => 'optimize_css'],
|
||||
];
|
||||
$options[] = [
|
||||
'id' => 'optimize_gfonts_inline',
|
||||
'name' => esc_html__('Inline Google Fonts CSS', 'debloat'),
|
||||
'desc' => esc_html__('Inline the Google Fonts CSS for a big boost on FCP and slight on LCP on mobile. Highly recommended.', 'debloat'),
|
||||
'type' => 'checkbox',
|
||||
'default' => 1,
|
||||
'attributes' => ['data-conditional-id' => 'optimize_css'],
|
||||
];
|
||||
$options[] = [
|
||||
'id' => 'optimize_css_minify',
|
||||
'name' => esc_html__('Minify CSS', 'debloat'),
|
||||
'desc' => esc_html__('Minify CSS to reduced the CSS size.', 'debloat'),
|
||||
'type' => 'checkbox',
|
||||
'default' => 1,
|
||||
'attributes' => ['data-conditional-id' => 'optimize_css'],
|
||||
];
|
||||
$options[] = [
|
||||
'id' => 'optimize_css_excludes',
|
||||
'name' => esc_html__('Exclude Styles', 'debloat'),
|
||||
'desc' =>
|
||||
esc_html__('Enter one per line to exclude certain CSS files from this optimizations. Examples:', 'debloat')
|
||||
. ' <code>id:my-css-id</code>
|
||||
<br /><code>wp-content/themes/my-theme/style.css</code>
|
||||
<br /><code>wp-content/themes/my-theme*</code>
|
||||
',
|
||||
'type' => 'textarea_small',
|
||||
'default' => '',
|
||||
'attributes' => ['data-conditional-id' => 'optimize_css'],
|
||||
];
|
||||
$options[] = [
|
||||
'id' => 'integrations_css',
|
||||
'name' => esc_html__('Enable Plugin Integrations', 'debloat'),
|
||||
'desc' => esc_html__('Special pre-made rules for CSS, specific to plugins, are applied if enabled.', 'debloat'),
|
||||
'type' => 'multicheck_inline',
|
||||
'options' => [
|
||||
'elementor' => 'Elementor',
|
||||
'wpbakery' => 'WPBakery Page Builder',
|
||||
],
|
||||
'default' => ['elementor', 'wpbakery'],
|
||||
'select_all_button' => false,
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'optimize_gfonts',
|
||||
'name' => esc_html__('Optimize Google Fonts', 'debloat'),
|
||||
'desc' => esc_html__('Add preconnect hints and add display swap for Google Fonts.', 'debloat'),
|
||||
'type' => 'checkbox',
|
||||
'default' => 1,
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'name' => esc_html__('Optimize CSS: Remove Unused', 'debloat'),
|
||||
// 'description' => 'foo',
|
||||
'type' => 'title',
|
||||
'id' => '_remove_unused',
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'remove_css',
|
||||
'name' => esc_html__('Remove Unused CSS', 'debloat'),
|
||||
'desc' => esc_html__('This is an expensive process. DO NOT use without a cache plugin.', 'debloat'),
|
||||
'type' => 'checkbox',
|
||||
'default' => 0,
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'remove_css_all',
|
||||
'name' => esc_html__('Remove from All Stylesheets', 'debloat'),
|
||||
'desc' => esc_html__('WARNING: Only use if you are sure your plugins and themes dont add classes using JS. May also be enabled when delay loading all the original CSS.', 'debloat'),
|
||||
'type' => 'checkbox',
|
||||
'default' => 0,
|
||||
'attributes' => ['data-conditional-id' => 'remove_css'],
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'remove_css_plugins',
|
||||
'name' => esc_html__('Enable for Plugins CSS', 'debloat'),
|
||||
'desc' => esc_html__('Removed unused CSS on all plugins CSS files.', 'debloat'),
|
||||
'type' => 'checkbox',
|
||||
'default' => 0,
|
||||
'attributes' => [
|
||||
'data-conditional-id' => [
|
||||
['key' => 'remove_css'],
|
||||
['key' => 'remove_css_all', 'value' => 'off'],
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'remove_css_theme',
|
||||
'name' => esc_html__('Enable for Theme CSS', 'debloat'),
|
||||
'desc' => esc_html__('Removed unused CSS from all theme CSS files.', 'debloat'),
|
||||
'type' => 'checkbox',
|
||||
'default' => 0,
|
||||
'attributes' => [
|
||||
'data-conditional-id' => [
|
||||
['key' => 'remove_css'],
|
||||
['key' => 'remove_css_all', 'value' => 'off'],
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'remove_css_includes',
|
||||
'name' => esc_html__('Target Stylesheets', 'debloat'),
|
||||
'desc' =>
|
||||
esc_html__('Will remove unused CSS from these targets. You may use an ID or the part of the URL. Examples:', 'debloat')
|
||||
. ' <code>id:my-css-id</code>
|
||||
<br /><code>wp-content/themes/my-theme/style.css</code>
|
||||
<br /><code>wp-content/themes/my-theme*</code>: All theme stylesheets.
|
||||
<br /><code>plugins/plugin-slug/*</code>: All stylesheets for plugin-slug.
|
||||
',
|
||||
'type' => 'textarea_small',
|
||||
'default' => 'id:wp-block-library',
|
||||
'attributes' => [
|
||||
'data-conditional-id' => [
|
||||
['key' => 'remove_css'],
|
||||
['key' => 'remove_css_all', 'value' => 'off'],
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'remove_css_excludes',
|
||||
'name' => esc_html__('Exclude Stylesheets', 'debloat'),
|
||||
'desc' =>
|
||||
esc_html__('Enter one per line to exclude certain CSS files from this optimizations. Examples:', 'debloat')
|
||||
. ' <code>id:my-css-id</code>
|
||||
<br /><code>wp-content/themes/my-theme/style.css</code>
|
||||
<br /><code>wp-content/themes/my-theme*</code>
|
||||
',
|
||||
'type' => 'textarea_small',
|
||||
'default' => '',
|
||||
'attributes' => ['data-conditional-id' => 'remove_css'],
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'allow_css_selectors',
|
||||
'name' => esc_html__('Always Keep Selectors', 'debloat'),
|
||||
'desc' =>
|
||||
esc_html__('Enter one per line. Partial or full matches for selectors (if any of these keywords found, the selector will be kept). Examples:', 'debloat')
|
||||
. ' <code>.myclass</code>
|
||||
<br /><code>.myclass*</code>: Will match selectors starting with .myclass, .myclass-xyz, .myclass_xyz etc.
|
||||
<br /><code>.myclass *</code>: Selectors starting with .myclass, .myclass .sub-class and so on.
|
||||
<br /><code>*.myclass *</code>: For matching .xyz .myclass, .myclass, .xyz .myclass .xyz and so on.
|
||||
',
|
||||
'type' => 'textarea_small',
|
||||
'default' => '',
|
||||
'attributes' => ['data-conditional-id' => 'remove_css'],
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'allow_css_conditionals',
|
||||
'name' => esc_html__('Advanced: Conditionally Keep Selectors', 'debloat'),
|
||||
'desc' => 'Add advanced conditions.',
|
||||
'type' => 'checkbox',
|
||||
'default' => 0,
|
||||
'attributes' => ['data-conditional-id' => 'remove_css'],
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'allow_conditionals_data',
|
||||
'name' => '',
|
||||
'desc' => 'Keep selector if a certain condition is true. For example, condition type class with match <code>.mfp-lightbox</code> can be used to search for <code>.mfp-</code> to keep all the CSS selectors that have .mfp- in selector.',
|
||||
'type' => 'group',
|
||||
'default' => [],
|
||||
'attributes' => ['data-conditional-id' => 'remove_css'],
|
||||
'options' => [
|
||||
'group_title' => 'Condition {#}',
|
||||
'add_button' => esc_html__('Add Condition', 'debloat'),
|
||||
'remove_button' => esc_html__('Remove', 'debloat'),
|
||||
'closed' => true,
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
$options[] = [
|
||||
'id' => 'remove_css_on',
|
||||
'name' => esc_html__('Remove CSS On', 'debloat'),
|
||||
'desc' => esc_html__('Pages where unused CSS should be removed.', 'debloat'),
|
||||
'type' => 'multicheck',
|
||||
'options' => self::get_common('enable_on'),
|
||||
'default' => ['all'],
|
||||
'select_all_button' => false,
|
||||
'attributes' => ['data-conditional-id' => 'remove_css'],
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'delay_css_load',
|
||||
'name' => esc_html__('Delay load Original CSS', 'debloat'),
|
||||
'desc' => esc_html__('Delay-loading all of the original CSS might be needed in situations where there are too many JS-based CSS classes that are added later such as sliders, that you cannot track down and add to exclusions right now. Or on pages that may have Auto-load Next Post.', 'debloat'),
|
||||
'type' => 'checkbox',
|
||||
'default' => 0,
|
||||
'attributes' => ['data-conditional-id' => 'remove_css'],
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'delay_css_on',
|
||||
'name' => esc_html__('Delay load Original On', 'debloat'),
|
||||
'desc' => esc_html__('Pages where original CSS should be delayed load.', 'debloat'),
|
||||
'type' => 'multicheck',
|
||||
'options' => self::get_common('enable_on'),
|
||||
'default' => ['all'],
|
||||
'select_all_button' => false,
|
||||
'attributes' => ['data-conditional-id' => 'delay_css_load'],
|
||||
];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public static function get_js()
|
||||
{
|
||||
$options = [];
|
||||
|
||||
/**
|
||||
* Optimize JS
|
||||
*/
|
||||
$options[] = [
|
||||
'name' => esc_html__('Optimize JS', 'debloat'),
|
||||
// 'description' => 'foo',
|
||||
'type' => 'title',
|
||||
'id' => '_defer_js',
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'defer_js',
|
||||
'name' => esc_html__('Defer Javascript', 'debloat'),
|
||||
'desc' => esc_html__('Delay JS execution till HTML is loaded to fix Render-Blocking JS issues.', 'debloat'),
|
||||
'type' => 'checkbox',
|
||||
'default' => 0,
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'defer_js_excludes',
|
||||
'name' => esc_html__('Exclude Scripts', 'debloat'),
|
||||
'desc' => esc_html__('Enter one per line to exclude certain JS files from being deferred.', 'debloat'),
|
||||
'type' => 'textarea_small',
|
||||
'default' => '',
|
||||
'attributes' => ['data-conditional-id' => 'defer_js'],
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'defer_js_inline',
|
||||
'name' => esc_html__('Defer Inline JS', 'debloat'),
|
||||
'desc' => sprintf(
|
||||
'%s<p><strong>%s</strong> %s</p>',
|
||||
esc_html__('Defer all inline JS.', 'debloat'),
|
||||
esc_html__('Note:', 'debloat'),
|
||||
esc_html__('Normally not needed. All correct dependent inline scripts are deferred by default. Enable if inline JS not enqueued using WordPress enqueue functions.', 'debloat')
|
||||
),
|
||||
'type' => 'checkbox',
|
||||
'default' => 0,
|
||||
'attributes' => ['data-conditional-id' => 'defer_js'],
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'minify_js',
|
||||
'name' => esc_html__('Minify Javascript', 'debloat'),
|
||||
'desc' => esc_html__('Minify all the deferred or delayed JS files.', 'debloat'),
|
||||
'type' => 'checkbox',
|
||||
'default' => 0,
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'integrations_js',
|
||||
'name' => esc_html__('Enable Plugin Integrations', 'debloat'),
|
||||
'desc' => esc_html__('Special pre-made rules for javascript, specific to plugins, are applied if enabled.', 'debloat'),
|
||||
'type' => 'multicheck_inline',
|
||||
'options' => [
|
||||
'elementor' => 'Elementor',
|
||||
'wpbakery' => 'WPBakery Page Builder',
|
||||
],
|
||||
'default' => ['elementor', 'wpbakery'],
|
||||
'select_all_button' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* Delay JS
|
||||
*/
|
||||
$options[] = [
|
||||
'name' => esc_html__('Delay Load JS', 'debloat'),
|
||||
// 'description' => 'foo',
|
||||
'type' => 'title',
|
||||
'id' => '_delay_js',
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'delay_js',
|
||||
'name' => esc_html__('Delay Javascript', 'debloat'),
|
||||
'desc' => esc_html__('Delay execution of the targeted JS files until user interaction.', 'debloat'),
|
||||
'type' => 'checkbox',
|
||||
'default' => 0,
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'delay_js_max',
|
||||
'name' => esc_html__('Maximum Delay (in seconds)', 'debloat'),
|
||||
'desc' => esc_html__('Max seconds to wait for interaction until delayed JS is loaded anyways.', 'debloat'),
|
||||
'type' => 'text_small',
|
||||
'default' => '',
|
||||
'attributes' => [
|
||||
'type' => 'number',
|
||||
'min' => 0,
|
||||
'data-conditional-id' => 'delay_js'
|
||||
],
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'delay_js_all',
|
||||
'name' => esc_html__('Delay All Scripts', 'debloat'),
|
||||
'desc' => esc_html__('CAREFUL. Delays all JS files. Its better to target scripts manually below. If there are scripts that setup sliders/carousels, animations, or other similar things, these won\'t be setup until the first user interaction.', 'debloat'),
|
||||
'type' => 'checkbox',
|
||||
'default' => 0,
|
||||
'attributes' => ['data-conditional-id' => 'delay_js'],
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'delay_js_includes',
|
||||
'name' => esc_html__('Target Scripts', 'debloat'),
|
||||
'desc' =>
|
||||
esc_html__('Will delay from these scripts. You may use an ID, part of the URL, or any code for inline scripts. One per line. Examples:', 'debloat')
|
||||
. ' <code>id:my-js-id</code>
|
||||
<br /><code>my-theme/js-file.js</code>
|
||||
<br /><code>wp-content/themes/my-theme/*</code>: All theme JS files.
|
||||
<br /><code>plugins/plugin-slug/*</code>: All JS files for plugin-slug.
|
||||
',
|
||||
'type' => 'textarea_small',
|
||||
'default' => implode("\n", [
|
||||
'twitter.com/widgets.js',
|
||||
'gtm.js',
|
||||
'id:google_gtagjs'
|
||||
]),
|
||||
'attributes' => [
|
||||
'data-conditional-id' => [
|
||||
['key' => 'delay_js'],
|
||||
['key' => 'delay_js_all', 'value' => 'off'],
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'delay_js_excludes',
|
||||
'name' => esc_html__('Exclude Scripts', 'debloat'),
|
||||
'desc' =>
|
||||
esc_html__('Enter one per line to exclude certain scripts from this optimizations. Examples:', 'debloat')
|
||||
. '<code>id:my-js-id</code>
|
||||
<br /><code>my-theme/js-file.js</code>
|
||||
<br /><code>wp-content/themes/my-theme/*</code>: All theme JS files.
|
||||
<br /><code>someStringInJs</code>: Exclude by some text in inline JS tag.
|
||||
',
|
||||
'type' => 'textarea_small',
|
||||
'default' => '',
|
||||
'attributes' => ['data-conditional-id' => 'delay_js'],
|
||||
];
|
||||
|
||||
$options[] = [
|
||||
'id' => 'delay_js_adsense',
|
||||
'name' => esc_html__('Delay Google Ads', 'debloat'),
|
||||
'desc' => esc_html__('Delay Google Adsense until first interaction. Note: This may not be ideal if you have ads that are in header.', 'debloat'),
|
||||
'type' => 'checkbox',
|
||||
'default' => 1,
|
||||
'attributes' => ['data-conditional-id' => 'delay_js'],
|
||||
];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public static function get_general()
|
||||
{
|
||||
$options = [];
|
||||
|
||||
$options[] = [
|
||||
'name' => esc_html__('Disable for Admins', 'debloat'),
|
||||
'desc' => esc_html__('Disable processing for logged in admin users or any user with capability "manage_options". (Useful if using a pagebuilder that conflicts)', 'debloat'),
|
||||
'id' => 'disable_for_admins',
|
||||
'type' => 'checkbox',
|
||||
'default' => 0,
|
||||
];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public static function get_all()
|
||||
{
|
||||
return array_merge(
|
||||
self::get_css(),
|
||||
self::get_js()
|
||||
);
|
||||
}
|
||||
}
|
98
wp-content/plugins/debloat/inc/autoloader.php
Normal file
98
wp-content/plugins/debloat/inc/autoloader.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat;
|
||||
|
||||
/**
|
||||
* Autoloader for loading classes off defined namespaces or a class map.
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Autoloader
|
||||
{
|
||||
public $class_map;
|
||||
public $namespaces = [];
|
||||
|
||||
public function __construct($namespaces = null, $prepend = false)
|
||||
{
|
||||
if (is_array($namespaces)) {
|
||||
$this->namespaces = $namespaces;
|
||||
}
|
||||
|
||||
spl_autoload_register(array($this, 'load'), true, $prepend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Autoloader the class either using a class map or via conversion of
|
||||
* class name to file.
|
||||
*
|
||||
* @param string $class
|
||||
*/
|
||||
public function load($class)
|
||||
{
|
||||
if (isset($this->class_map[$class])) {
|
||||
$file = $this->class_map[$class];
|
||||
}
|
||||
else {
|
||||
foreach ($this->namespaces as $namespace => $dir) {
|
||||
if (strpos($class, $namespace) !== false) {
|
||||
$file = $this->get_file_path($class, $namespace, $dir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($file)) {
|
||||
require_once $file;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file path to include.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* Bunyad_Theme_Foo_Bar to inc/foo/bar/bar.php (fallback to inc/foo/bar.php)
|
||||
* Bunyad\Blocks\FooBar to blocks/foo-bar/foo-bar.php (fallback to inc/foo-bar.php)
|
||||
*
|
||||
* @return string Relative path to the file from the theme dir
|
||||
*/
|
||||
public function get_file_path($class, $prefix = '', $path = '')
|
||||
{
|
||||
// Remove namespace and convert underscore as a namespace delim.
|
||||
$class = str_replace($prefix, '', $class);
|
||||
$class = str_replace('_', '\\', $class);
|
||||
|
||||
// Split to convert CamelCase.
|
||||
$parts = explode('\\', $class);
|
||||
foreach ($parts as $key => $part) {
|
||||
|
||||
$test = substr($part, 1);
|
||||
|
||||
// Convert CamelCase to Camel-Case
|
||||
if (strtolower($test) !== $test) {
|
||||
$part = preg_replace('/(.)(?=[A-Z])/u', '$1-', $part);
|
||||
}
|
||||
|
||||
$parts[$key] = $part;
|
||||
}
|
||||
|
||||
$name = strtolower(array_pop($parts));
|
||||
$path = $path . '/' . strtolower(
|
||||
implode('/', $parts)
|
||||
);
|
||||
$path = trailingslashit($path);
|
||||
|
||||
// Preferred and fallback file path.
|
||||
$pref_file = $path . "{$name}/{$name}.php";
|
||||
$alt_file = $path . "{$name}.php";
|
||||
|
||||
// Try with directory path pattern first.
|
||||
if (file_exists($pref_file)) {
|
||||
return $pref_file;
|
||||
}
|
||||
else if (file_exists($alt_file)) {
|
||||
return $alt_file;
|
||||
}
|
||||
}
|
||||
}
|
91
wp-content/plugins/debloat/inc/base/asset.php
Normal file
91
wp-content/plugins/debloat/inc/base/asset.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat\Base;
|
||||
|
||||
use Sphere\Debloat\Plugin;
|
||||
|
||||
/**
|
||||
* Base class for scripts and stylesheets.
|
||||
*
|
||||
* @uses Plugin::file_system()
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
abstract class Asset
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $orig_url;
|
||||
public $url;
|
||||
public $minified_url;
|
||||
|
||||
/**
|
||||
* Whether the asset is local or at a remote URL.
|
||||
* Note: Defaults to null for lazy init.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private $is_remote = null;
|
||||
|
||||
/**
|
||||
* Return URL to get content from. Minified URL if present.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function get_content_url()
|
||||
{
|
||||
return $this->minified_url ?: $this->url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render attributes using key values.
|
||||
*
|
||||
* @param array $orig_attrs Key value pair of attributes.
|
||||
* @param array $safe List of attributes pre-escaped.
|
||||
* @return array Array of attributes.
|
||||
*/
|
||||
public function render_attrs($orig_attrs, $safe = [])
|
||||
{
|
||||
$attrs = [];
|
||||
foreach ($orig_attrs as $key => $value) {
|
||||
|
||||
// For true, no value is needed in HTML.
|
||||
if ($value === true) {
|
||||
$attrs[] = $key;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Adding previously escaped, but escape again just in case it was done using
|
||||
// different quotes originally.
|
||||
$value = !in_array($key, $safe) ? esc_attr($value) : $value;
|
||||
$attrs[] = sprintf('%s="%s"', $key, $value);
|
||||
}
|
||||
|
||||
return $attrs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the asset is on a remote location.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_remote()
|
||||
{
|
||||
if ($this->is_remote || $this->minified_url) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->is_remote === null && !Plugin::file_system()->url_to_local($this->url)) {
|
||||
$this->is_remote = true;
|
||||
}
|
||||
|
||||
return $this->is_remote;
|
||||
}
|
||||
}
|
193
wp-content/plugins/debloat/inc/delay-load/delay-load.php
Normal file
193
wp-content/plugins/debloat/inc/delay-load/delay-load.php
Normal file
@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat;
|
||||
|
||||
use Sphere\Debloat\OptimizeCss\Stylesheet;
|
||||
use Sphere\Debloat\OptimizeJs\Script;
|
||||
|
||||
/**
|
||||
* Delay load assets singleton.
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class DelayLoad
|
||||
{
|
||||
public $enabled = true;
|
||||
public $use_js = false;
|
||||
public $js_type;
|
||||
|
||||
public $preload = [];
|
||||
|
||||
/**
|
||||
* Note: Should be used a singleton.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->register_hooks();
|
||||
}
|
||||
|
||||
public function register_hooks()
|
||||
{
|
||||
// add_action('wp_enqueue_scripts', [$this, 'register_assets']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable injection of required scripts and preloads, as needed.
|
||||
*
|
||||
* @param null|true $use_js Whether to inject delay/defer JS. null keeps the current.
|
||||
* @return void
|
||||
*/
|
||||
public function enable($use_js = null)
|
||||
{
|
||||
$this->enabled = true;
|
||||
|
||||
if ($use_js === true) {
|
||||
$this->use_js = $use_js;
|
||||
}
|
||||
}
|
||||
|
||||
public function disable()
|
||||
{
|
||||
$this->enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a preload.
|
||||
*
|
||||
* @param Stylesheet|Script $asset
|
||||
* @return void
|
||||
*/
|
||||
public function add_preload($asset)
|
||||
{
|
||||
$this->preload[] = $asset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add extras to the provided HTML buffer.
|
||||
*
|
||||
* @param string $html
|
||||
* @return string
|
||||
*/
|
||||
public function render($html)
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
$js = $this->render_js();
|
||||
$preloads = $this->render_preloads();
|
||||
|
||||
$append = $preloads . $js;
|
||||
|
||||
// Add to body or at the end.
|
||||
// Note: Has to be at the end to ensure all <script> tags with defer has been added.
|
||||
if (strpos($html, '</body>') !== false) {
|
||||
$html = str_replace('</body>', $append . "\n</body>", $html);
|
||||
} else {
|
||||
$html .= $append;
|
||||
}
|
||||
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
protected function render_preloads()
|
||||
{
|
||||
$preloads = [];
|
||||
foreach ($this->preload as $preload) {
|
||||
$media = 'all';
|
||||
$type = ($preload instanceof Stylesheet) ? 'style' : 'script';
|
||||
|
||||
if ($type === 'style') {
|
||||
$media = $preload->media ?: $media;
|
||||
}
|
||||
|
||||
$preloads[] = sprintf(
|
||||
'<link rel="prefetch" href="%1$s" as="%2$s" media="%3$s" />',
|
||||
esc_url($preload->get_content_url()),
|
||||
$type,
|
||||
esc_attr($media)
|
||||
);
|
||||
}
|
||||
|
||||
return implode('', $preloads);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get JS enqueues and inline scripts as needed.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function render_js()
|
||||
{
|
||||
if (!$this->use_js) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$js = '';
|
||||
$min = Plugin::get_instance()->env !== 'dev' ? '.min' : '';
|
||||
|
||||
// Defer JS is always inline.
|
||||
$js .= sprintf(
|
||||
'<script data-cfasync="false">%s</script>',
|
||||
Plugin::file_system()->get_contents(
|
||||
Plugin::get_instance()->dir_path . 'inc/delay-load/js/defer-load'. $min .'.js'
|
||||
)
|
||||
);
|
||||
|
||||
// Delay load JS comes after, just in case, to not mess up readyState.
|
||||
if ($this->js_type === 'inline') {
|
||||
$js .= sprintf(
|
||||
'<script data-cfasync="false">%s</script>',
|
||||
Plugin::file_system()->get_contents(
|
||||
Plugin::get_instance()->dir_path . 'inc/delay-load/js/delay-load'. $min .'.js'
|
||||
)
|
||||
);
|
||||
|
||||
} else {
|
||||
|
||||
$js .= sprintf(
|
||||
'<script type="text/javascript" src="%s" data-cfasync="false"></script>',
|
||||
esc_url(Plugin::get_instance()->dir_url . 'inc/delay-load/js/delay-load'. $min .'.js?ver=' . Plugin::VERSION)
|
||||
);
|
||||
}
|
||||
|
||||
if (!$js) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add configs.
|
||||
*/
|
||||
$configs = [
|
||||
'cssDelayType' => Plugin::options()->delay_css_type,
|
||||
'jsDelayType' => Plugin::options()->delay_js_type,
|
||||
'jsDelayMax' => Plugin::options()->delay_js_max
|
||||
];
|
||||
|
||||
$js = sprintf(
|
||||
'<script>var debloatConfig = %1$s;</script>%2$s',
|
||||
json_encode($configs),
|
||||
$js
|
||||
);
|
||||
|
||||
return $js;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for conditionals for delay load.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function should_delay_css()
|
||||
{
|
||||
if (!Plugin::options()->delay_css_load) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$valid = Plugin::process()->check_enabled(Plugin::options()->delay_css_on);
|
||||
|
||||
return apply_filters('debloat/should_delay_css', $valid);
|
||||
}
|
||||
}
|
36
wp-content/plugins/debloat/inc/delay-load/js/defer-load.js
Normal file
36
wp-content/plugins/debloat/inc/delay-load/js/defer-load.js
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Debloat plugin's defer load.
|
||||
* @preserve
|
||||
* @copyright asadkn 2021
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
(() => {
|
||||
const n = true;
|
||||
const e = [ ...document.querySelectorAll("script[defer]") ];
|
||||
if (e.length && document.readyState !== "complete") {
|
||||
let t = document.readyState;
|
||||
Object.defineProperty(document, "readyState", {
|
||||
configurable: true,
|
||||
get() {
|
||||
return t;
|
||||
},
|
||||
set(e) {
|
||||
return t = e;
|
||||
}
|
||||
});
|
||||
let e = false;
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
t = "interactive";
|
||||
n && console.log("DCL Ready.");
|
||||
e = true;
|
||||
document.dispatchEvent(new Event("readystatechange"));
|
||||
e = false;
|
||||
});
|
||||
document.addEventListener("readystatechange", () => {
|
||||
if (!e && t === "interactive") {
|
||||
t = "complete";
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
1
wp-content/plugins/debloat/inc/delay-load/js/defer-load.min.js
vendored
Normal file
1
wp-content/plugins/debloat/inc/delay-load/js/defer-load.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
"use strict";(()=>{if([...document.querySelectorAll("script[defer]")].length&&"complete"!==document.readyState){let t=document.readyState;Object.defineProperty(document,"readyState",{configurable:!0,get(){return t},set(e){return t=e}});let e=!1;document.addEventListener("DOMContentLoaded",()=>{t="interactive",e=!0,document.dispatchEvent(new Event("readystatechange")),e=!1}),document.addEventListener("readystatechange",()=>{e||"interactive"!==t||(t="complete")})}})();
|
254
wp-content/plugins/debloat/inc/delay-load/js/delay-load.js
Normal file
254
wp-content/plugins/debloat/inc/delay-load/js/delay-load.js
Normal file
@ -0,0 +1,254 @@
|
||||
/**
|
||||
* Delay load functionality of debloat plugin.
|
||||
* @preserve
|
||||
* @copyright asadkn 2021
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
(() => {
|
||||
const r = window.debloatConfig || {};
|
||||
const s = true;
|
||||
let c = [];
|
||||
const d = {
|
||||
HTMLDocument: document.addEventListener.bind(document),
|
||||
Window: window.addEventListener.bind(window)
|
||||
};
|
||||
const n = {};
|
||||
let a;
|
||||
let o = false;
|
||||
let i = false;
|
||||
let l = false;
|
||||
let u = false;
|
||||
let e = false;
|
||||
let f = [];
|
||||
let t = [];
|
||||
function m() {
|
||||
h();
|
||||
w();
|
||||
document.addEventListener("debloat-load-css", () => w(true));
|
||||
document.addEventListener("debloat-load-js", () => h(true));
|
||||
}
|
||||
function h(e) {
|
||||
f = [ ...document.querySelectorAll("script[data-debloat-delay]") ];
|
||||
if (f.length) {
|
||||
E();
|
||||
y("js", e);
|
||||
}
|
||||
}
|
||||
function w(e) {
|
||||
t = [ ...document.querySelectorAll("link[data-debloat-delay]") ];
|
||||
if (t.length) {
|
||||
y("css", e);
|
||||
}
|
||||
}
|
||||
function y(t, n) {
|
||||
t = t || "js";
|
||||
const o = n ? "onload" : r[t + "DelayType"] || "onload";
|
||||
const a = t === "js" ? p : g;
|
||||
if (t === "js") {
|
||||
n || o === "onload" ? v() : D(v);
|
||||
}
|
||||
switch (o) {
|
||||
case "onload":
|
||||
D(() => a(n));
|
||||
break;
|
||||
|
||||
case "interact":
|
||||
let e = false;
|
||||
const s = [ "mousemove", "mousedown", "keydown", "touchstart", "wheel" ];
|
||||
const c = () => {
|
||||
if (e) {
|
||||
return;
|
||||
}
|
||||
e = true;
|
||||
t === "js" ? O(() => setTimeout(a, 2)) : a();
|
||||
};
|
||||
s.forEach(e => {
|
||||
document.addEventListener(e, c, {
|
||||
passive: true,
|
||||
once: true
|
||||
});
|
||||
});
|
||||
if (t === "js" && r.jsDelayMax) {
|
||||
O(() => setTimeout(c, r.jsDelayMax * 1e3));
|
||||
}
|
||||
break;
|
||||
|
||||
case "custom-delay":
|
||||
D(() => {
|
||||
const e = parseInt(element.dataset.customDelay) * 1e3;
|
||||
setTimeout(a, e);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
function g() {
|
||||
t.forEach(e => b(e));
|
||||
}
|
||||
function p(e) {
|
||||
v();
|
||||
if (!e) {
|
||||
l = true;
|
||||
a = document.readyState;
|
||||
let t = "loading";
|
||||
Object.defineProperty(document, "readyState", {
|
||||
configurable: true,
|
||||
get() {
|
||||
return t;
|
||||
},
|
||||
set(e) {
|
||||
return t = e;
|
||||
}
|
||||
});
|
||||
}
|
||||
let t;
|
||||
const n = new Promise(e => t = e);
|
||||
const o = () => {
|
||||
if (!f.length) {
|
||||
t();
|
||||
return;
|
||||
}
|
||||
const e = b(f.shift());
|
||||
e.then(o);
|
||||
};
|
||||
o();
|
||||
n.then(j).catch(e => {
|
||||
console.error(e);
|
||||
j();
|
||||
});
|
||||
setTimeout(() => !c.length || j(), 45e3);
|
||||
}
|
||||
function v(o) {
|
||||
if (e) {
|
||||
return;
|
||||
}
|
||||
e = true;
|
||||
f.forEach(e => {
|
||||
const t = e.src || e.dataset.src;
|
||||
if (!t) {
|
||||
return;
|
||||
}
|
||||
const n = document.createElement("link");
|
||||
Object.assign(n, {
|
||||
rel: o || "preload",
|
||||
as: "script",
|
||||
href: t,
|
||||
...e.crossOrigin && {
|
||||
crossOrigin: e.crossOrigin
|
||||
}
|
||||
});
|
||||
document.head.append(n);
|
||||
});
|
||||
}
|
||||
function b(t) {
|
||||
let e;
|
||||
const n = t.dataset.src;
|
||||
const o = t => {
|
||||
return new Promise(e => {
|
||||
t.addEventListener("load", e);
|
||||
t.addEventListener("error", e);
|
||||
});
|
||||
};
|
||||
if (n) {
|
||||
const s = document.createElement("script");
|
||||
e = o(s);
|
||||
t.getAttributeNames().forEach(e => {
|
||||
e === "src" || (s[e] = t[e]);
|
||||
});
|
||||
s.async = false;
|
||||
s.src = n;
|
||||
t.parentNode.replaceChild(s, t);
|
||||
} else if (t.type && t.type === "text/debloat-script") {
|
||||
t.type = t.dataset.type || "text/javascript";
|
||||
t.text += " ";
|
||||
}
|
||||
const a = t.dataset.href;
|
||||
if (a) {
|
||||
e = o(t);
|
||||
t.href = a;
|
||||
}
|
||||
[ "debloatDelay", "src" ].forEach(e => {
|
||||
t.dataset[e] = "";
|
||||
delete t.dataset[e];
|
||||
});
|
||||
return e || Promise.resolve();
|
||||
}
|
||||
function E() {
|
||||
if (o) {
|
||||
return;
|
||||
}
|
||||
o = true;
|
||||
const e = (t, e) => {
|
||||
e.addEventListener(t, e => n[t] = e);
|
||||
};
|
||||
e("DOMContentLoaded", document);
|
||||
e("load", window);
|
||||
e("readystatechange", document);
|
||||
e("pageshow", window);
|
||||
const t = function(e, t, ...n) {
|
||||
const o = [ "readystatechange", "DOMContentLoaded", "load", "pageshow" ];
|
||||
if (l && !i && o.includes(e)) {
|
||||
s && console.log("Adding: ", e, t, n);
|
||||
const a = {
|
||||
event: e,
|
||||
cb: t,
|
||||
context: this,
|
||||
args: n
|
||||
};
|
||||
c.push(a);
|
||||
return;
|
||||
}
|
||||
if (d[this.constructor.name]) {
|
||||
d[this.constructor.name].call(this, e, t, ...n);
|
||||
}
|
||||
};
|
||||
document.addEventListener = t.bind(document);
|
||||
window.addEventListener = t.bind(window);
|
||||
Object.defineProperty(window, "onload", {
|
||||
set(e) {
|
||||
window.addEventListener("load", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
function L(e) {
|
||||
try {
|
||||
e.cb.call(e.context, n[e.event], ...e.args);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
function j() {
|
||||
if (u) {
|
||||
return;
|
||||
}
|
||||
s && console.log("Firing Load Events", c);
|
||||
u = true;
|
||||
const e = c.filter(e => e.event === "readystatechange");
|
||||
document.readyState = "interactive";
|
||||
e.forEach(e => L(e));
|
||||
for (const t of c) {
|
||||
t.event === "DOMContentLoaded" && L(t);
|
||||
}
|
||||
for (const t of c) {
|
||||
t.event === "load" && L(t);
|
||||
}
|
||||
c = [];
|
||||
u = false;
|
||||
i = true;
|
||||
l = false;
|
||||
D(() => {
|
||||
document.readyState = "complete";
|
||||
setTimeout(() => {
|
||||
e.forEach(e => L(e));
|
||||
}, 2);
|
||||
});
|
||||
}
|
||||
function D(e) {
|
||||
const t = a || document.readyState;
|
||||
t === "complete" ? e() : d.Window("load", () => e());
|
||||
}
|
||||
function O(e) {
|
||||
document.readyState !== "loading" ? e() : d.Window("DOMContentLoaded", () => e());
|
||||
}
|
||||
m();
|
||||
})();
|
1
wp-content/plugins/debloat/inc/delay-load/js/delay-load.min.js
vendored
Normal file
1
wp-content/plugins/debloat/inc/delay-load/js/delay-load.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
"use strict";(()=>{const s=window.debloatConfig||{},a=!0;let d=[];const c={HTMLDocument:document.addEventListener.bind(document),Window:window.addEventListener.bind(window)},n={};let r,o=!1,i=!1,l=!1,u=!1,e=!1,m=[],t=[];function f(e){var t;m=[...document.querySelectorAll("script[data-debloat-delay]")],m.length&&(o||(o=!0,(t=(t,e)=>{e.addEventListener(t,e=>n[t]=e)})("DOMContentLoaded",document),t("load",window),t("readystatechange",document),t("pageshow",window),t=function(e,t,...n){var o;l&&!i&&["readystatechange","DOMContentLoaded","load","pageshow"].includes(e)?(a,o={event:e,cb:t,context:this,args:n},d.push(o)):c[this.constructor.name]&&c[this.constructor.name].call(this,e,t,...n)},document.addEventListener=t.bind(document),window.addEventListener=t.bind(window),Object.defineProperty(window,"onload",{set(e){window.addEventListener("load",e)}})),w("js",e))}function h(e){t=[...document.querySelectorAll("link[data-debloat-delay]")],t.length&&w("css",e)}function w(t,n){t=t||"js";var o=!n&&s[t+"DelayType"]||"onload";const a="js"===t?v:y;switch("js"===t&&(n||"onload"===o?p():L(p)),o){case"onload":L(()=>a(n));break;case"interact":let e=!1;const d=["mousemove","mousedown","keydown","touchstart","wheel"],c=()=>{e||(e=!0,"js"===t?j(()=>setTimeout(a,2)):a())};d.forEach(e=>{document.addEventListener(e,c,{passive:!0,once:!0})}),"js"===t&&s.jsDelayMax&&j(()=>setTimeout(c,1e3*s.jsDelayMax));break;case"custom-delay":L(()=>{var e=1e3*parseInt(element.dataset.customDelay);setTimeout(a,e)})}}function y(){t.forEach(e=>g(e))}function v(e){if(p(),!e){l=!0,r=document.readyState;let t="loading";Object.defineProperty(document,"readyState",{configurable:!0,get(){return t},set(e){return t=e}})}let t;const n=new Promise(e=>t=e),o=()=>{if(m.length){const e=g(m.shift());e.then(o)}else t()};o(),n.then(E).catch(e=>{E()}),setTimeout(()=>!d.length||E(),45e3)}function p(o){e||(e=!0,m.forEach(e=>{var t,n=e.src||e.dataset.src;n&&(t=document.createElement("link"),Object.assign(t,{rel:o||"preload",as:"script",href:n,...e.crossOrigin&&{crossOrigin:e.crossOrigin}}),document.head.append(t))}))}function g(t){let e;var n=t.dataset.src,o=t=>new Promise(e=>{t.addEventListener("load",e),t.addEventListener("error",e)});if(n){const a=document.createElement("script");e=o(a),t.getAttributeNames().forEach(e=>{"src"===e||(a[e]=t[e])}),a.async=!1,a.src=n,t.parentNode.replaceChild(a,t)}else t.type&&"text/debloat-script"===t.type&&(t.type=t.dataset.type||"text/javascript",t.text+=" ");n=t.dataset.href;return n&&(e=o(t),t.href=n),["debloatDelay","src"].forEach(e=>{t.dataset[e]="",delete t.dataset[e]}),e||Promise.resolve()}function b(e){try{e.cb.call(e.context,n[e.event],...e.args)}catch(e){}}function E(){if(!u){a,u=!0;const e=d.filter(e=>"readystatechange"===e.event);document.readyState="interactive",e.forEach(e=>b(e));for(const t of d)"DOMContentLoaded"===t.event&&b(t);for(const n of d)"load"===n.event&&b(n);d=[],u=!1,i=!0,l=!1,L(()=>{document.readyState="complete",setTimeout(()=>{e.forEach(e=>b(e))},2)})}}function L(e){"complete"===(r||document.readyState)?e():c.Window("load",()=>e())}function j(e){"loading"!==document.readyState?e():c.Window("DOMContentLoaded",()=>e())}f(),h(),document.addEventListener("debloat-load-css",()=>h(!0)),document.addEventListener("debloat-load-js",()=>f(!0))})();
|
173
wp-content/plugins/debloat/inc/file-cache.php
Normal file
173
wp-content/plugins/debloat/inc/file-cache.php
Normal file
@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat;
|
||||
|
||||
/**
|
||||
* Filesystem based cache for assets.
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class FileCache
|
||||
{
|
||||
/**
|
||||
* @var Filesystem
|
||||
*/
|
||||
protected $fs;
|
||||
public $cache_path;
|
||||
public $cache_url;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->fs = Plugin::file_system();
|
||||
$this->cache_path = WP_CONTENT_DIR . '/cache/debloat/';
|
||||
$this->cache_url = WP_CONTENT_URL . '/cache/debloat/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cached file path.
|
||||
*
|
||||
* @param string $id
|
||||
* @return bool
|
||||
*/
|
||||
public function get($id)
|
||||
{
|
||||
$file = $this->get_file_name($id);
|
||||
if (!$file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file = $this->get_cache_path($id) . $file;
|
||||
if ($this->fs->is_file($file)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cached file URL.
|
||||
*
|
||||
* @param string $id
|
||||
* @return bool|string
|
||||
*/
|
||||
public function get_url($id)
|
||||
{
|
||||
if (!$this->get($id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file = $this->get_file_name($id);
|
||||
|
||||
if (!$file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->cache_url . $this->get_cache_type($file) . '/' . $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file name for cache storage/retrieval provided a URL or path.
|
||||
*
|
||||
* @param string $id URL or path of a file.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_file_name($id)
|
||||
{
|
||||
// Get a local path if a valid URL was provided. Remote URLs also work.
|
||||
$file = $this->fs->url_to_local($id);
|
||||
if (!$file) {
|
||||
// Remote here.
|
||||
}
|
||||
|
||||
// MD5 on the id instead of file, as it's likely to be a URL that can has a changing
|
||||
// query string reuqiring a new cached entry.
|
||||
$hash = md5($id);
|
||||
return $hash . '.' . $this->get_cache_type($file ?: $id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save a file in the cache.
|
||||
*
|
||||
* @param string $id Path, a local asset URL, or a unqiue name.
|
||||
* @param string $content
|
||||
* @return bool|string File path on success or false on failure.
|
||||
*/
|
||||
public function set($id, string $content = '')
|
||||
{
|
||||
if (!$content) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure path exists.
|
||||
$path = $this->get_cache_path($id);
|
||||
$this->fs->mkdir_p($path);
|
||||
|
||||
$file = $path . $this->get_file_name($id);
|
||||
if ($this->fs->put_contents($file, $content)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache path based on provided file path or id.
|
||||
*
|
||||
* @param string $asset A local path or a URL.
|
||||
* @return string
|
||||
*/
|
||||
public function get_cache_path($asset = '')
|
||||
{
|
||||
$path = $this->cache_path;
|
||||
$asset = $asset ? $this->fs->url_to_local($asset) : '';
|
||||
|
||||
// Path with asset type directory.
|
||||
return $path . $this->get_cache_type($asset) . '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of cache group based on file extension.
|
||||
*
|
||||
* @param string $id File path or name. URLs fine too.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_cache_type(string $file)
|
||||
{
|
||||
$extension = pathinfo($file, PATHINFO_EXTENSION);
|
||||
$type = $extension === 'js' ? 'js' : 'css';
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the cached files for specified cache.
|
||||
*
|
||||
* @param string $type
|
||||
* @return void
|
||||
*/
|
||||
public function delete_cache($type = 'js')
|
||||
{
|
||||
$dir = $this->cache_path . $type . '/';
|
||||
$files = (array) $this->fs->dirlist($dir);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$this->fs->delete($dir . $file['name']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of files in cache for a specific type.
|
||||
*
|
||||
* @param string $type
|
||||
* @return integer
|
||||
*/
|
||||
public function get_stats($type = 'js')
|
||||
{
|
||||
$dir = $this->cache_path . $type;
|
||||
$files = $this->fs->dirlist($dir);
|
||||
|
||||
return $files ? count($files) : 0;
|
||||
}
|
||||
}
|
231
wp-content/plugins/debloat/inc/file-system.php
Normal file
231
wp-content/plugins/debloat/inc/file-system.php
Normal file
@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat;
|
||||
|
||||
/**
|
||||
* Filesystem that mainly wraps the WP_File_System
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @mixin \WP_Filesystem_Base
|
||||
*/
|
||||
class FileSystem
|
||||
{
|
||||
/**
|
||||
* Pattern to remove protocol and www
|
||||
*/
|
||||
const PROTO_REMOVE_PATTERN = '#^(https?)?:?//(|www\.)#i';
|
||||
|
||||
/**
|
||||
* @var WP_Filesystem_Base
|
||||
*/
|
||||
public $filesystem;
|
||||
|
||||
protected $valid_hosts;
|
||||
protected $paths_urls;
|
||||
|
||||
/**
|
||||
* Setup file system
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
global $wp_filesystem;
|
||||
|
||||
if (empty($wp_filesystem)) {
|
||||
|
||||
require_once wp_normalize_path(ABSPATH . '/wp-admin/includes/file.php');
|
||||
|
||||
// At shutdown is usually a ob_start callback which doesn't permit calling ob_*
|
||||
if (did_action('shutdown') && ob_get_level() > 0) {
|
||||
$creds = request_filesystem_credentials('');
|
||||
}
|
||||
else {
|
||||
ob_start();
|
||||
$creds = request_filesystem_credentials('');
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
if (!$creds) {
|
||||
$creds = array();
|
||||
}
|
||||
|
||||
$filesystem = WP_Filesystem($creds);
|
||||
|
||||
if (!$filesystem) {
|
||||
|
||||
// Fallback to lax permissions
|
||||
$upload = wp_upload_dir();
|
||||
WP_Filesystem(false, $upload['basedir'], true);
|
||||
}
|
||||
}
|
||||
|
||||
$this->filesystem = $wp_filesystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively make parent directories for a path.
|
||||
*
|
||||
* Note: Adapted a bit from wp_mkdir_p().
|
||||
*
|
||||
* @param string $path
|
||||
* @return boolean true on success, false or failure.
|
||||
*/
|
||||
public function mkdir_p($path)
|
||||
{
|
||||
if ($this->is_dir($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$path = str_replace('//', '/', $path);
|
||||
|
||||
// Safe mode safety.
|
||||
$path = rtrim($path, '/');
|
||||
$path = $path ?: '/';
|
||||
|
||||
$created = $this->mkdir($path, FS_CHMOD_DIR);
|
||||
if ($created) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If it failed above, try again by creating the parent first, recursively.
|
||||
$parent = dirname($path);
|
||||
if ($parent !== '/' && $this->mkdir_p($parent)) {
|
||||
return $this->mkdir($path, FS_CHMOD_DIR);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a local file by the provided URL, if possible.
|
||||
*
|
||||
* @see wp_normalize_path()
|
||||
*
|
||||
* @return bool|string Either the path or false on failure.
|
||||
*/
|
||||
public function url_to_local($url)
|
||||
{
|
||||
$url = trim($url);
|
||||
|
||||
// Not a URL, just return the path.
|
||||
if (substr($url, 0, 4) !== 'http' && substr($url, 0, 2) !== '//') {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$url = explode('?', trim($url));
|
||||
$url = trim($url[0]);
|
||||
|
||||
// We're not working with encoded URLs.
|
||||
if (strpos($url, '%') !== false) {
|
||||
$url = urldecode($url);
|
||||
}
|
||||
|
||||
// Add https:// for parse_url() or it fails.
|
||||
$url_no_proto = preg_replace(self::PROTO_REMOVE_PATTERN, '', $url);
|
||||
$url_host = parse_url('https://' . $url_no_proto, PHP_URL_HOST);
|
||||
|
||||
// Not a known host / URL.
|
||||
if (!in_array($url_host, $this->get_valid_hosts())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through each known path url map and stop at first matched.
|
||||
*/
|
||||
$valid_urls = $this->get_paths_urls();
|
||||
$url_dirname = dirname($url_no_proto);
|
||||
$matched = [];
|
||||
|
||||
foreach ($valid_urls as $path_url) {
|
||||
|
||||
if (strpos($url_dirname, untrailingslashit($path_url['url'])) !== false) {
|
||||
$matched = $path_url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We have a matched path.
|
||||
if (!empty($matched['path'])) {
|
||||
$path = wp_normalize_path(
|
||||
$matched['path'] . str_replace($matched['url'], '', $url_dirname)
|
||||
);
|
||||
|
||||
$file = trailingslashit($path) . wp_basename($url_no_proto);
|
||||
|
||||
if (file_exists($file) && is_file($file) && is_readable($file)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recognized hostnames for stylesheet URLs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_valid_hosts()
|
||||
{
|
||||
if (!$this->valid_hosts) {
|
||||
$this->valid_hosts = wp_list_pluck(
|
||||
$this->get_paths_urls(),
|
||||
'host'
|
||||
);
|
||||
}
|
||||
|
||||
return apply_filters('debloat/file_system/valid_hosts', $this->valid_hosts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a map of known path URLs, associated local path, and host.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_paths_urls()
|
||||
{
|
||||
if (!$this->paths_urls) {
|
||||
|
||||
// We add https:// back for parse_url() to prevent it from failing
|
||||
$site_url = preg_replace(self::PROTO_REMOVE_PATTERN, '', site_url());
|
||||
$site_host = parse_url('https://' . $site_url, PHP_URL_HOST);
|
||||
|
||||
$content_url = preg_replace(self::PROTO_REMOVE_PATTERN, '', content_url());
|
||||
$content_host = parse_url('https://' . $content_url, PHP_URL_HOST);
|
||||
|
||||
/**
|
||||
* This array will be processed in order it's defined to find the matching host and URL.
|
||||
*/
|
||||
$hosts = [
|
||||
|
||||
// First priority to use content_host and content_url()
|
||||
'content' => [
|
||||
'url' => $content_url,
|
||||
'path' => WP_CONTENT_DIR,
|
||||
'host' => $content_host
|
||||
],
|
||||
|
||||
// Fallback to using site URL with ABSPATH
|
||||
'site' => [
|
||||
'url' => $site_url,
|
||||
'path' => ABSPATH,
|
||||
'host' => $site_host
|
||||
],
|
||||
];
|
||||
|
||||
$this->paths_urls = apply_filters('debloat/file_system/paths_urls', $hosts);
|
||||
}
|
||||
|
||||
return $this->paths_urls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxies to WP_Filesystem_Base
|
||||
*/
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
return call_user_func_array([$this->filesystem, $name], $arguments);
|
||||
}
|
||||
|
||||
}
|
156
wp-content/plugins/debloat/inc/integrations/elementor.php
Normal file
156
wp-content/plugins/debloat/inc/integrations/elementor.php
Normal file
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat\Integrations;
|
||||
|
||||
use Sphere\Debloat\Plugin;
|
||||
|
||||
/**
|
||||
* Rules specific to Elementor plugin.
|
||||
*/
|
||||
class Elementor
|
||||
{
|
||||
public $allow_selectors;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->register_hooks();
|
||||
}
|
||||
|
||||
public function register_hooks()
|
||||
{
|
||||
add_action('wp', [$this, 'setup']);
|
||||
}
|
||||
|
||||
public function setup()
|
||||
{
|
||||
if (in_array('elementor', Plugin::options()->integrations_css)) {
|
||||
$this->setup_remove_css();
|
||||
}
|
||||
|
||||
if (in_array('elementor', Plugin::options()->integrations_js)) {
|
||||
$this->setup_delay_js();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Special rules related to remove css when Elementor is active.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setup_remove_css()
|
||||
{
|
||||
// Add all Elementor frontend files for CSS processing.
|
||||
add_filter('debloat/remove_css_includes', function($include) {
|
||||
|
||||
$include[] = 'id:elementor-frontend-css';
|
||||
$include[] = 'id:elementor-pro-css';
|
||||
// $include[] = 'elementor/*font-awesome';
|
||||
return $include;
|
||||
});
|
||||
|
||||
add_filter('debloat/remove_css_excludes', function($exclude, \Sphere\Debloat\RemoveCss $remove_css) {
|
||||
|
||||
// Don't bother with animations CSS file as it won't remove much.
|
||||
if (!empty($remove_css->used_markup['classes']['elementor-invisible'])) {
|
||||
$exclude[] = 'id:elementor-animations';
|
||||
}
|
||||
|
||||
return $exclude;
|
||||
}, 10, 2);
|
||||
|
||||
/**
|
||||
* Elementor selectors extras.
|
||||
*/
|
||||
$this->allow_selectors = [
|
||||
[
|
||||
'type' => 'any',
|
||||
'sheet' => 'id:elementor-',
|
||||
'search' => [
|
||||
'*.e--ua-*',
|
||||
'.elementor-loading',
|
||||
'.elementor-invisible',
|
||||
'.elementor-background-video-embed',
|
||||
]
|
||||
],
|
||||
[
|
||||
'type' => 'class',
|
||||
'class' => 'elementor-invisible',
|
||||
'sheet' => 'id:elementor-',
|
||||
'search' => [
|
||||
'.animated'
|
||||
]
|
||||
],
|
||||
[
|
||||
'type' => 'class',
|
||||
'class' => 'elementor-invisible',
|
||||
'sheet' => 'id:elementor-',
|
||||
'search' => [
|
||||
'.animated'
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
if (is_user_logged_in()) {
|
||||
$this->allow_selectors[] = [
|
||||
'type' => 'any',
|
||||
'sheet' => 'id:elementor-',
|
||||
'search' => [
|
||||
'#wp-admin-bar*',
|
||||
'*#wpadminbar*',
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
if (defined('ELEMENTOR_PRO_VERSION')) {
|
||||
$this->allow_selectors = array_merge($this->allow_selectors, [
|
||||
[
|
||||
'type' => 'class',
|
||||
'class' => 'elementor-posts-container',
|
||||
// 'sheet' => 'id:elementor-',
|
||||
'search' => [
|
||||
'.elementor-posts-container',
|
||||
'.elementor-has-item-ratio'
|
||||
]
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
add_filter('debloat/allow_css_selectors', function($allow, \Sphere\Debloat\RemoveCss $remove_css) {
|
||||
|
||||
$html = $remove_css->html;
|
||||
if (strpos($html, 'background_slideshow_gallery') !== false) {
|
||||
array_push($this->allow_selectors, ...[
|
||||
[
|
||||
'type' => 'any',
|
||||
'sheet' => 'id:elementor-',
|
||||
'search' => [
|
||||
'*.swiper-*',
|
||||
'*.elementor-background-slideshow*',
|
||||
'.elementor-ken-burns*',
|
||||
]
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
return array_merge($allow, $this->allow_selectors);
|
||||
}, 10, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Special rules related to Delay JS when Elementor is active.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setup_delay_js()
|
||||
{
|
||||
add_filter('debloat/delay_js_includes', function($include) {
|
||||
$include[] = 'elementor/*';
|
||||
|
||||
// Admin bar should also be delayed as elementor creates admin bar items later
|
||||
// and the events won't register.
|
||||
$include[] = 'wp-includes/js/admin-bar*.js';
|
||||
|
||||
return $include;
|
||||
});
|
||||
}
|
||||
}
|
177
wp-content/plugins/debloat/inc/integrations/wpbakery.php
Normal file
177
wp-content/plugins/debloat/inc/integrations/wpbakery.php
Normal file
@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat\Integrations;
|
||||
use Sphere\Debloat\Plugin;
|
||||
|
||||
/**
|
||||
* Rules specific to WPBakery Page Builder plugin.
|
||||
*/
|
||||
class Wpbakery
|
||||
{
|
||||
public $allow_selectors;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->register_hooks();
|
||||
}
|
||||
|
||||
public function register_hooks()
|
||||
{
|
||||
add_action('wp', [$this, 'setup']);
|
||||
}
|
||||
|
||||
public function setup()
|
||||
{
|
||||
if (in_array('wpbakery', Plugin::options()->integrations_css)) {
|
||||
$this->setup_remove_css();
|
||||
}
|
||||
|
||||
if (in_array('wpbakery', Plugin::options()->integrations_js)) {
|
||||
$this->setup_delay_js();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Special rules related to remove css when WPBakery is active.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setup_remove_css()
|
||||
{
|
||||
// Add all WPBakery frontend files for CSS processing.
|
||||
add_filter('debloat/remove_css_includes', function($include) {
|
||||
|
||||
// Only need to remove unused on this. All other CSS files are modular and included only if needed.
|
||||
$include[] = 'id:js_composer_front';
|
||||
|
||||
// FontAwesome can also go through this.
|
||||
$include[] = 'js_composer/*font-awesome';
|
||||
|
||||
return $include;
|
||||
});
|
||||
|
||||
add_filter('debloat/remove_css_excludes', function($exclude, \Sphere\Debloat\RemoveCss $remove_css) {
|
||||
// // Don't bother with animations CSS file as it won't remove much.
|
||||
// $exclude[] = 'id:vc_animate-css';
|
||||
|
||||
// // Pageable owl carousel.
|
||||
// $exclude[] = 'id:vc_pageable_owl-carousel';
|
||||
|
||||
// // prettyPhoto would need all the CSS.
|
||||
// $exclude[] = 'js_composer/*prettyphoto';
|
||||
|
||||
// // owlcarousel is added only if needed.
|
||||
// $exclude[] = 'js_composer/*owl';
|
||||
|
||||
return $exclude;
|
||||
}, 10, 2);
|
||||
|
||||
/**
|
||||
* WPbakery selectors extras.
|
||||
*/
|
||||
$this->allow_selectors = [
|
||||
[
|
||||
'type' => 'any',
|
||||
'search' => [
|
||||
'.vc_mobile',
|
||||
'.vc_desktop',
|
||||
]
|
||||
],
|
||||
[
|
||||
'type' => 'class',
|
||||
'class' => 'vc_parallax',
|
||||
'sheet' => 'id:js_composer_front',
|
||||
'search' => [
|
||||
'*.vc_parallax*',
|
||||
'.vc_hidden'
|
||||
]
|
||||
],
|
||||
[
|
||||
'type' => 'class',
|
||||
'class' => 'vc_pie_chart',
|
||||
'sheet' => 'id:js_composer_front',
|
||||
'search' => [
|
||||
'.vc_ready'
|
||||
]
|
||||
],
|
||||
|
||||
[
|
||||
'type' => 'class',
|
||||
'class' => 'wpb_gmaps_widget',
|
||||
'sheet' => 'id:js_composer_front',
|
||||
'search' => [
|
||||
'.map_ready',
|
||||
]
|
||||
],
|
||||
|
||||
[
|
||||
'type' => 'class',
|
||||
'class' => 'wpb_animate_when_almost_visible',
|
||||
'sheet' => 'id:js_composer_front',
|
||||
'search' => [
|
||||
'.wpb_start_animation',
|
||||
'.animated'
|
||||
]
|
||||
],
|
||||
|
||||
[
|
||||
'type' => 'class',
|
||||
'class' => 'vc_toggle',
|
||||
'sheet' => 'id:js_composer_front',
|
||||
'search' => [
|
||||
'.vc_toggle_active',
|
||||
]
|
||||
],
|
||||
|
||||
[
|
||||
'type' => 'class',
|
||||
'class' => 'vc_grid',
|
||||
'sheet' => 'id:js_composer_front',
|
||||
'search' => [
|
||||
'.vc_grid-loading',
|
||||
'.vc_visible-item',
|
||||
'.vc_is-hover',
|
||||
|
||||
// -complete and -failed etc. too
|
||||
'*.vc-spinner*',
|
||||
'*.vc-grid*.vc_active*',
|
||||
|
||||
// Other dependencies maybe: prettyPhoto, owlcarousel for pagination of specific type.
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
add_filter('debloat/allow_css_selectors', function($allow, \Sphere\Debloat\RemoveCss $remove_css) {
|
||||
|
||||
// $html = $remove_css->html;
|
||||
// if (strpos($html, 'background_slideshow_gallery') !== false) {
|
||||
// array_push($this->allow_selectors, ...[
|
||||
// [
|
||||
// 'type' => 'any',
|
||||
// 'sheet' => 'id:elementor-',
|
||||
// 'search' => [
|
||||
// '*.swiper-*',
|
||||
// '*.elementor-background-slideshow*',
|
||||
// '.elementor-ken-burns*',
|
||||
// ]
|
||||
// ],
|
||||
// ]);
|
||||
// }
|
||||
|
||||
return array_merge($allow, $this->allow_selectors);
|
||||
}, 10, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Special rules related to Delay JS when WPBakery is active.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setup_delay_js()
|
||||
{
|
||||
add_filter('debloat/delay_js_includes', function($include) {
|
||||
$include[] = 'js_composer/*';
|
||||
return $include;
|
||||
});
|
||||
}
|
||||
}
|
201
wp-content/plugins/debloat/inc/minifier.php
Normal file
201
wp-content/plugins/debloat/inc/minifier.php
Normal file
@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat;
|
||||
|
||||
use Sphere\Debloat\Base\Asset;
|
||||
use Sphere\Debloat\OptimizeCss\Stylesheet;
|
||||
use MatthiasMullie\Minify;
|
||||
|
||||
/**
|
||||
* Minifer for assets with cache integration.
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Minifier
|
||||
{
|
||||
protected $asset_type = 'js';
|
||||
|
||||
/**
|
||||
* @var Sphere\Debloat\OptimizeCss\Stylesheet|Sphere\Debloat\OptimizeJs\Script
|
||||
*/
|
||||
protected $asset;
|
||||
|
||||
/**
|
||||
* @param \Sphere\Debloat\Base\Asset $asset
|
||||
*/
|
||||
public function __construct(Asset $asset)
|
||||
{
|
||||
if ($asset instanceof Stylesheet) {
|
||||
$this->asset_type = 'css';
|
||||
}
|
||||
|
||||
$this->asset = $asset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify the asset, cache it, and replace its URL in the asset object.
|
||||
*
|
||||
* @uses Plugin::file_cache()
|
||||
* @uses Plugin::file_system()
|
||||
*
|
||||
* @return string URL of the minified file.
|
||||
*/
|
||||
public function process()
|
||||
{
|
||||
// Not for inline assets.
|
||||
if (!$this->asset->url) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Debugging scripts. Do not minify.
|
||||
if (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) {
|
||||
return;
|
||||
}
|
||||
|
||||
$file = Plugin::file_cache()->get_url($this->asset->url);
|
||||
if (!$file) {
|
||||
$minify = $this->minify();
|
||||
if (!$minify) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For CSS assets, convert URLs to fully qualified.
|
||||
$this->maybe_convert_urls();
|
||||
|
||||
if (!Plugin::file_cache()->set($this->asset->url, $this->asset->content)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$file = Plugin::file_cache()->get_url($this->asset->url);
|
||||
}
|
||||
|
||||
$this->asset->minified_url = $file;
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify the file using the URL in the asset object.
|
||||
*
|
||||
* @return string|boolean
|
||||
*/
|
||||
public function minify()
|
||||
{
|
||||
// We support google fonts remote fetch.
|
||||
if (
|
||||
Plugin::options()->optimize_gfonts_inline &&
|
||||
$this->asset instanceof Stylesheet &&
|
||||
$this->asset->is_google_fonts()
|
||||
) {
|
||||
$this->fetch_remote_content();
|
||||
|
||||
} else {
|
||||
// We minify and cache local source only for now.
|
||||
$source_file = Plugin::file_system()->url_to_local($this->asset->url);
|
||||
if (!$source_file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$source = Plugin::file_system()->get_contents($source_file);
|
||||
$this->asset->content = $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* We want a cached file with source data whether existing is minified or not - as
|
||||
* post-processing is needed for URLs when inlining the CSS.
|
||||
*
|
||||
* For JS, caching the already min files also serves the purpose of not testing them
|
||||
* again, unnecessarily.
|
||||
*/
|
||||
if ($this->is_content_minified()) {
|
||||
return $this->asset->content;
|
||||
}
|
||||
|
||||
Util\debug_log('Minifying: ' . $this->asset->url);
|
||||
|
||||
// JS minifier.
|
||||
if ($this->asset_type === 'js') {
|
||||
|
||||
// Improper handling for older webpack: https://github.com/matthiasmullie/minify/issues/375
|
||||
if (strpos($source, 'var __webpack_require__ = function (moduleId)') !== false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$minifier = new Minify\JS($this->asset->content);
|
||||
$this->asset->content = $minifier->minify();
|
||||
|
||||
} else {
|
||||
|
||||
// CSS minifier. Set content and convert urls.
|
||||
$minifier = new Minify\CSS($this->asset->content);
|
||||
$this->asset->content = $minifier->minify();
|
||||
}
|
||||
|
||||
return $this->asset->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert URLs for CSS assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_convert_urls()
|
||||
{
|
||||
if ($this->asset_type !== 'css') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if any non-data URLs exist.
|
||||
if (!preg_match('/url\((?![\'"\s]*data:)/', $this->asset->content)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->asset->convert_urls();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote asset content and add to asset content.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fetch_remote_content()
|
||||
{
|
||||
$request = wp_remote_get($this->asset->url, [
|
||||
'timeout' => 5,
|
||||
// For google fonts mainly.
|
||||
'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36',
|
||||
]);
|
||||
|
||||
if (is_wp_error($request) || empty($request['body'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->asset->content = $request['body'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if provided content is already minified.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_content_minified($content = '')
|
||||
{
|
||||
$content = $content ?: $this->asset->content;
|
||||
|
||||
// Already minified asset.
|
||||
if (preg_match('/[\-\.]min\.(js|css)/', $this->asset->url)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$content = trim($content);
|
||||
if (!$content) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hacky, but will do.
|
||||
$new_lines = substr_count($content, "\n", 0, min(strlen($content), 2000));
|
||||
if ($new_lines < 5) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
106
wp-content/plugins/debloat/inc/optimize-css/google-fonts.php
Normal file
106
wp-content/plugins/debloat/inc/optimize-css/google-fonts.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat\OptimizeCss;
|
||||
|
||||
use Sphere\Debloat\Plugin;
|
||||
|
||||
/**
|
||||
* Add a few Google Font optimizations.
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class GoogleFonts
|
||||
{
|
||||
public $active = false;
|
||||
protected $renders = [];
|
||||
|
||||
public function enable()
|
||||
{
|
||||
$this->active = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add resource hints for preconnect and so on.
|
||||
*
|
||||
* @param string $html Full DOM HTML.
|
||||
* @return void
|
||||
*/
|
||||
public function add_hints($html)
|
||||
{
|
||||
if (!$this->active || !Plugin::options()->optimize_gfonts) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
// preconnect with dns-prefetch fallback for Firefox. Due to safari bug, can't be in same rel.
|
||||
$hint = '<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />';
|
||||
$hint .= '<link rel="dns-prefetch" href="https://fonts.gstatic.com" />';
|
||||
|
||||
// Only needed if not inlined.
|
||||
if (!Plugin::options()->optimize_css || !Plugin::options()->optimize_gfonts_inline) {
|
||||
$hint .= '<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin />';
|
||||
$hint .= '<link rel="dns-prefetch" href="https://fonts.googleapis.com" />';
|
||||
}
|
||||
|
||||
// Add in to head.
|
||||
$html = str_replace('<head>', '<head>' . $hint, $html);
|
||||
|
||||
// No longer needed as preconnect's better and we always want to use https.
|
||||
$html = str_replace("<link rel='dns-prefetch' href='//fonts.googleapis.com' />", '', $html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Late processing and injecting data in HTML DOM.
|
||||
*
|
||||
* @param string $html
|
||||
* @return string
|
||||
*/
|
||||
public function render_in_dom($html)
|
||||
{
|
||||
if ($this->renders) {
|
||||
$html = str_replace('<head>', '<head>' . implode('', $this->renders), $html);
|
||||
}
|
||||
|
||||
$html = $this->add_hints($html);
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function optimize(Stylesheet $sheet)
|
||||
{
|
||||
if (!$sheet->is_google_fonts()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set normal render if optimizations are disabled.
|
||||
if (!Plugin::options()->optimize_gfonts) {
|
||||
$sheet->render_type = 'normal';
|
||||
return;
|
||||
}
|
||||
|
||||
$sheet->delay_type = 'preload';
|
||||
if (strpos($sheet->url, 'display=') === false) {
|
||||
$sheet->url .= '&display=swap';
|
||||
}
|
||||
}
|
||||
|
||||
public function do_render(Stylesheet $sheet)
|
||||
{
|
||||
return $sheet->render();
|
||||
|
||||
// $orig_media = $sheet->media;
|
||||
|
||||
// // If no display= or if display=auto.
|
||||
// if (!preg_match('/display=(?!auto)/', $sheet->url)) {
|
||||
// $sheet->media = 'x';
|
||||
// }
|
||||
|
||||
// // Add the JS to render with display: swap on mobile.
|
||||
// $extra = "<script>const e = document.currentScript.previousElementSibling; window.innerWidth > 1024 || (e.href+='&display=swap'); e.media='" . esc_js($orig_media) ."'</script>";
|
||||
// $this->renders[] = $sheet->render() . $extra;
|
||||
|
||||
// // Return empty space to strip out the tag.
|
||||
// return ' ';
|
||||
}
|
||||
}
|
251
wp-content/plugins/debloat/inc/optimize-css/optimize-css.php
Normal file
251
wp-content/plugins/debloat/inc/optimize-css/optimize-css.php
Normal file
@ -0,0 +1,251 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat;
|
||||
use Sphere\Debloat\OptimizeCss\Stylesheet;
|
||||
|
||||
/**
|
||||
* Process stylesheets to debloat and optimize CSS.
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class OptimizeCss
|
||||
{
|
||||
/**
|
||||
* @var \DOMDocument
|
||||
*/
|
||||
protected $dom;
|
||||
protected $html;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $stylesheets;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $exclude_sheets;
|
||||
|
||||
public function __construct(\DOMDocument $dom, string $raw_html)
|
||||
{
|
||||
$this->dom = $dom;
|
||||
$this->html = $raw_html;
|
||||
}
|
||||
|
||||
public function process()
|
||||
{
|
||||
$this->find_stylesheets();
|
||||
|
||||
// Setup optimization excludes.
|
||||
$exclude = Util\option_to_array(Plugin::options()->optimize_css_excludes);
|
||||
$this->exclude_sheets = apply_filters('debloat/optimize_css_excludes', $exclude);
|
||||
|
||||
// Remove CSS first, if any.
|
||||
if ($this->should_remove_css()) {
|
||||
$remove_css = new RemoveCss($this->stylesheets, $this->dom, $this->html);
|
||||
$this->html = $remove_css->process();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process and replace stylesheets with CSS cleaned.
|
||||
*/
|
||||
$has_delayed = false;
|
||||
$has_gfonts = false;
|
||||
$replacements = [];
|
||||
|
||||
/** @var Stylesheet $sheet */
|
||||
foreach ($this->stylesheets as $sheet) {
|
||||
$replacement = '';
|
||||
|
||||
// Optimizations such as min and inline.
|
||||
$this->optimize($sheet);
|
||||
|
||||
if (Plugin::options()->optimize_gfonts && $sheet->is_google_fonts()) {
|
||||
$replacement = Plugin::google_fonts()->do_render($sheet);
|
||||
$has_gfonts = true;
|
||||
}
|
||||
|
||||
if (!$replacement) {
|
||||
// Get the rendered stylesheet to replace original with.
|
||||
$replacement = $sheet->render();
|
||||
}
|
||||
|
||||
if ($replacement) {
|
||||
// Util\debug_log('Replacing: ' . print_r($sheet, true));
|
||||
$replacements[$sheet->orig_url] = $replacement;
|
||||
|
||||
if ($sheet->has_delay()) {
|
||||
$has_delayed = true;
|
||||
|
||||
// onload and preload type doesn't need prefetch; both use a non-JS method.
|
||||
if (!in_array($sheet->delay_type, ['onload', 'preload'])) {
|
||||
Plugin::delay_load()->add_preload($sheet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Found Google Fonts.
|
||||
if ($sheet->is_google_fonts()) {
|
||||
$has_gfonts = true;
|
||||
}
|
||||
|
||||
// Free up memory.
|
||||
$sheet->content = null;
|
||||
$sheet->parsed_data = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make stylesheet replacements, if any. Slightly more efficient in one go.
|
||||
*/
|
||||
if ($replacements) {
|
||||
$urls = array_map('preg_quote', array_keys($replacements));
|
||||
|
||||
// Using callback to prevent issues with backreferences such as $1 or \0 in replacement string.
|
||||
$this->html = preg_replace_callback(
|
||||
'#<link[^>]*href=(?:"|\'|)('. implode('|', $urls) .')(?:"|\'|\s)[^>]*>#Usi',
|
||||
function ($match) use ($replacements) {
|
||||
if (!empty($replacements[ $match[1] ])) {
|
||||
return $replacements[ $match[1] ];
|
||||
}
|
||||
|
||||
return $match[0];
|
||||
},
|
||||
$this->html
|
||||
);
|
||||
}
|
||||
|
||||
if ($has_delayed) {
|
||||
Plugin::delay_load()->enable(
|
||||
Plugin::options()->delay_css_type === 'onload' ? true : null
|
||||
);
|
||||
}
|
||||
|
||||
if ($has_gfonts) {
|
||||
Plugin::google_fonts()->enable();
|
||||
$this->html = Plugin::google_fonts()->render_in_dom($this->html);
|
||||
}
|
||||
|
||||
return $this->html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply CSS optimizes such as minify and inline, if enabled.
|
||||
*
|
||||
* @param Stylesheet $sheet
|
||||
* @return void
|
||||
*/
|
||||
public function optimize(Stylesheet $sheet)
|
||||
{
|
||||
if (!$this->should_optimize($sheet)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We're going to use onload delay method (non-JS) fixing render blocking.
|
||||
$sheet->delay_type = 'onload';
|
||||
$sheet->set_render('delay');
|
||||
|
||||
// Should the sheet be minified.
|
||||
$minify = Plugin::options()->optimize_css_minify;
|
||||
|
||||
// For inline CSS, minification is enforced.
|
||||
if (Plugin::options()->optimize_css_to_inline) {
|
||||
$minify = true;
|
||||
}
|
||||
|
||||
// Will optimize if it's a google font. Note: Has to be done before minify.
|
||||
Plugin::google_fonts()->optimize($sheet);
|
||||
|
||||
// Google Fonts inline also relies on minification.
|
||||
if ($sheet->is_google_fonts() && Plugin::options()->optimize_gfonts_inline) {
|
||||
$minify = true;
|
||||
}
|
||||
|
||||
if ($minify) {
|
||||
$minifier = new Minifier($sheet);
|
||||
$minifier->process();
|
||||
}
|
||||
|
||||
if (Plugin::options()->optimize_css_to_inline) {
|
||||
if (!$sheet->content) {
|
||||
$file = Plugin::file_system()->url_to_local($sheet->get_content_url());
|
||||
|
||||
if ($file) {
|
||||
$sheet->content = Plugin::file_system()->get_contents($file);
|
||||
}
|
||||
}
|
||||
|
||||
// If we have content by now.
|
||||
if ($sheet->content) {
|
||||
$sheet->set_render('inline');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if stylesheet should be optimized, based on exclusion and inclusion
|
||||
* rules and settings.
|
||||
*
|
||||
* @param Stylesheet $sheet
|
||||
* @return boolean
|
||||
*/
|
||||
public function should_optimize(Stylesheet $sheet)
|
||||
{
|
||||
// Only go ahead if optimizations are enabled and remove css hasn't happened.
|
||||
if (!Plugin::options()->optimize_css || $sheet->render_type === 'remove_css') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Debugging scripts. Can't be minified, so can't be inline either.
|
||||
if (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle manual excludes first.
|
||||
if ($this->exclude_sheets) {
|
||||
foreach ($this->exclude_sheets as $exclude) {
|
||||
if (Util\asset_match($exclude, $sheet)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all the stylesheet links.
|
||||
*
|
||||
* @return Stylesheet[]
|
||||
*/
|
||||
public function find_stylesheets()
|
||||
{
|
||||
$stylesheets = [];
|
||||
|
||||
// Note: Can't use DOM parser as html entities in the URLs will be removed and
|
||||
// replacing won't be possible later.
|
||||
preg_match_all('#<link[^>]*stylesheet[^>]*>#Usi', $this->html, $matches);
|
||||
|
||||
foreach ($matches[0] as $sheet) {
|
||||
$sheet = Stylesheet::from_tag($sheet);
|
||||
|
||||
if ($sheet) {
|
||||
$stylesheets[] = $sheet;
|
||||
}
|
||||
}
|
||||
|
||||
$this->stylesheets = $stylesheets;
|
||||
return $this->stylesheets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should unused CSS be removed.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function should_remove_css()
|
||||
{
|
||||
$valid = Plugin::options()->remove_css && Plugin::process()->check_enabled(Plugin::options()->remove_css_on);
|
||||
return apply_filters('debloat/should_remove_css', $valid);
|
||||
}
|
||||
}
|
296
wp-content/plugins/debloat/inc/optimize-css/stylesheet.php
Normal file
296
wp-content/plugins/debloat/inc/optimize-css/stylesheet.php
Normal file
@ -0,0 +1,296 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat\OptimizeCss;
|
||||
|
||||
use Sphere\Debloat\Base\Asset;
|
||||
use Sphere\Debloat\Util;
|
||||
|
||||
/**
|
||||
* Value object for stylehseets.
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Stylesheet extends Asset
|
||||
{
|
||||
public $has_cache = false;
|
||||
|
||||
/**
|
||||
* Specify a different id to use while rendering the script.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $render_id = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $file;
|
||||
|
||||
/**
|
||||
* CSS content.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $content = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $parsed_data;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $render_type = '';
|
||||
public $delay_type = 'onload';
|
||||
|
||||
/**
|
||||
* Whether delay load exists. Some sheets may have a different render_type but
|
||||
* may still need to be delayed in addition.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $has_delay = false;
|
||||
|
||||
/**
|
||||
* Media type for this stylesheet.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $media;
|
||||
|
||||
/**
|
||||
* Pre-rendered content
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $render_string;
|
||||
|
||||
public $original_size = 0;
|
||||
public $new_size = 0;
|
||||
|
||||
/**
|
||||
* Whether the stylesheet is a google fonts link.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $is_google_fonts = false;
|
||||
|
||||
public function __construct(string $id, string $url)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->orig_url = $url;
|
||||
|
||||
// Cleaned URL.
|
||||
$this->url = html_entity_decode($url, ENT_COMPAT | ENT_SUBSTITUTE);
|
||||
|
||||
if (!$this->id) {
|
||||
$this->id = md5($this->url);
|
||||
}
|
||||
|
||||
$this->is_google_fonts = stripos($this->url, 'fonts.googleapis.com/css') !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method: Create an instance provided an HTML tag.
|
||||
*
|
||||
* @param string $tag
|
||||
* @return boolean|self
|
||||
*/
|
||||
public static function from_tag(string $tag)
|
||||
{
|
||||
$attrs = Util\parse_attrs($tag);
|
||||
|
||||
if (!isset($attrs['href'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sheet = new self($attrs['id'] ?? '', $attrs['href']);
|
||||
$sheet->media = $attrs['media'] ?? '';
|
||||
|
||||
return $sheet;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
if ($this->render_string) {
|
||||
return $this->render_string;
|
||||
}
|
||||
|
||||
$attrs = [
|
||||
'rel' => 'stylesheet',
|
||||
'id' => $this->render_id ?: $this->id,
|
||||
];
|
||||
|
||||
if ($this->media) {
|
||||
$attrs['media'] = $this->media;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Render type 'delay' at onload or delay via JS for later.
|
||||
*/
|
||||
if ($this->render_type === 'delay') {
|
||||
if ($this->media === 'print') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($this->delay_type === 'onload') {
|
||||
$media = $this->media ?? 'all';
|
||||
$attrs = array_replace($attrs, [
|
||||
'media' => 'print',
|
||||
'href' => $this->get_content_url(),
|
||||
'onload' => "this.media='{$media}'"
|
||||
]);
|
||||
|
||||
} else if ($this->delay_type === 'preload') {
|
||||
$attrs = array_replace($attrs, [
|
||||
'rel' => 'preload',
|
||||
'as' => 'style',
|
||||
'href' => $this->get_content_url(),
|
||||
'onload' => "this.rel='stylesheet'"
|
||||
]);
|
||||
|
||||
} else {
|
||||
/**
|
||||
* Other types of delayed loads are handled via JS, so add the relevant data.
|
||||
*/
|
||||
$attrs += [
|
||||
'data-debloat-delay' => true,
|
||||
'data-href' => $this->get_content_url()
|
||||
];
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<link %1$s/>',
|
||||
implode(' ', $this->render_attrs($attrs, ['onload']))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 2. Render type 'inline' when the CSS has to be inlined.
|
||||
*/
|
||||
if ($this->render_type === 'inline') {
|
||||
if (!$this->content) {
|
||||
return '';
|
||||
}
|
||||
|
||||
unset($attrs['rel']);
|
||||
|
||||
if ($attrs['media'] === 'all') {
|
||||
unset($attrs['media']);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<style %1$s>%2$s</style>',
|
||||
implode(' ', $this->render_attrs($attrs)),
|
||||
$this->content
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 3. Normal render. Usually for stylesheets that just have to be minified.
|
||||
*/
|
||||
if ($this->render_type === 'normal') {
|
||||
$attrs += [
|
||||
'href' => $this->get_content_url()
|
||||
];
|
||||
|
||||
return sprintf(
|
||||
'<link %1$s/>',
|
||||
implode(' ', $this->render_attrs($attrs))
|
||||
);
|
||||
}
|
||||
|
||||
// Default to nothing.
|
||||
return '';
|
||||
}
|
||||
|
||||
public function set_render($type, $render_string = '')
|
||||
{
|
||||
$this->render_type = $type;
|
||||
|
||||
if ($render_string) {
|
||||
$this->render_string = $render_string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not stylesheet is delayed or has a delayed factor to it (such as
|
||||
* remove_css + delay combo).
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function has_delay()
|
||||
{
|
||||
return $this->has_delay || $this->render_type === 'delay';
|
||||
}
|
||||
|
||||
public function convert_urls()
|
||||
{
|
||||
// Original base URL.
|
||||
$base_url = preg_replace('#[^/]+\?.*$#', '', $this->url);
|
||||
|
||||
// Borrowed and modified from MatthiasMullie\Minify\CSS.
|
||||
$regex = '/
|
||||
# open url()
|
||||
url\(
|
||||
\s*
|
||||
|
||||
# open path enclosure
|
||||
(?P<quotes>["\'])?
|
||||
|
||||
# fetch path
|
||||
(?P<path>.+?)
|
||||
|
||||
# close path enclosure, conditional
|
||||
(?(quotes)(?P=quotes))
|
||||
|
||||
\s*
|
||||
# close url()
|
||||
\)
|
||||
/ix';
|
||||
|
||||
preg_match_all($regex, $this->content, $matches, PREG_SET_ORDER);
|
||||
if (!$matches) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($matches as $match) {
|
||||
$url = trim($match['path']);
|
||||
|
||||
if (substr($url, 0, 5) === 'data:') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parsed_url = parse_url($url);
|
||||
|
||||
// Skip known host and protocol-relative paths.
|
||||
if (!empty($parsed_url['host']) || empty($parsed_url['path']) || $parsed_url['path'][0] === '/') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$new_url = $base_url . $url;
|
||||
|
||||
// URLs with quotes, #, brackets or characters above 0x7e should be quoted.
|
||||
// Restore original quotes.
|
||||
// Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/url()#syntax
|
||||
if (preg_match('/[\s\)\'"#\x{7f}-\x{9f}]/u', $new_url)) {
|
||||
$new_url = $match['quotes'] . $new_url . $match['quotes'];
|
||||
}
|
||||
|
||||
$new_url = 'url(' . $new_url. ')';
|
||||
$this->content = str_replace($match[0], $new_url, $this->content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the style is a google fonts URL.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_google_fonts()
|
||||
{
|
||||
return $this->is_google_fonts;
|
||||
}
|
||||
}
|
358
wp-content/plugins/debloat/inc/optimize-js/optimize-js.php
Normal file
358
wp-content/plugins/debloat/inc/optimize-js/optimize-js.php
Normal file
@ -0,0 +1,358 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat;
|
||||
use Sphere\Debloat\OptimizeJs\Script;
|
||||
|
||||
/**
|
||||
* JS Optimizations such as defer and delay.
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class OptimizeJs
|
||||
{
|
||||
protected $html;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $scripts = [];
|
||||
|
||||
/**
|
||||
* Include and exclude scripts for "delay".
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $include_scripts = [];
|
||||
protected $exclude_scripts = [];
|
||||
|
||||
/**
|
||||
* Exclude scripts from defer.
|
||||
*/
|
||||
protected $exclude_defer = [];
|
||||
|
||||
/**
|
||||
* Scripts already delayed or deffered.
|
||||
*/
|
||||
protected $done_delay = [];
|
||||
protected $done_defer = [];
|
||||
|
||||
/**
|
||||
* Scripts registered data from core.
|
||||
*/
|
||||
protected $enqueues = [];
|
||||
|
||||
public function __construct(string $html)
|
||||
{
|
||||
$this->html = $html;
|
||||
}
|
||||
|
||||
public function process()
|
||||
{
|
||||
$this->find_scripts();
|
||||
|
||||
// Figure out valid scripts.
|
||||
$this->setup_valid_scripts();
|
||||
|
||||
do_action('debloat/optimize_js_begin', $this);
|
||||
|
||||
// Early First pass to setup valid parents for child dependency checks.
|
||||
array_map(function($script) {
|
||||
$this->should_defer_script($script);
|
||||
}, $this->scripts);
|
||||
|
||||
/**
|
||||
* Setup scripts defer and delays and render the replacements.
|
||||
*/
|
||||
$has_delayed = false;
|
||||
foreach ($this->scripts as $script) {
|
||||
|
||||
// Has to be done for all scripts.
|
||||
if (Plugin::options()->minify_js) {
|
||||
$this->minify_js($script);
|
||||
$script->render = true;
|
||||
}
|
||||
|
||||
// Defer script if not already deferred.
|
||||
if ($this->should_defer_script($script)) {
|
||||
$script->defer = true;
|
||||
$script->render = true;
|
||||
|
||||
$has_delayed = true;
|
||||
}
|
||||
|
||||
// Should this script be delayed.
|
||||
if ($this->should_delay_script($script)) {
|
||||
$script->delay = true;
|
||||
$script->render = true;
|
||||
|
||||
$has_delayed = true;
|
||||
}
|
||||
else {
|
||||
Util\debug_log('Skipping: ' . print_r($script, true));
|
||||
}
|
||||
|
||||
if ($script->render) {
|
||||
$this->html = str_replace($script->orig_html, $script->render(), $this->html);
|
||||
}
|
||||
}
|
||||
|
||||
if ($has_delayed) {
|
||||
Plugin::delay_load()->enable(true);
|
||||
}
|
||||
|
||||
return $this->html;
|
||||
}
|
||||
|
||||
public function find_scripts()
|
||||
{
|
||||
/**
|
||||
* Collect all valid enqueues.
|
||||
*/
|
||||
$all_scripts = [];
|
||||
foreach (wp_scripts()->registered as $handle => $script) {
|
||||
|
||||
// Not an enqueued script, ignore.
|
||||
if (!in_array($handle, wp_scripts()->done)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't mutate original.
|
||||
$script = clone $script;
|
||||
|
||||
$script->deps = array_map(function($id) {
|
||||
// jQuery JS should be mapped to core. Add -js suffix for others.
|
||||
return $id === 'jquery' ? 'jquery-core-js' : $id . '-js';
|
||||
}, $script->deps);
|
||||
|
||||
// Add -js prefix to match the IDs that will retrieved from attrs.
|
||||
$handle .= '-js';
|
||||
$all_scripts[$handle] = $script;
|
||||
|
||||
// Pseudo entry for extras, adding a dependency.
|
||||
if (isset($script->extra['after'])) {
|
||||
$all_scripts[$handle . '-after'] = (object) [
|
||||
'deps' => [$handle]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->enqueues = $all_scripts;
|
||||
|
||||
/**
|
||||
* Find all scripts.
|
||||
*/
|
||||
if (!preg_match_all('#<script.*?</script>#si', $this->html, $matches)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($matches[0] as $script) {
|
||||
$script = Script::from_tag($script);
|
||||
if ($script) {
|
||||
$script->deps = $this->enqueues[$script->id]->deps ?? [];
|
||||
|
||||
// Note: There can be multiple scripts with same URL or id. So we don't use $script->id.
|
||||
$this->scripts[] = $script;
|
||||
|
||||
// Inline script has jQuery content? Ensure dependency.
|
||||
if (
|
||||
!$script->url &&
|
||||
!in_array('jquery-core-js', $script->deps) &&
|
||||
strpos($script->content, 'jQuery') !== false
|
||||
) {
|
||||
$script->deps[] = 'jquery-core-js';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the script has a parent dependency that is delayed/deferred.
|
||||
*
|
||||
* @param Script $script
|
||||
* @param array $valid
|
||||
* @return boolean
|
||||
*/
|
||||
public function check_dependency(Script $script, array $valid)
|
||||
{
|
||||
// Check if one of parent dependencies is delayed/deferred.
|
||||
foreach ((array) $script->deps as $dep) {
|
||||
if (isset($valid[$dep])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// For translations, if wp-i18n-js is valid, so should be translations.
|
||||
if (preg_match('/-js-translations/', $script->id, $matches)) {
|
||||
if (isset($valid['wp-i18n-js'])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Special case: Inline script with a parent dep. If parent is true, so is child.
|
||||
// Note: 'before' and 'extra' isn't accounted for and is not usually needed. Since 'extra' is
|
||||
// usually localization and 'before' has to happen before anyways.
|
||||
if (preg_match('/(.+?-js)-(before|after)/', $script->id, $matches)) {
|
||||
$handle = $matches[1];
|
||||
|
||||
// Parent was valid, so is the current child.
|
||||
if (isset($valid[$handle])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the script be delayed using one of the JS delay methods.
|
||||
*
|
||||
* @param Script $script
|
||||
* @return boolean
|
||||
*/
|
||||
public function should_delay_script(Script $script)
|
||||
{
|
||||
if (!Plugin::options()->delay_js) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Excludes should be handled before includes and parents check.
|
||||
foreach ($this->exclude_scripts as $exclude) {
|
||||
if (Util\asset_match($exclude, $script, 'orig_html')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Delay all.
|
||||
if (Plugin::options()->delay_js_all) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->check_dependency($script, $this->done_delay)) {
|
||||
$this->done_delay[$script->id] = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($this->include_scripts as $include) {
|
||||
if (Util\asset_match($include, $script, 'orig_html')) {
|
||||
$this->done_delay[$script->id] = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the script be deferred.
|
||||
*
|
||||
* @param Script $script
|
||||
* @return boolean
|
||||
*/
|
||||
public function should_defer_script(Script $script)
|
||||
{
|
||||
// Defer not enabled.
|
||||
if (!Plugin::options()->defer_js) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->exclude_defer as $exclude) {
|
||||
if (Util\asset_match($exclude, $script, 'orig_html')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// For inline scripts: By default not deferred, unless child of a deferred.
|
||||
if (!$script->url) {
|
||||
if (Plugin::options()->defer_js_inline) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->check_dependency($script, $this->done_defer)) {
|
||||
$this->done_defer[$script->id] = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// If defer or async attr already exists on original script.
|
||||
if ($script->defer || $script->async) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->done_defer[$script->id] = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup includes and excludes based on options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setup_valid_scripts()
|
||||
{
|
||||
// Used by both delay and defer.
|
||||
$shared_excludes = [
|
||||
// Lazyloads should generally be loaded as early as possible and not deferred.
|
||||
'lazysizes.js',
|
||||
'lazyload.js',
|
||||
'lazysizes.min.js',
|
||||
'lazyLoadOptions',
|
||||
'lazyLoadThumb',
|
||||
];
|
||||
|
||||
/**
|
||||
* Defer scripts.
|
||||
*/
|
||||
$defer_excludes = array_merge(
|
||||
Util\option_to_array(Plugin::options()->defer_js_excludes),
|
||||
$shared_excludes
|
||||
);
|
||||
|
||||
$defer_excludes[] = 'id:-js-extra';
|
||||
$this->exclude_defer = apply_filters('debloat/defer_js_excludes', $defer_excludes);
|
||||
|
||||
/**
|
||||
* Delayed load scripts.
|
||||
*/
|
||||
$excludes = Util\option_to_array(Plugin::options()->delay_js_excludes);
|
||||
$excludes = array_merge($excludes, $shared_excludes, [
|
||||
// Jetpack stats.
|
||||
'url://stats.wp.com',
|
||||
'_stq.push',
|
||||
|
||||
// WPML browser redirect.
|
||||
'browser-redirect/app.js',
|
||||
|
||||
// Skip -js-extra as it's global variables and localization that shouldn't be delayed.
|
||||
'id:-js-extra'
|
||||
]);
|
||||
|
||||
$this->exclude_scripts = apply_filters('debloat/delay_js_excludes', $excludes);
|
||||
|
||||
$this->include_scripts = array_merge(
|
||||
$this->include_scripts,
|
||||
Util\option_to_array(Plugin::options()->delay_js_includes)
|
||||
);
|
||||
|
||||
// Enable delay adsense.
|
||||
if (Plugin::options()->delay_js_adsense) {
|
||||
$this->include_scripts[] = 'adsbygoogle.js';
|
||||
}
|
||||
|
||||
$this->include_scripts = apply_filters('debloat/delay_js_includes', $this->include_scripts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify the JS file, if possible.
|
||||
*
|
||||
* @param Script $script
|
||||
* @return void
|
||||
*/
|
||||
public function minify_js(Script $script)
|
||||
{
|
||||
$minifier = new Minifier($script);
|
||||
$minifier->process();
|
||||
}
|
||||
}
|
208
wp-content/plugins/debloat/inc/optimize-js/script.php
Normal file
208
wp-content/plugins/debloat/inc/optimize-js/script.php
Normal file
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat\OptimizeJs;
|
||||
|
||||
use Sphere\Debloat\Util;
|
||||
use Sphere\Debloat\Base\Asset;
|
||||
|
||||
/**
|
||||
* Class for handling scripts.
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Script extends Asset
|
||||
{
|
||||
public $defer = false;
|
||||
public $async = false;
|
||||
public $delay = false;
|
||||
|
||||
/**
|
||||
* Dependencies to mirror from core script.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $deps = [];
|
||||
|
||||
/**
|
||||
* Inner content for inline scripts
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $content = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $attrs = [];
|
||||
|
||||
/**
|
||||
* Original HTML tag.
|
||||
*/
|
||||
public $orig_html = '';
|
||||
|
||||
/**
|
||||
* Whether the script is dirty and needs rendering.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $render = false;
|
||||
|
||||
/**
|
||||
* @var callable|null
|
||||
*/
|
||||
protected $minifier;
|
||||
|
||||
public function __construct(string $id, string $url = '', string $orig_html = '')
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->url = $url;
|
||||
$this->orig_url = $url;
|
||||
$this->orig_html = $orig_html;
|
||||
|
||||
if (!$this->id) {
|
||||
$this->id = md5($this->url ?: uniqid('debloat_script', true));
|
||||
}
|
||||
|
||||
// Has to be processed early as it might have to be searched for things like defer.
|
||||
$this->process_content();
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method: Create an instance provided an HTML tag.
|
||||
*
|
||||
* @param string $tag
|
||||
* @return boolean|self
|
||||
*/
|
||||
public static function from_tag(string $tag)
|
||||
{
|
||||
$attrs = Util\parse_attrs($tag);
|
||||
|
||||
// Only process scripts of type javascript or missing type (assumed JS by browser).
|
||||
if (isset($attrs['type']) && strpos($attrs['type'], 'javascript') === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$script = new self($attrs['id'] ?? '', $attrs['src'] ?? '', $tag);
|
||||
|
||||
// Note: We keep 'id' just in case if there was an original id.
|
||||
$script->attrs = \array_diff_key($attrs, array_flip(['src', 'defer', 'async']));
|
||||
|
||||
if (isset($attrs['defer'])) {
|
||||
$script->defer = true;
|
||||
}
|
||||
|
||||
if (isset($attrs['async'])) {
|
||||
$script->async = true;
|
||||
}
|
||||
|
||||
return $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the HTML.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
// Add src if available.
|
||||
if ($this->url) {
|
||||
$this->attrs += [
|
||||
'src' => $this->get_content_url(),
|
||||
'id' => $this->id,
|
||||
];
|
||||
}
|
||||
|
||||
$this->process_delay();
|
||||
|
||||
// Setting local to avoid mutating.
|
||||
$content = $this->content;
|
||||
|
||||
if (!$this->url && $content && is_callable($this->minifier)) {
|
||||
$content = call_user_func($this->minifier, $content);
|
||||
}
|
||||
|
||||
// Add defer for external scripts. For inline scripts, use data uri in src.
|
||||
if ($this->defer) {
|
||||
$this->attrs['defer'] = true;
|
||||
|
||||
if (!$this->url) {
|
||||
$this->attrs['src'] = 'data:text/javascript;base64,' . base64_encode($content);
|
||||
$content = '';
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->async) {
|
||||
$this->attrs['async'] = true;
|
||||
}
|
||||
|
||||
$render_atts = implode(' ', $this->render_attrs($this->attrs));
|
||||
$new_tag = sprintf(
|
||||
'<script%1$s>%2$s</script>',
|
||||
$render_atts ? " $render_atts" : '',
|
||||
!$this->url ? $content : ''
|
||||
);
|
||||
|
||||
return $new_tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the inner content, if any.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process_content()
|
||||
{
|
||||
if (!$this->url) {
|
||||
$this->content = preg_replace('#<script[^>]*>(.+?)</script>\s*$#is', '\\1', $this->orig_html);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a callable minifier function.
|
||||
*
|
||||
* @param callable $minifier
|
||||
* @return void
|
||||
*/
|
||||
public function set_minifier($minifier)
|
||||
{
|
||||
$this->minifier = $minifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process and add delayed script attributes if needed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process_delay()
|
||||
{
|
||||
if (!$this->delay) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Defer / async shouldn't be enabled anymore. Delay load logic implies deferred anyways.
|
||||
$this->defer = false;
|
||||
$this->async = false;
|
||||
|
||||
// Add delay.
|
||||
$this->attrs['data-debloat-delay'] = 1;
|
||||
|
||||
// Normal script with a URL.
|
||||
if (!empty($this->attrs['src'])) {
|
||||
$this->attrs['data-src'] = $this->attrs['src'];
|
||||
}
|
||||
// Inline script with content.
|
||||
elseif ($this->content) {
|
||||
|
||||
if (!empty($this->attrs['type'])) {
|
||||
$this->attrs['data-type'] = $this->attrs['type'];
|
||||
}
|
||||
|
||||
$this->attrs['type'] = 'text/debloat-script';
|
||||
}
|
||||
|
||||
unset($this->attrs['src']);
|
||||
}
|
||||
|
||||
}
|
91
wp-content/plugins/debloat/inc/options.php
Normal file
91
wp-content/plugins/debloat/inc/options.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat;
|
||||
use Sphere\Debloat\Admin\OptionsData;
|
||||
|
||||
/**
|
||||
* A very basic options class.
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Options
|
||||
{
|
||||
/**
|
||||
* @var string|array Option key to use for get_options().
|
||||
*/
|
||||
public $option_key;
|
||||
public $_options;
|
||||
public $defaults = [];
|
||||
|
||||
/**
|
||||
* @param string|array $option_key
|
||||
*/
|
||||
public function __construct($option_key)
|
||||
{
|
||||
$this->option_key = $option_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->load_defaults();
|
||||
|
||||
if (is_array($this->option_key)) {
|
||||
$this->_options = [];
|
||||
|
||||
foreach ($this->option_key as $key) {
|
||||
$this->_options = array_merge($this->_options, (array) get_option($key));
|
||||
}
|
||||
|
||||
} else {
|
||||
$this->_options = (array) get_option($this->option_key);
|
||||
}
|
||||
|
||||
$this->_options = apply_filters('debloat/init_options', $this->_options);
|
||||
}
|
||||
|
||||
public function load_defaults()
|
||||
{
|
||||
if (!class_exists('Sphere\Debloat\Admin\OptionsData')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->defaults = array_reduce(
|
||||
OptionsData::get_all(),
|
||||
function($acc, $option) {
|
||||
$acc[$option['id']] = isset($option['default']) ? $option['default'] : '';
|
||||
return $acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an option
|
||||
*/
|
||||
public function get($key, $fallback = '')
|
||||
{
|
||||
if (array_key_exists($key, $this->_options)) {
|
||||
return $this->_options[$key];
|
||||
}
|
||||
|
||||
if (array_key_exists($key, $this->defaults)) {
|
||||
return $this->defaults[$key];
|
||||
}
|
||||
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
public function __get($key)
|
||||
{
|
||||
return $this->get($key);
|
||||
}
|
||||
|
||||
public function __set($key, $value)
|
||||
{
|
||||
$this->_options[$key] = $value;
|
||||
}
|
||||
}
|
219
wp-content/plugins/debloat/inc/plugin.php
Normal file
219
wp-content/plugins/debloat/inc/plugin.php
Normal file
@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat;
|
||||
|
||||
/**
|
||||
* The Plugin Bootstrap and Setup.
|
||||
*
|
||||
* Dual acts as a container and a facade.
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @method static FileSystem file_system() Extends \WP_Filesystem_Base
|
||||
* @method static Process process()
|
||||
* @method static Options options()
|
||||
* @method static DelayLoad delay_load()
|
||||
* @method static FileCache file_cache()
|
||||
* @method static OptimizeCss\GoogleFonts google_fonts()
|
||||
*/
|
||||
class Plugin
|
||||
{
|
||||
/**
|
||||
* Plugin version
|
||||
*/
|
||||
const VERSION = '1.2.3';
|
||||
|
||||
public static $instance;
|
||||
|
||||
/**
|
||||
* @var string Can be 'dev' for debgging.
|
||||
*/
|
||||
public $env = 'production';
|
||||
|
||||
/**
|
||||
* Path to plugin folder, trailing slashed.
|
||||
*/
|
||||
public $dir_path;
|
||||
public $dir_url;
|
||||
|
||||
public $plugin_file;
|
||||
|
||||
/**
|
||||
* A pseudo service container
|
||||
*/
|
||||
public $container;
|
||||
|
||||
/**
|
||||
* Set it hooks on init.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->dir_path = plugin_dir_path($this->plugin_file);
|
||||
$this->dir_url = plugin_dir_url($this->plugin_file);
|
||||
|
||||
// Fire up the main autoloader.
|
||||
require_once $this->dir_path . 'inc/autoloader.php';
|
||||
new Autoloader([
|
||||
'Sphere\Debloat\\' => $this->dir_path . 'inc',
|
||||
]);
|
||||
|
||||
// Composer autoloader.
|
||||
require_once $this->dir_path . 'vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* Setup and init common requires.
|
||||
*/
|
||||
// Options object with bound constructor args.
|
||||
$this->container['options'] = $this->shared(
|
||||
__NAMESPACE__ . '\Options',
|
||||
[
|
||||
['debloat_options', 'debloat_options_js', 'debloat_options_general']
|
||||
]
|
||||
);
|
||||
|
||||
// The process handler.
|
||||
$this->container['process'] = $this->shared(__NAMESPACE__ . '\Process');
|
||||
|
||||
// File system with lazy init singleton.
|
||||
$this->container['file_system'] = $this->shared(__NAMESPACE__ . '\FileSystem');
|
||||
|
||||
// Delay Load assets.
|
||||
$this->container['delay_load'] = $this->shared(__NAMESPACE__ . '\DelayLoad');
|
||||
|
||||
// File cache handler.
|
||||
$this->container['file_cache'] = $this->shared(__NAMESPACE__ . '\FileCache');
|
||||
|
||||
// Google Fonts is a singleton.
|
||||
$this->container['google_fonts'] = $this->shared(__NAMESPACE__ . '\OptimizeCss\GoogleFonts');
|
||||
|
||||
/**
|
||||
* Admin only requires
|
||||
*
|
||||
* We load these only on Admin side to keep things lean and performant.
|
||||
*
|
||||
* Note on CMB2:
|
||||
* It's used ONLY as an admin side dependency and never even
|
||||
* loaded on the frontend. Use native WP options API on front.
|
||||
*/
|
||||
if (is_admin() || defined('WP_CLI')) {
|
||||
|
||||
$admin = new Admin;
|
||||
$admin->init();
|
||||
|
||||
// We don't want CMB2 backend loaded at all in AJAX requests (admin-ajax.php passes is_admin() test)
|
||||
if (!wp_doing_ajax()) {
|
||||
require_once $this->dir_path . 'vendor/cmb2/init.php';
|
||||
}
|
||||
|
||||
// Path bug fix for cmb2 in composer
|
||||
add_filter('cmb2_meta_box_url', function() {
|
||||
return self::get_instance()->dir_url . 'vendor/cmb2';
|
||||
});
|
||||
|
||||
// WP-CLI commands.
|
||||
if (class_exists('\WP_CLI')) {
|
||||
\WP_CLI::add_command('debloat', '\Sphere\Debloat\WpCli\Commands');
|
||||
}
|
||||
}
|
||||
|
||||
// Utility functions.
|
||||
require_once $this->dir_path . 'inc/util.php';
|
||||
|
||||
$this->register_hooks();
|
||||
|
||||
// Load the options.
|
||||
self::options()->init();
|
||||
|
||||
// Init process to setup hooks.
|
||||
self::process()->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a single instance class for container.
|
||||
*
|
||||
* @param string $class Fully-qualifed class name
|
||||
* @param array|null $args Bound args to pass to constructor
|
||||
*/
|
||||
public function shared($class, $args = null)
|
||||
{
|
||||
return function($fresh = false) use ($class, $args) {
|
||||
static $object;
|
||||
|
||||
if (!$object || $fresh) {
|
||||
|
||||
if (!$args) {
|
||||
$object = new $class;
|
||||
}
|
||||
else {
|
||||
$ref = new \ReflectionClass($class);
|
||||
$object = $ref->newInstanceArgs($args);
|
||||
}
|
||||
}
|
||||
|
||||
return $object;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup hooks actions.
|
||||
*/
|
||||
public function register_hooks()
|
||||
{
|
||||
// Translations
|
||||
add_action('plugins_loaded', array($this, 'load_textdomain'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup translations.
|
||||
*/
|
||||
public function load_textdomain()
|
||||
{
|
||||
load_plugin_textdomain(
|
||||
'debloat',
|
||||
false,
|
||||
basename($this->dir_path) . '/languages'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an object from container.
|
||||
*/
|
||||
public function get($name, $args = array())
|
||||
{
|
||||
if (!isset($this->container[$name])) {
|
||||
throw new \Exception("No container exists with key '{$name}'");
|
||||
}
|
||||
|
||||
$object = $this->container[$name];
|
||||
|
||||
if (is_callable($object)) {
|
||||
return call_user_func_array($object, $args);
|
||||
}
|
||||
else if (is_string($object)) {
|
||||
$object = new $object;
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @uses self::get()
|
||||
*/
|
||||
public static function __callStatic($name, $args = array())
|
||||
{
|
||||
return self::get_instance()->get($name, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public static function get_instance()
|
||||
{
|
||||
if (self::$instance == null) {
|
||||
self::$instance = new static();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
}
|
259
wp-content/plugins/debloat/inc/process.php
Normal file
259
wp-content/plugins/debloat/inc/process.php
Normal file
@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat;
|
||||
|
||||
/**
|
||||
* Debloat processing setup for JS and CSS optimizations.
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Process
|
||||
{
|
||||
/**
|
||||
* Init happens too early around plugins_loaded
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
// Setup a few extra options.
|
||||
Plugin::options()->delay_css_type = 'interact';
|
||||
Plugin::options()->delay_js_type = 'interact';
|
||||
|
||||
// Setup at init but before template_redirect.
|
||||
add_action('init', [$this, 'setup']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup filters for processing
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public function setup()
|
||||
{
|
||||
if ($this->should_process()) {
|
||||
|
||||
// Load integrations.
|
||||
$integrations = array_unique(array_merge(
|
||||
(array) Plugin::options()->integrations_js,
|
||||
(array) Plugin::options()->integrations_css
|
||||
));
|
||||
|
||||
if ($integrations) {
|
||||
if (in_array('elementor', $integrations) && class_exists('\Elementor\Plugin', false)) {
|
||||
new Integrations\Elementor;
|
||||
}
|
||||
|
||||
if (in_array('wpbakery', $integrations) && class_exists('Vc_Manager')) {
|
||||
new Integrations\Wpbakery;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process HTML for inline and local stylesheets.
|
||||
*
|
||||
* wp_ob_end_flush_all() will take care of flushing it.
|
||||
*
|
||||
* Note: Autoptimize starts at priority 2 so we use 3 to process BEFORE AO.
|
||||
*/
|
||||
add_action('template_redirect', function() {
|
||||
if (!apply_filters('debloat/should_process', true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Can't go in should_process() as that's too early.
|
||||
if (function_exists('\amp_is_request') && \amp_is_request()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Shouldn't process feeds, embeds (iframe), or robots.txt request.
|
||||
if (\is_feed() || \is_embed() || \is_robots()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ob_start([$this, 'process_markup']);
|
||||
}, -999);
|
||||
|
||||
// DEBUG: Devs if your output is disappearing - which you need for debugging,
|
||||
// uncomment below and comment the init action above.
|
||||
// add_action('template_redirect', function() { ob_start(); }, -999);
|
||||
// add_action('shutdown', function() {
|
||||
// $content = ob_get_clean();
|
||||
// echo $this->process_markup($content);
|
||||
// }, -10);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process DOM Markup provided with the html.
|
||||
*
|
||||
* @param string $html
|
||||
* @return string
|
||||
*/
|
||||
public function process_markup($html)
|
||||
{
|
||||
do_action('debloat/process_markup', $this);
|
||||
|
||||
if (!$this->is_valid_markup($html)) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
$dom = null;
|
||||
|
||||
if ($this->should_optimize_css()) {
|
||||
$dom = $this->get_dom($html);
|
||||
$optimize = new OptimizeCss($dom, $html);
|
||||
$html = $optimize->process();
|
||||
}
|
||||
|
||||
if ($this->should_optimize_js()) {
|
||||
$optimize_js = new OptimizeJs($html);
|
||||
$html = $optimize_js->process();
|
||||
}
|
||||
|
||||
// Add delay load JS and extras as needed.
|
||||
$html = Plugin::delay_load()->render($html);
|
||||
|
||||
// Failed at processing DOM, return original.
|
||||
if (!$dom) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function is_valid_markup($html)
|
||||
{
|
||||
if (stripos($html, '<html') === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DOM object for the provided HTML.
|
||||
*
|
||||
* @param string $html
|
||||
* @return boolean|\DOMDocument
|
||||
*/
|
||||
protected function get_dom($html)
|
||||
{
|
||||
if (!$html) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$libxml_previous = libxml_use_internal_errors(true);
|
||||
$dom = new \DOMDocument();
|
||||
$result = $dom->loadHTML($html);
|
||||
|
||||
libxml_clear_errors();
|
||||
libxml_use_internal_errors($libxml_previous);
|
||||
|
||||
// Deprecating xpath querying.
|
||||
// if ($result) {
|
||||
// $dom->xpath = new \DOMXPath($dom);
|
||||
// }
|
||||
|
||||
return $result ? $dom : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should any processing be done at all.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function should_process()
|
||||
{
|
||||
if (is_admin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (function_exists('is_customize_preview') && is_customize_preview()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($_GET['nodebloat'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Util\is_elementor()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// WPBakery Page Builder. vc_is_page_editable() isn't reliable at all hooks.
|
||||
if (!empty($_GET['vc_editable'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Plugin::options()->disable_for_admins && current_user_can('manage_options')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the JS be optimized.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function should_optimize_js()
|
||||
{
|
||||
$valid = true;
|
||||
return apply_filters('debloat/should_optimize_js', $valid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the CSS be optimized.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function should_optimize_css()
|
||||
{
|
||||
$valid = Plugin::options()->remove_css || Plugin::options()->optimize_css;
|
||||
return apply_filters('debloat/should_optimize_css', $valid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditions test to see if current page matches in the provided valid conditions.
|
||||
*
|
||||
* @param array $enable_on
|
||||
* @return boolean
|
||||
*/
|
||||
public function check_enabled(array $enable_on)
|
||||
{
|
||||
if (in_array('all', $enable_on)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$conditions = [
|
||||
'pages' => 'is_page',
|
||||
'posts' => 'is_single',
|
||||
'archives' => 'is_archive',
|
||||
'archive' => 'is_archive', // Alias
|
||||
'categories' => 'is_category',
|
||||
'tags' => 'is_tag',
|
||||
'search' => 'is_search',
|
||||
'404' => 'is_404',
|
||||
'home' => function() {
|
||||
return is_home() || is_front_page();
|
||||
},
|
||||
];
|
||||
|
||||
$satisfy = false;
|
||||
foreach ($enable_on as $key) {
|
||||
if (!isset($conditions[$key]) || !is_callable($conditions[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$satisfy = call_user_func($conditions[$key]);
|
||||
|
||||
// Stop going further in loop once satisfied.
|
||||
if ($satisfy) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $satisfy;
|
||||
}
|
||||
}
|
388
wp-content/plugins/debloat/inc/remove-css/remove-css.php
Normal file
388
wp-content/plugins/debloat/inc/remove-css/remove-css.php
Normal file
@ -0,0 +1,388 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat;
|
||||
use Sphere\Debloat\RemoveCss\Sanitizer;
|
||||
use Sphere\Debloat\OptimizeCss\Stylesheet;
|
||||
|
||||
/**
|
||||
* Process stylesheets to debloat and optimize CSS.
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class RemoveCss
|
||||
{
|
||||
/**
|
||||
* @var \DOMDocument
|
||||
*/
|
||||
public $dom;
|
||||
public $html;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $used_markup = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $stylesheets;
|
||||
|
||||
protected $include_sheets = [];
|
||||
protected $exclude_sheets = [];
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @param Stylesheet[] $stylesheets
|
||||
* @param \DOMDocument $dom
|
||||
* @param string $raw_html
|
||||
*/
|
||||
public function __construct($stylesheets, \DOMDocument $dom, string $raw_html)
|
||||
{
|
||||
$this->stylesheets = $stylesheets;
|
||||
$this->dom = $dom;
|
||||
$this->html = $raw_html;
|
||||
}
|
||||
|
||||
public function process()
|
||||
{
|
||||
// Collect all the classes, ids, tags used in DOM.
|
||||
$this->find_used_selectors();
|
||||
|
||||
// Figure out valid sheets.
|
||||
$this->setup_valid_sheets();
|
||||
|
||||
/**
|
||||
* Process and replace stylesheets with CSS cleaned.
|
||||
*/
|
||||
do_action('debloat/remove_css_begin', $this);
|
||||
$allow_selectors = $this->get_allowed_selectors();
|
||||
|
||||
foreach ($this->stylesheets as $sheet) {
|
||||
if (!$this->should_process_stylesheet($sheet)) {
|
||||
// Util\debug_log('Skipping: ' . print_r($sheet, true));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Perhaps not a local file or unreadable.
|
||||
$file_data = $this->process_file_by_url($sheet->url);
|
||||
if (!$file_data) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sheet->content = $file_data['content'];
|
||||
$sheet->file = $file_data['file'];
|
||||
|
||||
// Parsed sheet will be cached instead of being parsed by Sabberworm again.
|
||||
$this->setup_sheet_cache($sheet);
|
||||
|
||||
/**
|
||||
* Fire up sanitizer to process and remove unused CSS.
|
||||
*/
|
||||
$sanitizer = new Sanitizer($sheet, $this->used_markup, $allow_selectors);
|
||||
$sanitized_css = $sanitizer->sanitize();
|
||||
|
||||
// Store sizes for debug info.
|
||||
$sheet->original_size = strlen($sheet->content);
|
||||
$sheet->new_size = $sanitized_css ? strlen($sanitized_css) : $sheet->original_size;
|
||||
|
||||
if ($sanitized_css) {
|
||||
|
||||
// Pre-render as we'll have to add a delayed css tag as well, if enabled.
|
||||
$sheet->content = $sanitized_css;
|
||||
$sheet->render_id = 'debloat-' . $sheet->id;
|
||||
$sheet->set_render('inline');
|
||||
$replacement = $sheet->render();
|
||||
|
||||
// Add tags for delayed CSS files.
|
||||
if (Plugin::delay_load()->should_delay_css()) {
|
||||
|
||||
$sheet->delay_type = Plugin::options()->delay_css_type;
|
||||
$sheet->set_render('delay');
|
||||
$sheet->has_delay = true;
|
||||
|
||||
// Add the delay load CSS tag in addition to inlined sanitized CSS above.
|
||||
$replacement .= $sheet->render();
|
||||
}
|
||||
|
||||
$sheet->set_render('remove_css', $replacement);
|
||||
|
||||
// Save in parsed css cache if not already saved.
|
||||
$this->save_sheet_cache($sheet);
|
||||
}
|
||||
|
||||
// Free up memory.
|
||||
$sheet->content = '';
|
||||
$sheet->parsed_data = '';
|
||||
}
|
||||
|
||||
// $this->stylesheets = array_map(function($sheet) {
|
||||
// if (isset($sheet->original_size)) {
|
||||
// $sheet->saved = $sheet->original_size - $sheet->new_size;
|
||||
// }
|
||||
// return $sheet;
|
||||
// }, $this->stylesheets);
|
||||
|
||||
$total = array_reduce($this->stylesheets, function($acc, $item) {
|
||||
if (!empty($item->original_size)) {
|
||||
$acc += ($item->original_size - $item->new_size);
|
||||
}
|
||||
return $acc;
|
||||
}, 0);
|
||||
|
||||
$this->html .= "\n<!-- Debloat Remove CSS Saved: {$total} bytes. -->";
|
||||
|
||||
return $this->html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add parsed data cache to stylesheet object. Will be used by save_sheet_cache later.
|
||||
*
|
||||
* @param Stylesheet $sheet
|
||||
* @return void
|
||||
*/
|
||||
public function setup_sheet_cache(Stylesheet $sheet)
|
||||
{
|
||||
if (!isset($sheet->file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cache = get_transient($this->get_transient_id($sheet));
|
||||
if ($cache && $cache['mtime'] < Plugin::file_system()->mtime($sheet->file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($cache && !empty($cache['data'])) {
|
||||
$sheet->parsed_data = $cache['data'];
|
||||
$sheet->has_cache = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected function get_transient_id($sheet)
|
||||
{
|
||||
return substr('debloat_sheet_cache_' . $sheet->id, 0, 190);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache the parsed data.
|
||||
*
|
||||
* Note: This doesn't cache whole CSS as that would vary based on found selectors.
|
||||
*
|
||||
* @param Stylesheet $sheet
|
||||
* @return void
|
||||
*/
|
||||
public function save_sheet_cache(Stylesheet $sheet)
|
||||
{
|
||||
if ($sheet->has_cache) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cache_data = [
|
||||
'data' => $sheet->parsed_data,
|
||||
'mtime' => Plugin::file_system()->mtime($sheet->file)
|
||||
];
|
||||
|
||||
// With expiry; won't be auto-loaded.
|
||||
set_transient($this->get_transient_id($sheet), $cache_data, MONTH_IN_SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup includes and excludes based on options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setup_valid_sheets()
|
||||
{
|
||||
$default_excludes = [
|
||||
'wp-includes/css/dashicons.css',
|
||||
'admin-bar.css',
|
||||
'wp-mediaelement'
|
||||
];
|
||||
|
||||
$excludes = array_merge(
|
||||
$default_excludes,
|
||||
Util\option_to_array(Plugin::options()->remove_css_excludes)
|
||||
);
|
||||
|
||||
$this->exclude_sheets = apply_filters('debloat/remove_css_excludes', $excludes, $this);
|
||||
|
||||
if (Plugin::options()->remove_css_all) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Plugin::options()->remove_css_theme) {
|
||||
$this->include_sheets[] = content_url('themes') . '/*';
|
||||
}
|
||||
|
||||
if (Plugin::options()->remove_css_plugins) {
|
||||
$this->include_sheets[] = content_url('plugins') . '/*';
|
||||
}
|
||||
|
||||
$this->include_sheets = array_merge(
|
||||
$this->include_sheets,
|
||||
Util\option_to_array(Plugin::options()->remove_css_includes)
|
||||
);
|
||||
|
||||
$this->include_sheets = apply_filters('debloat/remove_css_includes', $this->include_sheets, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if stylesheet should be processed, based on exclusion and inclusion
|
||||
* rules and settings.
|
||||
*
|
||||
* @param Stylesheet $sheet
|
||||
* @return boolean
|
||||
*/
|
||||
public function should_process_stylesheet(Stylesheet $sheet)
|
||||
{
|
||||
// Handle manual excludes first.
|
||||
if ($this->exclude_sheets) {
|
||||
foreach ($this->exclude_sheets as $exclude) {
|
||||
if (Util\asset_match($exclude, $sheet)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All stylesheets are valid.
|
||||
if (Plugin::options()->remove_css_all) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($this->include_sheets as $include) {
|
||||
if (Util\asset_match($include, $sheet)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the allowed selectors in correct data format to be used by sanitizer.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_allowed_selectors()
|
||||
{
|
||||
// Allowed selectors of type 'any': simple match in selector string.
|
||||
$allowed_any = array_map(
|
||||
function($value) {
|
||||
if (!$value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'any',
|
||||
'search' => [$value]
|
||||
];
|
||||
},
|
||||
Util\option_to_array((string) Plugin::options()->allow_css_selectors)
|
||||
);
|
||||
|
||||
// Conditional selectors.
|
||||
$allowed_conditionals = [];
|
||||
$conditionals = Plugin::options()->allow_css_conditionals
|
||||
? (array) Plugin::options()->allow_conditionals_data
|
||||
: [];
|
||||
|
||||
if ($conditionals) {
|
||||
$allowed_conditionals = array_map(
|
||||
function($value) {
|
||||
if (!isset($value['match'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$value['class'] = preg_replace('/^\./', '', trim($value['match']));
|
||||
|
||||
if ($value['type'] !== 'prefix' && isset($value['selectors'])) {
|
||||
$value['search'] = Util\option_to_array($value['selectors']);
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
$conditionals
|
||||
);
|
||||
}
|
||||
|
||||
$allowed = apply_filters(
|
||||
'debloat/allow_css_selectors',
|
||||
array_filter(array_merge($allowed_any, $allowed_conditionals)),
|
||||
$this
|
||||
);
|
||||
|
||||
return $allowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all the classes, ids, and tags used in the document.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function find_used_selectors()
|
||||
{
|
||||
$this->used_markup = [
|
||||
'tags' => [],
|
||||
'classes' => [],
|
||||
'ids' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var DOMElement $node
|
||||
*/
|
||||
$classes = [];
|
||||
foreach ($this->dom->getElementsByTagName('*') as $node) {
|
||||
$this->used_markup['tags'][ $node->tagName ] = 1;
|
||||
|
||||
// Collect tag classes.
|
||||
if ($node->hasAttribute('class')) {
|
||||
$class = $node->getAttribute('class');
|
||||
$ele_classes = preg_split('/\s+/', $class);
|
||||
array_push($classes, ...$ele_classes);
|
||||
}
|
||||
|
||||
if ($node->hasAttribute('id')) {
|
||||
$this->used_markup['ids'][ $node->getAttribute('id') ] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the classes.
|
||||
$classes = array_filter(array_unique($classes));
|
||||
if ($classes) {
|
||||
$this->used_markup['classes'] = array_fill_keys($classes, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a stylesheet via URL
|
||||
*
|
||||
* @uses Plugin::file_system()
|
||||
* @uses Plugin::file_system()->url_to_local()
|
||||
* @return boolean|array With 'content' and 'file'.
|
||||
*/
|
||||
public function process_file_by_url($url)
|
||||
{
|
||||
// Try to get local path for this stylesheet
|
||||
$file = Plugin::file_system()->url_to_local($url);
|
||||
if (!$file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We can only support .css files yet
|
||||
if (substr($file, -4) !== '.css') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$content = Plugin::file_system()->get_contents($file);
|
||||
if (!$content) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
'content' => $content,
|
||||
'file' => $file
|
||||
];
|
||||
}
|
||||
}
|
529
wp-content/plugins/debloat/inc/remove-css/sanitizer.php
Normal file
529
wp-content/plugins/debloat/inc/remove-css/sanitizer.php
Normal file
@ -0,0 +1,529 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat\RemoveCss;
|
||||
|
||||
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\RuleSet\DeclarationBlock;
|
||||
use Sabberworm\CSS\Settings;
|
||||
use Sabberworm\CSS\Value\URL;
|
||||
|
||||
use Sphere\Debloat\OptimizeCss\Stylesheet;
|
||||
use Sphere\Debloat\Util;
|
||||
|
||||
/**
|
||||
* Sanitizer removes the unnecessary CSS, provided a stylesheet.
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Sanitizer
|
||||
{
|
||||
/**
|
||||
* @var Stylesheet
|
||||
*/
|
||||
protected $sheet;
|
||||
|
||||
protected $css;
|
||||
protected $used_markup = [
|
||||
'classes' => [],
|
||||
'tags' => [],
|
||||
'ids' => []
|
||||
];
|
||||
|
||||
protected $allow_selectors = [];
|
||||
|
||||
/**
|
||||
* @param Stylesheet $sheet
|
||||
* @param array $used_markup {
|
||||
* @type array $classes
|
||||
* @type array $tags
|
||||
* @type array $ids
|
||||
* }
|
||||
*/
|
||||
public function __construct(Stylesheet $sheet, array $used_markup, $allow = [])
|
||||
{
|
||||
$this->sheet = $sheet;
|
||||
$this->css = $sheet->content;
|
||||
$this->used_markup = array_replace($this->used_markup, $used_markup);
|
||||
|
||||
$this->allow_selectors = $allow;
|
||||
|
||||
}
|
||||
|
||||
public function set_cache($data)
|
||||
{
|
||||
$this->cache = $data;
|
||||
}
|
||||
|
||||
public function sanitize()
|
||||
{
|
||||
$data = $this->sheet->parsed_data ?: [];
|
||||
if (!$data) {
|
||||
|
||||
// Strip the dreaded UTF-8 byte order mark (BOM, \uFEFF). Ref: https://github.com/sabberworm/PHP-CSS-Parser/issues/150
|
||||
$this->css = preg_replace('/^\xEF\xBB\xBF/', '', $this->css);
|
||||
|
||||
$config = Settings::create()->withMultibyteSupport(false);
|
||||
$parser = new CSSParser($this->css, $config);
|
||||
$parsed = $parser->parse();
|
||||
|
||||
// Fix relative URLs.
|
||||
$this->convert_urls($parsed);
|
||||
$data = $this->transform_data($parsed);
|
||||
|
||||
$this->sheet->parsed_data = $data;
|
||||
}
|
||||
|
||||
$this->process_allowed_selectors();
|
||||
|
||||
return $this->render_css($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert relative URLs to full URLs for inline inclusion or changed paths.
|
||||
*
|
||||
* @param Document $data
|
||||
* @return void
|
||||
*/
|
||||
public function convert_urls(Document $data)
|
||||
{
|
||||
$base_url = preg_replace('#[^/]+\?.*$#', '', $this->sheet->url);
|
||||
|
||||
$values = $data->getAllValues();
|
||||
foreach ($values as $value) {
|
||||
if (!($value instanceof URL)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = $value->getURL()->getString();
|
||||
// if (substr($url, 0, 5) === 'data:') {
|
||||
// continue;
|
||||
// }
|
||||
if (preg_match('/^(https?|data):/', $url)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parsed_url = parse_url($url);
|
||||
|
||||
// Skip known host and protocol-relative paths.
|
||||
if (!empty($parsed_url['host']) || empty($parsed_url['path']) || $parsed_url['path'][0] === '/') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$new_url = $base_url . $url;
|
||||
$value->getUrl()->setString($new_url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform data structure to store in our format. This data will be used without
|
||||
* loading CSS Parser on further requests.
|
||||
*
|
||||
* @param CSSBlockList $data
|
||||
* @return array
|
||||
*/
|
||||
public function transform_data(CSSBlockList $data)
|
||||
{
|
||||
$items = [];
|
||||
foreach ($data->getContents() as $content) {
|
||||
if ($content instanceof AtRuleBlockList) {
|
||||
$items[] = [
|
||||
'rulesets' => $this->transform_data($content),
|
||||
'at_rule' => "@{$content->atRuleName()} {$content->atRuleArgs()}",
|
||||
];
|
||||
}
|
||||
else {
|
||||
$item = [
|
||||
//'css' => $content->render(OutputFormat::createPretty())
|
||||
'css' => $content->render(OutputFormat::createCompact())
|
||||
];
|
||||
|
||||
if ($content instanceof DeclarationBlock) {
|
||||
$item['selectors'] = $this->parse_selectors($content->getSelectors());
|
||||
}
|
||||
|
||||
$items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse selectors to get classes, id, tags and attrs.
|
||||
*
|
||||
* @param array $selectors
|
||||
* @return array
|
||||
*/
|
||||
protected function parse_selectors($selectors)
|
||||
{
|
||||
$selectors = array_map(
|
||||
function($sel) {
|
||||
return $sel->__toString();
|
||||
},
|
||||
$selectors
|
||||
);
|
||||
|
||||
$selectors_data = [];
|
||||
foreach ($selectors as $selector) {
|
||||
$data = [
|
||||
'classes' => [],
|
||||
'ids' => [],
|
||||
'tags' => [],
|
||||
// 'pseudo' => [],
|
||||
'attrs' => [],
|
||||
'selector' => trim($selector),
|
||||
];
|
||||
|
||||
// if (strpos($selector, ':root') !== false) {
|
||||
// $data['pseudo'][':root'] = 1;
|
||||
// }
|
||||
|
||||
// Based on AMP plugin.
|
||||
// Handle :not() and pseudo selectors to eliminate false negatives.
|
||||
$selector = preg_replace('/(?<!\\\\)::?[a-zA-Z0-9_-]+(\(.+?\))?/', '', $selector);
|
||||
|
||||
// Get attributes but remove them from the selector to prevent false positives
|
||||
// from within attribute selector.
|
||||
$selector = preg_replace_callback(
|
||||
'/\[([A-Za-z0-9_:-]+)(\W?=[^\]]+)?\]/',
|
||||
function($matches) use (&$data) {
|
||||
$data['attrs'][] = $matches[1];
|
||||
return '';
|
||||
},
|
||||
$selector
|
||||
);
|
||||
|
||||
// Extract class names.
|
||||
$selector = preg_replace_callback(
|
||||
// The `\\\\.` will allow any char via escaping, like the colon in `.lg\:w-full`.
|
||||
'/\.((?:[a-zA-Z0-9_-]+|\\\\.)+)/',
|
||||
function($matches) use (&$data) {
|
||||
$data['classes'][] = stripslashes($matches[1]);
|
||||
return '';
|
||||
},
|
||||
$selector
|
||||
);
|
||||
|
||||
// Extract IDs.
|
||||
$selector = preg_replace_callback(
|
||||
'/#([a-zA-Z0-9_-]+)/',
|
||||
function( $matches ) use (&$data) {
|
||||
$data['ids'][] = $matches[1];
|
||||
return '';
|
||||
},
|
||||
$selector
|
||||
);
|
||||
|
||||
// Extract tag names.
|
||||
$selector = preg_replace_callback(
|
||||
'/[a-zA-Z0-9_-]+/',
|
||||
function($matches) use (&$data) {
|
||||
$data['tags'][] = $matches[0];
|
||||
return '';
|
||||
},
|
||||
$selector
|
||||
);
|
||||
|
||||
$selectors_data[] = array_filter($data);
|
||||
}
|
||||
|
||||
return array_filter($selectors_data);
|
||||
}
|
||||
|
||||
public function render_css($data)
|
||||
{
|
||||
$rendered = [];
|
||||
foreach ($data as $item) {
|
||||
|
||||
// Has CSS.
|
||||
if (isset($item['css'])) {
|
||||
$css = $item['css'];
|
||||
|
||||
// Render only if at least one selector meets the should_include criteria.
|
||||
$should_render = !isset($item['selectors']) ||
|
||||
0 !== count(
|
||||
array_filter(
|
||||
$item['selectors'],
|
||||
function($selector) {
|
||||
return $this->should_include($selector);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if ($should_render) {
|
||||
$rendered[] = $css;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Nested ruleset.
|
||||
if (!empty($item['rulesets'])) {
|
||||
$child_rulesets = $this->render_css($item['rulesets']);
|
||||
|
||||
if ($child_rulesets) {
|
||||
$rendered[] = sprintf(
|
||||
'%s { %s }',
|
||||
$item['at_rule'],
|
||||
$child_rulesets
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return implode("", $rendered);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-process allowed selectors.
|
||||
*
|
||||
* Convert data structures in proper format, mainly for performance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function process_allowed_selectors()
|
||||
{
|
||||
foreach ($this->allow_selectors as $key => $value) {
|
||||
|
||||
// Check if selector rule valid for current sheet.
|
||||
if (isset($value['sheet']) && !Util\asset_match($value['sheet'], $this->sheet)) {
|
||||
unset($this->allow_selectors[$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->add_search_regex($value);
|
||||
$regex = $value['search_regex'] ?? '';
|
||||
|
||||
// Pre-compute the matching regex for performance.
|
||||
if (isset($value['search'])) {
|
||||
$value['search'] = array_filter((array) $value['search']);
|
||||
|
||||
// If we still have something.
|
||||
if ($value['search']) {
|
||||
$loose_regex = '(' . implode('|', array_map('preg_quote', $value['search'])) . ')(?=\s|\.|\:|,|\[|$)';
|
||||
|
||||
// Combine with search_regex if available.
|
||||
$regex = $regex ? "($loose_regex|$regex)" : $loose_regex;
|
||||
}
|
||||
}
|
||||
|
||||
if ($regex) {
|
||||
$value['computed_search_regex'] = $regex;
|
||||
}
|
||||
|
||||
$this->allow_selectors[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add search regex to array by converting astrisks to proper regex search.
|
||||
*
|
||||
* @param array $value
|
||||
* @return array
|
||||
*/
|
||||
protected function add_search_regex(array $value)
|
||||
{
|
||||
if (isset($value['search_regex'])) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (isset($value['search'])) {
|
||||
|
||||
$value['search'] = (array) $value['search'];
|
||||
$regex = [];
|
||||
|
||||
foreach ($value['search'] as $key => $search) {
|
||||
if (strpos($search, '*') !== false) {
|
||||
$search = trim($search);
|
||||
|
||||
// Optimize regex for starting.
|
||||
// Note: Ending asterisk removal isn't necessary. PCRE engine is optimized for that.
|
||||
$has_first_asterisk = 0;
|
||||
$search = preg_replace('/^\*(.+?)/', '\\1', $search, 1, $has_first_asterisk);
|
||||
|
||||
// 1. Space and asterisk matches a class itself, followed by space (child), or comma separator.
|
||||
// 2. Only asterisk is considered more of a prefix/suffix and .class* will match .classname too.
|
||||
$search = preg_quote($search);
|
||||
$search = str_replace(' \*', '(\s|$|,|\:).*?', $search);
|
||||
$search = str_replace('\*', '.*?', $search);
|
||||
|
||||
// Note: To prevent ^(.*?) which is slow, we add starting position match only
|
||||
// if the search doesn't start with asterisk match.
|
||||
$regex[] = ($has_first_asterisk ? '' : '^') . $search;
|
||||
|
||||
unset($value['search'][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($regex) {
|
||||
$value['search_regex'] = '(' . implode('|', $regex) . ')';
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Whether to include a selector in the output.
|
||||
*
|
||||
* @param array $selector {
|
||||
* @type string[]|null $classes
|
||||
* @type string[]|null $ids
|
||||
* @type string[]|null $tags
|
||||
* }
|
||||
* @return boolean
|
||||
*/
|
||||
public function should_include($selector)
|
||||
{
|
||||
// :root is always valid.
|
||||
// Note: Selectors of type `:root .class` will not match this but will be validated below
|
||||
// if .class is used, as intended.
|
||||
if ($selector['selector'] === ':root') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If it's an attribute selector with nothing else, it should be kept. Perhaps *[attr] or [attr].
|
||||
if (!empty($selector['attrs'])
|
||||
&& (empty($selector['classes']) && empty($selector['ids']) && empty($selector['tags']))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check allow list.
|
||||
// @todo move to cached pre-processed. Clear on settings change.
|
||||
|
||||
// $this->allow_selectors = [
|
||||
// [
|
||||
// 'type' => 'any',
|
||||
// 'search' => '.auth-modal',
|
||||
// ],
|
||||
// [
|
||||
// 'type' => 'prefix',
|
||||
// 'class' => 's-dark'
|
||||
// ],
|
||||
// [
|
||||
// 'type' => 'class',
|
||||
// 'class' => 'has-lb',
|
||||
// 'search' => ['.mfp-']
|
||||
// ],
|
||||
// [
|
||||
// 'type' => 'any',
|
||||
// 'class' => 'has-lb',
|
||||
// 'search' => ['.mfp-']
|
||||
// ],
|
||||
// ];
|
||||
|
||||
if ($this->allow_selectors) {
|
||||
foreach ($this->allow_selectors as $include) {
|
||||
|
||||
/**
|
||||
* Prefix-based + all other classes/tags/etc. in selector exist in doc.
|
||||
*
|
||||
* Note: It's basically to ignore the first class and include the sub-classes based
|
||||
* on their existence in doc. Example: .scheme-dark or .scheme-light.
|
||||
*/
|
||||
if ($include['type'] === 'prefix') {
|
||||
|
||||
// Check if exact match.
|
||||
if (('.' . $include['class']) === $selector['selector']) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if first class matches.
|
||||
$has_prefix = $include['class'] === substr($selector['selector'], 1, strlen($include['class']));
|
||||
if ($has_prefix) {
|
||||
|
||||
// Will check for validity later below. Remove first class as it's allowed.
|
||||
if (isset($selector['classes'])) {
|
||||
$selector['classes'] = array_diff($selector['classes'], [$include['class']]);
|
||||
}
|
||||
|
||||
// WARNING: Due to this break, if there's a rule to allow all selectors of this prefix
|
||||
// that appear later, it won't be validated.
|
||||
// @todo Sort prefixes to be at the end or run them later.
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if a class exists in document.
|
||||
if ($include['type'] === 'class') {
|
||||
if (!$this->is_used($include['class'], 'classes')) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Simple search selector string.
|
||||
// $search = !empty($include['search']) ? (array) $include['search'] : [];
|
||||
|
||||
// Any type, normal selector string match.
|
||||
// Note: The regex is equal at n=1 and faster at n>1, surprisingly.
|
||||
// if ($search) {
|
||||
// foreach ($search as $to_match) {
|
||||
// if (strpos($selector['selector'], $to_match) !== false) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Pre-computed regex - combined 'search' and 'search_regex'.
|
||||
if (!empty($include['computed_search_regex'])) {
|
||||
if (preg_match('#' . $include['computed_search_regex'] . '#', $selector['selector'])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$valid = true;
|
||||
if (
|
||||
// Check if all classes are used.
|
||||
(!empty($selector['classes']) && !$this->is_used($selector['classes'], 'classes'))
|
||||
|
||||
// Check if all the ids are used.
|
||||
|| (!empty($selector['ids']) && !$this->is_used($selector['ids'], 'ids'))
|
||||
|
||||
// Check for the target tags in used.
|
||||
|| (!empty($selector['tags']) && !$this->is_used($selector['tags'], 'tags'))
|
||||
) {
|
||||
$valid = false;
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if a selector classes, ids, or tags are used in the doc (provided in $this->used_markup).
|
||||
*
|
||||
* @param string|array $targets
|
||||
* @param string $type 'classes', 'tags', or 'ids'.
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_used($targets, $type = '')
|
||||
{
|
||||
if (!$type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_array($targets)) {
|
||||
$targets = (array) $targets;
|
||||
}
|
||||
|
||||
foreach ($targets as $target) {
|
||||
// All targets must exist.
|
||||
if (!isset($this->used_markup[$type][$target])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
126
wp-content/plugins/debloat/inc/util.php
Normal file
126
wp-content/plugins/debloat/inc/util.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat\Util;
|
||||
use Sphere\Debloat\Plugin;
|
||||
|
||||
/**
|
||||
* Convert an option value to array format, and clean it.
|
||||
*
|
||||
* @param string|array $value
|
||||
* @return array
|
||||
*/
|
||||
function option_to_array($value, $separator = "\n")
|
||||
{
|
||||
$value = $value ?: [];
|
||||
|
||||
if (is_string($value)) {
|
||||
$value = array_map('trim', explode($separator, $value));
|
||||
}
|
||||
|
||||
if (!is_array($value)) {
|
||||
throw new \Exception('A string or array is expected.');
|
||||
}
|
||||
|
||||
$value = array_filter($value);
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attributes given an HTML tag.
|
||||
*
|
||||
* @param string $tag
|
||||
* @return array
|
||||
*/
|
||||
function parse_attrs($tag)
|
||||
{
|
||||
// Should be a valid html tag with attributes.
|
||||
preg_match('#<\w+\s*([^>]*)/?>#', $tag, $match);
|
||||
if (!$match) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$attr_string = $match[1];
|
||||
|
||||
$regex = '(?P<keys>[_a-zA-Z][-\w:.]*)'
|
||||
. '(?:' // Attribute value.
|
||||
. '\s*=\s*' // All values begin with '='.
|
||||
. '(?:'
|
||||
. '"([^"]*)"' // Double-quoted.
|
||||
. '|'
|
||||
. "'([^']*)'" // Single-quoted.
|
||||
. '|'
|
||||
. '([^\s"\']+)' // Non-quoted.
|
||||
. '(?:\s|$)' // Must have a space.
|
||||
. ')'
|
||||
. '|'
|
||||
. '(?:\s|$)' // If attribute has no value, space is required.
|
||||
. ')';
|
||||
|
||||
preg_match_all('#' . $regex . '#i', $attr_string, $matches);
|
||||
|
||||
$attrs = [];
|
||||
foreach ((array) $matches['keys'] as $i => $key) {
|
||||
|
||||
// Any of the capturing groups for double-quoted, single-quoted or non-quoted.
|
||||
$value = $matches[2][$i] ?: $matches[3][$i] ?: $matches[4][$i];
|
||||
$attrs[$key] = $value;
|
||||
}
|
||||
|
||||
return $attrs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match an asset against a string based search.
|
||||
*
|
||||
* @param string $match Search string.
|
||||
* @param object $asset Asset object.
|
||||
* @param string $search_prop Property to search in.
|
||||
* @return boolean
|
||||
*/
|
||||
function asset_match($match, $asset, $search_prop = 'url')
|
||||
{
|
||||
$search_prop = $search_prop ?: 'url';
|
||||
$search = trim($match);
|
||||
|
||||
// Starts with id: or url: - search in specific properties.
|
||||
if (preg_match('/^(id|url):\s*(.+?)$/', $search, $matches)) {
|
||||
$search_prop = $matches[1];
|
||||
$search = $matches[2];
|
||||
}
|
||||
|
||||
$search = preg_quote(trim($search));
|
||||
$search = str_replace('\*', '(.+?)', $search);
|
||||
|
||||
return preg_match('#' . $search . '#i', $asset->{$search_prop});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if it's a call from Elementor Page Builder preview.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
function is_elementor($preview_check = true)
|
||||
{
|
||||
// Elmentor may use AJAX to get widget configs etc.
|
||||
if (isset($_REQUEST['action']) && $_REQUEST['action'] === 'elementor_ajax') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!class_exists('\Elementor\Plugin', false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($preview_check && isset($_REQUEST['elementor-preview'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return \Elementor\Plugin::$instance->editor->is_edit_mode();
|
||||
}
|
||||
|
||||
function debug_log($message)
|
||||
{
|
||||
if (Plugin::get_instance()->env === 'dev') {
|
||||
echo wp_kses_post($message); // phpcs:ignore WordPress.Security.EscapeOutput - Hardcoded debug output only enabled when environment is set to 'dev'.
|
||||
}
|
||||
}
|
55
wp-content/plugins/debloat/inc/wp-cli/commands.php
Normal file
55
wp-content/plugins/debloat/inc/wp-cli/commands.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat\WpCli;
|
||||
use Sphere\Debloat\Admin\Cache;
|
||||
|
||||
/**
|
||||
* Commands for WPCLI
|
||||
*/
|
||||
class Commands extends \WP_CLI_Command {
|
||||
|
||||
/**
|
||||
* Flush the Debloat cache.
|
||||
*
|
||||
* [--network]
|
||||
* Flush CSS Cache for all the sites in the network.
|
||||
*
|
||||
* ## EXAMPLES
|
||||
*
|
||||
* 1. wp debloat empty-cache
|
||||
* - Delete all the cached content.
|
||||
*
|
||||
* 2. wp debloat empty-cache --network
|
||||
* - Delete all the cached content including all transients for sites in a network.
|
||||
*
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
* @alias empty-cache
|
||||
*/
|
||||
public function empty_cache($args, $assoc_args)
|
||||
{
|
||||
$network = !empty($assoc_args['network']) && is_multisite();
|
||||
$cache = new Cache;
|
||||
|
||||
if ($network) {
|
||||
/** @var \WP_Site[] $blogs */
|
||||
$blogs = get_sites();
|
||||
|
||||
foreach ($blogs as $key => $blog) {
|
||||
$blog_id = $blog->blog_id;
|
||||
switch_to_blog($blog_id);
|
||||
|
||||
$cache->empty();
|
||||
|
||||
\WP_CLI::success('Emptied debloat cache for site - ' . get_option('home'));
|
||||
|
||||
restore_current_blog();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$cache->empty();
|
||||
\WP_CLI::success('Emptied all debloat cache.');
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user