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,113 @@
<?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 WHTM_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();
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;
}
/**
* Выполнение действий после загрузки плагина
* Подключаем все классы оптимизации и запускаем процесс
*
* @throws \Exception
*/
public function plugins_loaded() {
require_once( WHTM_PLUGIN_DIR . '/includes/classes/class-base.php' );
require_once( WHTM_PLUGIN_DIR . '/includes/classes/class-html.php' );
require_once( WHTM_PLUGIN_DIR . '/includes/classes/class-main.php' );
require_once( WHTM_PLUGIN_DIR . '/includes/classes/ext/php/class-minify-html.php' );
$plugin = new WHTM_PluginMain();
$plugin->start();
}
/**
* Регистрирует классы страниц в плагине
*
* Мы указываем плагину, где найти файлы страниц и какое имя у их класса. Чтобы плагин
* выполнил подключение классов страниц. После регистрации, страницы будут доступные по url
* и в меню боковой панели администратора. Регистрируемые страницы будут связаны с текущим плагином
* все операции выполняемые внутри классов страниц, имеют отношение только текущему плагину.
*
* @throws \Exception
*/
private function register_pages() {
if ( defined( 'WMAC_PLUGIN_ACTIVE' ) ) {
return;
}
$admin_path = WHTM_PLUGIN_DIR . '/admin/pages';
// Пример основной страницы настроек
self::app()->registerPage( 'WHTM_SettingsPage', $admin_path . '/class-pages-settings.php' );
}
/**
* Подключаем функции бекенда
*/
private function admin_scripts() {
require( WHTM_PLUGIN_DIR . '/admin/boot.php' );
}
}

View File

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

View File

@@ -0,0 +1,114 @@
<?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 WHTM_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;
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;
}
/**
* Выполнение действий после загрузки плагина
* Подключаем все классы оптимизации и запускаем процесс
*
* @throws \Exception
*/
public function plugins_loaded() {
require_once( WHTM_PLUGIN_DIR . '/includes/classes/class-base.php' );
require_once( WHTM_PLUGIN_DIR . '/includes/classes/class-html.php' );
require_once( WHTM_PLUGIN_DIR . '/includes/classes/class-main.php' );
require_once( WHTM_PLUGIN_DIR . '/includes/classes/ext/php/class-minify-html.php' );
$plugin = new WHTM_PluginMain();
$plugin->start();
}
/**
* Регистрирует классы страниц в плагине
*
* Мы указываем плагину, где найти файлы страниц и какое имя у их класса. Чтобы плагин
* выполнил подключение классов страниц. После регистрации, страницы будут доступные по url
* и в меню боковой панели администратора. Регистрируемые страницы будут связаны с текущим плагином
* все операции выполняемые внутри классов страниц, имеют отношение только текущему плагину.
*
* @throws \Exception
*/
private function register_pages() {
if ( defined( 'WMAC_PLUGIN_ACTIVE' ) ) {
return;
}
$admin_path = WHTM_PLUGIN_DIR . '/admin/pages';
// Пример основной страницы настроек
self::app()->registerPage( 'WHTM_SettingsPage', $admin_path . '/class-pages-settings.php' );
}
/**
* Подключаем функции бекенда
*/
private function admin_scripts() {
require( WHTM_PLUGIN_DIR . '/admin/boot.php' );
}
}

View File

@@ -0,0 +1,260 @@
<?php
/**
* Base class
* @author Webcraftic <wordpress.webraftic@gmail.com>
* @copyright (c) 2018 Webraftic Ltd
* @version 1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class WHTM_PluginBase
*/
abstract class WHTM_PluginBase
{
/**
* Holds content being processed (html, scripts, styles)
*
* @var string
*/
protected $content = '';
/**
* Controls debug logging.
*
* @var bool
*/
public $debug_log = false;
/**
* WHTM_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();
/**
* Returns the content
*
* @return string
*/
abstract public function getContent();
/**
* 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 );
}
/**
* 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 . WHTM_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 `WHTM_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 WHTM_PluginBase::buildMarker( $marker, $matches[0] );
},
$content
);
}
return $content;
}
/**
* Complements `WHTM_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 . WHTM_HASH . '%%(.*?)%%' . $marker . '%%#is',
function ( $matches ) {
return base64_decode( $matches[1] );
},
$content
);
}
return $content;
}
}

View File

@@ -0,0 +1,126 @@
<?php
/**
* Operations with HTML
*
* @author Webcraftic <wordpress.webraftic@gmail.com>
* @copyright (c) 2018 Webraftic Ltd
* @version 1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class WHTM_PluginHTML
*/
class WHTM_PluginHTML extends WHTM_PluginBase
{
/**
* Force xhtml.
*
* @var bool
*/
private $forcexhtml = false;
/**
* Whether HTML comments are kept.
*
* @var bool
*/
private $keepcomments = false;
/**
* Things to exclude from being minifed.
*
* @var array
*/
private $exclude = array(
'<!-- ngg_resource_manager_marker -->',
'<!--noindex-->',
'<!--/noindex-->',
);
public function read( $options )
{
// Remove the HTML comments?
$this->keepcomments = (bool) $options['keepcomments'];
// Filter to force xhtml.
$this->forcexhtml = (bool) apply_filters( 'whm_filter_html_forcexhtml', false );
// Filterable strings to be excluded from HTML minification.
$exclude = apply_filters( 'whm_filter_html_exclude', '' );
if ( '' !== $exclude ) {
$exclude_arr = array_filter( array_map( 'trim', explode( ',', $exclude ) ) );
$this->exclude = array_merge( $exclude_arr, $this->exclude );
}
return true;
}
/**
* Minifies HTML.
*
* @return bool
*/
public function minify()
{
$noptimize = apply_filters( 'whm_filter_html_noptimize', false, $this->content );
if ( $noptimize ) {
return false;
}
// Wrap the to-be-excluded strings in no optimize tags.
foreach ( $this->exclude as $str ) {
if ( false !== strpos( $this->content, $str ) ) {
$replacement = '<!--noptimize-->' . $str . '<!--/noptimize-->';
$this->content = str_replace( $str, $replacement, $this->content );
}
}
// No optimize.
$this->content = $this->hideNoptimize( $this->content );
// Preparing options for WHTM_Minify_HTML.
$options = array( 'keepComments' => $this->keepcomments );
if ( $this->forcexhtml ) {
$options['xhtml'] = true;
}
$tmp_content = WHTM_Minify_HTML::minify( $this->content, $options );
if ( ! empty( $tmp_content ) ) {
$this->content = $tmp_content;
unset( $tmp_content );
}
// Restore no optimize.
$this->content = $this->restoreNoptimize( $this->content );
// Remove the noptimize-wrapper from around the excluded strings.
foreach ( $this->exclude as $str ) {
$replacement = '<!--noptimize-->' . $str . '<!--/noptimize-->';
if ( false !== strpos( $this->content, $replacement ) ) {
$this->content = str_replace( $replacement, $str, $this->content );
}
}
// Revslider data attribs somehow suffer from HTML optimization, this fixes that!
if ( class_exists( 'RevSlider' ) && apply_filters( 'whm_filter_html_dataattrib_cleanup', false ) ) {
$this->content = preg_replace( '#\n(data-.*$)\n#Um', ' $1 ', $this->content );
$this->content = preg_replace( '#<[^>]*(=\"[^"\'<>\s]*\")(\w)#', '$1 $2', $this->content );
}
return true;
}
/**
* Returns the HTML markup.
*
* @return string
*/
public function getContent()
{
return $this->content;
}
}

View File

@@ -0,0 +1,296 @@
<?php
/**
* Main class
* @author Webcraftic <wordpress.webraftic@gmail.com>
* @copyright (c) 2018 Webraftic Ltd
* @version 1.0
*/
if( !defined('ABSPATH') ) {
exit;
}
/**
* Class WHTM_PluginMain
*/
class WHTM_PluginMain {
const INIT_EARLIER_PRIORITY = -1;
const DEFAULT_HOOK_PRIORITY = 2;
/**
* Constructor.
*/
public function __construct()
{
}
/**
* Start processing
*/
public function start()
{
$this->setup();
$this->hook();
$this->run();
}
/**
* Initialization hooks
*/
public function hook()
{
if( WHTM_Plugin::app()->getPopulateOption('html_optimize') ) {
add_action('wp_loaded', array($this, 'removeCacheMessage'));
}
}
/**
* Setting the parameters
*/
public function setup()
{
// These can be overridden by specifying them in wp-config.php or such.
if( !defined('WHTM_WP_CONTENT_NAME') ) {
define('WHTM_WP_CONTENT_NAME', '/' . wp_basename(WP_CONTENT_DIR));
}
define('WHTM_ROOT_DIR', substr(WP_CONTENT_DIR, 0, strlen(WP_CONTENT_DIR) - strlen(WHTM_WP_CONTENT_NAME)));
if( !defined('WHTM_WP_SITE_URL') ) {
// domain_mapping_siteurl функция из плагина, который позволяет задавать свой домен для подсайта
if( function_exists('domain_mapping_siteurl') ) {
define('WHTM_WP_SITE_URL', domain_mapping_siteurl(get_current_blog_id()));
} else {
define('WHTM_WP_SITE_URL', site_url());
}
}
if( !defined('WHTM_WP_CONTENT_URL') ) {
// get_original_url функция из плагина, который позволяет задавать свой домен для подсайта
if( function_exists('get_original_url') ) {
define('WHTM_WP_CONTENT_URL', str_replace(get_original_url(WHTM_WP_SITE_URL), WHTM_WP_SITE_URL, content_url()));
} else {
define('WHTM_WP_CONTENT_URL', content_url());
}
}
if( !defined('WHTM_WP_ROOT_URL') ) {
define('WHTM_WP_ROOT_URL', str_replace(WHTM_WP_CONTENT_NAME, '', WHTM_WP_CONTENT_URL));
}
if( !defined('WHTM_HASH') ) {
define('WHTM_HASH', wp_hash(time()));
}
}
/**
* Run
*/
public function run()
{
if( WHTM_Plugin::app()->getPopulateOption('html_optimize') ) {
// Hook into WordPress frontend.
if( defined('WHTM_INIT_EARLIER') ) {
add_action('init', array($this, 'startBuffering'), self::INIT_EARLIER_PRIORITY);
} else {
if( !defined('WHTM_HOOK_INTO') ) {
define('WHTM_HOOK_INTO', 'template_redirect');
}
add_action(constant('WHTM_HOOK_INTO'), array($this, 'startBuffering'), self::DEFAULT_HOOK_PRIORITY);
}
}
}
/**
* Setup output buffering if needed.
*
* @return void
*/
public function startBuffering()
{
if( $this->shouldBuffer() ) {
if( apply_filters('whm_filter_obkiller', false) ) {
while( ob_get_level() > 0 ) {
ob_end_clean();
}
}
// Now, start the real thing!
ob_start(array($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 ) {
$whm_noptimize = false;
// Checking for DONOTMINIFY constant as used by e.g. WooCommerce POS.
if( defined('DONOTMINIFY') && (constant('DONOTMINIFY') === true || constant('DONOTMINIFY') === 'true') ) {
$whm_noptimize = true;
}
// Skip checking query strings if they're disabled.
if( apply_filters('whm_filter_honor_qs_noptimize', true) ) {
// Check for `whm_noptimize` (and other) keys in the query string
// to get non-optimized page for debugging.
$keys = array(
'whm_noptimize',
'whm_noptirocket',
);
foreach($keys as $key) {
if( array_key_exists($key, $_GET) && '1' === $_GET[$key] ) {
$whm_noptimize = true;
break;
}
}
}
// Allows blocking of auto optimization on your own terms regardless of above decisions.
$whm_noptimize = (bool)apply_filters('whm_filter_noptimize', $whm_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
* WHTM_INIT_EARLIER in tests.
*/
$do_buffering = (!is_admin() && !is_feed() && !$whm_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 = array();
if( WHTM_Plugin::app()->getPopulateOption('html_optimize') ) {
$classes[] = 'WHTM_PluginHTML';
}
$classoptions = array(
'WHTM_PluginHTML' => array(
'keepcomments' => WHTM_Plugin::app()->getPopulateOption('html_keepcomments'),
),
);
$content = apply_filters('whm_filter_html_before_minify', $content);
// Run the classes!
foreach($classes as $name) {
$instance = new $name($content);
if( $instance->read($classoptions[$name]) ) {
$instance->minify();
$content = $instance->getContent();
}
unset($instance);
}
$content = apply_filters('whm_html_after_minify', $content);
return $content;
}
/**
* Remove Cache Status Messages
*/
public function removeCacheMessage()
{
// For WP Super Cache
if( file_exists(WP_CONTENT_DIR . '/wp-cache-config.php') && function_exists('prune_super_cache') ) {
global $wp_super_cache_comments;
$wp_super_cache_comments = 0;
}
// For WP Fastest Cache
if( class_exists('WpFastestCache') ) {
define('WPFC_REMOVE_FOOTER_COMMENT', true);
}
// For WP Total Cache
if( function_exists('w3tc_pgcache_flush') ) {
add_filter('w3tc_footer_comment', '__return_empty_array');
}
}
}

View File

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

View File

@@ -0,0 +1,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.