first commit

This commit is contained in:
User A0264400
2026-04-01 23:20:16 +03:00
commit a766acdc90
23071 changed files with 4933189 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
.factory-control-dont_move_jquery_to_footer input[type="checkbox"] {
float: left;
margin-left: 3px;
}
.factory-control-dont_move_jquery_to_footer .help-block {
margin: 0;
padding: 3px 0 3px 25px;
}
/*# sourceMappingURL=settings.css.map */

View File

@@ -0,0 +1,159 @@
<?php
/**
* Admin boot
* @author Webcraftic <wordpress.webraftic@gmail.com>
* @copyright Webcraftic 25.05.2017
* @version 1.0
*/
// Exit if accessed directly
if( !defined('ABSPATH') ) {
exit;
}
/**
* Печатает ошибки совместимости с похожими плагинами
*/
add_action('wbcr/factory/admin_notices', function ($notices, $plugin_name) {
if( $plugin_name != WMAC_Plugin::app()->getPluginName() ) {
return $notices;
}
if( is_plugin_active('autoptimize/autoptimize.php') ) {
$notice_text = __('Clearfy: Minify and Combine component is not compatible with the Autoptimize plugin, please do not use them together to avoid conflicts. Please disable the Minify and Combine component', 'minify-and-combine');
if( class_exists('WCL_Plugin') ) {
$component_button = WCL_Plugin::app()->getInstallComponentsButton('internal', 'minify_and_combine');
$notice_text .= ' ' . $component_button->getLink();
}
$notices[] = array(
'id' => 'mac_plugin_compatibility',
'type' => 'error',
'classes' => array('wbcr-hide-after-action'),
'dismissible' => false,
'dismiss_expires' => 0,
'text' => '<p>' . $notice_text . '</p>'
);
}
return $notices;
}, 10, 2);
add_filter("wbcr_clearfy_group_options", function ($options) {
/**
* Js optimize
*/
$options[] = array(
'name' => 'js_optimize',
'title' => __('Optimize JavaScript Code?', 'minify-and-combine'),
'tags' => array('optimize_performance', 'optimize_code', 'hide_my_wp')
);
$options[] = array(
'name' => 'js_aggregate',
'title' => __('Aggregate JS-files?', 'minify-and-combine'),
'tags' => array('optimize_performance', 'optimize_code')
);
$options[] = array(
'name' => 'js_include_inline',
'title' => __('Also aggregate inline JS?', 'minify-and-combine'),
'tags' => array()
);
$options[] = array(
'name' => 'js_forcehead',
'title' => __('Force JavaScript in &lt;head&gt;?', 'minify-and-combine'),
'tags' => array()
);
$options[] = array(
'name' => 'js_exclude',
'title' => __('Exclude scripts from Мinify And Combine:', 'minify-and-combine'),
'tags' => array()
);
$options[] = array(
'name' => 'js_trycatch',
'title' => __('Add try-catch wrapping?', 'minify-and-combine'),
'tags' => array()
);
/**
* CSS optimize
*/
$options[] = array(
'name' => 'css_optimize',
'title' => __('Optimize CSS Code?', 'minify-and-combine'),
'tags' => array('optimize_performance', 'optimize_code', 'hide_my_wp')
);
$options[] = array(
'name' => 'css_aggregate',
'title' => __('Aggregate CSS-files?', 'minify-and-combine'),
'tags' => array('optimize_performance')
);
$options[] = array(
'name' => 'css_include_inline',
'title' => __('Also aggregate inline CSS?', 'minify-and-combine'),
'tags' => array()
);
$options[] = array(
'name' => 'css_datauris',
'title' => __('Generate data: URIs for images?', 'minify-and-combine'),
'tags' => array()
);
$options[] = array(
'name' => 'css_defer',
'title' => __('Inline and Defer CSS?', 'minify-and-combine'),
'tags' => array()
);
$options[] = array(
'name' => 'css_inline',
'title' => __('Inline all CSS?', 'minify-and-combine'),
'tags' => array()
);
$options[] = array(
'name' => 'css_exclude',
'title' => __('Exclude CSS from Мinify And Combine', 'minify-and-combine'),
'tags' => array()
);
$options[] = array(
'name' => 'css_critical',
'title' => __('Critical CSS files', 'minify-and-combine'),
'tags' => array()
);
$options[] = array(
'name' => 'css_critical_style',
'title' => __('Critical CSS code', 'minify-and-combine'),
'tags' => array()
);
return $options;
});
/**
* Adds a new mode to the Quick Setup page
*
* @param array $mods
* @return mixed
*/
add_filter("wbcr_clearfy_allow_quick_mods", function ($mods) {
if( !defined('WHTM_PLUGIN_ACTIVE') ) {
$title = __('One click optimize scripts (js, css)', 'minify-and-combine');
} else {
$title = __('One click optimize html code and scripts', 'minify-and-combine');
}
$mod['optimize_code'] = array(
'title' => $title,
'icon' => 'dashicons-performance'
);
return $mod + $mods;
});

View File

@@ -0,0 +1,2 @@
<?php
// Silence is golden.

View File

@@ -0,0 +1,601 @@
<?php
// Exit if accessed directly
if( !defined('ABSPATH') ) {
exit;
}
/**
* The page Settings.
*
* @since 1.0.0
*/
class WMAC_MinifyAndCombineSettingsPage extends WBCR\Factory_Templates_134\Pages\PageBase {
/**
* {@inheritDoc}
*
* @var string
*/
public $id = "minify_and_combine"; // Уникальный идентификатор страницы
/**
* {@inheritDoc}
*
* @var string
*/
public $page_menu_dashicon = 'dashicons-testimonial'; // Иконка для закладки страницы, дашикон
/**
* {@inheritDoc}
*
* @var string
*/
public $page_parent_page = "performance"; // Уникальный идентификатор родительской страницы
/**
* {@inheritDoc}
*
* @var bool
*/
public $available_for_multisite = true;
/**
* {@inheritDoc}
*
* @since 1.1.0
* @var bool
*/
public $show_right_sidebar_in_options = true;
/**
* WMAC_MinifyAndCombineSettingsPage constructor.
*
* @param \Wbcr_Factory480_Plugin $plugin
*
* @author Alexander Kovalev <alex.kovalevv@gmail.com>
*
*/
public function __construct(Wbcr_Factory480_Plugin $plugin)
{
$this->menu_title = __('Optimize CSS & JS', 'minify-and-combine');
$this->page_menu_short_description = __('Optimize CSS & JS', 'minify-and-combine');
if( !defined('LOADING_MINIFY_AND_COMBINE_AS_ADDON') ) {
$this->internal = false;
$this->menu_target = 'options-general.php';
$this->add_link_to_plugin_actions = true;
$this->page_parent_page = null;
}
parent::__construct($plugin);
}
/**
* {@inheritDoc}
*
* @return string|void
* @since 1.0.0
* @author Alexander Kovalev <alex.kovalevv@gmail.com>
*/
public function getMenuTitle()
{
return defined('LOADING_MINIFY_AND_COMBINE_AS_ADDON') ? __('Optimize CSS & JS', 'minify-and-combine') : __('General', 'minify-and-combine');
}
/**
* Requests assets (js and css) for the page.
*
* @param Wbcr_Factory480_ScriptList $scripts
* @param Wbcr_Factory480_StyleList $styles
*
* @return void
* @see Wbcr_FactoryPages480_AdminPage
*
*/
public function assets($scripts, $styles)
{
parent::assets($scripts, $styles);
$this->styles->add(WMAC_PLUGIN_URL . '/admin/assets/css/settings.css');
}
/**
* {@inheritDoc}
*
* @param $notices
*
* @return array
*/
public function getActionNotices($notices)
{
$notices[] = [
'conditions' => [
'wbcr_mac_clear_cache_success' => 1
],
'type' => 'success',
'message' => __('The cache has been successfully cleaned.', 'minify-and-combine')
];
return $notices;
}
/**
* {@inheritDoc}
*
* @return mixed[]
* @since 1.0.0
*/
public function getPageOptions()
{
$options = [];
$options[] = [
'type' => 'html',
'html' => '<div class="wbcr-factory-page-group-header"><strong>' . __('Misc Options', 'minify-and-combine') . '</strong><p></p></div>'
];
$options[] = [
'type' => 'checkbox',
'way' => 'buttons',
'name' => 'optimize_scripts_for_logged',
'title' => __('Also optimize JS/CSS for logged in editors/administrators?', 'minify-and-combine'),
'layout' => ['hint-type' => 'icon', 'hint-icon-color' => 'grey'],
'hint' => __('By default plugin is also active for logged on editors/administrators, uncheck this option if you don\'t want Autoptimize to optimize when logged in e.g. to use a pagebuilder.', 'minify-and-combine'),
'default' => false
];
$options[] = [
'type' => 'html',
'html' => '<div class="wbcr-factory-page-group-header"><strong>' . __('Optimize CSS', 'minify-and-combine') . '</strong><p></p></div>'
];
$options[] = [
'type' => 'checkbox',
'way' => 'buttons',
'name' => 'css_optimize',
'title' => __('Optimize CSS Code?', 'minify-and-combine'),
'layout' => ['hint-type' => 'icon', 'hint-icon-color' => 'green'],
'hint' => __('If your scripts break because of a JS-error, you might want to try this.', 'minify-and-combine'),
'default' => false,
'eventsOn' => [
'show' => '#wbcr-clr-optimize-css-fields'
],
'eventsOff' => [
'hide' => '#wbcr-clr-optimize-css-fields'
]
];
$options[] = [
'type' => 'checkbox',
'way' => 'buttons',
'name' => 'css_aggregate',
'title' => __('Aggregate CSS-files?', 'minify-and-combine'),
'layout' => ['hint-type' => 'icon', 'hint-icon-color' => 'red'],
'hint' => __('Aggregate all linked CSS-files? If this option is off, the individual CSS-files will remain in place but will be minified.', 'minify-and-combine'),
'default' => false,
'eventsOn' => [
'show' => '#wbcr-mac-optimization-danger-message-4'
],
'eventsOff' => [
'hide' => '#wbcr-mac-optimization-danger-message-4'
]
];
$options[] = [
'type' => 'html',
'html' => [$this, 'optimizationDangerMessage4']
];
$options[] = [
'type' => 'checkbox',
'way' => 'buttons',
'name' => 'remove_style_version',
'title' => __('Remove Version from Stylesheet', 'clearfy') . ' <span class="wbcr-clearfy-recomended-text">(' . __('Recommended', 'clearfy') . ')</span>',
'layout' => ['hint-type' => 'icon'],
'hint' => __('To make it more difficult for others to hack your website you can remove the WordPress version number from your site, your css and js. Without that number it\'s not possible to see if you run not the current version to exploit bugs from the older versions. <br><br>
Additionally it can improve the loading speed of your site, because without query strings in the URL the css and js files can be cached.', 'clearfy') . '<br><br><b>Clearfy: </b>' . __('Removes the wordpress version number from stylesheets (not logged in user only).', 'clearfy'),
'default' => false,
/*'eventsOn' => array(
'show' => '.factory-control-disable_remove_style_version_for_auth_users'
),
'eventsOff' => array(
'hide' => '.factory-control-disable_remove_style_version_for_auth_users'
)*/
];
$options[] = [
'type' => 'textarea',
'name' => 'css_exclude',
'title' => __('Exclude CSS from Мinify And Combine:', 'minify-and-combine'),
//'layout' => array('hint-type' => 'icon', 'hint-icon-color' => 'grey'),
'hint' => __('A comma-separated list of CSS you want to exclude from being optimized.', 'minify-and-combine'),
'default' => 'wp-content/cache/, wp-content/uploads/, admin-bar.min.css, dashicons.min.css'
];
$options[] = [
'type' => 'more-link',
'name' => 'css-group',
'title' => __('Advanced options', 'clearfy'),
'count' => 6,
'items' => [
[
'type' => 'checkbox',
'way' => 'buttons',
'name' => 'css_include_inline',
'title' => __('Also aggregate inline CSS?', 'minify-and-combine'),
'layout' => ['hint-type' => 'icon', 'hint-icon-color' => 'grey'],
'hint' => __('Check this option for Мinify And Combine to also aggregate CSS in the HTML.', 'minify-and-combine'),
'default' => false,
'eventsOn' => [
'show' => '#wbcr-mac-optimization-danger-message-5'
],
'eventsOff' => [
'hide' => '#wbcr-mac-optimization-danger-message-5'
]
],
[
'type' => 'html',
'html' => [$this, 'optimizationDangerMessage5']
],
[
'type' => 'checkbox',
'way' => 'buttons',
'name' => 'css_datauris',
'title' => __('Generate data: URIs for images?', 'minify-and-combine'),
'layout' => ['hint-type' => 'icon', 'hint-icon-color' => 'grey'],
'hint' => __('Enable this to include small background-images in the CSS itself instead of as separate downloads.', 'minify-and-combine'),
'default' => false
],
[
'type' => 'checkbox',
'way' => 'buttons',
'name' => 'css_defer',
'title' => __('Inline and Defer CSS?', 'minify-and-combine'),
'layout' => ['hint-type' => 'icon', 'hint-icon-color' => 'grey'],
'hint' => __('Inline "above the fold CSS" while loading the main auto optimized CSS only after page load. Check the FAQ for more info.
This can be fully automated for different types of pages with the Мinify And Combine CriticalCSS Power-Up.', 'minify-and-combine'),
'default' => false
],
[
'type' => 'checkbox',
'way' => 'buttons',
'name' => 'css_inline',
'title' => __('Inline all CSS?', 'minify-and-combine'),
'layout' => ['hint-type' => 'icon', 'hint-icon-color' => 'grey'],
'hint' => __('Inlining all CSS can improve performance for sites with a low pageviews/ visitor-rate, but may slow down performance otherwise.', 'minify-and-combine'),
'default' => false
],
/*[
'type' => 'html',
'html' => '<div class="wbcr-factory-page-group-header"><strong>' . __('Critical CSS', 'minify-and-combine') . '</strong><p></p></div>'
],*/
[
'type' => 'textarea',
'name' => 'css_critical',
'title' => __('Critical CSS files:', 'minify-and-combine'),
'hint' => __('A comma-separated list of Critical CSS files. (You can use the * mask in file names. * - these are any characters.)', 'minify-and-combine'),
'htmlAttrs' => ['placeholder'=>'style.css, themes/*/style.css, style.min.css, themes/*/style.min.css'],
'default' => ''
],
[
'type' => 'textarea',
'name' => 'css_critical_style',
'title' => __('Critical CSS code:', 'minify-and-combine'),
'hint' => htmlspecialchars(__('Add critical CSS here. We will insert it into <style> tags in your <head> section of each page.', 'minify-and-combine')),
'default' => ''
]
]
];
$options[] = [
'type' => 'html',
'html' => '<div class="wbcr-factory-page-group-header"><strong>' . __('Optimize JavaScript', 'minify-and-combine') . '</strong><p></p></div>'
];
$options[] = [
'type' => 'checkbox',
'way' => 'buttons',
'name' => 'js_optimize',
'title' => __('Optimize JavaScript Code?', 'minify-and-combine'),
'layout' => ['hint-type' => 'icon', 'hint-icon-color' => 'green'],
'hint' => __('Minify JavaScript removes whitespace and comments to reduce the file size.', 'minify-and-combine'),
'default' => false,
'eventsOn' => [
'show' => '#wbcr-mac-optimize-js-fields,#wbcr-mac-optimization-danger-message-1'
],
'eventsOff' => [
'hide' => '#wbcr-mac-optimize-js-fields,#wbcr-mac-optimization-danger-message-1'
]
];
$mtf_checkbox = [
'type' => 'checkbox',
'way' => 'buttons',
'name' => 'move_js_to_footer',
'title' => __('Move js to footer?', 'minify-and-combine'),
'layout' => ['hint-type' => 'icon', 'hint-icon-color' => 'grey'],
//'hint' => __('Optimize JavaScript Code.', 'minify-and-combine'),
'default' => false,
'eventsOn' => [
'show' => '.factory-control-dont_move_jquery_to_footer, .factory-control-excluded_move_to_footer_scripts'
],
'eventsOff' => [
'hide' => '.factory-control-dont_move_jquery_to_footer, .factory-control-excluded_move_to_footer_scripts'
]
];
if( !($this->plugin->has_premium() && $this->plugin->premium->is_activate()) ) {
$mtf_checkbox['value'] = false;
$mtf_checkbox['cssClass'] = ['factory-checkbox-disabled wbcr-factory-clearfy-icon-pro'];
}
/*$options[] = [
'type' => 'html',
'html' => [$this, 'move_to_footer_js_options']
];*/
/*$options[] = array(
'type' => 'html',
'html' => array( $this, 'optimizationDangerMessage1' )
);*/
$options[] = [
'type' => 'checkbox',
'way' => 'buttons',
'name' => 'js_aggregate',
'title' => __('Aggregate JS-files?', 'minify-and-combine'),
'layout' => ['hint-type' => 'icon', 'hint-icon-color' => 'red'],
'hint' => __('Aggregate all linked JS-files to have them loaded non-render blocking? If this option is off, the individual JS-files will remain in place but will be minified.', 'minify-and-combine'),
'default' => false,
'eventsOn' => [
'show' => '#wbcr-mac-optimization-danger-message-2'
],
'eventsOff' => [
'hide' => '#wbcr-mac-optimization-danger-message-2'
]
];
$options[] = [
'type' => 'html',
'html' => [$this, 'optimizationDangerMessage2']
];
$options[] = [
'type' => 'checkbox',
'way' => 'buttons',
'name' => 'remove_js_version',
'title' => __('Remove Version from Script', 'clearfy') . ' <span class="wbcr-clearfy-recomended-text">(' . __('Recommended', 'clearfy') . ')</span>',
'layout' => ['hint-type' => 'icon'],
'hint' => __('To make it more difficult for others to hack your website you can remove the WordPress version number from your site, your css and js. Without that number it\'s not possible to see if you run not the current version to exploit bugs from the older versions. <br><br>
Additionally it can improve the loading speed of your site, because without query strings in the URL the css and js files can be cached.', 'clearfy') . '<br><br><b>Clearfy: </b>' . __('Removes wordpress version number from scripts (not logged in user only).', 'clearfy'),
'default' => false
];
$options[] = [
'type' => 'textarea',
'name' => 'js_exclude',
'title' => __('Exclude scripts from Мinify And Combine:', 'minify-and-combine'),
//'layout' => array('hint-type' => 'icon', 'hint-icon-color' => 'grey'),
'hint' => __('A comma-separated list of scripts you want to exclude from being optimized, for example \'whatever.js, another.js\' (without the quotes) to exclude those scripts from being aggregated and minimized by Мinify And Combine.', 'minify-and-combine'),
'default' => 'seal.js, js/jquery/jquery.js'
];
/*$options[] = [
'type' => 'div',
'id' => 'wbcr-mac-optimize-js-fields',
'items' => $js_options
];*/
$options[] = [
'type' => 'more-link',
'name' => 'js-group',
'title' => __('Advanced options', 'clearfy'),
'count' => 4,
'items' => [
[
'type' => 'checkbox',
'way' => 'buttons',
'name' => 'js_include_inline',
'title' => __('Also aggregate inline JS?', 'minify-and-combine'),
'layout' => ['hint-type' => 'icon', 'hint-icon-color' => 'grey'],
'hint' => __('Let Мinify And Combine also extract JS from the HTML. Warning: this can make Мinify And Combine\'s cache size grow quickly, so only enable this if you know what you\'re doing.', 'minify-and-combine'),
'default' => false,
'eventsOn' => [
'show' => '#wbcr-mac-optimization-danger-message-3'
],
'eventsOff' => [
'hide' => '#wbcr-mac-optimization-danger-message-3'
]
],
[
'type' => 'html',
'html' => [$this, 'optimizationDangerMessage3']
],
$mtf_checkbox,
[
'type' => 'checkbox',
'name' => 'dont_move_jquery_to_footer',
'title' => "",
'hint' => __("Don't move jQuery to footer", 'minify-and-combine'),
'default' => true
],
[
'type' => 'textarea',
'name' => 'excluded_move_to_footer_scripts',
'title' => "",
'hint' => __("A comma-separated list of scripts you want to exclude from being optimized, for example 'whatever.js, another.js' (without the quotes) to exclude those scripts from being aggregated and minimized by Мinify And Combine.", 'minify-and-combine'),
'default' => ''
],
[
'type' => 'checkbox',
'way' => 'buttons',
'name' => 'js_forcehead',
'title' => __('Force JavaScript in &lt;head&gt;?', 'minify-and-combine'),
'layout' => ['hint-type' => 'icon', 'hint-icon-color' => 'grey'],
'hint' => __('Load JavaScript early, this can potentially fix some JS-errors, but makes the JS render blocking.', 'minify-and-combine'),
'default' => false
],
[
'type' => 'checkbox',
'way' => 'buttons',
'name' => 'js_trycatch',
'title' => __('Add try-catch wrapping?', 'minify-and-combine'),
'layout' => ['hint-type' => 'icon', 'hint-icon-color' => 'grey'],
'hint' => __('If your scripts break because of a JS-error, you might want to try this.', 'minify-and-combine'),
'default' => false
]
]
];
/*$options[] = [
'type' => 'div',
'id' => 'wbcr-clr-optimize-css-fields',
'items' => $css_options
];*/
/*$options[] = [
'type' => 'html',
'html' => '<div class="wbcr-factory-page-group-header"><strong>' . __('JS & CSS files cache details', 'minify-and-combine') . '</strong><p></p></div>'
];*/
// Произвольный html код
/*$options[] = [
'type' => 'html',
'html' => [$this, 'cacheInfo']
];*/
$formOptions = [];
$formOptions[] = [
'type' => 'form-group',
'items' => apply_filters('wmac/pages/settings_form_options', $options),
//'cssClass' => 'postbox'
];
/**
* @since 1.1.1 - является устаревшим
*/
return wbcr_factory_480_apply_filters_deprecated('wbcr_mac_settings_form_options', [$formOptions], '1.1.1', 'wmac/pages/settings_form_options');
}
public function cacheInfo()
{
$is_network = is_network_admin();
$cache = $is_network ? WMAC_PluginCache::getUsedCacheMultisite() : WMAC_PluginCache::getUsedCache();
?>
<div class="form-group">
<label for="wbcr_mac_css_optimize" class="col-sm-4 control-label">
Cache folder<?php echo $is_network ? 's' : '' ?>
</label>
<div class="control-group col-sm-8">
<?php echo $is_network ? WP_CONTENT_DIR . WMAC_CACHE_CHILD_DIR . '[...]/' : WMAC_PluginCache::getCacheDir() ?>
</div>
</div>
<div class="form-group">
<label for="wbcr_mac_css_optimize" class="col-sm-4 control-label">
Can we write?
</label>
<div class="control-group col-sm-8">
Yes
</div>
</div>
<div class="form-group">
<label for="wbcr_mac_css_optimize" class="col-sm-4 control-label">
Cached styles and scripts<?php echo $is_network ? ' (all sites)' : '' ?>
</label>
<div class="control-group col-sm-8">
<?php if( $is_network ) : ?>
<?php echo $cache['files'] ?> files, totalling <?php echo $cache['size'] ?> (calculated
at <?php echo gmdate('H:i') ?> UTC)
<?php else: ?>
<?php echo $cache['percent'] . '%, ' . $cache['files'] ?> files,
totalling <?php echo $cache['size'] ?> (calculated at <?php echo gmdate('H:i') ?> UTC)
<?php endif; ?>
</div>
</div>
<div class="form-group">
<label for="wbcr_mac_css_optimize" class="col-sm-4 control-label">
</label>
<div class="control-group col-sm-8">
<a class="btn btn-default"
href="<?= wp_nonce_url($this->getActionUrl('clear-cache'), 'clear_cache_' . $this->getResultId()); ?>">
<?php _e('Clear cache', 'minify-and-combine') ?>
</a>
</div>
</div>
<?php
}
/**
* Adds an html warning notification html markup.
*
* @param int $selector_id
*/
public function optimizationDangerMessage($selector_id = 1)
{
?>
<div class="form-group">
<label class="col-sm-4 control-label"></label>
<div class="control-group col-sm-8">
<div id="wbcr-mac-optimization-danger-message-<?= $selector_id ?>" class="wbcr-clearfy-danger-message">
<?php _e('<b>This could break things!</b><br>If you notice any errors on your website after having activated this setting, just deactivate it again, and your site will be back to normal.', 'clearfy') ?>
</div>
</div>
</div>
<?php
}
public function optimizationDangerMessage1()
{
$this->optimizationDangerMessage(1);
}
public function optimizationDangerMessage2()
{
$this->optimizationDangerMessage(2);
}
public function optimizationDangerMessage3()
{
$this->optimizationDangerMessage(3);
}
public function optimizationDangerMessage4()
{
$this->optimizationDangerMessage(4);
}
public function optimizationDangerMessage5()
{
$this->optimizationDangerMessage(5);
}
/**
* Действие rollback
* Если мы перейдем по ссылке, которую мы создали для кнопки "Восстановить" для метода rollbackButton,
* То выполнится это действие
*/
public function clearCacheAction()
{
check_admin_referer('clear_cache_' . $this->getResultId());
if( is_network_admin() ) {
WMAC_PluginCache::clearAllMultisite();
} else {
WMAC_PluginCache::clearAll();
}
// редирект с выводом уведомления
$this->redirectToAction('index', ['wbcr_mac_clear_cache_success' => 1]);
}
}

View File

@@ -0,0 +1,2 @@
<?php
// Silence is golden.

View File

@@ -0,0 +1,52 @@
<?php
/**
* Этот файл инициализирует этот плагин, как аддон для плагина Clearfy.
*
* Файл будет подключен только в плагине Clearfy, используя особый вариант загрузки. Это более простое решение
* пришло на смену встроенной системы подключения аддонов в фреймворке.
*
* @author Alex Kovalev <alex.kovalevv@gmail.com>, Github: https://github.com/alexkovalevv
* @copyright (c) 2018 Webraftic Ltd
*/
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! defined( 'WMAC_PLUGIN_ACTIVE' ) ) {
define( 'WMAC_PLUGIN_VERSION', '1.1.2' );
define( 'WMAC_TEXT_DOMAIN', 'minify-and-combine' );
define( 'WMAC_PLUGIN_ACTIVE', true );
// Этот плагин загружен, как аддон для плагина Clearfy
define( 'LOADING_MINIFY_AND_COMBINE_AS_ADDON', true );
if ( ! defined( 'WMAC_PLUGIN_DIR' ) ) {
define( 'WMAC_PLUGIN_DIR', dirname( __FILE__ ) );
}
if ( ! defined( 'WMAC_PLUGIN_BASE' ) ) {
define( 'WMAC_PLUGIN_BASE', plugin_basename( __FILE__ ) );
}
if ( ! defined( 'WMAC_PLUGIN_URL' ) ) {
define( 'WMAC_PLUGIN_URL', plugins_url( '', __FILE__ ) );
}
try {
// Global scripts
require_once( WMAC_PLUGIN_DIR . '/includes/3rd-party/class-clearfy-plugin.php' );
new WMAC_Plugin();
} catch( Exception $e ) {
$wmac_plugin_error_func = function () use ( $e ) {
$error = sprintf( "The %s plugin has stopped. <b>Error:</b> %s Code: %s", 'Webcraftic Minify and Combine', $e->getMessage(), $e->getCode() );
echo '<div class="notice notice-error"><p>' . $error . '</p></div>';
};
add_action( 'admin_notices', $wmac_plugin_error_func );
add_action( 'network_admin_notices', $wmac_plugin_error_func );
}
}

View File

@@ -0,0 +1,133 @@
<?php
// Exit if accessed directly
if( !defined('ABSPATH') ) {
exit;
}
/**
* Disable comments
*
* @author Alex Kovalev <alex.kovalevv@gmail.com>, Github: https://github.com/alexkovalevv
*
* @copyright (c) 2018 Webraftic Ltd
*/
class WMAC_Plugin {
/**
* @see self::app()
* @var WCL_Plugin
*/
private static $app;
/**
* Конструктор
*
* Применяет конструктор родительского класса и записывает экземпляр текущего класса в свойство $app.
* Подробнее о свойстве $app см. self::app()
*
* @param string $plugin_path
* @param array $data
*
* @throws Exception
*/
public function __construct()
{
if( !class_exists('WCL_Plugin') ) {
throw new Exception('Plugin Clearfy is not installed!');
}
self::$app = WCL_Plugin::app();
$this->global_scripts();
if( is_admin() ) {
$this->admin_scripts();
}
add_action('plugins_loaded', [$this, 'plugins_loaded']);
// Wordpress 6.7 fix
add_action( 'init', function () {
if ( is_admin() ) {
$this->register_pages();
}
} );
}
/**
* Статический метод для быстрого доступа к интерфейсу плагина.
*
* Позволяет разработчику глобально получить доступ к экземпляру класса плагина в любом месте
* плагина, но при этом разработчик не может вносить изменения в основной класс плагина.
*
* Используется для получения настроек плагина, информации о плагине, для доступа к вспомогательным
* классам.
*
* @return WCL_Plugin
*/
public static function app()
{
return self::$app;
}
/**
* Выполнение действий после загрузки плагина
* Подключаем все классы оптимизации и запускаем процесс
*/
public function plugins_loaded()
{
require_once(WMAC_PLUGIN_DIR . '/includes/classes/class-base.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/class-cache.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/class-cache-checker.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/class-scripts.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/class-css-min.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/class-styles.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/class-criticalcss.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/class-main.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/class-helper.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/ext/php/jsmin.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/ext/php/yui-php-cssmin-bundled/Colors.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/ext/php/yui-php-cssmin-bundled/Minifier.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/ext/php/yui-php-cssmin-bundled/Utils.php');
$plugin = new WMAC_PluginMain();
$plugin->start();
}
/**
* Регистрирует классы страниц в плагине
*
* Мы указываем плагину, где найти файлы страниц и какое имя у их класса. Чтобы плагин
* выполнил подключение классов страниц. После регистрации, страницы будут доступные по url
* и в меню боковой панели администратора. Регистрируемые страницы будут связаны с текущим плагином
* все операции выполняемые внутри классов страниц, имеют отношение только текущему плагину.
*
* @throws \Exception
*/
private function register_pages()
{
$admin_path = WMAC_PLUGIN_DIR . '/admin/pages';
self::app()->registerPage('WMAC_MinifyAndCombineSettingsPage', $admin_path . '/class-pages-settings.php');
}
/**
* Подключаем функции бекенда
*
* @throws \Exception
*/
private function admin_scripts()
{
require_once(WMAC_PLUGIN_DIR . '/admin/boot.php');
}
/**
* Подключаем глобальные функции
*/
private function global_scripts()
{
require_once(WMAC_PLUGIN_DIR . '/includes/boot.php');
}
}

View File

@@ -0,0 +1,2 @@
<?php
// Silence is golden.

View File

@@ -0,0 +1,122 @@
<?php
/**
* Global boot
*
* @author Webcraftic <wordpress.webraftic@gmail.com>
* @copyright (c) 01.07.2018, Webcraftic
* @version 1.0
*/
// Exit if accessed directly
if( !defined('ABSPATH') ) {
exit;
}
/**
* Remove wp version from any enqueued scripts
*
* @param string $target_url
*
* @return string
*/
function wbcr_mac_hide_file_versions($src, $handle)
{
if( is_user_logged_in() && !WMAC_Plugin::app()->getPopulateOption('optimize_scripts_for_logged') ) {
return $src;
}
$filename_arr = explode('?', basename($src));
$exclude_file_list = WMAC_Plugin::app()->getPopulateOption('remove_version_exclude', '');
$exclude_files_arr = array_map('trim', explode(PHP_EOL, $exclude_file_list));
if( strpos($src, 'ver=') && !in_array(str_replace('?' . $filename_arr[1], '', $src), $exclude_files_arr, true) ) {
$src = remove_query_arg('ver', $src);
}
return $src;
}
/**
* Priority set to 9999. Higher numbers correspond with later execution.
* Hook into the style loader and remove the version information.
*/
if( WMAC_Plugin::app()->getPopulateOption('remove_style_version') ) {
add_filter('style_loader_src', 'wbcr_mac_hide_file_versions', 9999, 2);
}
/**
* Hook into the script loader and remove the version information.
*/
if( WMAC_Plugin::app()->getPopulateOption('remove_js_version') ) {
add_filter('script_loader_src', 'wbcr_mac_hide_file_versions', 9999, 2);
}
function wbcr_mac_clear_cache()
{
if( isset($_GET['wbcr_mac_clear_cache']) && isset($_GET['_wpnonce']) && wp_verify_nonce($_GET['_wpnonce'], 'clear_all_cache') ) {
$page = isset($_GET['page']) ? $_GET['page'] : 'minify_and_combine-' . WMAC_Plugin::app()->getPluginName();
if( is_network_admin() ) {
WMAC_PluginCache::clearAllMultisite();
$base_url = network_admin_url('settings.php') . '?page=' . $page;
} else {
WMAC_PluginCache::clearAll();
$base_url = admin_url('options-general.php') . '?page=' . $page;
}
wp_safe_redirect(add_query_arg(['wbcr_mac_clear_cache_success' => 1], $base_url));
}
}
add_action('init', 'wbcr_mac_clear_cache');
/**
* Добавляем кнопку сброса кеша в админ бар
*
* @param $wp_admin_bar
*/
function wbcr_mac_admin_bar_menu($wp_admin_bar)
{
if( !current_user_can('manage_options') ) {
return;
}
$current_url = esc_url(wp_nonce_url(add_query_arg(['wbcr_mac_clear_cache' => 1]), 'clear_all_cache'));
$percent = '';
if( !is_network_admin() ) {
$get_used_cache = WMAC_PluginCache::getUsedCache();
$percent = ' (' . $get_used_cache['percent'] . '%)';
}
$args = [
'id' => 'clear-cache-btn',
'title' => __('Clear cache', 'minify-and-combine') . $percent,
'href' => $current_url
];
$wp_admin_bar->add_menu($args);
}
/**
* Добавляем кнопку сброса кеша в Clearfy меню
*/
function wbcr_mac_clearfy_admin_bar_menu($menu_items)
{
$current_url = esc_url(wp_nonce_url(add_query_arg(['wbcr_mac_clear_cache' => 1]), 'clear_all_cache'));
$percent = '';
if( !is_network_admin() ) {
$get_used_cache = WMAC_PluginCache::getUsedCache();
$percent = ' (' . $get_used_cache['percent'] . '%)';
}
$menu_items['mac-clear-cache'] = [
'title' => '<span class="dashicons dashicons-image-rotate"></span> ' . __('Clear cache', 'minify-and-combine') . $percent,
'href' => $current_url
];
return $menu_items;
}
if( defined('LOADING_MINIFY_AND_COMBINE_AS_ADDON') ) {
add_action('wbcr/clearfy/adminbar_menu_items', 'wbcr_mac_clearfy_admin_bar_menu');
} else {
add_action('admin_bar_menu', 'wbcr_mac_admin_bar_menu');
}

View File

@@ -0,0 +1,134 @@
<?php
/**
* Основной класс плагина
*
* @author Webcraftic <wordpress.webraftic@gmail.com>
* @copyright (c) 19.02.2018, Webcraftic
* @version 1.0
*/
// Exit if accessed directly
if( !defined('ABSPATH') ) {
exit;
}
class WMAC_Plugin extends Wbcr_Factory480_Plugin {
/**
* @see self::app()
* @var Wbcr_Factory480_Plugin
*/
private static $app;
/**
* Конструктор
*
* Применяет конструктор родительского класса и записывает экземпляр текущего класса в свойство $app.
* Подробнее о свойстве $app см. self::app()
*
* @param string $plugin_path
* @param array $data
*
* @throws Exception
*/
public function __construct($plugin_path, $data)
{
parent::__construct($plugin_path, $data);
self::$app = $this;
$this->global_scripts();
if( is_admin() ) {
$this->admin_scripts();
}
add_action('plugins_loaded', [$this, 'plugins_loaded']);
// Wordpress 6.7 fix
add_action( 'init', function () {
if ( is_admin() ) {
$this->register_pages();
}
} );
}
/**
* Статический метод для быстрого доступа к интерфейсу плагина.
*
* Позволяет разработчику глобально получить доступ к экземпляру класса плагина в любом месте
* плагина, но при этом разработчик не может вносить изменения в основной класс плагина.
*
* Используется для получения настроек плагина, информации о плагине, для доступа к вспомогательным
* классам.
*
* @return \Wbcr_Factory480_Plugin|\WCM_Plugin
*/
public static function app()
{
return self::$app;
}
/**
* Выполнение действий после загрузки плагина
* Подключаем все классы оптимизации и запускаем процесс
*/
public function plugins_loaded()
{
require_once(WMAC_PLUGIN_DIR . '/includes/classes/class-base.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/class-cache.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/class-cache-checker.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/class-scripts.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/class-css-min.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/class-styles.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/class-criticalcss.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/class-main.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/class-helper.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/ext/php/jsmin.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/ext/php/yui-php-cssmin-bundled/Colors.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/ext/php/yui-php-cssmin-bundled/Minifier.php');
require_once(WMAC_PLUGIN_DIR . '/includes/classes/ext/php/yui-php-cssmin-bundled/Utils.php');
$plugin = new WMAC_PluginMain();
$plugin->start();
}
/**
* Регистрирует классы страниц в плагине
*
* Мы указываем плагину, где найти файлы страниц и какое имя у их класса. Чтобы плагин
* выполнил подключение классов страниц. После регистрации, страницы будут доступные по url
* и в меню боковой панели администратора. Регистрируемые страницы будут связаны с текущим плагином
* все операции выполняемые внутри классов страниц, имеют отношение только текущему плагину.
*
* @throws \Exception
*/
private function register_pages()
{
$admin_path = WMAC_PLUGIN_DIR . '/admin/pages';
self::app()->registerPage('WMAC_MinifyAndCombineSettingsPage', $admin_path . '/class-pages-settings.php');
}
/**
* Подключаем функции бекенда
*
* @throws \Exception
*/
private function admin_scripts()
{
require_once(WMAC_PLUGIN_DIR . '/admin/boot.php');
}
/**
* Подключаем глобальные функции
*/
private function global_scripts()
{
require_once(WMAC_PLUGIN_DIR . '/includes/boot.php');
}
}

View File

@@ -0,0 +1,621 @@
<?php
/**
* Base class
*
* @author Webcraftic <wordpress.webraftic@gmail.com>
* @copyright (c) 2018 Webraftic Ltd
* @version 1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class WMAC_PluginBase
*/
abstract class WMAC_PluginBase {
/**
* Holds content being processed (scripts, styles)
*
* @var string
*/
protected $content = '';
/**
* Controls debug logging.
*
* @var bool
*/
public $debug_log = false;
/**
* WMAC_PluginBase constructor.
*
* @param $content
*/
public function __construct( $content ) {
$this->content = $content;
}
/**
* Reads the page and collects tags.
*
* @param array $options Options.
*
* @return bool
*/
abstract public function read( $options );
/**
* Joins and optimizes collected things.
*
* @return bool
*/
abstract public function minify();
/**
* Caches the things.
*
* @return void
*/
abstract public function cache();
/**
* Returns the content
*
* @return string
*/
abstract public function getContent();
/**
* Tranfsorms a given URL to a full local filepath if possible.
* Returns local filepath or false.
*
* @param string $url URL to transform.
*
* @return bool|string
*/
public function getPath( $url ) {
$url = apply_filters( 'wmac_filter_cssjs_alter_url', $url );
if ( false !== strpos( $url, '%' ) ) {
$url = urldecode( $url );
}
$site_url = WMAC_PluginMain::getSiteUrl();
$site_host = parse_url( $site_url, PHP_URL_HOST );
$content_host = parse_url( WMAC_WP_ROOT_URL, PHP_URL_HOST );
// Normalizing attempts...
$double_slash_position = strpos( $url, '//' );
if ( 0 === $double_slash_position ) {
if ( is_ssl() ) {
$url = 'https:' . $url;
} else {
$url = 'http:' . $url;
}
} else if ( ( false === $double_slash_position ) && ( false === strpos( $url, $site_host ) ) ) {
if ( $site_url === $site_host ) {
$url = $site_url . $url;
} else {
$url = $site_url . WMAC_PluginHelper::pathCanonicalize( $url );
}
}
if ( $site_host !== $content_host ) {
$url = str_replace( WMAC_PluginMain::getContentUrl(), $site_url . WMAC_WP_CONTENT_NAME, $url );
}
// First check; hostname wp site should be hostname of url!
$url_host = @parse_url( $url, PHP_URL_HOST ); // @codingStandardsIgnoreLine
if ( $url_host !== $site_host ) {
/**
* First try to get all domains from WPML (if available)
* then apply own filter wmac_filter_cssjs_multidomain takes an array of hostnames
* each item in that array will be considered part of the same WP multisite installation
*/
$multidomains = [];
$multidomains_wpml = apply_filters( 'wpml_setting', [], 'language_domains' );
if ( ! empty( $multidomains_wpml ) ) {
$multidomains = array_map( [ $this, 'getUrlHostname' ], $multidomains_wpml );
}
$multidomains = apply_filters( 'wmac_filter_cssjs_multidomain', $multidomains );
if ( ! empty( $multidomains ) ) {
if ( in_array( $url_host, $multidomains ) ) {
$url = str_replace( $url_host, $site_host, $url );
} else {
return false;
}
} else {
return false;
}
}
// Try to remove "wp root url" from url while not minding http<>https.
$tmp_ao_root = preg_replace( '/https?:/', '', WMAC_WP_ROOT_URL );
if ( $site_host !== $content_host ) {
// As we replaced the content-domain with the site-domain, we should match against that.
$tmp_ao_root = preg_replace( '/https?:/', '', $site_url );
}
$tmp_url = preg_replace( '/https?:/', '', $url );
$path = str_replace( $tmp_ao_root, '', $tmp_url );
// If path starts with :// or //, this is not a URL in the WP context and
// we have to assume we can't aggregate.
if ( preg_match( '#^:?//#', $path ) ) {
// External script/css (adsense, etc).
return false;
}
// Prepend with WMAC_ROOT_DIR to have full path to file.
$path = str_replace( '//', '/', WMAC_ROOT_DIR . $path );
// Final check: does file exist and is it readable?
if ( file_exists( $path ) && is_file( $path ) && is_readable( $path ) ) {
return $path;
} else {
return false;
}
}
/**
* Returns the hostname part of a given $url if we're able to parse it.
* If not, it returns the original url (prefixed with http:// scheme in case
* it was missing).
* Used as callback for WPML multidomains filter.
*
* @param string $url URL.
*
* @return string
*/
protected function getUrlHostname( $url ) {
// Checking that the url starts with something vaguely resembling a protocol.
if ( ( 0 !== strpos( $url, 'http' ) ) && ( 0 !== strpos( $url, '//' ) ) ) {
$url = 'http://' . $url;
}
// Grab the hostname.
$hostname = parse_url( $url, PHP_URL_HOST );
// Fallback when parse_url() fails.
if ( empty( $hostname ) ) {
$hostname = $url;
}
return $hostname;
}
/**
* Hides everything between noptimize-comment tags.
*
* @param string $markup Markup to process.
*
* @return string
*/
protected function hideNoptimize( $markup ) {
return $this->replaceContentsWithMarkerIfExists( 'NOPTIMIZE', '/<!--\s?noptimize\s?-->/', '#<!--\s?noptimize\s?-->.*?<!--\s?/\s?noptimize\s?-->#is', $markup );
}
/**
* Unhide noptimize-tags.
*
* @param string $markup Markup to process.
*
* @return string
*/
protected function restoreNoptimize( $markup ) {
return $this->restoreMarkedContent( 'NOPTIMIZE', $markup );
}
/**
* Hides "iehacks" content.
*
* @param string $markup Markup to process.
*
* @return string
*/
protected function hideIEhacks( $markup ) {
return $this->replaceContentsWithMarkerIfExists( 'IEHACK', // Marker name...
'<!--[if', // Invalid regex, will fallback to search using strpos()...
'#<!--\[if.*?\[endif\]-->#is', // Replacement regex...
$markup );
}
/**
* Restores "hidden" iehacks content.
*
* @param string $markup Markup to process.
*
* @return string
*/
protected function restoreIEhacks( $markup ) {
return $this->restoreMarkedContent( 'IEHACK', $markup );
}
/**
* "Hides" content within HTML comments using a regex-based replacement
* if HTML comment markers are found.
* `<!--example-->` becomes `%%COMMENTS%%ZXhhbXBsZQ==%%COMMENTS%%`
*
* @param string $markup Markup to process.
*
* @return string
*/
protected function hideComments( $markup ) {
return $this->replaceContentsWithMarkerIfExists( 'COMMENTS', '<!--', '#<!--.*?-->#is', $markup );
}
/**
* Restores original HTML comment markers inside a string whose HTML
* comments have been "hidden" by using `hideComments()`.
*
* @param string $markup Markup to process.
*
* @return string
*/
protected function restoreComments( $markup ) {
return $this->restoreMarkedContent( 'COMMENTS', $markup );
}
/**
* Injects/replaces the given payload markup into `$this->content`
* at the specified location.
* If the specified tag cannot be found, the payload is appended into
* $this->content along with a warning wrapped inside <!--noptimize--> tags.
*
* @param string $payload Markup to inject.
* @param array $where Array specifying the tag name and method of injection.
* Index 0 is the tag name (i.e., `</body>`).
* Index 1 specifies ˛'before', 'after' or 'replace'. Defaults to 'before'.
*
* @return void
*/
protected function injectInHtml( $payload, $where ) {
$warned = false;
$position = WMAC_PluginHelper::strpos( $this->content, $where[0] );
if ( false !== $position ) {
// Found the tag, setup content/injection as specified.
if ( 'after' === $where[1] ) {
$content = $where[0] . $payload;
} else if ( 'replace' === $where[1] ) {
$content = $payload;
} else {
$content = $payload . $where[0];
}
// Place where specified.
$this->content = WMAC_PluginHelper::substrReplace( $this->content, $content, $position, // Using plain strlen() should be safe here for now, since
// we're not searching for multibyte chars here still...
strlen( $where[0] ) );
} else {
// Couldn't find what was specified, just append and add a warning.
$this->content .= $payload;
if ( ! $warned ) {
$tag_display = str_replace( [ '<', '>' ], '', $where[0] );
$this->content .= '<!--noptimize--><!-- Мinify And Combine found a problem with the HTML in your Theme, tag `' . $tag_display . '` missing --><!--/noptimize-->';
}
}
}
/**
* Returns true if given `$tag` is found in the list of `$removables`.
*
* @param string $tag Tag to search for.
* @param array $removables List of things considered completely removable.
*
* @return bool
*/
protected function isremovable( $tag, $removables ) {
foreach ( $removables as $match ) {
if ( false !== strpos( $tag, $match ) ) {
return true;
}
}
return false;
}
/**
* Callback used in `self::injectMinified()`.
*
* @param array $matches Regex matches.
*
* @return string
*/
public function injectMinifiedCallback( $matches ) {
/**
* $matches[1] holds the whole match caught by regex in self::injectMinified(),
* so we take that and split the string on `|`.
* First element is the filepath, second is the md5 hash of contents
* the filepath had when it was being processed.
* If we don't have those, we'll bail out early.
*/
$filepath = null;
$filehash = null;
// Grab the parts we need.
$parts = explode( '|', $matches[1] );
if ( ! empty( $parts ) ) {
$filepath = isset( $parts[0] ) ? base64_decode( $parts[0] ) : null;
$filehash = isset( $parts[1] ) ? $parts[1] : null;
}
// Bail early if something's not right...
if ( ! $filepath || ! $filehash ) {
return "\n";
}
$filecontent = file_get_contents( $filepath );
// Some things are differently handled for css/js...
$is_js_file = ( '.js' === substr( $filepath, - 3, 3 ) );
$is_css_file = false;
if ( ! $is_js_file ) {
$is_css_file = ( '.css' === substr( $filepath, - 4, 4 ) );
}
// BOMs being nuked here unconditionally (regardless of where they are)!
$filecontent = preg_replace( "#\x{EF}\x{BB}\x{BF}#", '', $filecontent );
// Remove comments and blank lines.
if ( $is_js_file ) {
$filecontent = preg_replace( '#^\s*\/\/.*$#Um', '', $filecontent );
}
// Nuke un-important comments.
$filecontent = preg_replace( '#^\s*\/\*[^!].*\*\/\s?#Um', '', $filecontent );
// Normalize newlines.
$filecontent = preg_replace( '#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#', "\n", $filecontent );
// JS specifics.
if ( $is_js_file ) {
// Append a semicolon at the end of js files if it's missing.
$last_char = substr( $filecontent, - 1, 1 );
if ( ';' !== $last_char && '}' !== $last_char ) {
$filecontent .= ';';
}
// Check if try/catch should be used.
$opt_js_try_catch = WMAC_Plugin::app()->getPopulateOption( 'js_trycatch' );
if ( $opt_js_try_catch ) {
// It should, wrap in try/catch.
$filecontent = 'try{' . $filecontent . '}catch(e){}';
}
} else if ( $is_css_file ) {
$filecontent = WMAC_PluginStyles::fixurls( $filepath, $filecontent );
} else {
$filecontent = '';
}
// Return modified (or empty!) code/content.
return "\n" . $filecontent;
}
/**
* Inject already minified code in optimized JS/CSS.
*
* @param string $in Markup.
*
* @return string
*/
protected function injectMinified( $in ) {
$out = $in;
if ( false !== strpos( $in, '%%INJECTLATER%%' ) ) {
$out = preg_replace_callback( '#\/\*\!%%INJECTLATER' . WMAC_HASH . '%%(.*?)%%INJECTLATER%%\*\/#is', [
$this,
'injectMinifiedCallback'
], $in );
}
return $out;
}
/**
* Specialized method to create the INJECTLATER marker.
* These are somewhat "special", in the sense that they're additionally wrapped
* within an "exclamation mark style" comment, so that they're not stripped
* out by minifiers.
* They also currently contain the hash of the file's contents too (unlike other markers).
*
* @param string $filepath Filepath.
* @param string $hash Hash.
*
* @return string
*/
public static function buildInjectlaterMarker( $filepath, $hash ) {
$contents = '/*!' . self::buildMarker( 'INJECTLATER', $filepath, $hash ) . '*/';
return $contents;
}
/**
* Creates and returns a `%%`-style named marker which holds
* the base64 encoded `$data`.
* If `$hash` is provided, it's appended to the base64 encoded string
* using `|` as the separator (in order to support building the
* somewhat special/different INJECTLATER marker).
*
* @param string $name Marker name.
* @param string $data Marker data which will be base64-encoded.
* @param string|null $hash Optional.
*
* @return string
*/
public static function buildMarker( $name, $data, $hash = null ) {
// Start the marker, add the data.
$marker = '%%' . $name . WMAC_HASH . '%%' . base64_encode( $data );
// Add the hash if provided.
if ( null !== $hash ) {
$marker .= '|' . $hash;
}
// Close the marker.
$marker .= '%%' . $name . '%%';
return $marker;
}
/**
* Returns true if the string is a valid regex.
*
* @param string $string String, duh.
*
* @return bool
*/
protected function strIsValidRegex( $string ) {
set_error_handler( function () {
}, E_WARNING );
$is_regex = ( false !== preg_match( $string, '' ) );
restore_error_handler();
return $is_regex;
}
/**
* Searches for `$search` in `$content` (using either `preg_match()`
* or `strpos()`, depending on whether `$search` is a valid regex pattern or not).
* If something is found, it replaces `$content` using `$re_replace_pattern`,
* effectively creating our named markers (`%%{$marker}%%`.
* These are then at some point replaced back to their actual/original/modified
* contents using `WMAC_PluginBase::restoreMarkedContent()`.
*
* @param string $marker Marker name (without percent characters).
* @param string $search A string or full blown regex pattern to search for in $content. Uses `strpos()` or `preg_match()`.
* @param string $re_replace_pattern Regex pattern to use when replacing contents.
* @param string $content Content to work on.
*
* @return string
*/
protected function replaceContentsWithMarkerIfExists( $marker, $search, $re_replace_pattern, $content ) {
$is_regex = $this->strIsValidRegex( $search );
if ( $is_regex ) {
$found = preg_match( $search, $content );
} else {
$found = ( false !== strpos( $content, $search ) );
}
if ( $found ) {
$content = preg_replace_callback( $re_replace_pattern, function ( $matches ) use ( $marker ) {
return WMAC_PluginBase::buildMarker( $marker, $matches[0] );
}, $content );
}
return $content;
}
/**
* Complements `WMAC_PluginBase::replaceContentsWithMarkerIfExists()`.
*
* @param string $marker Marker.
* @param string $content Markup.
*
* @return string
*/
protected function restoreMarkedContent( $marker, $content ) {
if ( false !== strpos( $content, $marker ) ) {
$content = preg_replace_callback( '#%%' . $marker . WMAC_HASH . '%%(.*?)%%' . $marker . '%%#is', function ( $matches ) {
return base64_decode( $matches[1] );
}, $content );
}
return $content;
}
/**
* Logs given `$data` for debugging purposes (when debug logging is on).
*
* @param mixed $data Data to log.
*
* @return void
*/
protected function debugLog( $data ) {
if ( ! isset( $this->debug_log ) || ! $this->debug_log ) {
return;
}
if ( ! is_string( $data ) && ! is_resource( $data ) ) {
$data = var_export( $data, true );
}
error_log( $data );
}
/**
* Checks if a single local css/js file can be minified and returns source if so.
*
* @param string $filepath Filepath.
*
* @return bool|string to be minified code or false.
*/
protected function prepareMinifySingle( $filepath ) {
// Decide what we're dealing with, return false if we don't know.
if ( $this->strEndsIn( $filepath, '.js' ) ) {
$type = 'js';
} else if ( $this->strEndsIn( $filepath, '.css' ) ) {
$type = 'css';
} else {
return false;
}
// Bail if it looks like its already minifed (by having -min or .min
// in filename) or if it looks like WP jquery.js (which is minified).
$minified_variants = apply_filters( 'wmac_minified_variants', [
'-min.' . $type,
'.min.' . $type,
'js/jquery/jquery.js',
] );
foreach ( $minified_variants as $ending ) {
if ( $this->strEndsIn( $filepath, $ending ) ) {
return false;
}
}
// Get file contents, bail if empty.
$contents = file_get_contents( $filepath );
return $contents;
}
/**
* Given an WMAC_PluginCache instance returns the url of
* the cached file.
*
* @param WMAC_PluginCache $cache WMAC_PluginCache instance.
*
* @return string
*/
protected function buildMinifySingleUrl( WMAC_PluginCache $cache ) {
$url = WMAC_PluginCache::getCacheUrl() . $cache->getname();
return $url;
}
/**
* Returns true if given $str ends with given $test.
*
* @param string $str String to check.
* @param string $test Ending to match.
*
* @return bool
*/
protected function strEndsIn( $str, $test ) {
// @codingStandardsIgnoreStart
// substr_compare() is bugged on 5.5.11: https://3v4l.org/qGYBH
// return ( 0 === substr_compare( $str, $test, -strlen( $test ) ) );
// @codingStandardsIgnoreEnd
$length = strlen( $test );
return ( substr( $str, - $length, $length ) === $test );
}
}

View File

@@ -0,0 +1,106 @@
<?php
/**
* CacheChecker - new in AO 2.0
*
* Daily cronned job (filter to change freq. + filter to disable).
* Checks if cachesize is > 0.5GB (size is filterable), if so, an option is set which controls showing an admin notice.
*
* @author Webcraftic <wordpress.webraftic@gmail.com>
* @copyright (c) 2018 Webraftic Ltd
* @version 1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class WMAC_PluginCacheChecker
*/
class WMAC_PluginCacheChecker {
const SCHEDULE_HOOK = 'wmac_cachechecker';
/**
* WMAC_PluginCacheChecker constructor.
*/
public function __construct() {
}
/**
* Run
*/
public function run() {
if ( is_admin() ) {
$this->setup();
}
$this->addHooks();
}
/**
* Add hooks
*/
public function addHooks() {
add_action( self::SCHEDULE_HOOK, [ $this, 'cronjob' ] );
add_action( 'admin_notices', [ $this, 'showAdminNotice' ] );
}
/**
* Setup
*/
public function setup() {
$do_cache_check = (bool) apply_filters( 'wmac_filter_cachecheck_do', true );
$schedule = wp_get_schedule( self::SCHEDULE_HOOK );
$frequency = apply_filters( 'wmac_filter_cachecheck_frequency', 'daily' );
if ( ! in_array( $frequency, [ 'hourly', 'daily', 'weekly', 'monthly' ] ) ) {
$frequency = 'daily';
}
if ( $do_cache_check && ( ! $schedule || $schedule !== $frequency ) ) {
wp_schedule_event( time(), $frequency, self::SCHEDULE_HOOK );
} else if ( $schedule && ! $do_cache_check ) {
wp_clear_scheduled_hook( self::SCHEDULE_HOOK );
}
}
/**
* Cron job
*/
public function cronjob() {
$max_size = (int) apply_filters( 'wmac_filter_cachecheck_maxsize', 536870912 );
$do_cache_check = (bool) apply_filters( 'wmac_filter_cachecheck_do', true );
$stat_array = WMAC_PluginCache::stats();
$cache_size = round( $stat_array[1] );
if ( ( $cache_size > $max_size ) && ( $do_cache_check ) ) {
update_option( 'wmac_cachesize_notice', true );
if ( apply_filters( 'wmac_filter_cachecheck_sendmail', true ) ) {
$site_url = esc_url( site_url() );
$ao_mailto = apply_filters( 'wmac_filter_cachecheck_mailto', get_option( 'admin_email', '' ) );
$ao_mailsubject = __( 'Мinify And Combine cache size warning', 'minify-and-combine' ) . ' (' . $site_url . ')';
$ao_mailbody = __( 'Мinify And Combine\'s cache size is getting big, consider purging the cache. Have a look at https://wordpress.org/plugins/ to see how you can keep the cache size under control.', 'minify-and-combine' ) . ' (site: ' . $site_url . ')';
if ( ! empty( $ao_mailto ) ) {
$ao_mailresult = wp_mail( $ao_mailto, $ao_mailsubject, $ao_mailbody );
if ( ! $ao_mailresult ) {
error_log( 'Мinify And Combine could not send cache size warning mail.' );
}
}
}
}
// Nukes advanced cache clearing artifacts if they exists...
WMAC_PluginCache::deleteAdvancedCacheClearArtifacts();
}
/**
* Notice
*/
public function showAdminNotice() {
if ( (bool) get_option( 'wmac_cachesize_notice', false ) ) {
echo '<div class="notice notice-warning"><p>';
_e( '<strong>Мinify And Combine\'s cache size is getting big</strong>, consider purging the cache. Have a look at <a href="https://wordpress.org/plugins/" target="_blank" rel="noopener noreferrer">the Мinify And Combine FAQ</a> to see how you can keep the cache size under control.', 'minify-and-combine' );
echo '</p></div>';
update_option( 'wmac_cachesize_notice', false );
}
}
}

View File

@@ -0,0 +1,669 @@
<?php
/**
* Operations with disc cache
*
* @author Webcraftic <wordpress.webraftic@gmail.com>
* @copyright (c) 2018 Webraftic Ltd
* @version 1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class WMAC_PluginCache
*/
class WMAC_PluginCache {
/**
* Cache filename.
*
* @var string
*/
private $filename;
/**
* Whether gzipping is done by the web server or us.
* True => we don't gzip, the web server does it.
* False => we do it ourselves.
*
* @var bool
*/
private $nogzip;
/**
* Ctor.
*
* @param string $md5 Hash.
* @param string $ext Extension.
*/
public function __construct( $md5, $ext = 'php' ) {
$this->nogzip = WMAC_CACHE_NOGZIP;
if ( ! $this->nogzip ) {
$this->filename = WMAC_CACHEFILE_PREFIX . $md5 . '.php';
} else {
if ( in_array( $ext, [ 'js', 'css' ] ) ) {
$this->filename = $ext . '/' . WMAC_CACHEFILE_PREFIX . $md5 . '.' . $ext;
} else {
$this->filename = WMAC_CACHEFILE_PREFIX . $md5 . '.' . $ext;
}
}
}
/**
* Get cache dir
*
* @return string
*/
public static function getCacheDir() {
return WMAC_PluginCache::getPathname();
}
/**
* Get cache url
*
* @return string
*/
public static function getCacheUrl() {
if ( is_multisite() && apply_filters( 'wmac_separate_blog_caches', true ) ) {
$blog_id = get_current_blog_id();
return WMAC_PluginMain::getContentUrl() . WMAC_CACHE_CHILD_DIR . $blog_id . '/';
} else {
return WMAC_PluginMain::getContentUrl() . WMAC_CACHE_CHILD_DIR;
}
}
/**
* Returns true if the cached file exists on disk.
*
* @return bool
*/
public function check() {
return file_exists( self::getCacheDir() . $this->filename );
}
/**
* Returns cache contents if they exist, false otherwise.
*
* @return string|false
*/
public function retrieve() {
if ( $this->check() ) {
if ( false == $this->nogzip ) {
return file_get_contents( self::getCacheDir() . $this->filename . '.none' );
} else {
return file_get_contents( self::getCacheDir() . $this->filename );
}
}
return false;
}
/**
* Stores given $data in cache.
*
* @param string $data Data to cache.
* @param string $mime Mimetype.
*
* @return void
*/
public function cache( $data, $mime ) {
if ( false === $this->nogzip ) {
// We handle gzipping ourselves.
$file = 'default.php';
$phpcode = file_get_contents( WMAC_PLUGIN_DIR . '/config/' . $file );
$phpcode = str_replace( [ '%%CONTENT%%', 'exit;' ], [ $mime, '' ], $phpcode );
file_put_contents( self::getCacheDir() . $this->filename, $phpcode );
file_put_contents( self::getCacheDir() . $this->filename . '.none', $data );
} else {
// Write code to cache without doing anything else.
file_put_contents( self::getCacheDir() . $this->filename, $data );
if ( apply_filters( 'wmac_filter_cache_create_static_gzip', false ) ) {
// Create an additional cached gzip file.
file_put_contents( self::getCacheDir() . $this->filename . '.gz', gzencode( $data, 9, FORCE_GZIP ) );
}
}
}
/**
* Get cache filename.
*
* @return string
*/
public function getname() {
// NOTE: This could've maybe been a do_action() instead, however,
// that ship has sailed.
// The original idea here was to provide 3rd party code a hook so that
// it can "listen" to all the complete auto optimized-urls that the page
// will emit... Or something to that effect I think?
apply_filters( 'wmac_filter_cache_getname', WMAC_PluginCache::getCacheUrl() . $this->filename );
return $this->filename;
}
/**
* Returns true if given `$file` is considered a valid Мinify And Combine cache file,
* false otherwise.
*
* @param string $dir Directory name (with a trailing slash).
* @param string $file Filename.
*
* @return bool
*/
protected static function isValidCacheFile( $dir, $file ) {
if ( '.' !== $file && '..' !== $file && false !== strpos( $file, WMAC_CACHEFILE_PREFIX ) && is_file( $dir . $file ) ) {
// It's a valid file!
return true;
}
// Everything else is considered invalid!
return false;
}
/**
* Clears contents of cache dir.
*
* @return void
*/
protected static function clearCacheClassic() {
$contents = self::getCacheContents();
foreach ( $contents as $name => $files ) {
$dir = rtrim( self::getCacheDir() . $name, '/' ) . '/';
foreach ( $files as $file ) {
if ( self::isValidCacheFile( $dir, $file ) ) {
@unlink( $dir . $file ); // @codingStandardsIgnoreLine
}
}
}
@unlink( self::getCacheDir() . '/.htaccess' ); // @codingStandardsIgnoreLine
}
/**
* Recursively deletes the specified pathname (file/directory) if possible.
* Returns true on success, false otherwise.
*
* @param string $pathname Pathname to remove.
*
* @return bool
*/
protected static function rmdir( $pathname ) {
$files = self::getDirContents( $pathname );
foreach ( $files as $file ) {
$path = $pathname . '/' . $file;
if ( is_dir( $path ) ) {
self::rmdir( $path );
} else {
unlink( $path );
}
}
return rmdir( $pathname );
}
/**
* Clears contents of cache dir by renaming the current
* cache directory into a new one with a unique name and then
* re-creating the default (empty) cache directory.
*
* @return bool Returns true when everything is done successfully, false otherwise.
*/
protected static function clearCacheViaRename() {
$ok = false;
$dir = self::getPathnameBase();
$new_name = self::getUniqueName();
// Makes sure the new pathname is on the same level...
$new_pathname = dirname( $dir ) . '/' . $new_name;
$renamed = @rename( $dir, $new_pathname ); // @codingStandardsIgnoreLine
// When renamed, re-create the default cache directory back so it's
// available again...
if ( $renamed ) {
$ok = self::cacheAvail();
}
return $ok;
}
/**
* Returns true when advanced cache clearing is enabled.
*
* @return bool
*/
public static function advancedCacheClearEnabled() {
return apply_filters( 'wmac_filter_cache_clear_advanced', false );
}
/**
* Returns a (hopefully) unique new cache folder name for renaming purposes.
*
* @return string
*/
protected static function getUniqueName() {
$prefix = self::getAdvancedCacheClearPrefix();
$new_name = uniqid( $prefix, true );
return $new_name;
}
/**
* Get cache prefix name used in advanced cache clearing mode.
*
* @return string
*/
protected static function getAdvancedCacheClearPrefix() {
$pathname = self::getPathnameBase();
$basename = basename( $pathname );
$prefix = $basename . '-';
return $prefix;
}
/**
* Returns an array of file and directory names found within
* the given $pathname without '.' and '..' elements.
*
* @param string $pathname Pathname.
*
* @return array
*/
protected static function getDirContents( $pathname ) {
return array_slice( scandir( $pathname ), 2 );
}
/**
* Wipes directories which were created as part of the fast cache clearing
* routine (which renames the current cache directory into a new one with
* a custom-prefixed unique name).
*
* @return bool
*/
public static function deleteAdvancedCacheClearArtifacts() {
$dir = self::getPathnameBase();
$prefix = self::getAdvancedCacheClearPrefix();
$parent = dirname( $dir );
$ok = false;
// Returns the list of files without '.' and '..' elements.
$files = self::getDirContents( $parent );
foreach ( $files as $file ) {
$path = $parent . '/' . $file;
$prefixed = ( false !== strpos( $path, $prefix ) );
// Removing only our own (prefixed) directories...
if ( is_dir( $path ) && $prefixed ) {
$ok = self::rmdir( $path );
}
}
return $ok;
}
/**
* Returns the cache directory pathname used.
* Done as a function so we canSlightly different
* if multisite is used and `wmac_separate_blog_caches` filter
* is used.
*
* @return string
*/
public static function getPathname() {
$pathname = self::getPathnameBase();
if ( is_multisite() && apply_filters( 'wmac_separate_blog_caches', true ) ) {
$blog_id = get_current_blog_id();
$pathname .= $blog_id . '/';
}
return $pathname;
}
/**
* Returns the base path of our cache directory.
*
* @return string
*/
protected static function getPathnameBase() {
$pathname = WP_CONTENT_DIR . WMAC_CACHE_CHILD_DIR;
return $pathname;
}
/**
* Deletes everything from the cache directories for all sites.
*
* @param bool $propagate Whether to trigger additional actions when cache is purged.
*/
public static function clearAllMultisite( $propagate = true ) {
$sites = get_sites( [
'archived' => 0,
'mature' => 0,
'spam' => 0,
'deleted' => 0,
] );
foreach ( $sites as $site ) {
switch_to_blog( $site->blog_id );
self::clearAll( $propagate );
restore_current_blog();
}
}
/**
* Deletes everything from the cache directories.
*
* @param bool $propagate Whether to trigger additional actions when cache is purged.
*
* @return bool
*/
public static function clearAll( $propagate = true ) {
if ( ! self::cacheAvail() ) {
return false;
}
// TODO/FIXME: If cache is big, switch to advanced/new cache clearing automatically?
if ( self::advancedCacheClearEnabled() ) {
self::clearCacheViaRename();
} else {
self::clearCacheClassic();
}
// Remove the transient so it gets regenerated...
delete_transient( 'wmac_stats' );
// Cache was just purged, clear page cache and allow others to hook into our purging...
if ( true === $propagate ) {
if ( ! function_exists( 'wmac_do_cachepurged_action' ) ) {
function wmac_do_cachepurged_action() {
do_action( 'wmac_action_cachepurged' );
}
}
add_action( 'shutdown', 'wmac_do_cachepurged_action', 11 );
add_action( 'wmac_action_cachepurged', [ 'WBCR\Factory_Templates_134\Helpers', 'flushPageCache' ], 10, 0 );
}
// Warm cache (part of speedupper)!
if ( apply_filters( 'wmac_filter_speedupper', true ) ) {
$url = site_url() . '/?ao_speedup_cachebuster=' . rand( 1, 100000 );
$cache = @wp_remote_get( $url ); // @codingStandardsIgnoreLine
unset( $cache );
}
return true;
}
/**
* Wrapper for clearAll but with false param
* to ensure the event is not propagated to others
* through our own hooks (to avoid infinite loops).
*
* @return bool
*/
public static function clearAllActionless() {
return self::clearAll( false );
}
/**
* Returns the contents of our cache dirs.
*
* @return array
*/
protected static function getCacheContents() {
$contents = [];
foreach ( [ '', 'js', 'css' ] as $dir ) {
$contents[ $dir ] = scandir( self::getCacheDir() . $dir );
}
return $contents;
}
/**
* Returns stats about cached contents.
*
* @return array|int|mixed
*/
public static function stats() {
$stats = get_transient( 'wmac_stats' );
// If no transient, do the actual scan!
if ( ! is_array( $stats ) ) {
if ( ! self::cacheAvail() ) {
return 0;
}
$stats = self::statsScan();
$count = $stats[0];
if ( $count > 100 ) {
// Store results in transient.
set_transient( 'wmac_stats', $stats, apply_filters( 'wmac_filter_cache_statsexpiry', HOUR_IN_SECONDS ) );
}
}
return $stats;
}
/**
* Return cache used data
*
* @return array
*/
public static function getUsedCache() {
// Retrieve the Autoptimize Cache Stats information.
$stats = WMAC_PluginCache::stats();
// Set the Max Size recommended for cache files.
$max_size = apply_filters( 'wmac_filter_cachecheck_maxsize', 512 * 1024 * 1024 );
// Retrieve the current Total Files in cache.
$files = $stats[0];
// Retrieve the current Total Size of the cache.
$bytes = $stats[1];
$size = WMAC_PluginHelper::format_filesize( $bytes );
// Calculate the percentage of cache used.
$percent = ceil( $bytes / $max_size * 100 );
if ( $percent > 100 ) {
$percent = 100;
}
return [
'files' => $files,
'size' => $size,
'percent' => $percent,
];
}
/**
* Return cache used data
*
* @return array
*/
public static function getUsedCacheMultisite() {
$files = $bytes = 0;
$sites = get_sites( [
'archived' => 0,
'mature' => 0,
'spam' => 0,
'deleted' => 0,
] );
foreach ( $sites as $site ) {
switch_to_blog( $site->blog_id );
// Retrieve the Autoptimize Cache Stats information.
$stats = WMAC_PluginCache::stats();
// Retrieve the current Total Files in cache.
$files += $stats[0];
// Retrieve the current Total Size of the cache.
$bytes += $stats[1];
restore_current_blog();
}
$size = WMAC_PluginHelper::format_filesize( $bytes );
return [
'files' => $files,
'size' => $size,
];
}
/**
* Performs a scan of cache directory contents and returns an array
* with 3 values: count, size, timestamp.
* count = total number of found files
* size = total filesize (in bytes) of found files
* timestamp = unix timestamp when the scan was last performed/finished.
*
* @return array
*/
protected static function statsScan() {
$count = 0;
$size = 0;
// Scan everything in our cache directories.
foreach ( self::getCacheContents() as $name => $files ) {
$dir = rtrim( self::getCacheDir() . $name, '/' ) . '/';
foreach ( $files as $file ) {
if ( self::isValidCacheFile( $dir, $file ) ) {
if ( WMAC_CACHE_NOGZIP && ( false !== strpos( $file, '.js' ) || false !== strpos( $file, '.css' ) || false !== strpos( $file, '.img' ) || false !== strpos( $file, '.txt' ) ) ) {
// Web server is gzipping, we count .js|.css|.img|.txt files.
$count ++;
} else if ( ! WMAC_CACHE_NOGZIP && false !== strpos( $file, '.none' ) ) {
// We are gzipping ourselves via php, counting only .none files.
$count ++;
}
$size += filesize( $dir . $file );
}
}
}
$stats = [ $count, $size, time() ];
return $stats;
}
/**
* Ensures the cache directory exists, is writeable and contains the
* required .htaccess files.
* Returns false in case it fails to ensure any of those things.
*
* @return bool
*/
public static function cacheAvail() {
foreach ( [ '', 'js', 'css' ] as $dir ) {
if ( ! self::checkCacheDir( self::getCacheDir() . $dir ) ) {
return false;
}
}
// Using .htaccess inside our cache folder to overrule wp-super-cache.
$htaccess = self::getCacheDir() . '/.htaccess';
if ( ! is_file( $htaccess ) ) {
/**
* Create `wp-content/AO_htaccess_tmpl` file with
* whatever htaccess rules you might need
* if you want to override default AO htaccess
*/
$htaccess_tmpl = WP_CONTENT_DIR . '/AO_htaccess_tmpl';
if ( is_file( $htaccess_tmpl ) ) {
$content = file_get_contents( $htaccess_tmpl );
} else if ( is_multisite() || ! WMAC_CACHE_NOGZIP ) {
$content = '<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css A30672000
ExpiresByType text/javascript A30672000
ExpiresByType application/javascript A30672000
</IfModule>
<IfModule mod_headers.c>
Header append Cache-Control "public, immutable"
</IfModule>
<IfModule mod_deflate.c>
<FilesMatch "\.(js|css)$">
SetOutputFilter DEFLATE
</FilesMatch>
</IfModule>
<IfModule mod_authz_core.c>
<Files *.php>
Require all granted
</Files>
</IfModule>
<IfModule !mod_authz_core.c>
<Files *.php>
Order allow,deny
Allow from all
</Files>
</IfModule>';
} else {
$content = '<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css A30672000
ExpiresByType text/javascript A30672000
ExpiresByType application/javascript A30672000
</IfModule>
<IfModule mod_headers.c>
Header append Cache-Control "public, immutable"
</IfModule>
<IfModule mod_deflate.c>
<FilesMatch "\.(js|css)$">
SetOutputFilter DEFLATE
</FilesMatch>
</IfModule>
<IfModule mod_authz_core.c>
<Files *.php>
Require all denied
</Files>
</IfModule>
<IfModule !mod_authz_core.c>
<Files *.php>
Order deny,allow
Deny from all
</Files>
</IfModule>';
}
@file_put_contents( $htaccess, $content ); // @codingStandardsIgnoreLine
}
// All OK!
return true;
}
/**
* Ensures the specified `$dir` exists and is writeable.
* Returns false if that's not the case.
*
* @param string $dir Directory to check/create.
*
* @return bool
*/
protected static function checkCacheDir( $dir ) {
// Try creating the dir if it doesn't exist.
if ( ! file_exists( $dir ) ) {
@mkdir( $dir, 0775, true ); // @codingStandardsIgnoreLine
if ( ! file_exists( $dir ) ) {
return false;
}
}
// If we still cannot write, bail.
if ( ! is_writable( $dir ) ) {
return false;
}
// Create an index.html in there to avoid prying eyes!
$idx_file = rtrim( $dir, '/\\' ) . '/index.html';
if ( ! is_file( $idx_file ) ) {
@file_put_contents( $idx_file, '<html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/" rel="nofollow">Мinify And Combine</a></body></html>' ); // @codingStandardsIgnoreLine
}
return true;
}
}

View File

@@ -0,0 +1,89 @@
<?php
/**
* Operations with Critical CSS
*
* @author Webcraftic <wordpress.webraftic@gmail.com>
* @copyright (c) 2020 Webraftic Ltd
* @version 1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class WMAC_PluginCriticalCss
*/
class WMAC_PluginCriticalCss extends WMAC_PluginStyles {
private $css_critical_style = '';
private $css_critical = [];
private $replaceTag = [ '<title', 'before' ];
/**
* Reads the page and collects style tags.
*
* @param array $options
*
* @return bool
*/
public function read( $options ) {
$this->replaceTag = apply_filters( 'wmac_filter_css_replacetag', $this->replaceTag, $this->content );
// Set critical css code
// value: string
$this->css_critical_style = $options['css_critical_style'];
$this->css_critical_style = apply_filters( 'wmac_filter_css_critical_style', $this->css_critical_style, $this->content );
$style = "<style type='text/css'>{$this->css_critical_style}</style>";
$this->injectInHtml( $style, $this->replaceTag );
// Set critical css files
// value: array
$this->css_critical = $options['css_critical'];
$this->css_critical = apply_filters( 'wmac_filter_css_critical', $this->css_critical, $this->content );
if ( '' !== $this->css_critical ) {
$this->css_critical = array_filter( array_map( 'trim', explode( ',', $this->css_critical ) ) );
} else {
$this->css_critical = [];
}
// Get <style> and <link>.
if ( preg_match_all( '#(<style[^>]*>.*</style>)|(<link[^>]*stylesheet[^>]*>)#Usmi', $this->content, $matches ) ) {
foreach ( $matches[0] as $tag ) {
if ( $this->isCritical( $tag ) ) {
$this->content = str_replace( $tag, '', $this->content );
$this->injectInHtml( $tag, $this->replaceTag );
}
}
return true;
}
// Really, no styles?
return false;
}
/**
* @param $tag
*
* @return bool
*/
private function isCritical( $tag ) {
if ( is_array( $this->css_critical ) && ! empty( $this->css_critical ) ) {
foreach ( $this->css_critical as $match ) {
$pattern = str_replace( '.', '\.', $match );
$pattern = str_replace( '*', '.*', $pattern );
$pattern = str_replace( '/', '\/', $pattern );
if ( preg_match( "/{$pattern}/", $tag ) ) {
return true;
}
}
}
return false;
}
}

View File

@@ -0,0 +1,61 @@
<?php
/**
* Thin wrapper around css minifiers to avoid rewriting a bunch of existing code.
*
* @author Webcraftic <wordpress.webraftic@gmail.com>
* @copyright (c) 2018 Webraftic Ltd
* @version 1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class WMAC_PluginCSSmin
*/
class WMAC_PluginCSSmin {
/**
* Minifier instance.
*
* @var WMAC\tubalmartin\CssMin\Minifier|null
*/
protected $minifier = null;
/**
* Construtor.
*
* @param bool $raise_limits Whether to raise memory limits or not. Default true.
*/
public function __construct( $raise_limits = true ) {
$this->minifier = new WMAC\tubalmartin\CssMin\Minifier( $raise_limits );
}
/**
* Runs the minifier on given string of $css.
* Returns the minified css.
*
* @param string $css CSS to minify.
*
* @return string
*/
public function run( $css ) {
$result = $this->minifier->run( $css );
return $result;
}
/**
* Static helper.
*
* @param string $css CSS to minify.
*
* @return string
*/
public static function minify( $css ) {
$minifier = new self();
return $minifier->run( $css );
}
}

View File

@@ -0,0 +1,246 @@
<?php
/**
* Helper
*
* @author Webcraftic <wordpress.webraftic@gmail.com>
* @copyright (c) 2018 Webraftic Ltd
* @version 1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class WMAC_PluginHelper
*/
class WMAC_PluginHelper {
/**
* Returns true when mbstring is available.
*
* @param bool|null $override Allows overriding the decision.
*
* @return bool
*/
public static function mbstringAvailable( $override = null ) {
static $available = null;
if ( null === $available ) {
$available = \extension_loaded( 'mbstring' );
}
if ( null !== $override ) {
$available = $override;
}
return $available;
}
/**
* Multibyte-capable strpos() if support is available on the server.
* If not, it falls back to using \strpos().
*
* @param string $haystack Haystack.
* @param string $needle Needle.
* @param int $offset Offset.
* @param string|null $encoding Encoding. Default null.
*
* @return int|false
*/
public static function strpos( $haystack, $needle, $offset = 0, $encoding = null ) {
if ( self::mbstringAvailable() ) {
return ( null === $encoding ) ? \mb_strpos( $haystack, $needle, $offset ) : \mb_strlen( $haystack, $needle, $offset, $encoding );
} else {
return \strpos( $haystack, $needle, $offset );
}
}
/**
* Attempts to return the number of characters in the given $string if
* mbstring is available. Returns the number of bytes
* (instead of characters) as fallback.
*
* @param string $string String.
* @param string|null $encoding Encoding.
*
* @return int Number of charcters or bytes in given $string
* (characters if/when supported, bytes otherwise).
*/
public static function strlen( $string, $encoding = null ) {
if ( self::mbstringAvailable() ) {
return ( null === $encoding ) ? \mb_strlen( $string ) : \mb_strlen( $string, $encoding );
} else {
return \strlen( $string );
}
}
/**
* Our wrapper around implementations of \substr_replace()
* that attempts to not break things horribly if at all possible.
* Uses mbstring if available, before falling back to regular
* substr_replace() (which works just fine in the majority of cases).
*
* @param string $string String.
* @param string $replacement Replacement.
* @param int $start Start offset.
* @param int|null $length Length.
* @param string|null $encoding Encoding.
*
* @return string
*/
public static function substrReplace( $string, $replacement, $start, $length = null, $encoding = null ) {
if ( self::mbstringAvailable() ) {
$strlen = self::strlen( $string, $encoding );
if ( $start < 0 ) {
if ( - $start < $strlen ) {
$start = $strlen + $start;
} else {
$start = 0;
}
} else if ( $start > $strlen ) {
$start = $strlen;
}
if ( null === $length || '' === $length ) {
$start2 = $strlen;
} else if ( $length < 0 ) {
$start2 = $strlen + $length;
if ( $start2 < $start ) {
$start2 = $start;
}
} else {
$start2 = $start + $length;
}
if ( null === $encoding ) {
$leader = $start ? \mb_substr( $string, 0, $start ) : '';
$trailer = ( $start2 < $strlen ) ? \mb_substr( $string, $start2, null ) : '';
} else {
$leader = $start ? \mb_substr( $string, 0, $start, $encoding ) : '';
$trailer = ( $start2 < $strlen ) ? \mb_substr( $string, $start2, null, $encoding ) : '';
}
return "{$leader}{$replacement}{$trailer}";
}
return ( null === $length ) ? \substr_replace( $string, $replacement, $start ) : \substr_replace( $string, $replacement, $start, $length );
}
/**
* Decides whether this is a "subdirectory site" or not.
*
* @param bool $override Allows overriding the decision when needed.
*
* @return bool
*/
public static function siteurlNotRoot( $override = null ) {
static $subdir = null;
if ( null === $subdir ) {
$parts = self::getMacWpSiteUrlParts();
$subdir = ( isset( $parts['path'] ) && ( '/' !== $parts['path'] ) );
}
if ( null !== $override ) {
$subdir = $override;
}
return $subdir;
}
/**
* Parse site URL into components using \parse_url(), but do
* so only once per request/lifecycle.
*
* @return array
*/
public static function getMacWpSiteUrlParts() {
static $parts = [];
if ( empty( $parts ) ) {
$parts = \parse_url( WMAC_PluginMain::getSiteUrl() );
}
return $parts;
}
/**
* Given an array or components returned from \parse_url(), assembles back
* the complete URL.
* If optional
*
* @param array $parsed_url URL components array.
* @param bool $schemeless Whether the assembled URL should be
* protocol-relative (schemeless) or not.
*
* @return string
*/
public static function assembleParsedUrl( array $parsed_url, $schemeless = false ) {
$scheme = isset( $parsed_url['scheme'] ) ? $parsed_url['scheme'] . '://' : '';
if ( $schemeless ) {
$scheme = '//';
}
$host = isset( $parsed_url['host'] ) ? $parsed_url['host'] : '';
$port = isset( $parsed_url['port'] ) ? ':' . $parsed_url['port'] : '';
$user = isset( $parsed_url['user'] ) ? $parsed_url['user'] : '';
$pass = isset( $parsed_url['pass'] ) ? ':' . $parsed_url['pass'] : '';
$pass = ( $user || $pass ) ? "$pass@" : '';
$path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '';
$query = isset( $parsed_url['query'] ) ? '?' . $parsed_url['query'] : '';
$fragment = isset( $parsed_url['fragment'] ) ? '#' . $parsed_url['fragment'] : '';
return "$scheme$user$pass$host$port$path$query$fragment";
}
/**
* Returns true if given $url is protocol-relative.
*
* @param string $url URL to check.
*
* @return bool
*/
public static function isProtocolRelative( $url ) {
return ( '/' === $url[1] ); // second char is `/`.
}
/**
* Canonicalizes the given path regardless of it existing or not.
*
* @param string $path Path to normalize.
*
* @return string
*/
public static function pathCanonicalize( $path ) {
$patterns = [
'~/{2,}~',
'~/(\./)+~',
'~([^/\.]+/(?R)*\.{2,}/)~',
'~\.\./~',
];
$replacements = [
'/',
'/',
'',
'',
];
return preg_replace( $patterns, $replacements, $path );
}
/**
* @param $bytes
* @param int $decimals
*
* @return string
*/
public static function format_filesize( $bytes, $decimals = 2 ) {
$units = [ 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ];
for ( $i = 0; ( $bytes / 1024 ) > 0.9; $i ++, $bytes /= 1024 ) {
} // @codingStandardsIgnoreLine
return sprintf( "%1.{$decimals}f %s", round( $bytes, $decimals ), $units[ $i ] );
}
}

View File

@@ -0,0 +1,452 @@
<?php
/**
* Main class
*
* @author Webcraftic <wordpress.webraftic@gmail.com>
* @copyright (c) 2018 Webraftic Ltd
* @version 1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class WMAC_PluginMain
*/
class WMAC_PluginMain {
const INIT_EARLIER_PRIORITY = - 1;
const DEFAULT_HOOK_PRIORITY = 2;
/**
* Constructor.
*/
public function __construct() {
}
/**
* Start processing
*/
public function start() {
$this->setup();
$this->run();
$this->check();
$this->clear();
}
/**
* Runs cache size checker
*/
public function check() {
$checker = new WMAC_PluginCacheChecker();
$checker->run();
}
/**
* Setting the parameters
*/
public function setup() {
// Do we gzip in php when caching or is the webserver doing it?
if ( ! defined( 'WMAC_CACHE_NOGZIP' ) ) {
define( 'WMAC_CACHE_NOGZIP', true /*(bool) get_option( 'wmac_cache_nogzip' )*/ );
}
// These can be overridden by specifying them in wp-config.php or such.
if ( ! defined( 'WMAC_WP_CONTENT_NAME' ) ) {
define( 'WMAC_WP_CONTENT_NAME', '/' . wp_basename( WP_CONTENT_DIR ) );
}
if ( ! defined( 'WMAC_CACHE_CHILD_DIR' ) ) {
define( 'WMAC_CACHE_CHILD_DIR', '/cache/wmac/' );
}
if ( ! defined( 'WMAC_CACHEFILE_PREFIX' ) ) {
define( 'WMAC_CACHEFILE_PREFIX', 'wmac_' );
}
if ( ! defined( 'WMAC_ROOT_DIR' ) ) {
define( 'WMAC_ROOT_DIR', substr( WP_CONTENT_DIR, 0, strlen( WP_CONTENT_DIR ) - strlen( WMAC_WP_CONTENT_NAME ) ) );
}
if ( ! defined( 'WMAC_WP_ROOT_URL' ) ) {
define( 'WMAC_WP_ROOT_URL', str_replace( WMAC_WP_CONTENT_NAME, '', self::getContentUrl() ) );
}
if ( ! defined( 'WMAC_HASH' ) ) {
define( 'WMAC_HASH', wp_hash( WMAC_PluginCache::getCacheUrl() ) );
}
// Multibyte-capable string replacements are available with a filter.
// Also requires 'mbstring' extension.
$with_mbstring = apply_filters( 'wbcr/mac/main_use_mbstring', false );
if ( $with_mbstring ) {
WMAC_PluginHelper::mbstringAvailable( \extensions_loaded( 'mbstring' ) );
} else {
WMAC_PluginHelper::mbstringAvailable( false );
}
}
/**
* Run
*/
public function run() {
if ( WMAC_PluginCache::cacheAvail() ) {
if ( WMAC_Plugin::app()->getPopulateOption( 'js_optimize' )
|| WMAC_Plugin::app()->getPopulateOption( 'css_optimize' )
|| WMAC_Plugin::app()->getPopulateOption( 'css_critical' )
|| WMAC_Plugin::app()->getPopulateOption( 'css_critical_style' ) ) {
// Hook into WordPress frontend.
if ( defined( 'WMAC_INIT_EARLIER' ) ) {
add_action( 'init', [ $this, 'startBuffering' ], self::INIT_EARLIER_PRIORITY );
} else {
if ( ! defined( 'WMAC_HOOK_INTO' ) ) {
define( 'WMAC_HOOK_INTO', 'template_redirect' );
}
add_action( constant( 'WMAC_HOOK_INTO' ), [
$this,
'startBuffering'
], self::DEFAULT_HOOK_PRIORITY );
}
}
} else {
add_action( 'admin_notices', 'WMAC_PluginMain::noticeCacheUnavailable' );
}
}
/**
* Clear cache
*/
public function clear() {
// hook into a collection of page cache purge actions if filter allows.
if ( apply_filters( 'wmac_filter_main_hookpagecachepurge', true ) ) {
$page_cache_purge_actions = [
'after_rocket_clean_domain', // exists.
'hyper_cache_purged', // Stefano confirmed this will be added.
'w3tc_flush_posts', // exits.
'w3tc_flush_all', // exists.
'ce_action_cache_cleared', // Sven confirmed this will be added.
'comet_cache_wipe_cache', // still to be confirmed by Raam.
'wp_cache_cleared', // cfr. https://github.com/Automattic/wp-super-cache/pull/537.
'wpfc_delete_cache', // Emre confirmed this will be added this.
'swift_performance_after_clear_all_cache', // swift perf. yeah!
];
$page_cache_purge_actions = apply_filters( 'wmac_filter_main_pagecachepurgeactions', $page_cache_purge_actions );
foreach ( $page_cache_purge_actions as $purge_action ) {
add_action( $purge_action, 'WMAC_PluginCache::clearAllActionless' );
}
}
}
/**
* Setup output buffering if needed.
*
* @return void
*/
public function startBuffering() {
if ( $this->shouldBuffer() ) {
// Load speedupper conditionally (true by default).
if ( apply_filters( 'wmac_filter_extend', true ) ) {
$this->extend();
}
if ( WMAC_Plugin::app()->getPopulateOption( 'js_optimize' ) ) {
if ( ! defined( 'CONCATENATE_SCRIPTS' ) ) {
define( 'CONCATENATE_SCRIPTS', false );
}
if ( ! defined( 'COMPRESS_SCRIPTS' ) ) {
define( 'COMPRESS_SCRIPTS', false );
}
}
if ( WMAC_Plugin::app()->getPopulateOption( 'css_optimize' ) ) {
if ( ! defined( 'COMPRESS_CSS' ) ) {
define( 'COMPRESS_CSS', false );
}
}
if ( apply_filters( 'wmac_filter_obkiller', false ) ) {
while ( ob_get_level() > 0 ) {
ob_end_clean();
}
}
// Now, start the real thing!
ob_start( [ $this, 'endBuffering' ] );
}
}
/**
* Returns true if all the conditions to start output buffering are satisfied.
*
* @param bool $doing_tests Allows overriding the optimization of only
* deciding once per request (for use in tests).
*
* @return bool
*/
public function shouldBuffer( $doing_tests = false ) {
static $do_buffering = null;
// Only check once in case we're called multiple times by others but
// still allows multiple calls when doing tests.
if ( null === $do_buffering || $doing_tests ) {
$wmac_noptimize = false;
// Checking for DONOTMINIFY constant as used by e.g. WooCommerce POS.
if ( defined( 'DONOTMINIFY' ) && ( constant( 'DONOTMINIFY' ) === true || constant( 'DONOTMINIFY' ) === 'true' ) ) {
$wmac_noptimize = true;
}
// Skip checking query strings if they're disabled.
if ( apply_filters( 'wmac_filter_honor_qs_noptimize', true ) ) {
// Check for `wmac_noptimize` (and other) keys in the query string
// to get non-optimized page for debugging.
$keys = [
'wmac_noptimize',
'wmac_noptirocket',
];
foreach ( $keys as $key ) {
if ( array_key_exists( $key, $_GET ) && '1' === $_GET[ $key ] ) {
$wmac_noptimize = true;
break;
}
}
}
// If setting says not to optimize logged in user and user is logged in...
if( false === $wmac_noptimize && !WMAC_Plugin::app()->getPopulateOption('optimize_scripts_for_logged') && is_user_logged_in() && current_user_can('edit_posts') ) {
$wmac_noptimize = true;
}
// Allows blocking of auto optimization on your own terms regardless of above decisions.
$wmac_noptimize = (bool) apply_filters( 'wmac_filter_noptimize', $wmac_noptimize );
// Check for site being previewed in the Customizer (available since WP 4.0).
$is_customize_preview = false;
if ( function_exists( 'is_customize_preview' ) && is_customize_preview() ) {
$is_customize_preview = is_customize_preview();
}
/**
* We only buffer the frontend requests (and then only if not a feed
* and not turned off explicitly and not when being previewed in Customizer)!
* NOTE: Tests throw a notice here due to is_feed() being called
* while the main query hasn't been ran yet. Thats why we use
* WMAC_INIT_EARLIER in tests.
*/
$do_buffering = ( ! is_admin() && ! is_feed() && ! $wmac_noptimize && ! $is_customize_preview );
}
return $do_buffering;
}
/**
* Returns true if given markup is considered valid/processable/optimizable.
*
* @param string $content Markup.
*
* @return bool
*/
public function isValidBuffer( $content ) {
// Defaults to true.
$valid = true;
$has_no_html_tag = ( false === stripos( $content, '<html' ) );
$has_xsl_stylesheet = ( false !== stripos( $content, '<xsl:stylesheet' ) );
$has_html5_doctype = ( preg_match( '/^<!DOCTYPE.+html>/i', $content ) > 0 );
if ( $has_no_html_tag ) {
// Can't be valid amp markup without an html tag preceding it.
$is_amp_markup = false;
} else {
$is_amp_markup = self::isAmpMarkup( $content );
}
// If it's not html, or if it's amp or contains xsl stylesheets we don't touch it.
if ( $has_no_html_tag && ! $has_html5_doctype || $is_amp_markup || $has_xsl_stylesheet ) {
$valid = false;
}
return $valid;
}
/**
* Returns true if given $content is considered to be AMP markup.
* This is far from actual validation against AMP spec, but it'll do for now.
*
* @param string $content Markup to check.
*
* @return bool
*/
public static function isAmpMarkup( $content ) {
$is_amp_markup = preg_match( '/<html[^>]*(?:amp|⚡)/i', $content );
return (bool) $is_amp_markup;
}
/**
* Processes/optimizes the output-buffered content and returns it.
* If the content is not processable, it is returned unmodified.
*
* @param string $content Buffered content.
*
* @return string
*/
public function endBuffering( $content ) {
// Bail early without modifying anything if we can't handle the content.
if ( ! $this->isValidBuffer( $content ) ) {
return $content;
}
// Determine what needs to be ran.
$classes[] = 'WMAC_PluginCriticalCss';
if ( WMAC_Plugin::app()->getPopulateOption( 'js_optimize' ) ) {
$classes[] = 'WMAC_PluginScripts';
}
if ( WMAC_Plugin::app()->getPopulateOption( 'css_optimize' ) ) {
$classes[] = 'WMAC_PluginStyles';
}
$classoptions = [
'WMAC_PluginScripts' => [
'aggregate' => WMAC_Plugin::app()->getPopulateOption( 'js_aggregate' ),
'forcehead' => WMAC_Plugin::app()->getPopulateOption( 'js_forcehead' ),
'trycatch' => WMAC_Plugin::app()->getPopulateOption( 'js_trycatch' ),
'js_exclude' => WMAC_Plugin::app()->getPopulateOption( 'js_exclude' ),
'include_inline' => WMAC_Plugin::app()->getPopulateOption( 'js_include_inline' ),
],
'WMAC_PluginStyles' => [
'aggregate' => WMAC_Plugin::app()->getPopulateOption( 'css_aggregate' ),
'datauris' => WMAC_Plugin::app()->getPopulateOption( 'css_datauris' ),
'defer' => WMAC_Plugin::app()->getPopulateOption( 'css_defer' ),
'inline' => WMAC_Plugin::app()->getPopulateOption( 'css_inline' ),
'css_exclude' => WMAC_Plugin::app()->getPopulateOption( 'css_exclude' ),
'include_inline' => WMAC_Plugin::app()->getPopulateOption( 'css_include_inline' ),
],
'WMAC_PluginCriticalCss' => [
'css_critical' => WMAC_Plugin::app()->getPopulateOption( 'css_critical' ),
'css_critical_style' => WMAC_Plugin::app()->getPopulateOption( 'css_critical_style' ),
],
];
$content = apply_filters( 'wmac_filter_html_before_minify', $content );
// Run the classes!
foreach ( $classes as $name ) {
$instance = new $name( $content );
if ( $instance->read( $classoptions[ $name ] ) ) {
$instance->minify();
$instance->cache();
$content = $instance->getContent();
}
unset( $instance );
}
$content = apply_filters( 'wmac_html_after_minify', $content );
return $content;
}
/**
* Extended functional
*/
public function extend() {
if ( apply_filters( 'wmac_js_do_minify', true ) ) {
add_filter( 'wmac_js_individual_script', [ $this, 'jsSnippetcacher' ], 10, 2 );
add_filter( 'wmac_js_after_minify', [ $this, 'jsCleanup' ], 10, 1 );
}
}
/**
* @param $jsin
* @param $jsfilename
*
* @return false|mixed|string
*/
public function jsSnippetcacher( $jsin, $jsfilename ) {
$md5hash = 'snippet_' . md5( $jsin );
$ccheck = new WMAC_PluginCache( $md5hash, 'js' );
if ( $ccheck->check() ) {
$scriptsrc = $ccheck->retrieve();
} else {
if ( false === ( strpos( $jsfilename, 'min.js' ) ) && ( false === strpos( $jsfilename, 'js/jquery/jquery.js' ) ) && ( str_replace( apply_filters( 'wmac_filter_js_consider_minified', false ), '', $jsfilename ) === $jsfilename ) ) {
$tmp_jscode = trim( WMAC\JSMin::minify( $jsin ) );
if ( ! empty( $tmp_jscode ) ) {
$scriptsrc = $tmp_jscode;
unset( $tmp_jscode );
} else {
$scriptsrc = $jsin;
}
} else {
// Removing comments, linebreaks and stuff!
$scriptsrc = preg_replace( '#^\s*\/\/.*$#Um', '', $jsin );
$scriptsrc = preg_replace( '#^\s*\/\*[^!].*\*\/\s?#Us', '', $scriptsrc );
$scriptsrc = preg_replace( "#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#", "\n", $scriptsrc );
}
$last_char = substr( $scriptsrc, - 1, 1 );
if ( ';' !== $last_char && '}' !== $last_char ) {
$scriptsrc .= ';';
}
if ( ! empty( $jsfilename ) && str_replace( apply_filters( 'wmac_filter_js_speedup_cache', false ), '', $jsfilename ) === $jsfilename ) {
// Don't cache inline CSS or if filter says no!
$ccheck->cache( $scriptsrc, 'text/javascript' );
}
}
unset( $ccheck );
return $scriptsrc;
}
/**
* JS cleanup
*
* @param $jsin
*
* @return string
*/
public function jsCleanup( $jsin ) {
return trim( $jsin );
}
/**
* Notice
*/
public static function noticeCacheUnavailable() {
echo '<div class="error"><p>';
// Translators: %s is the cache directory location.
printf( __( 'Мinify And Combine cannot write to the cache directory (%s), please fix to enable CSS/ JS optimization!', 'minify-and-combine' ), WMAC_PluginCache::getCacheDir() );
echo '</p></div>';
}
/**
* Get site url
*
* @return string
*/
public static function getSiteUrl() {
if ( function_exists( 'domain_mapping_siteurl' ) ) {
return domain_mapping_siteurl( get_current_blog_id() );
} else {
return site_url();
}
}
/**
* Get content url
*
* @return string
*/
public static function getContentUrl() {
if ( function_exists( 'get_original_url' ) ) {
$site_url = self::getSiteUrl();
return str_replace( get_original_url( $site_url ), $site_url, content_url() );
} else {
return content_url();
}
}
}

View File

@@ -0,0 +1,624 @@
<?php
/**
* Operations with scripts
*
* @author Webcraftic <wordpress.webraftic@gmail.com>
* @copyright (c) 2018 Webraftic Ltd
* @version 1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class WMAC_PluginScripts
*/
class WMAC_PluginScripts extends WMAC_PluginBase {
private $scripts = [];
private $move = [
'first' => [],
'last' => []
];
private $dontmove = [
'document.write',
'html5.js',
'show_ads.js',
'google_ad',
'histats.com/js',
'statcounter.com/counter/counter.js',
'ws.amazon.com/widgets',
'media.fastclick.net',
'/ads/',
'comment-form-quicktags/quicktags.php',
'edToolbar',
'intensedebate.com',
'scripts.chitika.net/',
'_gaq.push',
'jotform.com/',
'admin-bar.min.js',
'GoogleAnalyticsObject',
'plupload.full.min.js',
'syntaxhighlighter',
'adsbygoogle',
'gist.github.com',
'_stq',
'nonce',
'post_id',
'data-noptimize',
'logHuman'
];
private $domove = [
'gaJsHost',
'load_cmc',
'jd.gallery.transitions.js',
'swfobject.embedSWF(',
'tiny_mce.js',
'tinyMCEPreInit.go'
];
private $domovelast = [
'addthis.com',
'/afsonline/show_afs_search.js',
'disqus.js',
'networkedblogs.com/getnetworkwidget',
'infolinks.com/js/',
'jd.gallery.js.php',
'jd.gallery.transitions.js',
'swfobject.embedSWF(',
'linkwithin.com/widget.js',
'tiny_mce.js',
'tinyMCEPreInit.go'
];
private $aggregate = true;
private $trycatch = false;
private $alreadyminified = false;
private $forcehead = true;
private $include_inline = false;
private $jscode = '';
private $url = '';
private $md5hash = '';
private $whitelist = [];
private $jsremovables = [];
private $inject_min_late = '';
/**
* Reads the page and collects script tags
*
* @param array $options
*
* @return bool
*/
public function read( $options ) {
$noptimizeJS = apply_filters( 'wmac_filter_js_noptimize', false, $this->content );
if ( $noptimizeJS ) {
return false;
}
// only optimize known good JS?
$whitelistJS = apply_filters( 'wmac_filter_js_whitelist', '', $this->content );
if ( ! empty( $whitelistJS ) ) {
$this->whitelist = array_filter( array_map( 'trim', explode( ',', $whitelistJS ) ) );
}
// is there JS we should simply remove
$removableJS = apply_filters( 'wmac_filter_js_removables', '', $this->content );
if ( ! empty( $removableJS ) ) {
$this->jsremovables = array_filter( array_map( 'trim', explode( ',', $removableJS ) ) );
}
// Determine whether we're doing JS-files aggregation or not.
if ( ! $options['aggregate'] ) {
$this->aggregate = false;
}
// Returning true for "dontaggregate" turns off aggregation.
if ( $this->aggregate && apply_filters( 'wmac_filter_js_dontaggregate', false ) ) {
$this->aggregate = false;
}
// include inline?
if ( apply_filters( 'wmac_js_include_inline', $options['include_inline'] ) ) {
$this->include_inline = true;
}
// filter to "late inject minified JS", default to true for now (it is faster)
$this->inject_min_late = apply_filters( 'wmac_filter_js_inject_min_late', true );
// filters to override hardcoded do(nt)move(last) array contents (array in, array out!)
$this->dontmove = apply_filters( 'wmac_filter_js_dontmove', $this->dontmove );
$this->domovelast = apply_filters( 'wmac_filter_js_movelast', $this->domovelast );
$this->domove = apply_filters( 'wmac_filter_js_domove', $this->domove );
// get extra exclusions settings or filter
$excludeJS = $options['js_exclude'];
$excludeJS = apply_filters( 'wmac_filter_js_exclude', $excludeJS, $this->content );
if ( '' !== $excludeJS ) {
if ( is_array( $excludeJS ) ) {
if ( ( $removeKeys = array_keys( $excludeJS, 'remove' ) ) !== false ) {
foreach ( $removeKeys as $removeKey ) {
unset( $excludeJS[ $removeKey ] );
$this->jsremovables[] = $removeKey;
}
}
$exclJSArr = array_keys( $excludeJS );
} else {
$exclJSArr = array_filter( array_map( 'trim', explode( ',', $excludeJS ) ) );
}
$this->dontmove = array_merge( $exclJSArr, $this->dontmove );
}
// Should we add try-catch?
if ( $options['trycatch'] ) {
$this->trycatch = true;
}
// force js in head?
if ( $options['forcehead'] ) {
$this->forcehead = true;
} else {
$this->forcehead = false;
}
$this->forcehead = apply_filters( 'wmac_filter_js_forcehead', $this->forcehead );
// noptimize me
$this->content = $this->hideNoptimize( $this->content );
// Save IE hacks
$this->content = $this->hideIEhacks( $this->content );
// comments
$this->content = $this->hideComments( $this->content );
// Get script files
if ( preg_match_all( '#<script.*</script>#Usmi', $this->content, $matches ) ) {
foreach ( $matches[0] as $tag ) {
// only consider script aggregation for types whitelisted in should_aggregate-function
$should_aggregate = $this->shouldAggregate( $tag );
if ( ! $should_aggregate ) {
$tag = '';
continue;
}
if ( preg_match( '#<script[^>]*src=("|\')([^>]*)("|\')#Usmi', $tag, $source ) ) {
// non-inline script
if ( $this->isremovable( $tag, $this->jsremovables ) ) {
$this->content = str_replace( $tag, '', $this->content );
continue;
}
$origTag = null;
$url = current( explode( '?', $source[2], 2 ) );
$path = $this->getPath( $url );
if ( false !== $path && preg_match( '#\.js$#', $path ) && $this->isMergeable( $tag ) ) {
// ok to optimize, add to array
$this->scripts[] = $path;
} else {
$origTag = $tag;
$newTag = $tag;
// non-mergeable script (excluded or dynamic or external)
if ( is_array( $excludeJS ) ) {
// should we add flags?
foreach ( $excludeJS as $exclTag => $exclFlags ) {
if ( false !== strpos( $origTag, $exclTag ) && in_array( $exclFlags, [
'async',
'defer'
] ) ) {
$newTag = str_replace( '<script ', '<script ' . $exclFlags . ' ', $newTag );
}
}
}
// Should we minify the non-aggregated script?
if ( $path && apply_filters( 'wmac_filter_js_minify_excluded', true, $url ) ) {
$minified_url = $this->minifySingle( $path );
// replace orig URL with minified URL from cache if so
if ( ! empty( $minified_url ) ) {
$newTag = str_replace( $url, $minified_url, $newTag );
}
// remove querystring from URL in newTag
if ( ! empty( $explUrl[1] ) ) {
$newTag = str_replace( '?' . $explUrl[1], '', $newTag );
}
}
if ( $this->isMovable( $newTag ) ) {
// can be moved, flags and all
if ( $this->moveToLast( $newTag ) ) {
$this->move['last'][] = $newTag;
} else {
$this->move['first'][] = $newTag;
}
} else {
// cannot be moved, so if flag was added re-inject altered tag immediately
if ( $origTag !== $newTag ) {
$this->content = str_replace( $origTag, $newTag, $this->content );
}
// and forget about the $tag (not to be touched any more)
$tag = '';
}
}
} else {
// Inline script
if ( $this->isremovable( $tag, $this->jsremovables ) ) {
$this->content = str_replace( $tag, '', $this->content );
continue;
}
// unhide comments, as javascript may be wrapped in comment-tags for old times' sake
$tag = $this->restoreComments( $tag );
if ( $this->isMergeable( $tag ) && $this->include_inline ) {
preg_match( '#<script.*>(.*)</script>#Usmi', $tag, $code );
$code = preg_replace( '#.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*#sm', '$1', $code[1] );
$code = preg_replace( '/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $code );
$this->scripts[] = 'INLINE;' . $code;
} else {
// Can we move this?
$wmac_js_moveable = apply_filters( 'wmac_js_moveable', '', $tag );
if ( $this->isMovable( $tag ) || '' !== $wmac_js_moveable ) {
if ( $this->moveToLast( $tag ) || 'last' === $wmac_js_moveable ) {
$this->move['last'][] = $tag;
} else {
$this->move['first'][] = $tag;
}
} else {
// We shouldn't touch this
$tag = '';
}
}
// Re-hide comments to be able to do the removal based on tag from $this->content
$tag = $this->hideComments( $tag );
}
//Remove the original script tag
$this->content = str_replace( $tag, '', $this->content );
}
return true;
}
return false;
}
/**
* Determines wheter a certain `<script>` $tag should be aggregated or not.
*
* We consider these as "aggregation-safe" currently:
* - script tags without a `type` attribute
* - script tags with these `type` attribute values: `text/javascript`, `text/ecmascript`, `application/javascript`,
* and `application/ecmascript`
*
* Everything else should return false.
*
* @link https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type
*
* @param string $tag
*
* @return bool
*/
public function shouldAggregate( $tag ) {
// We're only interested in the type attribute of the <script> tag itself, not any possible
// inline code that might just contain the 'type=' string...
$tag_parts = [];
preg_match( '#<(script[^>]*)>#i', $tag, $tag_parts );
$tag_without_contents = null;
if ( ! empty( $tag_parts[1] ) ) {
$tag_without_contents = $tag_parts[1];
}
$has_type = ( strpos( $tag_without_contents, 'type' ) !== false );
$type_valid = false;
if ( $has_type ) {
$type_valid = (bool) preg_match( '/type\s*=\s*[\'"]?(?:text|application)\/(?:javascript|ecmascript)[\'"]?/i', $tag_without_contents );
}
$should_aggregate = false;
if ( ! $has_type || $type_valid ) {
$should_aggregate = true;
}
return $should_aggregate;
}
/**
* Joins and optimizes JS
*
* @return bool
*/
public function minify() {
foreach ( $this->scripts as $script ) {
// TODO/FIXME: some duplicate code here, can be reduced/simplified
if ( preg_match( '#^INLINE;#', $script ) ) {
// Inline script
$script = preg_replace( '#^INLINE;#', '', $script );
$script = rtrim( $script, ";\n\t\r" ) . ';';
// Add try-catch?
if ( $this->trycatch ) {
$script = 'try{' . $script . '}catch(e){}';
}
$tmpscript = apply_filters( 'wmac_js_individual_script', $script, '' );
if ( has_filter( 'wmac_js_individual_script' ) && ! empty( $tmpscript ) ) {
$script = $tmpscript;
$this->alreadyminified = true;
}
$this->jscode .= "\n" . $script;
} else {
// External script
if ( false !== $script && file_exists( $script ) && is_readable( $script ) ) {
$scriptsrc = file_get_contents( $script );
$scriptsrc = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $scriptsrc );
$scriptsrc = rtrim( $scriptsrc, ";\n\t\r" ) . ';';
// Add try-catch?
if ( $this->trycatch ) {
$scriptsrc = 'try{' . $scriptsrc . '}catch(e){}';
}
$tmpscriptsrc = apply_filters( 'wmac_js_individual_script', $scriptsrc, $script );
if ( has_filter( 'wmac_js_individual_script' ) && ! empty( $tmpscriptsrc ) ) {
$scriptsrc = $tmpscriptsrc;
$this->alreadyminified = true;
} else if ( $this->canInjectLate( $script ) ) {
$scriptsrc = self::buildInjectlaterMarker( $script, md5( $scriptsrc ) );
}
$this->jscode .= "\n" . $scriptsrc;
}/*else{
//Couldn't read JS. Maybe getPath isn't working?
}*/
}
}
// Check for already-minified code
$this->md5hash = md5( $this->jscode );
$ccheck = new WMAC_PluginCache( $this->md5hash, 'js' );
if ( $ccheck->check() ) {
$this->jscode = $ccheck->retrieve();
return true;
}
unset( $ccheck );
// $this->jscode has all the uncompressed code now.
if ( true !== $this->alreadyminified ) {
if ( apply_filters( 'wmac_js_do_minify', true ) ) {
$tmp_jscode = trim( WMAC\JSMin::minify( $this->jscode ) );
if ( ! empty( $tmp_jscode ) ) {
$this->jscode = $tmp_jscode;
unset( $tmp_jscode );
}
$this->jscode = $this->injectMinified( $this->jscode );
$this->jscode = apply_filters( 'wmac_js_after_minify', $this->jscode );
return true;
} else {
$this->jscode = $this->injectMinified( $this->jscode );
return false;
}
}
$this->jscode = apply_filters( 'wmac_js_after_minify', $this->jscode );
return true;
}
/**
* Caches the JS in uncompressed, deflated and gzipped form.
*/
public function cache() {
$cache = new WMAC_PluginCache( $this->md5hash, 'js' );
if ( ! $cache->check() ) {
// Cache our code
$cache->cache( $this->jscode, 'text/javascript' );
}
$this->url = WMAC_PluginCache::getCacheUrl() . $cache->getname();
}
/**
* Returns the content
*
* @return string
*/
public function getContent() {
// Add the scripts taking forcehead/ deferred (default) into account
if ( $this->forcehead ) {
$replaceTag = [ '</head>', 'before' ];
$defer = '';
} else {
$replaceTag = [ '</body>', 'before' ];
$defer = 'defer ';
}
$defer = apply_filters( 'wmac_filter_js_defer', $defer );
$bodyreplacementpayload = '<script type="text/javascript" ' . $defer . 'src="' . $this->url . '"></script>';
$bodyreplacementpayload = apply_filters( 'wmac_filter_js_bodyreplacementpayload', $bodyreplacementpayload );
$bodyreplacement = implode( '', $this->move['first'] );
$bodyreplacement .= $bodyreplacementpayload;
$bodyreplacement .= implode( '', $this->move['last'] );
$replaceTag = apply_filters( 'wmac_filter_js_replacetag', $replaceTag );
if ( strlen( $this->jscode ) > 0 ) {
$this->injectInHtml( $bodyreplacement, $replaceTag );
}
// Restore comments.
$this->content = $this->restoreComments( $this->content );
// Restore IE hacks.
$this->content = $this->restoreIEhacks( $this->content );
// Restore noptimize.
$this->content = $this->restoreNoptimize( $this->content );
// Return the modified HTML.
return $this->content;
}
/**
* Checks against the white- and blacklists
*
* @param $tag
*
* @return bool
*/
private function isMergeable( $tag ) {
if ( ! $this->aggregate ) {
return false;
}
if ( ! empty( $this->whitelist ) ) {
foreach ( $this->whitelist as $match ) {
if ( false !== strpos( $tag, $match ) ) {
return true;
}
}
// no match with whitelist
return false;
} else {
foreach ( $this->domove as $match ) {
if ( false !== strpos( $tag, $match ) ) {
// Matched something
return false;
}
}
if ( $this->moveToLast( $tag ) ) {
return false;
}
foreach ( $this->dontmove as $match ) {
if ( false !== strpos( $tag, $match ) ) {
// Matched something
return false;
}
}
// If we're here it's safe to merge
return true;
}
}
/**
* Checks agains the blacklist
*
* @param $tag
*
* @return bool
*/
private function isMovable( $tag ) {
if ( true !== $this->include_inline || apply_filters( 'wmac_filter_js_unmovable', true ) ) {
return false;
}
foreach ( $this->domove as $match ) {
if ( false !== strpos( $tag, $match ) ) {
// Matched something
return true;
}
}
if ( $this->moveToLast( $tag ) ) {
return true;
}
foreach ( $this->dontmove as $match ) {
if ( false !== strpos( $tag, $match ) ) {
// Matched something
return false;
}
}
// If we're here it's safe to move
return true;
}
/**
* @param $tag
*
* @return bool
*/
private function moveToLast( $tag ) {
foreach ( $this->domovelast as $match ) {
if ( false !== strpos( $tag, $match ) ) {
// Matched, return true
return true;
}
}
// Should be in 'first'
return false;
}
/**
* Determines wheter a <script> $tag can be excluded from minification (as already minified) based on:
* - inject_min_late being active
* - filename ending in `min.js`
* - filename matching `js/jquery/jquery.js` (wordpress core jquery, is minified)
* - filename matching one passed in the consider minified filter
*
* @param string $jsPath
*
* @return bool
*/
private function canInjectLate( $jsPath ) {
$consider_minified_array = apply_filters( 'wmac_filter_js_consider_minified', false );
if ( true !== $this->inject_min_late ) {
// late-inject turned off
return false;
} else if ( ( false === strpos( $jsPath, 'min.js' ) ) && ( false === strpos( $jsPath, 'wp-includes/js/jquery/jquery.js' ) ) && ( str_replace( $consider_minified_array, '', $jsPath ) === $jsPath ) ) {
// file not minified based on filename & filter
return false;
} else {
// phew, all is safe, we can late-inject
return true;
}
}
/**
* Returns whether we're doing aggregation or not.
*
* @return bool
*/
public function aggregating() {
return $this->aggregate;
}
/**
* Minifies a single local js file and returns its (cached) url.
*
* @param string $filepath Filepath.
* @param bool $cache_miss Optional. Force a cache miss. Default false.
*
* @return bool|string Url pointing to the minified js file or false.
*/
public function minifySingle( $filepath, $cache_miss = false ) {
$contents = $this->prepareMinifySingle( $filepath );
if ( empty( $contents ) ) {
return false;
}
// Check cache.
$hash = 'single_' . md5( $contents );
$cache = new WMAC_PluginCache( $hash, 'js' );
// If not in cache already, minify...
if ( ! $cache->check() || $cache_miss ) {
$contents = trim( WMAC\JSMin::minify( $contents ) );
// Store in cache.
$cache->cache( $contents, 'text/javascript' );
}
$url = $this->buildMinifySingleUrl( $cache );
return $url;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
<?php
// Silence is golden.

View File

@@ -0,0 +1,2 @@
<?php
// Silence is golden.

View File

@@ -0,0 +1,2 @@
<?php
// Silence is golden.

View File

@@ -0,0 +1,273 @@
# Translation of Plugins - Clearfy in Spanish (Spain)
# This file is distributed under the same license as the Plugins - Clearfy WordPress optimization plugin and disable ultimate tweaker - Development (trunk) package.
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2019-04-28 06:29+0300\n"
"PO-Revision-Date: 2019-04-28 06:29+0300\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Poedit 2.1.1\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Poedit 2.1.1\n"
"X-Poedit-Basepath: ..\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Poedit-SourceCharset: UTF-8\n"
"X-Poedit-KeywordsList: __;_e;_n:1,2;_x:1,2c;_ex:1,2c\n"
"X-Poedit-SearchPath-0: .\n"
"X-Poedit-SearchPathExcluded-0: libs\n"
"X-Poedit-SearchPathExcluded-1: components\n"
"X-Poedit-SearchPathExcluded-2: cache\n"
#: admin/boot.php:23
msgid ""
"Clearfy: Minify and Combine component is not compatible with the Autoptimize "
"plugin, please do not use them together to avoid conflicts. Please disable "
"the Minify and Combine component"
msgstr ""
"Clearfy: Nuestro componente Minify and Combine no es compatible con el "
"plugin ( Autoptimize ), no los use juntos para evitar conflictos. Por favor "
"deshabilite el componente Minify y Combine"
#: admin/boot.php:50 admin/pages/settings.php:103
msgid "Optimize JavaScript Code?"
msgstr "¿Optimizar código JavaScript?"
#: admin/boot.php:55 admin/pages/settings.php:124
msgid "Aggregate JS-files?"
msgstr "¿Agregar archivos JS?"
#: admin/boot.php:60 admin/pages/settings.php:145
msgid "Also aggregate inline JS?"
msgstr "¿También agregar inline JS?"
#: admin/boot.php:65 admin/pages/settings.php:166
msgid "Force JavaScript in &lt;head&gt;?"
msgstr "Forzar JavaScript en &lt;head&gt;?"
#: admin/boot.php:70 admin/pages/settings.php:175
msgid "Exclude scripts from Мinify And Combine:"
msgstr "Excluir scripts de Мinify And Combine:"
#: admin/boot.php:75 admin/pages/settings.php:185
msgid "Add try-catch wrapping?"
msgstr "¿Añadir envoltura try-catch?"
#: admin/boot.php:83 admin/pages/settings.php:206
msgid "Optimize CSS Code?"
msgstr "¿Optimizar código CSS?"
#: admin/boot.php:89 admin/pages/settings.php:222
msgid "Aggregate CSS-files?"
msgstr "¿Agregar archivos CSS?"
#: admin/boot.php:95 admin/pages/settings.php:243
msgid "Also aggregate inline CSS?"
msgstr "¿También agregar inline CSS?"
#: admin/boot.php:101 admin/pages/settings.php:264
msgid "Generate data: URIs for images?"
msgstr "Generar datos: ¿URIs para imágenes?"
#: admin/boot.php:107 admin/pages/settings.php:274
msgid "Inline and Defer CSS?"
msgstr "¿Inline y aplazar CSS?"
#: admin/boot.php:113 admin/pages/settings.php:285
msgid "Inline all CSS?"
msgstr "¿Inline todo el CSS?"
#: admin/boot.php:119
msgid "Exclude CSS from Мinify And Combine"
msgstr "Excluir CSS de Мinify And Combine"
#: admin/boot.php:135
msgid "One click optimize scripts (js, css)"
msgstr "Optimizar scripts con un clik (js, css)"
#: admin/boot.php:137
msgid "One click optimize html code and scripts"
msgstr "Optimizar html y scripts con un Click"
#: admin/pages/settings.php:39
msgid "Minify (JS/CSS)"
msgstr "Minificar (JS/CSS)"
#: admin/pages/settings.php:60
msgid "Minify (Html/JS/CSS)"
msgstr "Minificar (Html/JS/CSS)"
#: admin/pages/settings.php:60
msgid "General"
msgstr "General"
#: admin/pages/settings.php:78
msgid "The cache has been successfully cleaned."
msgstr "El caché se ha limpiado con éxito."
#: admin/pages/settings.php:96
msgid "JavaScript Options"
msgstr "Opciones de JavaScript"
#: admin/pages/settings.php:126
msgid ""
"Aggregate all linked JS-files to have them loaded non-render blocking? If "
"this option is off, the individual JS-files will remain in place but will be "
"minified."
msgstr ""
"¿Agregar todos los archivos JS vinculados para que sean cargados sin "
"procesamiento? Si esta opción está desactivada, los archivos JS individuales "
"permanecerán en su lugar pero se minimizarán."
#: admin/pages/settings.php:147
msgid ""
"Let Мinify And Combine also extract JS from the HTML. Warning: this can make "
"Мinify And Combine's cache size grow quickly, so only enable this if you "
"know what you're doing."
msgstr ""
"Deje que Мinify And Combine también extraiga JS del HTML. Advertencia: esto "
"puede hacer que el tamaño del caché de Мinify And Combine crezca "
"rápidamente, así que habilítelo solo si sabe lo que está haciendo."
#: admin/pages/settings.php:168
msgid ""
"Load JavaScript early, this can potentially fix some JS-errors, but makes "
"the JS render blocking."
msgstr ""
"Cargar primero JavaScript puede corregir algunos errores potenciales de JS, "
"pero hace que JS renderice el bloqueo."
#: admin/pages/settings.php:177
msgid ""
"A comma-separated list of scripts you want to exclude from being optimized, "
"for example 'whatever.js, another.js' (without the quotes) to exclude those "
"scripts from being aggregated and minimized by Мinify And Combine."
msgstr ""
"Lista de scripts separados por comas para ser excluídos de la optimización, "
"ejemplo, 'whatever.js, another.js' (sin las comillas) excluir estos scripts "
"de ser agregados para minimizarlos por Мinify And Combine."
#: admin/pages/settings.php:187 admin/pages/settings.php:208
msgid ""
"If your scripts break because of a JS-error, you might want to try this."
msgstr "Si sus scripts se rompen debido a un error de JS, intente esto."
#: admin/pages/settings.php:199
msgid "CSS Options"
msgstr "Opciones de CSS"
#: admin/pages/settings.php:224
msgid ""
"Aggregate all linked CSS-files? If this option is off, the individual CSS-"
"files will remain in place but will be minified."
msgstr ""
"¿Agregar todos los archivos CSS vinculados? Si esta opción es desactivada, "
"los archivos CSS individuales permanecerán en su lugar pero se minimizarán."
#: admin/pages/settings.php:245
msgid ""
"Check this option for Мinify And Combine to also aggregate CSS in the HTML."
msgstr ""
"Marque esta opción para Мinificar y combinar asi como agregar también CSS en "
"el HTML."
#: admin/pages/settings.php:266
msgid ""
"Enable this to include small background-images in the CSS itself instead of "
"as separate downloads."
msgstr ""
"Habilite esto para incluir pequeñas imágenes de fondo en el CSS, "
"descarguelas de forma separada."
#: admin/pages/settings.php:276
msgid ""
"Inline \"above the fold CSS\" while loading the main auto optimized CSS only "
"after page load. Check the FAQ for more info.\n"
"This can be fully automated for different types of pages with the Мinify And "
"Combine CriticalCSS Power-Up."
msgstr ""
"En línea \"above the fold CSS\" mientras se carga el auto optimizado de CSS "
"principal. Consulte FAQ para obtener más información.\n"
"Esto puede automatizarse completamente para diferentes tipos de páginas con "
"la función de \"Мinify And Combine CriticalCSS\"."
#: admin/pages/settings.php:287
msgid ""
"Inlining all CSS can improve performance for sites with a low pageviews/ "
"visitor-rate, but may slow down performance otherwise."
msgstr ""
"La incorporación de todo el CSS puede mejorar el rendimiento de los sitios "
"con un bajo pageviews/ visitor-rate, pero también podría relentizar el "
"rendimiento."
#: admin/pages/settings.php:294
msgid "Exclude CSS from Мinify And Combine:"
msgstr "Excluir CSS de Мinify And Combine:"
#: admin/pages/settings.php:296
msgid "A comma-separated list of CSS you want to exclude from being optimized."
msgstr "Lista de CSS separada por comas para ser excluídas de la optimización."
#: admin/pages/settings.php:308
msgid "Cache Info"
msgstr "Información de caché"
#: admin/pages/settings.php:368 includes/boot.php:52 includes/boot.php:71
msgid "Clear cache"
msgstr "Limpiar cache"
#: admin/pages/settings.php:386
msgid ""
"<b>This could break things!</b><br>If you notice any errors on your website "
"after having activated this setting, just deactivate it again, and your site "
"will be back to normal."
msgstr ""
"<b>¡Esto podría dañar algo!</b> <br> Si observa algún error en su sitio web "
"después de haber activado esta configuración, simplemente desactívela "
"nuevamente y su sitio volverá a la normalidad."
#: includes/classes/class.mac-cache-checker.php:84
msgid "Мinify And Combine cache size warning"
msgstr "Advertencia de tamaño Мinify And Combine en Cache"
#: includes/classes/class.mac-cache-checker.php:85
msgid ""
"Мinify And Combine's cache size is getting big, consider purging the cache. "
"Have a look at https://wordpress.org/plugins/ to see how you can keep the "
"cache size under control."
msgstr ""
"El tamaño del caché de Мinify And Combine está incrementándose, considere "
"purgar el caché. Busque en https://wordpress.org/plugins/ para ver cómo "
"puedes mantener el tamaño del caché bajo control."
#: includes/classes/class.mac-cache-checker.php:107
msgid ""
"<strong>Мinify And Combine's cache size is getting big</strong>, consider "
"purging the cache. Have a look at <a href=\"https://wordpress.org/plugins/\" "
"target=\"_blank\" rel=\"noopener noreferrer\">the Мinify And Combine FAQ</a> "
"to see how you can keep the cache size under control."
msgstr ""
"<strong>El tamaño del caché de Мinify And Combine está incrementándose</"
"strong>, considere purgar el caché. Vea cómo hacerlo: <a href=\"https://"
"wordpress.org/plugins/\" target=\"_blank\" rel=\"noopener noreferrer\">the "
"Мinify And Combine FAQ</a> para saber cómo mantener el tamaño de caché bajo "
"control."
#: includes/classes/class.mac-main.php:426
#, php-format
msgid ""
"Мinify And Combine cannot write to the cache directory (%s), please fix to "
"enable CSS/ JS optimization!"
msgstr ""
"Мinify And Combine no puede escribir en el directorio de caché (% s), "
"corríjalo para habilitar la optimización CSS / JS."
#: minify-and-combine.php:101
msgid "Webcraftic minify and combine"
msgstr "Webcraftic minify and combine"

View File

@@ -0,0 +1,573 @@
msgid ""
msgstr ""
"Project-Id-Version: clearfy\n"
"POT-Creation-Date: 2018-08-19 03:48+0300\n"
"PO-Revision-Date: 2018-08-19 03:54+0300\n"
"Last-Translator: alex.kovalevv@gmail.com <alex.kovalevv@gmail.com>\n"
"Language-Team: Alex Kovalev <alex.kovalevv@gmail.com>\n"
"Language: ru_RU\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.1.1\n"
"X-Poedit-Basepath: ..\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Poedit-SourceCharset: UTF-8\n"
"X-Poedit-KeywordsList: __;_e;_n:1,2;_x:1,2c;_ex:1,2c\n"
"X-Poedit-SearchPath-0: .\n"
"X-Poedit-SearchPathExcluded-0: libs\n"
#: admin/boot.php:23
msgid ""
"Clearfy: Minify and Combine component is not compatible with the Autoptimize "
"plugin, please do not use them together to avoid conflicts. Please disable "
"the Minify and Combine component"
msgstr ""
"Clearfy: Компонент Сжатие и Объединение (JS,CSS) не совместим с плагином "
"Autoptimize, пожалуйста, не используйте их вместе, чтобы избежать "
"конфликтов. Отключите компонент Сжатие и Объединение (JS,CSS)"
#: admin/boot.php:50 admin/pages/settings.php:122
msgid "Optimize JavaScript Code?"
msgstr "Оптимизировать код JavaScript?"
#: admin/boot.php:55 admin/pages/settings.php:138
msgid "Aggregate JS-files?"
msgstr "Объединять JS-файлы"
#: admin/boot.php:60 admin/pages/settings.php:148
msgid "Also aggregate inline JS?"
msgstr "Также объединять встроенный JS?"
#: admin/boot.php:65 admin/pages/settings.php:158
msgid "Force JavaScript in &lt;head&gt;?"
msgstr "Принудительно переместить JS в &lt;head&gt;?"
#: admin/boot.php:70 admin/pages/settings.php:167
msgid "Exclude scripts from Мinify And Combine:"
msgstr "Исключить скрипты:"
#: admin/boot.php:75 admin/pages/settings.php:177
msgid "Add try-catch wrapping?"
msgstr "Добавить обертку try-catch?"
#: admin/boot.php:83 admin/pages/settings.php:198
msgid "Optimize CSS Code?"
msgstr "Оптимизировать CSS код?"
#: admin/boot.php:89 admin/pages/settings.php:214
msgid "Aggregate CSS-files?"
msgstr "Объединять CSS файлы?"
#: admin/boot.php:95 admin/pages/settings.php:224
msgid "Also aggregate inline CSS?"
msgstr "Извлекать встроенный CSS код для объединения?"
#: admin/boot.php:101 admin/pages/settings.php:234
msgid "Generate data: URIs for images?"
msgstr "Преобразовать изображения в CSS код?"
#: admin/boot.php:107 admin/pages/settings.php:244
msgid "Inline and Defer CSS?"
msgstr "Встроить и отложить выполнение CSS?"
#: admin/boot.php:113 admin/pages/settings.php:255
msgid "Inline all CSS?"
msgstr "Поместить весь CSS код в теле страницы?"
#: admin/boot.php:119
msgid "Exclude CSS from Мinify And Combine"
msgstr "Исключить CSS файлы"
#: admin/boot.php:135
msgid "One click optimize scripts (js, css)"
msgstr "Оптимизировать скрипты (js, css) одним нажатием"
#: admin/boot.php:137
msgid "One click optimize html code and scripts"
msgstr "Оптимизировать Html код и скрипты одним нажатием"
#: admin/pages/settings.php:35
msgid "Minify And Combine (JS/CSS)"
msgstr "Сжатие и Объединение (JS/CSS)"
#: admin/pages/settings.php:60
msgid "Scripts Minify And Combine"
msgstr "Сжатие и объединение скриптов"
#: admin/pages/settings.php:61
msgid "General"
msgstr "Основные"
#: admin/pages/settings.php:96
msgid "The cache has been successfully cleaned."
msgstr "Кэш был успешно очищен."
#: admin/pages/settings.php:115
msgid "JavaScript Options"
msgstr "Настройки JavaScript"
#: admin/pages/settings.php:140
msgid ""
"Aggregate all linked JS-files to have them loaded non-render blocking? If "
"this option is off, the individual JS-files will remain in place but will be "
"minified."
msgstr ""
#: admin/pages/settings.php:150
msgid ""
"Let Мinify And Combine also extract JS from the HTML. Warning: this can make "
"Мinify And Combine's cache size grow quickly, so only enable this if you "
"know what you're doing."
msgstr ""
"Пусть Minify And Combine также извлекает JS из HTML. Предупреждение: это "
"может привести к быстрому увеличению размера кеша Minify и Combine, поэтому "
"включите его, только если вы знаете, что делаете."
#: admin/pages/settings.php:160
msgid ""
"Load JavaScript early, this can potentially fix some JS-errors, but makes "
"the JS render blocking."
msgstr ""
#: admin/pages/settings.php:169
msgid ""
"A comma-separated list of scripts you want to exclude from being optimized, "
"for example 'whatever.js, another.js' (without the quotes) to exclude those "
"scripts from being aggregated and minimized by Мinify And Combine."
msgstr ""
"Список сценариев, которые вы хотите исключить из оптимизации, например, "
"«whatever.js, another.js» (без кавычек), чтобы исключить агрегирование и "
"минимизацию этих сценариев с помощью Мinify And Combine."
#: admin/pages/settings.php:179 admin/pages/settings.php:200
msgid ""
"If your scripts break because of a JS-error, you might want to try this."
msgstr ""
"Если ваши сценарии прерываются из-за ошибки JS, вы можете попробовать это."
#: admin/pages/settings.php:191
msgid "CSS Options"
msgstr ""
#: admin/pages/settings.php:216
msgid ""
"Aggregate all linked CSS-files? If this option is off, the individual CSS-"
"files will remain in place but will be minified."
msgstr ""
#: admin/pages/settings.php:226
msgid ""
"Check this option for Мinify And Combine to also aggregate CSS in the HTML."
msgstr ""
"Установите этот параметр для Minify And Combine, чтобы агрегировать CSS в "
"HTML."
#: admin/pages/settings.php:236
msgid ""
"Enable this to include small background-images in the CSS itself instead of "
"as separate downloads."
msgstr ""
#: admin/pages/settings.php:246
msgid ""
"Inline \"above the fold CSS\" while loading the main auto optimized CSS only "
"after page load. Check the FAQ for more info.\n"
"This can be fully automated for different types of pages with the Мinify And "
"Combine CriticalCSS Power-Up."
msgstr ""
"Inline «над слоем CSS» при загрузке основного автоматически "
"оптимизированного CSS только после загрузки страницы. Проверьте часто "
"задаваемые вопросы для получения дополнительной информации. Это может быть "
"полностью автоматизировано для разных типов страниц с помощью Powerin Up и "
"Mineify и Combine CriticalCSS."
#: admin/pages/settings.php:257
msgid ""
"Inlining all CSS can improve performance for sites with a low pageviews/ "
"visitor-rate, but may slow down performance otherwise."
msgstr ""
#: admin/pages/settings.php:264
msgid "Exclude CSS from Мinify And Combine:"
msgstr ""
#: admin/pages/settings.php:266
msgid "A comma-separated list of CSS you want to exclude from being optimized."
msgstr ""
#: admin/pages/settings.php:278
msgid "Cache Info"
msgstr ""
#: admin/pages/settings.php:278
msgid "Описание раздела оптимизация"
msgstr ""
#: admin/pages/settings.php:351 includes/boot.php:48 includes/boot.php:64
msgid "Clear cache"
msgstr ""
#: includes/classes/class.mac-cache-checker.php:84
msgid "Мinify And Combine cache size warning"
msgstr ""
#: includes/classes/class.mac-cache-checker.php:85
msgid ""
"Мinify And Combine's cache size is getting big, consider purging the cache. "
"Have a look at https://wordpress.org/plugins/ to see how you can keep the "
"cache size under control."
msgstr ""
#: includes/classes/class.mac-cache-checker.php:107
msgid ""
"<strong>Мinify And Combine's cache size is getting big</strong>, consider "
"purging the cache. Have a look at <a href=\"https://wordpress.org/plugins/\" "
"target=\"_blank\" rel=\"noopener noreferrer\">the Мinify And Combine FAQ</a> "
"to see how you can keep the cache size under control."
msgstr ""
#: includes/classes/class.mac-main.php:417
#, php-format
msgid ""
"Мinify And Combine cannot write to the cache directory (%s), please fix to "
"enable CSS/ JS optimization!"
msgstr ""
#: minify-and-combine.php:34
msgid ""
"The job of the component \"Minify and Combine\" component has been "
"suspended! You are using the old version of PHP. Please update the PHP "
"version to 5.4 or later to continue to use this component!"
msgstr ""
#: minify-and-combine.php:53
msgid ""
"We found that you have the \"Clearfy - wordpress optimization plugin\" "
"plugin installed, this plugin already has \"Minify and combine\" functions, "
"so you can deactivate plugin \"Minify and combine\"!"
msgstr ""
#: minify-and-combine.php:130
msgid "Webcraftic minify and combine"
msgstr ""
#~ msgid "Disable comments on the entire site"
#~ msgstr "Отключить комментарии на всем сайте"
#~ msgid "Select post types"
#~ msgstr "Выбрать тип записи"
#~ msgid "Replace external links in comments on the JavaScript code"
#~ msgstr "Заменить внешние ссылки в комментариях на JavaScript код"
#~ msgid "Replace external links from comment authors on the JavaScript code"
#~ msgstr "Заменить внешние ссылки от авторов комментариев на код JavaScript"
#~ msgid "Disable X-Pingback"
#~ msgstr "Убрать ссылку на X-Pingback и возможность спамить pingback-ами"
#~ msgid "Remove field \"site\" in comment form"
#~ msgstr "Удаляет поле \"Сайт\" в форме комментариев"
#~ msgid "One click disable all comments"
#~ msgstr "Отключить все комментарии в один клик"
#~ msgid "Get ultimate plugin free"
#~ msgstr "Получите полную версию плагина бесплатно"
#~ msgid "Disable comments"
#~ msgstr "Отключить комментарии"
#~ msgid "Comments"
#~ msgstr "Комментарии"
#~ msgid "Global disabling of comments"
#~ msgstr "Глобальное отключение комментариев"
#~ msgid ""
#~ "What is the difference between these and native WordPress functions? "
#~ "WordPress disables comments only for new posts! Using the functions "
#~ "below, you can disable comments globally, even for old posts, and you can "
#~ "choose which post types comments to disable. The plugin also disables the "
#~ "comment functionality itself, which creates a certain load on the site."
#~ msgstr ""
#~ "Чем отличается эти функции от нативных функций Wordpress? Wordpress "
#~ "отключает комментарии только для новых записей! С помощью функций ниже, "
#~ "вы можете отключить комментарии глобально, даже для старых записей, "
#~ "причем вы можете выбрать для каких типов записей нужно отключить "
#~ "комментарии. Плагин также отключает сам функционал комментариев, который "
#~ "создает определенную нагрузку на сайт."
#~ msgid "Not disable"
#~ msgstr "Не отключать"
#~ msgid "Everywhere"
#~ msgstr "Повсюду"
#~ msgid ""
#~ "You can delete all comments in the database by clicking on this link (<a "
#~ "href=\"%s\">cleaning comments in database</a>)."
#~ msgstr ""
#~ "Вы можете удалить все комментарии в базе данных, нажав на эту ссылку ( <a "
#~ "href=\"%s\">очистка комментариев в базе данных</a> )."
#~ msgid "On certain post types"
#~ msgstr "Только выбранные типы записей"
#~ msgid ""
#~ "You can delete all comments for the selected post types. Select the post "
#~ "types below and save the settings. After that, click the link (<a href="
#~ "\"%s\">delete all comments for the selected post types in database</a>)."
#~ msgstr ""
#~ "Вы можете удалить все комментарии для выбранных типов записей. Выберите "
#~ "типы записей ниже и сохраните настройки. После этого нажмите ссылку ( <a "
#~ "href=\"%s\">удалите все комментарии для выбранных типов записей в базе "
#~ "данных</a> )."
#~ msgid ""
#~ "Everywhere - Warning: This option is global and will affect your entire "
#~ "site. Use it only if you want to disable comments everywhere. A complete "
#~ "description of what this option does is available here"
#~ msgstr ""
#~ "Повсюду - предупреждение: этот параметр является глобальным и повлияет на "
#~ "весь ваш сайт. Используйте его только в том случае, если вы хотите "
#~ "отключить комментарии повсюду. "
#~ msgid ""
#~ "On certain post types - Disabling comments will also disable trackbacks "
#~ "and pingbacks. All comment-related fields will also be hidden from the "
#~ "edit/quick-edit screens of the affected posts. These settings cannot be "
#~ "overridden for individual posts."
#~ msgstr ""
#~ "В некоторых типах сообщений - отключение комментариев также отключает "
#~ "трекбэки и pingback. Все поля, связанные с комментариями, также будут "
#~ "скрыты от экранов редактирования / быстрого редактирования затронутых "
#~ "сообщений. Эти настройки нельзя переопределять для отдельных сообщений."
#~ msgid "Select the post types for which comments will be disabled"
#~ msgstr "Выберите типы записей, для которых комментарии будут отключены."
#~ msgid "General settings for comments"
#~ msgstr "Общие настройки комментариев"
#~ msgid ""
#~ "These settings will help you improve SEO and reduce the amount of spam."
#~ msgstr ""
#~ "Эти настройки помогут вам улучшить SEO и уменьшить количество спама."
#~ msgid ""
#~ "Tired of spam in the comments? Do visitors leave \"blank\" comments for "
#~ "the sake of a link to their site?"
#~ msgstr ""
#~ "Надоел спам в комментариях? Посетители оставляют «пустые» комментарии "
#~ "ради ссылки на свой сайт?"
#~ msgid "Removes the \"Site\" field from the comment form."
#~ msgstr "Убирает поле «Сайт» из формы комментирования."
#~ msgid ""
#~ "Works with the standard comment form, if the form is manually written in "
#~ "your theme-it probably will not work!"
#~ msgstr ""
#~ "Работает со стандартной формой комментирования, если в Вашей теме форма "
#~ "прописана вручную - скорей всего не сработает!"
#~ msgid ""
#~ "Superfluous external links from comments, which can be typed from a dozen "
#~ "and more for one article, do not bring anything good for promotion."
#~ msgstr ""
#~ "Внешние ссылки в комментариях, которых может быть десятки или больше на "
#~ "одной странице, могут ухудшить продвижение вашего сайта."
#~ msgid "Replaces the links of this kind of %s, on links of this kind %s"
#~ msgstr ""
#~ "Заменяет ссылки %s, на span тег и устанавливает переход с помощью "
#~ "JavaScript %s"
#~ msgid ""
#~ "Up to 90 percent of comments in the blog can be left for the sake of an "
#~ "external link. Even nofollow from page weight loss here does not help."
#~ msgstr ""
#~ "До 90 процентов комментариев в блоге оставляют ради внешней ссылки. Не "
#~ "поможет даже nofollow от потери веса страницы."
#~ msgid ""
#~ "Replaces the links of the authors of comments on the JavaScript code, it "
#~ "is impossible to distinguish it from usual links."
#~ msgstr ""
#~ "Заменяет ссылки авторов комментариев на JavaScript код, его невозможно "
#~ "отличить от обычной ссылки."
#~ msgid "In some Wordpress topics this may not work."
#~ msgstr "В некоторых темах Wordpress это может не сработать."
#~ msgid "Disable XML-RPC"
#~ msgstr "Отключить XML-RPC"
#~ msgid ""
#~ "A pingback is basically an automated comment that gets created when "
#~ "another blog links to you. A self-pingback is created when you link to an "
#~ "article within your own blog. Pingbacks are essentially nothing more than "
#~ "spam and simply waste resources."
#~ msgstr ""
#~ "Pingback по-существу автоматизированных комментарий, который создается, "
#~ "когда другой блог ссылается на вас. Self-pingback создается, когда вы "
#~ "оставили ссылку на статью в своем блоге. Pingbacks по существу являются "
#~ "не более чем спам и пустая трата ресурсов вашего сайта."
#~ msgid "Removes the server responses a reference to the xmlrpc file."
#~ msgstr "Удаляет ссылку на xmlrpc-файл и ответ сервера."
#~ msgid "Comments cleaner"
#~ msgstr "Очистка комментариев"
#~ msgid "All comments have been deleted."
#~ msgstr "Все комментарии были удалены."
#~ msgid ""
#~ "An error occurred while trying to delete comments. Internal error "
#~ "occured. Please try again later."
#~ msgstr ""
#~ "При попытке удалить комментарии произошла ошибка. Пожалуйста, повторите "
#~ "попытку позже."
#~ msgid ""
#~ "Are you sure you want to delete comments from the database without "
#~ "restoring?"
#~ msgstr ""
#~ "Вы уверены, что вы хотите удалить комментарии из базы данных без "
#~ "восстановления?"
#~ msgid "Comments clearing tools"
#~ msgstr "Комментарии очищающие инструменты"
#~ msgid ""
#~ "These functions can be useful for global disabling comments or bulk "
#~ "cleaning spam comments."
#~ msgstr ""
#~ "Эти функции могут быть полезны для глобальных отключений комментариев или "
#~ "массовой очистки спама комментариев."
#~ msgid "Remove all comments"
#~ msgstr "Удалить все комментарии"
#~ msgid "You can delete all comments in your database with one click."
#~ msgstr ""
#~ "Вы можете удалить все комментарии в базе данных с одним щелчком мыши."
#~ msgid "Choose post types"
#~ msgstr "Выберите типы записей"
#~ msgid "Select all"
#~ msgstr "Выбрать все"
#~ msgid "Delete Woocommerce order notices? (%d)"
#~ msgstr "Удалять заметки от заказов Woocommerce? (%d)"
#~ msgid "Delete (%d)"
#~ msgstr "Удалить (%d)"
#~ msgid "Remove spam comments"
#~ msgstr "Удалить спам комментарии"
#~ msgid "You can remove only spam comments from the database with one click."
#~ msgstr ""
#~ "Вы можете одним нажатием удалить только спам комментарии из базы данных."
#~ msgid "Remove unapproved comments"
#~ msgstr "Удалить неподтвержденные комментарии"
#~ msgid ""
#~ "You can remove only unapproved comments from the database with one click."
#~ msgstr ""
#~ "Вы можете одним нажатием удалить только не подтвержденные комментарии из "
#~ "базы данных."
#~ msgid "Remove trashed comments"
#~ msgstr "Удалить комментарии из корзины"
#~ msgid ""
#~ "You can remove only trashed comments from the database with one click."
#~ msgstr "Вы можете одним нажатием удалить только комментарии из корзины."
#~ msgid ""
#~ "We found that you have the \"Clearfy - disable unused features\" plugin "
#~ "installed, this plugin already has disable comments functions, so you can "
#~ "deactivate plugin \"Disable comments\"!"
#~ msgstr ""
#~ "Мы обнаружили, что у вас установлен плагин «Clearfy - отключить "
#~ "неиспользуемые функции», этот плагин уже имеет функции отключения "
#~ "комментариев, поэтому вы можете отключить плагин «Отключить комментарии»!"
#~ msgid "Webcraftic Disable comments"
#~ msgstr "Webcraftic отключить комментарии"
#~ msgid "Comments are closed."
#~ msgstr "Комментарии Закрыты."
#~ msgid ""
#~ "Note: The <em>%s</em> plugin is currently active, and comments are "
#~ "completely disabled on: %s. Many of the settings below will not be "
#~ "applicable for those post types."
#~ msgstr ""
#~ "Примечание. Плагин <em>%s</em> в настоящий момент активен, и комментарии "
#~ "полностью отключены: %s. Многие из приведенных ниже настроек не будут "
#~ "применяться для этих типов сообщений."
#~ msgid "Recommended"
#~ msgstr "Рекомендовано"
#~ msgid "You are not allowed to view this page."
#~ msgstr "Вам не разрешено просматривать эту страницу."
#~ msgid "You do not have the selected post types!"
#~ msgstr "Вы не выбрали еще ни одного типа записей!"
#~ msgid "No comments available for deletion."
#~ msgstr "Нет комментариев для удаления."
#~ msgid ""
#~ "Are you sure that you desire to delete all comments from the database?"
#~ msgstr "Вы уверены, что хотите удалить все комментарии из базы данных?"
#~ msgid ""
#~ "Deleting comments will remove existing comment entries in the database "
#~ "and cannot be reverted without a database backup."
#~ msgstr ""
#~ "При удалении комментариев удаляются существующие записи комментариев в "
#~ "базе данных, они не могут быть восстановлены без резервного копирования "
#~ "базы данных."
#~ msgid "You have %s comments"
#~ msgstr "У вас есть %s комментариев"
#~ msgid "Yes, I'm sure"
#~ msgstr "Да, я уверен"
#~ msgid "No, return back"
#~ msgstr "Нет, вернуться"
#~ msgid ""
#~ "Are you sure that you desire to delete all comments from the database for "
#~ "the selected post types (%s)?"
#~ msgstr ""
#~ "Вы уверены, что хотите удалить все комментарии из базы данных для "
#~ "выбранных типов записей (%s)?"
#~ msgid "Webcraftic comments tweaks"
#~ msgstr "Webcraftic инструменты комментариев"
#~ msgid ""
#~ "We found that you have the \"Clearfy - disable unused features\" plugin "
#~ "installed, this plugin already has disable comments functions, so you can "
#~ "deactivate plugin \"Comments tweaks\"!"
#~ msgstr ""
#~ "Мы обнаружили, что у вас установлен плагин «Clearfy - отключить "
#~ "неиспользуемые функции», этот плагин уже имеет функции отключения "
#~ "комментариев, поэтому вы можете отключить плагин «Инструменты "
#~ "комментариев»!"
#~ msgid "Disable all comments"
#~ msgstr "Отключить все комментарии"

View File

@@ -0,0 +1,2 @@
<?php
// Silence is golden.

View File

@@ -0,0 +1,130 @@
<?php
/**
* Plugin Name: Мinify And Combine
* Plugin URI: https://webcraftic.com
* Description: Optimizes your website, concatenating the CSS and JavaScript code, and compressing it.
* Author: Webcraftic <wordpress.webraftic@gmail.com>
* Version: 1.1.2
* Text Domain: minify-and-combine
* Domain Path: /languages/
* Author URI: https://webcraftic.com
* Framework Version: FACTORY_480_VERSION
*/
/*
* #### CREDITS ####
* This plugin is based on the plugin Autoptimize by the author Frank Goossens, we have finalized this code for our project and our goals.
* Many thanks to Frank Goossens for the quality solution for optimizing scripts in Wordpress.
*
* Public License is a GPLv2 compatible license allowing you to change and use this version of the plugin for free.
*/
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* -----------------------------------------------------------------------------
* CHECK REQUIREMENTS
* Check compatibility with php and wp version of the user's site. As well as checking
* compatibility with other plugins from Webcraftic.
* -----------------------------------------------------------------------------
*/
require_once( dirname( __FILE__ ) . '/libs/factory/core/includes/class-factory-requirements.php' );
// @formatter:off
$wmac_plugin_info = array(
'prefix' => 'wbcr_mac_', // префикс для базы данных и полей формы
'plugin_name' => 'wbcr_minify_and_combine', // имя плагина, как уникальный идентификатор
'plugin_title' => __( 'Webcraftic minify and combine', 'minify-and-combine' ), // заголовок плагина
// PLUGIN SUPPORT
'support_details' => array(
'url' => 'https://webcraftic.com',
'pages_map' => array(
'support' => 'support', // {site}/support
'docs' => 'docs' // {site}/docs
)
),
// PLUGIN ADVERTS
'render_adverts' => true,
'adverts_settings' => array(
'dashboard_widget' => true, // show dashboard widget (default: false)
'right_sidebar' => true, // show adverts sidebar (default: false)
'notice' => true, // show notice message (default: false)
),
// FRAMEWORK MODULES
'load_factory_modules' => array(
array( 'libs/factory/bootstrap', 'factory_bootstrap_482', 'admin' ),
array( 'libs/factory/forms', 'factory_forms_480', 'admin' ),
array( 'libs/factory/pages', 'factory_pages_480', 'admin' ),
array( 'libs/factory/clearfy', 'factory_templates_134', 'all' ),
array( 'libs/factory/adverts', 'factory_adverts_159', 'admin')
)
);
$wmac_compatibility = new Wbcr_Factory480_Requirements( __FILE__, array_merge( $wmac_plugin_info, array(
'plugin_already_activate' => defined( 'WMAC_PLUGIN_ACTIVE' ),
'required_php_version' => '5.4',
'required_wp_version' => '4.2.0',
'required_clearfy_check_component' => false
) ) );
/**
* If the plugin is compatible, then it will continue its work, otherwise it will be stopped,
* and the user will throw a warning.
*/
if ( ! $wmac_compatibility->check() ) {
return;
}
/**
* -----------------------------------------------------------------------------
* CONSTANTS
* Install frequently used constants and constants for debugging, which will be
* removed after compiling the plugin.
* -----------------------------------------------------------------------------
*/
// This plugin is activated
define( 'WMAC_PLUGIN_ACTIVE', true );
define( 'WMAC_PLUGIN_VERSION', $wmac_compatibility->get_plugin_version() );
define( 'WMAC_PLUGIN_DIR', dirname( __FILE__ ) );
define( 'WMAC_PLUGIN_BASE', plugin_basename( __FILE__ ) );
define( 'WMAC_PLUGIN_URL', plugins_url( '', __FILE__ ) );
/**
* -----------------------------------------------------------------------------
* PLUGIN INIT
* -----------------------------------------------------------------------------
*/
require_once( WMAC_PLUGIN_DIR . '/libs/factory/core/boot.php' );
require_once( WMAC_PLUGIN_DIR . '/includes/class-plugin.php' );
try {
new WMAC_Plugin( __FILE__, array_merge( $wmac_plugin_info, array(
'plugin_version' => WMAC_PLUGIN_VERSION,
'plugin_text_domain' => $wmac_compatibility->get_text_domain(),
) ) );
} catch( Exception $e ) {
// Plugin wasn't initialized due to an error
define( 'WMAC_PLUGIN_THROW_ERROR', true );
$wmac_plugin_error_func = function () use ( $e ) {
$error = sprintf( "The %s plugin has stopped. <b>Error:</b> %s Code: %s", 'Webcraftic Disable Comments', $e->getMessage(), $e->getCode() );
echo '<div class="notice notice-error"><p>' . $error . '</p></div>';
};
add_action( 'admin_notices', $wmac_plugin_error_func );
add_action( 'network_admin_notices', $wmac_plugin_error_func );
}
// @formatter:on

View File

@@ -0,0 +1,50 @@
<?php
// if uninstall.php is not called by WordPress, die
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
die;
}
if ( ! defined( 'WMAC_PLUGIN_DIR' ) ) {
define( 'WMAC_PLUGIN_DIR', dirname( __FILE__ ) );
}
if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
}
require_once( WMAC_PLUGIN_DIR . '/includes/classes/class.mac-cache.php' );
require_once( WMAC_PLUGIN_DIR . '/includes/classes/class.mac-main.php' );
function uninstall() {
// remove plugin options
global $wpdb;
$plugin = new WMAC_PluginMain();
$plugin->setup();
WMAC_PluginCache::clearAll();
$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE 'wbcr_mac_%';" );
}
if ( is_multisite() ) {
global $wpdb, $wp_version;
$wpdb->query( "DELETE FROM {$wpdb->sitemeta} WHERE meta_key LIKE 'wbcr_mac_%';" );
$blogs = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
if ( ! empty( $blogs ) ) {
foreach ( $blogs as $id ) {
switch_to_blog( $id );
uninstall();
restore_current_blog();
}
}
} else {
uninstall();
}