modified file upgrade-temp-backup

This commit is contained in:
KawaiiPunk 2024-04-19 10:59:51 +00:00 committed by Gitium
parent 1a790bdd29
commit 5de19fe451
2762 changed files with 343927 additions and 0 deletions

View File

@ -0,0 +1,281 @@
<?php
/**
* File: Base_Page_Settings.php
*
* @package W3TC
*/
namespace W3TC;
/**
* Class: Base_Page_Settings
*
* phpcs:disable PSR2.Classes.PropertyDeclaration.Underscore
*/
class Base_Page_Settings {
/**
* Config
*
* @var Config
*/
protected $_config = null;
/**
* Notes
*
* @var array
*/
protected $_notes = array();
/**
* Errors
*
* @var array
*/
protected $_errors = array();
/**
* Used in PHPMailer init function
*
* @var string
*/
protected $_phpmailer_sender = '';
/**
* Master configuration
*
* @var Config
*/
protected $_config_master;
/**
* Page
*
* @var number
*/
protected $_page;
/**
* Constructor.
*/
public function __construct() {
$this->_config = Dispatcher::config();
$this->_config_master = Dispatcher::config_master();
$this->_page = Util_Admin::get_current_page();
}
/**
* Render header.
*/
public function options() {
$this->view();
}
/**
* Render footer.
*/
public function render_footer() {
include W3TC_INC_OPTIONS_DIR . '/common/footer.php';
}
/**
* Returns true if config section is sealed.
*
* @param string $section Config section.
*
* @return boolean
*/
protected function is_sealed( $section ) {
return true;
}
/**
* Returns true if we edit master config.
*
* @return boolean
*/
protected function is_master() {
return $this->_config->is_master();
}
/**
* Prints checkbox with config option value.
*
* @param string $option_id Option ID.
* @param bool $disabled Disabled flag.
* @param string $class_prefix Class prefix.
* @param bool $label Label.
* @param bool $force_value Override value.
*/
protected function checkbox( $option_id, $disabled = false, $class_prefix = '', $label = true, $force_value = null ) {
$disabled = $disabled || $this->_config->is_sealed( $option_id );
$name = Util_Ui::config_key_to_http_name( $option_id );
if ( ! $disabled ) {
echo '<input type="hidden" name="' . esc_attr( $name ) . '" value="0" />';
}
if ( $label ) {
echo '<label>';
}
echo '<input class="' . esc_attr( $class_prefix ) . 'enabled" type="checkbox" id="' . esc_attr( $name ) . '" name="' . esc_attr( $name ) . '" value="1" ';
if ( ! is_null( $force_value ) ) {
checked( $force_value, true );
} elseif ( 'cdn.flush_manually' === $option_id ) {
checked(
$this->_config->get_boolean(
$option_id,
Cdn_Util::get_flush_manually_default_override( $this->_config->get_string( 'cdn.engine' ) )
),
true
);
} else {
checked( $this->_config->get_boolean( $option_id ), true );
}
if ( $disabled ) {
echo 'disabled="disabled" ';
}
echo ' />';
}
/**
* Prints a radio button and if config value matches value
*
* @param string $option_id Option id.
* @param unknown $value Value.
* @param bool $disabled Disabled flag.
* @param string $class_prefix Class prefix.
*/
protected function radio( $option_id, $value, $disabled = false, $class_prefix = '' ) {
if ( is_bool( $value ) ) {
$r_value = $value ? '1' : '0';
} else {
$r_value = $value;
}
$disabled = $disabled || $this->_config->is_sealed( $option_id );
$name = Util_Ui::config_key_to_http_name( $option_id );
echo '<label>';
echo '<input class="' . esc_attr( $class_prefix ) . 'enabled" type="radio" id="' . esc_attr( $name ) . '" name="' . esc_attr( $name ) . '" value="' . esc_attr( $r_value ) . '" ';
checked( $this->_config->get_boolean( $option_id ), $value );
if ( $disabled ) {
echo 'disabled="disabled" ';
}
echo ' />';
}
/**
* Prints checkbox for debug option.
*
* @param string $option_id Option ID.
*/
protected function checkbox_debug( $option_id ) {
if ( is_array( $option_id ) ) {
$section = $option_id[0];
$section_enabled = $this->_config->is_extension_active_frontend( $section );
} else {
$section = substr( $option_id, 0, strrpos( $option_id, '.' ) );
$section_enabled = $this->_config->get_boolean( $section . '.enabled' );
}
$disabled = $this->_config->is_sealed( $option_id ) || ! $section_enabled;
$name = Util_Ui::config_key_to_http_name( $option_id );
if ( ! $disabled ) {
echo '<input type="hidden" name="' . esc_attr( $name ) . '" value="0" />';
}
echo '<label>';
echo '<input class="enabled" type="checkbox" id="' . esc_attr( $name ) . '" name="' . esc_attr( $name ) . '" value="1" ';
checked( $this->_config->get_boolean( $option_id ) && $section_enabled, true );
if ( $disabled ) {
echo 'disabled="disabled" ';
}
echo ' />';
}
/**
* Prints checkbox for debug option for pro.
*
* @param string $option_id Option ID.
* @param unknown $label Label.
* @param unknown $label_pro Pro label.
*/
protected function checkbox_debug_pro( $option_id, $label, $label_pro ) {
if ( is_array( $option_id ) ) {
$section = $option_id[0];
$section_enabled = $this->_config->is_extension_active_frontend( $section );
} else {
$section = substr( $option_id, 0, strrpos( $option_id, '.' ) );
$section_enabled = $this->_config->get_boolean( $section . '.enabled' );
}
$is_pro = Util_Environment::is_w3tc_pro( $this->_config );
$disabled = $this->_config->is_sealed( $option_id ) || ! $section_enabled || ! $is_pro;
$name = Util_Ui::config_key_to_http_name( $option_id );
if ( ! $disabled ) {
echo '<input type="hidden" name="' . esc_attr( $name ) . '" value="0" />';
}
echo '<label>';
echo '<input class="enabled" type="checkbox" id="' . esc_attr( $name ) . '" name="' . esc_attr( $name ) . '" value="1" ';
checked( $this->_config->get_boolean( $option_id ) && $is_pro, true );
if ( $disabled ) {
echo 'disabled="disabled" ';
}
echo ' />';
echo esc_html( $label );
if ( $is_pro ) {
echo wp_kses(
$label_pro,
array(
'a' => array(
'href' => array(),
'id' => array(),
'class' => array(),
),
)
);
}
echo '</label>';
}
/**
* Prints checkbox for debug option for pro.
*
* @param string $option_id Option ID.
* @param bool $disabled Disabled flag.
* @param unknown $value_when_disabled Override value when disabled.
*/
protected function value_with_disabled( $option_id, $disabled, $value_when_disabled ) {
if ( $disabled ) {
echo 'value="' . esc_attr( $value_when_disabled ) . '" disabled="disabled" ';
} else {
echo 'value="' . esc_attr( $this->_config->get_string( $option_id ) ) . '" ';
}
}
/**
* Render header.
*/
protected function view() {
include W3TC_INC_DIR . '/options/common/header.php';
}
}

View File

@ -0,0 +1,110 @@
<?php
namespace W3TC;
class BrowserCache_ConfigLabels {
public function config_labels( $config_labels ) {
return array_merge( $config_labels, array(
'browsercache.enabled' => __( 'Browser Cache:', 'w3-total-cache' ),
'browsercache.replace.exceptions' => __( 'Prevent caching exception list:', 'w3-total-cache' ),
'browsercache.no404wp' => __( 'Do not process 404 errors for static objects with WordPress', 'w3-total-cache' ),
'browsercache.no404wp.exceptions' => __( '404 error exception list:', 'w3-total-cache' ),
'browsercache.cssjs.last_modified' => __( 'Set Last-Modified header', 'w3-total-cache' ),
'browsercache.cssjs.expires' => __( 'Set expires header', 'w3-total-cache' ),
'browsercache.cssjs.lifetime' => __( 'Expires header lifetime:', 'w3-total-cache' ),
'browsercache.cssjs.cache.control' => __( 'Set cache control header', 'w3-total-cache' ),
'browsercache.cssjs.cache.policy' => __( 'Cache Control policy:', 'w3-total-cache' ),
'browsercache.cssjs.etag' => __( 'Set entity tag (eTag)', 'w3-total-cache' ),
'browsercache.cssjs.w3tc' => __( 'Set W3 Total Cache header', 'w3-total-cache' ),
'browsercache.cssjs.compression' => __( 'Enable <acronym title="Hypertext Transfer Protocol">HTTP</acronym> (gzip) compression', 'w3-total-cache' ),
'browsercache.cssjs.brotli' => __( 'Enable <acronym title="Hypertext Transfer Protocol">HTTP</acronym> (brotli) compression', 'w3-total-cache' ),
'browsercache.cssjs.replace' => __( 'Prevent caching of objects after settings change', 'w3-total-cache' ),
'browsercache.cssjs.nocookies' => __( 'Disable cookies for static files', 'w3-total-cache' ),
'browsercache.html.last_modified' => __( 'Set Last-Modified header', 'w3-total-cache' ),
'browsercache.html.expires' => __( 'Set expires header', 'w3-total-cache' ),
'browsercache.html.lifetime' => __( 'Expires header lifetime:', 'w3-total-cache' ),
'browsercache.html.cache.control' => __( 'Set cache control header', 'w3-total-cache' ),
'browsercache.html.cache.policy' => __( 'Cache Control policy:', 'w3-total-cache' ),
'browsercache.html.etag' => __( 'Set entity tag (ETag)', 'w3-total-cache' ),
'browsercache.html.w3tc' => __( 'Set W3 Total Cache header', 'w3-total-cache' ),
'browsercache.html.compression' => __( 'Enable <acronym title="Hypertext Transfer Protocol">HTTP</acronym> (gzip) compression', 'w3-total-cache' ),
'browsercache.html.brotli' => __( 'Enable <acronym title="Hypertext Transfer Protocol">HTTP</acronym> (brotli) compression', 'w3-total-cache' ),
'browsercache.other.last_modified' => __( 'Set Last-Modified header', 'w3-total-cache' ),
'browsercache.other.expires' => __( 'Set expires header', 'w3-total-cache' ),
'browsercache.other.lifetime' => __( 'Expires header lifetime:', 'w3-total-cache' ),
'browsercache.other.cache.control' => __( 'Set cache control header', 'w3-total-cache' ),
'browsercache.other.cache.policy' => __( 'Cache Control policy:', 'w3-total-cache' ),
'browsercache.other.etag' => __( 'Set entity tag (ETag)', 'w3-total-cache' ),
'browsercache.other.w3tc' => __( 'Set W3 Total Cache header', 'w3-total-cache' ),
'browsercache.other.compression' => __( 'Enable <acronym title="Hypertext Transfer Protocol">HTTP</acronym> (gzip) compression', 'w3-total-cache' ),
'browsercache.other.brotli' => __( 'Enable <acronym title="Hypertext Transfer Protocol">HTTP</acronym> (brotli) compression', 'w3-total-cache' ),
'browsercache.other.replace' => __( 'Prevent caching of objects after settings change', 'w3-total-cache' ),
'browsercache.other.nocookies' => __( 'Disable cookies for static files', 'w3-total-cache' ),
'browsercache.security.session.cookie_httponly' => __( 'Access session cookies through the <acronym title="Hypertext Transfer Protocol">HTTP</acronym> only:', 'w3-total-cache' ),
'browsercache.security.session.cookie_secure' => __( 'Send session cookies only to secure connections:', 'w3-total-cache' ),
'browsercache.security.session.use_only_cookies' => __( 'Use cookies to store session IDs:', 'w3-total-cache' ),
'browsercache.hsts' => __( '<acronym title="Hypertext Transfer Protocol">HTTP</acronym> Strict Transport Security policy', 'w3-total-cache' ),
'browsercache.security.hsts.directive' => __( 'Directive:', 'w3-total-cache' ),
'browsercache.security.xfo' => __( 'X-Frame-Options', 'w3-total-cache' ),
'browsercache.security.xfo.directive' => __( 'Directive:', 'w3-total-cache' ),
'browsercache.security.xss' => __( 'X-<acronym title="Cross-Site Scripting">XSS</acronym>-Protection', 'w3-total-cache' ),
'browsercache.security.xss.directive' => __( 'Directive:', 'w3-total-cache' ),
'browsercache.security.xcto' => __( 'X-Content-Type-Options', 'w3-total-cache' ),
'browsercache.security.pkp' => __( '<acronym title="Hypertext Transfer Protocol">HTTP</acronym> Public Key Pinning', 'w3-total-cache' ),
'browsercache.security.pkp.pin' => __( 'Public Key:', 'w3-total-cache' ),
'browsercache.security.pkp.pin.backup' => __( 'Public Key (Backup):', 'w3-total-cache' ),
'browsercache.security.pkp.extra' => __( 'Extra Parameters:', 'w3-total-cache' ),
'browsercache.security.pkp.report.url' => __( 'Report <acronym title="Uniform Resource Locator">URL</acronym>:', 'w3-total-cache' ),
'browsercache.security.pkp.report.only' => __( 'Report Mode Only:', 'w3-total-cache' ),
'browsercache.security.referrer.policy' => __( 'Referrer Policy', 'w3-total-cache' ),
'browsercache.security.referrer.policy.directive' => __( 'Directive:', 'w3-total-cache' ),
'browsercache.security.csp' => __( 'Content Security Policy', 'w3-total-cache' ),
'browsercache.security.csp.reporturi' => __( 'report-uri:', 'w3-total-cache' ),
'browsercache.security.csp.reportto' => __( 'report-to:', 'w3-total-cache' ),
'browsercache.security.csp.base' => __( 'base-uri:', 'w3-total-cache' ),
'browsercache.security.csp.frame' => __( 'frame-src:', 'w3-total-cache' ),
'browsercache.security.csp.connect' => __( 'connect-src:', 'w3-total-cache' ),
'browsercache.security.csp.font' => __( 'font-src:', 'w3-total-cache' ),
'browsercache.security.csp.script' => __( 'script-src:', 'w3-total-cache' ),
'browsercache.security.csp.style' => __( 'style-src:', 'w3-total-cache' ),
'browsercache.security.csp.img' => __( 'img-src:', 'w3-total-cache' ),
'browsercache.security.csp.media' => __( 'media-src:', 'w3-total-cache' ),
'browsercache.security.csp.object' => __( 'object-src:', 'w3-total-cache' ),
'browsercache.security.csp.plugin' => __( 'plugin-types:', 'w3-total-cache' ),
'browsercache.security.csp.form' => __( 'form-action:', 'w3-total-cache' ),
'browsercache.security.csp.frame.ancestors' => __( 'frame-ancestors:', 'w3-total-cache' ),
'browsercache.security.csp.sandbox' => __( 'sandbox:', 'w3-total-cache' ),
'browsercache.security.csp.child' => __( 'child-src:', 'w3-total-cache' ),
'browsercache.security.csp.manifest' => __( 'manifest-src:', 'w3-total-cache' ),
'browsercache.security.csp.scriptelem' => __( 'script-src-elem:', 'w3-total-cache' ),
'browsercache.security.csp.scriptattr' => __( 'script-src-attr:', 'w3-total-cache' ),
'browsercache.security.csp.styleelem' => __( 'style-src-elem:', 'w3-total-cache' ),
'browsercache.security.csp.styleattr' => __( 'style-src-attr:', 'w3-total-cache' ),
'browsercache.security.csp.worker' => __( 'worker-src:', 'w3-total-cache' ),
'browsercache.security.csp.default' => __( 'default-src:', 'w3-total-cache' ),
'browsercache.security.cspro' => __( 'Content Security Policy Report Only', 'w3-total-cache' ),
'browsercache.security.cspro.reporturi' => __( 'report-uri:', 'w3-total-cache' ),
'browsercache.security.cspro.reportto' => __( 'report-to:', 'w3-total-cache' ),
'browsercache.security.cspro.base' => __( 'base-uri:', 'w3-total-cache' ),
'browsercache.security.cspro.frame' => __( 'frame-src:', 'w3-total-cache' ),
'browsercache.security.cspro.connect' => __( 'connect-src:', 'w3-total-cache' ),
'browsercache.security.cspro.font' => __( 'font-src:', 'w3-total-cache' ),
'browsercache.security.cspro.script' => __( 'script-src:', 'w3-total-cache' ),
'browsercache.security.cspro.style' => __( 'style-src:', 'w3-total-cache' ),
'browsercache.security.cspro.img' => __( 'img-src:', 'w3-total-cache' ),
'browsercache.security.cspro.media' => __( 'media-src:', 'w3-total-cache' ),
'browsercache.security.cspro.object' => __( 'object-src:', 'w3-total-cache' ),
'browsercache.security.cspro.plugin' => __( 'plugin-types:', 'w3-total-cache' ),
'browsercache.security.cspro.form' => __( 'form-action:', 'w3-total-cache' ),
'browsercache.security.cspro.frame.ancestors' => __( 'frame-ancestors:', 'w3-total-cache' ),
'browsercache.security.cspro.sandbox' => __( 'sandbox:', 'w3-total-cache' ),
'browsercache.security.cspro.child' => __( 'child-src:', 'w3-total-cache' ),
'browsercache.security.cspro.manifest' => __( 'manifest-src:', 'w3-total-cache' ),
'browsercache.security.cspro.scriptelem' => __( 'script-src-elem:', 'w3-total-cache' ),
'browsercache.security.cspro.scriptattr' => __( 'script-src-attr:', 'w3-total-cache' ),
'browsercache.security.cspro.styleelem' => __( 'style-src-elem:', 'w3-total-cache' ),
'browsercache.security.cspro.styleattr' => __( 'style-src-attr:', 'w3-total-cache' ),
'browsercache.security.cspro.worker' => __( 'worker-src:', 'w3-total-cache' ),
'browsercache.security.cspro.default' => __( 'default-src:', 'w3-total-cache' ),
) );
}
}

View File

@ -0,0 +1,109 @@
<?php
namespace W3TC;
/**
* Browsercache core
*/
class BrowserCache_Core {
/**
* Returns replace extensions
*
* @return array
*/
public function get_replace_extensions( $config ) {
$types = array();
$extensions = array();
if ( $config->get_boolean( 'browsercache.cssjs.replace' ) ) {
$types = array_merge( $types, array_keys( $this->_get_cssjs_types() ) );
}
if ( $config->get_boolean( 'browsercache.html.replace' ) ) {
$types = array_merge( $types, array_keys( $this->_get_html_types() ) );
}
if ( $config->get_boolean( 'browsercache.other.replace' ) ) {
$types = array_merge( $types, array_keys( $this->_get_other_types() ) );
}
foreach ( $types as $type ) {
$extensions = array_merge( $extensions, explode( '|', $type ) );
}
return $extensions;
}
/**
* Returns replace extensions
*
* @return array
*/
public function get_replace_querystring_extensions( $config ) {
$extensions = array();
if ( $config->get_boolean( 'browsercache.cssjs.replace' ) )
$this->_fill_extensions( $extensions, $this->_get_cssjs_types(), 'replace' );
if ( $config->get_boolean( 'browsercache.html.replace' ) )
$this->_fill_extensions( $extensions, $this->_get_html_types(), 'replace' );
if ( $config->get_boolean( 'browsercache.other.replace' ) )
$this->_fill_extensions( $extensions, $this->_get_other_types(), 'replace' );
if ( $config->get_boolean( 'browsercache.cssjs.querystring' ) )
$this->_fill_extensions( $extensions, $this->_get_cssjs_types(), 'querystring' );
if ( $config->get_boolean( 'browsercache.html.querystring' ) )
$this->_fill_extensions( $extensions, $this->_get_html_types(), 'querystring' );
if ( $config->get_boolean( 'browsercache.other.querystring' ) )
$this->_fill_extensions( $extensions, $this->_get_other_types(), 'querystring' );
return $extensions;
}
private function _fill_extensions( &$extensions, $types, $operation ) {
foreach ( array_keys( $types ) as $type ) {
$type_extensions = explode( '|', $type );
foreach ( $type_extensions as $ext ) {
if ( !isset( $extensions[$ext] ) )
$extensions[$ext] = array();
$extensions[$ext][$operation] = true;
}
}
}
/**
* Returns CSS/JS mime types
*
* @return array
*/
private function _get_cssjs_types() {
$mime_types = include W3TC_INC_DIR . '/mime/cssjs.php';
return $mime_types;
}
/**
* Returns HTML mime types
*
* @return array
*/
private function _get_html_types() {
$mime_types = include W3TC_INC_DIR . '/mime/html.php';
return $mime_types;
}
/**
* Returns other mime types
*
* @return array
*/
private function _get_other_types() {
$mime_types = include W3TC_INC_DIR . '/mime/other.php';
return $mime_types;
}
}

View File

@ -0,0 +1,716 @@
<?php
namespace W3TC;
/**
* class BrowserCache_Environment
*/
class BrowserCache_Environment {
public function __construct() {
add_filter( 'w3tc_cdn_rules_section',
array( $this, 'w3tc_cdn_rules_section' ), 10, 2 );
}
/**
* Fixes environment in each wp-admin request
*
* @param Config $config
* @param bool $force_all_checks
* @throws Util_Environment_Exceptions
*/
public function fix_on_wpadmin_request( $config, $force_all_checks ) {
$exs = new Util_Environment_Exceptions();
if ( $config->get_boolean( 'config.check' ) || $force_all_checks ) {
if ( $config->get_boolean( 'browsercache.enabled' ) ) {
$this->rules_cache_add( $config, $exs );
} else {
$this->rules_cache_remove( $exs );
}
}
if ( count( $exs->exceptions() ) > 0 )
throw $exs;
}
/**
* Fixes environment once event occurs
*
* @throws Util_Environment_Exceptions
*/
public function fix_on_event( $config, $event, $old_config = null ) {
}
/**
* Fixes environment after plugin deactivation
*
* @throws Util_Environment_Exceptions
*/
public function fix_after_deactivation() {
$exs = new Util_Environment_Exceptions();
$this->rules_cache_remove( $exs );
if ( count( $exs->exceptions() ) > 0 )
throw $exs;
}
/**
* Returns required rules for module
*
* @param Config $config
* @return array
*/
public function get_required_rules( $config ) {
if ( ! $config->get_boolean( 'browsercache.enabled' ) ) {
return array();
}
$mime_types = $this->get_mime_types();
switch ( true ) {
case Util_Environment::is_apache():
$generator_apache = new BrowserCache_Environment_Apache( $config );
$rewrite_rules = array(
array(
'filename' => Util_Rule::get_apache_rules_path(),
'content' =>
W3TC_MARKER_BEGIN_BROWSERCACHE_CACHE . "\n" .
$this->rules_cache_generate_apache( $config ) .
$generator_apache->rules_no404wp( $mime_types ) .
W3TC_MARKER_END_BROWSERCACHE_CACHE . "\n"
)
);
break;
case Util_Environment::is_litespeed():
$generator_litespeed = new BrowserCache_Environment_LiteSpeed( $config );
$rewrite_rules = $generator_litespeed->get_required_rules( $mime_types );
break;
case Util_Environment::is_nginx():
$generator_nginx = new BrowserCache_Environment_Nginx( $config );
$rewrite_rules = $generator_nginx->get_required_rules( $mime_types );
break;
default:
$rewrite_rules = array();
}
return $rewrite_rules;
}
/**
* Returns mime types
*
* @return array
*/
public function get_mime_types() {
$a = Util_Mime::sections_to_mime_types_map();
$other_compression = $a['other'];
unset( $other_compression['asf|asx|wax|wmv|wmx'] );
unset( $other_compression['avi'] );
unset( $other_compression['avif'] );
unset( $other_compression['avifs'] );
unset( $other_compression['divx'] );
unset( $other_compression['gif'] );
unset( $other_compression['br'] );
unset( $other_compression['gz|gzip'] );
unset( $other_compression['jpg|jpeg|jpe'] );
unset( $other_compression['mid|midi'] );
unset( $other_compression['mov|qt'] );
unset( $other_compression['mp3|m4a'] );
unset( $other_compression['mp4|m4v'] );
unset( $other_compression['ogv'] );
unset( $other_compression['mpeg|mpg|mpe'] );
unset( $other_compression['png'] );
unset( $other_compression['ra|ram'] );
unset( $other_compression['tar'] );
unset( $other_compression['webp'] );
unset( $other_compression['wma'] );
unset( $other_compression['zip'] );
$a['other_compression'] = $other_compression;
return $a;
}
/**
* Generate rules for FTP upload
*
* @param Config $config
* @return string
*/
public function rules_cache_generate_for_ftp( $config ) {
return $this->rules_cache_generate_apache( $config );
}
/*
* rules cache
*/
/**
* Writes cache rules
*
* @throws Util_WpFile_FilesystemOperationException with S/FTP form if it can't get the required filesystem credentials
*/
private function rules_cache_add( $config, $exs ) {
$rules = $this->get_required_rules( $config );
foreach ( $rules as $i ) {
Util_Rule::add_rules( $exs,
$i['filename'],
$i['content'],
W3TC_MARKER_BEGIN_BROWSERCACHE_CACHE,
W3TC_MARKER_END_BROWSERCACHE_CACHE,
array(
W3TC_MARKER_BEGIN_MINIFY_CORE => 0,
W3TC_MARKER_BEGIN_PGCACHE_CORE => 0,
W3TC_MARKER_BEGIN_WORDPRESS => 0,
W3TC_MARKER_END_PGCACHE_CACHE => strlen( W3TC_MARKER_END_PGCACHE_CACHE ) + 1,
W3TC_MARKER_END_MINIFY_CACHE => strlen( W3TC_MARKER_END_MINIFY_CACHE ) + 1
)
);
}
}
/**
* Removes cache directives
*
* @throws Util_WpFile_FilesystemOperationException with S/FTP form if it can't get the required filesystem credentials
*/
private function rules_cache_remove( $exs ) {
$filenames = array();
switch ( true ) {
case Util_Environment::is_apache():
$filenames[] = Util_Rule::get_apache_rules_path();
break;
case Util_Environment::is_litespeed():
$filenames[] = Util_Rule::get_apache_rules_path();
$filenames[] = Util_Rule::get_litespeed_rules_path();
break;
case Util_Environment::is_nginx():
$filenames[] = Util_Rule::get_nginx_rules_path();
break;
}
foreach ( $filenames as $i ) {
Util_Rule::remove_rules( $exs,
$i,
W3TC_MARKER_BEGIN_BROWSERCACHE_CACHE,
W3TC_MARKER_END_BROWSERCACHE_CACHE );
}
}
/**
* Returns cache rules
*
* @param Config $config
* @return string
*/
private function rules_cache_generate_apache( $config ) {
$mime_types2 = $this->get_mime_types();
$cssjs_types = $mime_types2['cssjs'];
$cssjs_types = array_unique( $cssjs_types );
$html_types = $mime_types2['html'];
$other_types = $mime_types2['other'];
$other_compression_types = $mime_types2['other_compression'];
$cssjs_expires = $config->get_boolean( 'browsercache.cssjs.expires' );
$html_expires = $config->get_boolean( 'browsercache.html.expires' );
$other_expires = $config->get_boolean( 'browsercache.other.expires' );
$cssjs_lifetime = $config->get_integer( 'browsercache.cssjs.lifetime' );
$html_lifetime = $config->get_integer( 'browsercache.html.lifetime' );
$other_lifetime = $config->get_integer( 'browsercache.other.lifetime' );
$compatibility = $config->get_boolean( 'pgcache.compatibility' );
$mime_types = array();
if ( $cssjs_expires && $cssjs_lifetime ) {
$mime_types = array_merge( $mime_types, $cssjs_types );
}
if ( $html_expires && $html_lifetime ) {
$mime_types = array_merge( $mime_types, $html_types );
}
if ( $other_expires && $other_lifetime ) {
$mime_types = array_merge( $mime_types, $other_types );
}
$rules = '';
if ( count( $mime_types ) ) {
$rules .= "<IfModule mod_mime.c>\n";
foreach ( $mime_types as $ext => $mime_type ) {
$extensions = explode( '|', $ext );
if ( !is_array( $mime_type ) )
$mime_type = (array)$mime_type;
foreach ( $mime_type as $mime_type2 ) {
$rules .= " AddType " . $mime_type2;
foreach ( $extensions as $extension ) {
$rules .= " ." . $extension;
}
$rules .= "\n";
}
}
$rules .= "</IfModule>\n";
$rules .= "<IfModule mod_expires.c>\n";
$rules .= " ExpiresActive On\n";
if ( $cssjs_expires && $cssjs_lifetime ) {
foreach ( $cssjs_types as $mime_type ) {
$rules .= " ExpiresByType " . $mime_type . " A" . $cssjs_lifetime . "\n";
}
}
if ( $html_expires && $html_lifetime ) {
foreach ( $html_types as $mime_type ) {
$rules .= " ExpiresByType " . $mime_type . " A" . $html_lifetime . "\n";
}
}
if ( $other_expires && $other_lifetime ) {
foreach ( $other_types as $mime_type ) {
if ( is_array( $mime_type ) )
foreach ( $mime_type as $mime_type2 )
$rules .= " ExpiresByType " . $mime_type2 . " A" . $other_lifetime . "\n";
else
$rules .= " ExpiresByType " . $mime_type . " A" . $other_lifetime . "\n";
}
}
$rules .= "</IfModule>\n";
}
$cssjs_brotli = $config->get_boolean( 'browsercache.cssjs.brotli' );
$html_brotli = $config->get_boolean( 'browsercache.html.brotli' );
$other_brotli = $config->get_boolean( 'browsercache.other.brotli' );
if ( $cssjs_brotli || $html_brotli || $other_brotli ) {
$brotli_types = array();
if ( $cssjs_brotli ) {
$brotli_types = array_merge( $brotli_types, $cssjs_types );
}
if ( $html_brotli ) {
$brotli_types = array_merge( $brotli_types, $html_types );
}
if ( $other_brotli ) {
$brotli_types = array_merge( $brotli_types,
$other_compression_types );
}
$rules .= "<IfModule mod_brotli.c>\n";
if ( version_compare( Util_Environment::get_server_version(), '2.3.7', '>=' ) ) {
$rules .= " <IfModule mod_filter.c>\n";
}
$rules .= " AddOutputFilterByType BROTLI_COMPRESS " . implode( ' ', $brotli_types ) . "\n";
$rules .= " <IfModule mod_mime.c>\n";
$rules .= " # BROTLI_COMPRESS by extension\n";
$rules .= " AddOutputFilter BROTLI_COMPRESS js css htm html xml\n";
$rules .= " </IfModule>\n";
if ( version_compare( Util_Environment::get_server_version(), '2.3.7', '>=' ) ) {
$rules .= " </IfModule>\n";
}
$rules .= "</IfModule>\n";
}
$cssjs_compression = $config->get_boolean( 'browsercache.cssjs.compression' );
$html_compression = $config->get_boolean( 'browsercache.html.compression' );
$other_compression = $config->get_boolean( 'browsercache.other.compression' );
if ( $cssjs_compression || $html_compression || $other_compression ) {
$compression_types = array();
if ( $cssjs_compression ) {
$compression_types = array_merge( $compression_types, $cssjs_types );
}
if ( $html_compression ) {
$compression_types = array_merge( $compression_types, $html_types );
}
if ( $other_compression ) {
$compression_types = array_merge( $compression_types,
$other_compression_types );
}
$rules .= "<IfModule mod_deflate.c>\n";
if ( $compatibility ) {
$rules .= " <IfModule mod_setenvif.c>\n";
$rules .= " BrowserMatch ^Mozilla/4 gzip-only-text/html\n";
$rules .= " BrowserMatch ^Mozilla/4\\.0[678] no-gzip\n";
$rules .= " BrowserMatch \\bMSIE !no-gzip !gzip-only-text/html\n";
$rules .= " BrowserMatch \\bMSI[E] !no-gzip !gzip-only-text/html\n";
$rules .= " </IfModule>\n";
}
if ( version_compare( Util_Environment::get_server_version(), '2.3.7', '>=' ) ) {
$rules .= " <IfModule mod_filter.c>\n";
}
$rules .= " AddOutputFilterByType DEFLATE " . implode( ' ', $compression_types ) . "\n";
$rules .= " <IfModule mod_mime.c>\n";
$rules .= " # DEFLATE by extension\n";
$rules .= " AddOutputFilter DEFLATE js css htm html xml\n";
$rules .= " </IfModule>\n";
if ( version_compare( Util_Environment::get_server_version(), '2.3.7', '>=' ) ) {
$rules .= " </IfModule>\n";
}
$rules .= "</IfModule>\n";
}
$rules .= $this->_rules_cache_generate_apache_for_type( $config,
$mime_types2['cssjs'], 'cssjs' );
$rules .= $this->_rules_cache_generate_apache_for_type( $config,
$mime_types2['html'], 'html' );
$rules .= $this->_rules_cache_generate_apache_for_type( $config,
$mime_types2['other'], 'other' );
if ( $config->get_boolean( 'browsercache.hsts' ) ||
$config->get_boolean( 'browsercache.security.xfo' ) ||
$config->get_boolean( 'browsercache.security.xss' ) ||
$config->get_boolean( 'browsercache.security.xcto' ) ||
$config->get_boolean( 'browsercache.security.pkp' ) ||
$config->get_boolean( 'browsercache.security.referrer.policy' ) ||
$config->get_boolean( 'browsercache.security.csp' ) ||
$config->get_boolean( 'browsercache.security.cspro' ) ||
$config->get_boolean( 'browsercache.security.fp' )
) {
$lifetime = $config->get_integer( 'browsercache.other.lifetime' );
$rules .= "<IfModule mod_headers.c>\n";
if ( $config->get_boolean( 'browsercache.hsts' ) ) {
$dir = $config->get_string( 'browsercache.security.hsts.directive' );
$rules .= " Header always set Strict-Transport-Security \"max-age=$lifetime" . ( strpos( $dir,"inc" ) ? "; includeSubDomains" : "" ) . ( strpos( $dir, "pre" ) ? "; preload" : "" ) . "\"\n";
}
if ( $config->get_boolean( 'browsercache.security.xfo' ) ) {
$dir = $config->get_string( 'browsercache.security.xfo.directive' );
$url = trim( $config->get_string( 'browsercache.security.xfo.allow' ) );
if ( empty( $url ) ) {
$url = Util_Environment::home_url_maybe_https();
}
$rules .= " Header always append X-Frame-Options \"" . ( $dir == "same" ? "SAMEORIGIN" : ( $dir == "deny" ? "DENY" : "ALLOW-FROM $url" ) ) . "\"\n";
}
if ( $config->get_boolean( 'browsercache.security.xss' ) ) {
$dir = $config->get_string( 'browsercache.security.xss.directive' );
$rules .= " Header set X-XSS-Protection \"" . ( $dir == "block" ? "1; mode=block" : $dir ) . "\"\n";
}
if ( $config->get_boolean( 'browsercache.security.xcto' ) ) {
$rules .= " Header set X-Content-Type-Options \"nosniff\"\n";
}
if ( $config->get_boolean( 'browsercache.security.pkp' ) ) {
$pin = trim( $config->get_string( 'browsercache.security.pkp.pin' ) );
$pinbak = trim( $config->get_string( 'browsercache.security.pkp.pin.backup' ) );
$extra = $config->get_string( 'browsercache.security.pkp.extra' );
$url = trim( $config->get_string( 'browsercache.security.pkp.report.url' ) );
$rep_only = $config->get_string( 'browsercache.security.pkp.report.only' ) == '1' ? true : false;
$rules .= " Header set " . ( $rep_only ? "Public-Key-Pins-Report-Only" : "Public-Key-Pins" ) . " \"pin-sha256=\\\"$pin\\\"; pin-sha256=\\\"$pinbak\\\"; max-age=$lifetime" . ( strpos( $extra,"inc" ) ? "; includeSubDomains" : "" ) . ( !empty( $url ) ? "; report-uri=\\\"$url\\\"" : "" ) . "\"\n";
}
if ( $config->get_boolean( 'browsercache.security.referrer.policy' ) ) {
$dir = $config->get_string( 'browsercache.security.referrer.policy.directive' );
$rules .= " Header set Referrer-Policy \"" . ($dir == "0" ? "" : $dir ) . "\"\n";
}
if ( $config->get_boolean( 'browsercache.security.csp' ) ) {
$base = trim( $config->get_string( 'browsercache.security.csp.base' ) );
$reporturi = trim( $config->get_string( 'browsercache.security.csp.reporturi' ) );
$reportto = trim( $config->get_string( 'browsercache.security.csp.reportto' ) );
$frame = trim( $config->get_string( 'browsercache.security.csp.frame' ) );
$connect = trim( $config->get_string( 'browsercache.security.csp.connect' ) );
$font = trim( $config->get_string( 'browsercache.security.csp.font' ) );
$script = trim( $config->get_string( 'browsercache.security.csp.script' ) );
$style = trim( $config->get_string( 'browsercache.security.csp.style' ) );
$img = trim( $config->get_string( 'browsercache.security.csp.img' ) );
$media = trim( $config->get_string( 'browsercache.security.csp.media' ) );
$object = trim( $config->get_string( 'browsercache.security.csp.object' ) );
$plugin = trim( $config->get_string( 'browsercache.security.csp.plugin' ) );
$form = trim( $config->get_string( 'browsercache.security.csp.form' ) );
$frame_ancestors = trim( $config->get_string( 'browsercache.security.csp.frame.ancestors' ) );
$sandbox = trim( $config->get_string( 'browsercache.security.csp.sandbox' ) );
$child = trim( $config->get_string( 'browsercache.security.csp.child' ) );
$manifest = trim( $config->get_string( 'browsercache.security.csp.manifest' ) );
$scriptelem = trim( $config->get_string( 'browsercache.security.csp.scriptelem' ) );
$scriptattr = trim( $config->get_string( 'browsercache.security.csp.scriptattr' ) );
$styleelem = trim( $config->get_string( 'browsercache.security.csp.styleelem' ) );
$scriptelem = trim( $config->get_string( 'browsercache.security.csp.styleattr' ) );
$worker = trim( $config->get_string( 'browsercache.security.csp.worker' ) );
$default = trim( $config->get_string( 'browsercache.security.csp.default' ) );
$dir = rtrim(
( ! empty( $base ) ? "base-uri $base; " : '' ) .
( ! empty( $reporturi ) ? "report-uri $reporturi; " : '' ) .
( ! empty( $reportto ) ? "report-to $reportto; " : '' ) .
( ! empty( $frame ) ? "frame-src $frame; " : '' ) .
( ! empty( $connect ) ? "connect-src $connect; " : '' ) .
( ! empty( $font ) ? "font-src $font; " : '' ) .
( ! empty( $script ) ? "script-src $script; " : '' ) .
( ! empty( $style ) ? "style-src $style; " : '' ) .
( ! empty( $img ) ? "img-src $img; " : '' ) .
( ! empty( $media ) ? "media-src $media; " : '' ) .
( ! empty( $object ) ? "object-src $object; " : '' ) .
( ! empty( $plugin ) ? "plugin-types $plugin; " : '' ) .
( ! empty( $form ) ? "form-action $form; " : '' ) .
( ! empty( $frame_ancestors ) ? "frame-ancestors $frame_ancestors; " : '' ) .
( ! empty( $sandbox ) ? "sandbox $sandbox; " : '' ) .
( ! empty( $child ) ? "child-src $child; " : '' ) .
( ! empty( $manifest ) ? "manifest-src $manifest; " : '' ) .
( ! empty( $scriptelem ) ? "script-src-elem $scriptelem; " : '' ) .
( ! empty( $scriptattr ) ? "script-src-attr $scriptattr; " : '' ) .
( ! empty( $styleelem ) ? "style-src-elem $styleelem; " : '' ) .
( ! empty( $styleattr ) ? "style-src-attr $styleattr; " : '' ) .
( ! empty( $worker ) ? "worker-src $worker; " : '' ) .
( ! empty( $default ) ? "default-src $default;" : '' ),
'; '
);
if ( !empty( $dir ) ) {
$rules .= " Header set Content-Security-Policy \"$dir\"\n";
}
}
if ( $config->get_boolean( 'browsercache.security.cspro' ) && ( ! empty( $config->get_string( 'browsercache.security.cspro.reporturi' ) ) || ! empty( $config->get_string( 'browsercache.security.cspro.reportto' ) ) ) ) {
$base = trim( $config->get_string( 'browsercache.security.cspro.base' ) );
$reporturi = trim( $config->get_string( 'browsercache.security.cspro.reporturi' ) );
$reportto = trim( $config->get_string( 'browsercache.security.cspro.reportto' ) );
$frame = trim( $config->get_string( 'browsercache.security.cspro.frame' ) );
$connect = trim( $config->get_string( 'browsercache.security.cspro.connect' ) );
$font = trim( $config->get_string( 'browsercache.security.cspro.font' ) );
$script = trim( $config->get_string( 'browsercache.security.cspro.script' ) );
$style = trim( $config->get_string( 'browsercache.security.cspro.style' ) );
$img = trim( $config->get_string( 'browsercache.security.cspro.img' ) );
$media = trim( $config->get_string( 'browsercache.security.cspro.media' ) );
$object = trim( $config->get_string( 'browsercache.security.cspro.object' ) );
$plugin = trim( $config->get_string( 'browsercache.security.cspro.plugin' ) );
$form = trim( $config->get_string( 'browsercache.security.cspro.form' ) );
$frame_ancestors = trim( $config->get_string( 'browsercache.security.cspro.frame.ancestors' ) );
$sandbox = trim( $config->get_string( 'browsercache.security.cspro.sandbox' ) );
$child = trim( $config->get_string( 'browsercache.security.cspro.child' ) );
$manifest = trim( $config->get_string( 'browsercache.security.cspro.manifest' ) );
$scriptelem = trim( $config->get_string( 'browsercache.security.cspro.scriptelem' ) );
$scriptattr = trim( $config->get_string( 'browsercache.security.cspro.scriptattr' ) );
$styleelem = trim( $config->get_string( 'browsercache.security.cspro.styleelem' ) );
$scriptelem = trim( $config->get_string( 'browsercache.security.cspro.styleattr' ) );
$worker = trim( $config->get_string( 'browsercache.security.cspro.worker' ) );
$default = trim( $config->get_string( 'browsercache.security.cspro.default' ) );
$dir = rtrim(
( ! empty( $base ) ? "base-uri $base; " : '' ) .
( ! empty( $reporturi ) ? "report-uri $reporturi; " : '' ) .
( ! empty( $reportto ) ? "report-to $reportto; " : '' ) .
( ! empty( $frame ) ? "frame-src $frame; " : '' ) .
( ! empty( $connect ) ? "connect-src $connect; " : '' ) .
( ! empty( $font ) ? "font-src $font; " : '' ) .
( ! empty( $script ) ? "script-src $script; " : '' ) .
( ! empty( $style ) ? "style-src $style; " : '' ) .
( ! empty( $img ) ? "img-src $img; " : '' ) .
( ! empty( $media ) ? "media-src $media; " : '' ) .
( ! empty( $object ) ? "object-src $object; " : '' ) .
( ! empty( $plugin ) ? "plugin-types $plugin; " : '' ) .
( ! empty( $form ) ? "form-action $form; " : '' ) .
( ! empty( $frame_ancestors ) ? "frame-ancestors $frame_ancestors; " : '' ) .
( ! empty( $sandbox ) ? "sandbox $sandbox; " : '' ) .
( ! empty( $child ) ? "child-src $child; " : '' ) .
( ! empty( $manifest ) ? "manifest-src $manifest; " : '' ) .
( ! empty( $scriptelem ) ? "script-src-elem $scriptelem; " : '' ) .
( ! empty( $scriptattr ) ? "script-src-attr $scriptattr; " : '' ) .
( ! empty( $styleelem ) ? "style-src-elem $styleelem; " : '' ) .
( ! empty( $styleattr ) ? "style-src-attr $styleattr; " : '' ) .
( ! empty( $worker ) ? "worker-src $worker; " : '' ) .
( ! empty( $default ) ? "default-src $default;" : '' ),
'; '
);
if ( !empty( $dir ) ) {
$rules .= " Header set Content-Security-Policy-Report-Only \"$dir\"\n";
}
}
if ( $config->get_boolean( 'browsercache.security.fp' ) ) {
$fp_values = $config->get_array( 'browsercache.security.fp.values' );
$feature_v = array();
$permission_v = array();
foreach ( $fp_values as $key => $value ) {
if ( ! empty( $value ) ) {
$value = str_replace( array( '"', "'" ), '', $value );
$feature_v[] = "$key '$value'";
$permission_v[] = "$key=($value)";
}
}
if ( ! empty( $feature_v ) ) {
$rules .= ' Header set Feature-Policy "' . implode( ';', $feature_v ) . "\"\n";
}
if ( ! empty( $permission_v ) ) {
$rules .= ' Header set Permissions-Policy "' . implode( ',', $permission_v ) . "\"\n";
}
}
$rules .= "</IfModule>\n";
}
$g = new BrowserCache_Environment_Apache( $config );
$rules .= $g->rules_rewrite();
return $rules;
}
/**
* Writes cache rules
*
* @param Config $config
* @param array $mime_types
* @param string $section
* @return string
*/
private function _rules_cache_generate_apache_for_type( $config, $mime_types,
$section ) {
$is_disc_enhanced = $config->get_boolean( 'pgcache.enabled' ) &&
$config->get_string( 'pgcache.engine' ) == 'file_generic';
$cache_control = $config->get_boolean( 'browsercache.' . $section . '.cache.control' );
$etag = $config->get_boolean( 'browsercache.' . $section . '.etag' );
$w3tc = $config->get_boolean( 'browsercache.' . $section . '.w3tc' );
$unset_setcookie = $config->get_boolean( 'browsercache.' . $section . '.nocookies' );
$set_last_modified = $config->get_boolean( 'browsercache.' . $section . '.last_modified' );
$compatibility = $config->get_boolean( 'pgcache.compatibility' );
$mime_types2 = apply_filters( 'w3tc_browsercache_rules_section_extensions',
$mime_types, $config, $section );
$extensions = array_keys( $mime_types2 );
// Remove ext from filesmatch if its the same as permalink extension
$pext = strtolower( pathinfo( get_option( 'permalink_structure' ), PATHINFO_EXTENSION ) );
if ( $pext ) {
$extensions = Util_Rule::remove_extension_from_list( $extensions, $pext );
}
$extensions_lowercase = array_map( 'strtolower', $extensions );
$extensions_uppercase = array_map( 'strtoupper', $extensions );
$rules = '';
$headers_rules = '';
if ( $cache_control ) {
$cache_policy = $config->get_string( 'browsercache.' . $section . '.cache.policy' );
switch ( $cache_policy ) {
case 'cache':
$headers_rules .= " Header set Pragma \"public\"\n";
$headers_rules .= " Header set Cache-Control \"public\"\n";
break;
case 'cache_public_maxage':
$expires = $config->get_boolean( 'browsercache.' . $section . '.expires' );
$lifetime = $config->get_integer( 'browsercache.' . $section . '.lifetime' );
$headers_rules .= " Header set Pragma \"public\"\n";
if ( $expires )
$headers_rules .= " Header append Cache-Control \"public\"\n";
else
$headers_rules .= " Header set Cache-Control \"max-age=" . $lifetime . ", public\"\n";
break;
case 'cache_validation':
$headers_rules .= " Header set Pragma \"public\"\n";
$headers_rules .= " Header set Cache-Control \"public, must-revalidate, proxy-revalidate\"\n";
break;
case 'cache_noproxy':
$headers_rules .= " Header set Pragma \"public\"\n";
$headers_rules .= " Header set Cache-Control \"private, must-revalidate\"\n";
break;
case 'cache_maxage':
$expires = $config->get_boolean( 'browsercache.' . $section . '.expires' );
$lifetime = $config->get_integer( 'browsercache.' . $section . '.lifetime' );
$headers_rules .= " Header set Pragma \"public\"\n";
if ( $expires )
$headers_rules .= " Header append Cache-Control \"public, must-revalidate, proxy-revalidate\"\n";
else
$headers_rules .= " Header set Cache-Control \"max-age=" . $lifetime . ", public, must-revalidate, proxy-revalidate\"\n";
break;
case 'no_cache':
$headers_rules .= " Header set Pragma \"no-cache\"\n";
$headers_rules .= " Header set Cache-Control \"max-age=0, private, no-store, no-cache, must-revalidate\"\n";
break;
}
}
if ( $etag ) {
$rules .= " FileETag MTime Size\n";
} else {
if ( $compatibility ) {
$rules .= " FileETag None\n";
$headers_rules .= " Header unset ETag\n";
}
}
if ( $unset_setcookie )
$headers_rules .= " Header unset Set-Cookie\n";
if ( !$set_last_modified )
$headers_rules .= " Header unset Last-Modified\n";
if ( $w3tc )
$headers_rules .= " Header set X-Powered-By \"" .
Util_Environment::w3tc_header() . "\"\n";
if ( strlen( $headers_rules ) > 0 ) {
$rules .= " <IfModule mod_headers.c>\n";
$rules .= $headers_rules;
$rules .= " </IfModule>\n";
}
if ( strlen( $rules ) > 0 ) {
$rules = "<FilesMatch \"\\.(" . implode( '|',
array_merge( $extensions_lowercase, $extensions_uppercase ) ) .
")$\">\n" . $rules;
$rules .= "</FilesMatch>\n";
}
return $rules;
}
/*
* rules_no404wp
*/
public function w3tc_cdn_rules_section( $section_rules, $config ) {
if ( Util_Environment::is_litespeed() ) {
$o = new BrowserCache_Environment_LiteSpeed( $config );
$section_rules = $o->w3tc_cdn_rules_section( $section_rules );
}
return $section_rules;
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace W3TC;
/**
* Environment (rules) generation for apache
* TODO: move all apache-specific code here from BrowserCache_Environment
*/
class BrowserCache_Environment_Apache {
private $c;
public function __construct( $config ) {
$this->c = $config;
}
public function rules_rewrite() {
if ( ! $this->c->get_boolean( 'browsercache.rewrite' ) ) {
return '';
}
$core = Dispatcher::component( 'BrowserCache_Core' );
$extensions = $core->get_replace_extensions( $this->c );
$rules = array();
$rules[] = '<IfModule mod_rewrite.c>';
$rules[] = ' RewriteCond %{REQUEST_FILENAME} !-f';
$rules[] = ' RewriteRule ^(.+)\.(x[0-9]{5})\.(' .
implode( '|', $extensions ) . ')$ $1.$3 [L]';
$rules[] = '</IfModule>';
$rules[] = '';
return implode( "\n", $rules );
}
/**
* Generate rules related to prevent for media 404 error by WP
*
* @param Config $config
* @return string
*/
public function rules_no404wp( $mime_types ) {
if ( ! $this->c->get_boolean( 'browsercache.no404wp' ) ) {
return '';
}
$cssjs_types = $mime_types['cssjs'];
$html_types = $mime_types['html'];
$other_types = $mime_types['other'];
$extensions = array_merge( array_keys( $cssjs_types ),
array_keys( $html_types ), array_keys( $other_types ) );
$permalink_structure = get_option( 'permalink_structure' );
$permalink_structure_ext = ltrim( strrchr( $permalink_structure, '.' ),
'.' );
if ( $permalink_structure_ext != '' ) {
foreach ( $extensions as $index => $extension ) {
if ( strstr( $extension, $permalink_structure_ext ) !== false ) {
$extensions[$index] = preg_replace( '~\|?' .
Util_Environment::preg_quote( $permalink_structure_ext ) .
'\|?~', '', $extension );
}
}
}
$exceptions = $this->c->get_array( 'browsercache.no404wp.exceptions' );
$wp_uri = network_home_url( '', 'relative' );
$wp_uri = rtrim( $wp_uri, '/' );
$rules = '';
$rules .= "<IfModule mod_rewrite.c>\n";
$rules .= " RewriteEngine On\n";
// in subdir - rewrite theme files and similar to upper folder if file exists
if ( Util_Environment::is_wpmu() &&
!Util_Environment::is_wpmu_subdomain() ) {
$rules .= " RewriteCond %{REQUEST_FILENAME} !-f\n";
$rules .= " RewriteCond %{REQUEST_FILENAME} !-d\n";
$rules .= " RewriteCond %{REQUEST_URI} ^$wp_uri/([_0-9a-zA-Z-]+/)(.*\.)(" .
implode( '|', $extensions ) . ")$ [NC]\n";
$document_root = Util_Rule::apache_docroot_variable();
$rules .= ' RewriteCond "' . $document_root . $wp_uri .
'/%2%3" -f' . "\n";
$rules .= " RewriteRule .* $wp_uri/%2%3 [L]\n\n";
}
$rules .= " RewriteCond %{REQUEST_FILENAME} !-f\n";
$rules .= " RewriteCond %{REQUEST_FILENAME} !-d\n";
$imploded = implode( '|', $exceptions );
if ( !empty( $imploded ) )
$rules .= " RewriteCond %{REQUEST_URI} !(" . $imploded. ")\n";
$rules .= " RewriteCond %{REQUEST_URI} \\.(" .
implode( '|', $extensions ) . ")$ [NC]\n";
$rules .= " RewriteRule .* - [L]\n";
$rules .= "</IfModule>\n";
return $rules;
}
}

View File

@ -0,0 +1,230 @@
<?php
namespace W3TC;
/**
* Rules generation for OpenLiteSpeed
*/
class BrowserCache_Environment_LiteSpeed {
private $c;
public function __construct( $config ) {
$this->c = $config;
}
public function get_required_rules( $mime_types ) {
$rewrite_rules = array();
$rewrite_rules[] = array(
'filename' => Util_Rule::get_litespeed_rules_path(),
'content' => $this->generate( $mime_types )
);
if ( $this->c->get_boolean( 'browsercache.rewrite' ) ||
$this->c->get_boolean( 'browsercache.no404wp' ) ) {
$g = new BrowserCache_Environment_Apache( $this->c );
$rewrite_rules[] = array(
'filename' => Util_Rule::get_apache_rules_path(),
'content' =>
W3TC_MARKER_BEGIN_BROWSERCACHE_CACHE . "\n" .
$g->rules_rewrite() .
$g->rules_no404wp( $mime_types ) .
W3TC_MARKER_END_BROWSERCACHE_CACHE . "\n"
);
}
return $rewrite_rules;
}
/**
* Returns cache rules
*/
public function generate( $mime_types, $cdnftp = false ) {
$cssjs_types = $mime_types['cssjs'];
$cssjs_types = array_unique( $cssjs_types );
$html_types = $mime_types['html'];
$other_types = $mime_types['other'];
$other_compression_types = $mime_types['other_compression'];
$rules = '';
$rules .= W3TC_MARKER_BEGIN_BROWSERCACHE_CACHE . "\n";
$this->generate_section( $rules, $mime_types['cssjs'], 'cssjs' );
$this->generate_section( $rules, $mime_types['html'], 'html' );
$this->generate_section( $rules, $mime_types['other'], 'other' );
if ( $this->c->get_boolean( 'browsercache.rewrite' ) ) {
$core = Dispatcher::component( 'BrowserCache_Core' );
$extensions = $core->get_replace_extensions( $this->c );
$rules .= "<IfModule mod_rewrite.c>\n";
$rules .= " RewriteCond %{REQUEST_FILENAME} !-f\n";
$rules .= ' RewriteRule ^(.+)\.(x[0-9]{5})\.(' .
implode( '|', $extensions ) . ')$ $1.$3 [L]' . "\n";
$rules .= "</IfModule>\n";
}
$rules .= W3TC_MARKER_END_BROWSERCACHE_CACHE . "\n";
return $rules;
}
/**
* Adds cache rules for type to &$rules.
*
* @param string $rules Rules.
* @param array $mime_types MIME types.
* @param string $section Section.
* @return void
*/
private function generate_section( &$rules, $mime_types, $section ) {
$expires = $this->c->get_boolean( 'browsercache.' . $section . '.expires' );
$cache_control = $this->c->get_boolean( 'browsercache.' . $section . '.cache.control' );
$w3tc = $this->c->get_boolean( 'browsercache.' . $section . '.w3tc' );
$last_modified = $this->c->get_boolean( 'browsercache.' . $section . '.last_modified' );
if ( $expires || $cache_control || $w3tc || ! $last_modified ) {
$mime_types2 = apply_filters(
'w3tc_browsercache_rules_section_extensions',
$mime_types,
$this->c,
$section
);
$extensions = array_keys( $mime_types2 );
// Remove ext from filesmatch if its the same as permalink extension.
$pext = strtolower( pathinfo( get_option( 'permalink_structure' ), PATHINFO_EXTENSION ) );
if ( $pext ) {
$extensions = Util_Rule::remove_extension_from_list( $extensions, $pext );
}
$extensions_string = implode( '|', $extensions );
$section_rules = self::section_rules( $section );
$section_rules = apply_filters( 'w3tc_browsercache_rules_section',
$section_rules, $this->c, $section );
$context_rules = $section_rules['other'];
if ( !empty( $section_rules['add_header'] ) ) {
$context_rules[] = " extraHeaders <<<END_extraHeaders";
foreach ( $section_rules['add_header'] as $line ) {
$context_rules[] = ' ' . $line;
}
$context_rules[] = " END_extraHeaders";
}
if ( $section_rules['rewrite'] ) {
$context_rules[] = ' rewrite {';
$context_rules[] = ' RewriteFile .htaccess';
$context_rules[] = ' }';
}
$rules .= "context exp:^.*($extensions_string)\$ {\n";
$rules .= " location \$DOC_ROOT/\$0\n";
$rules .= " allowBrowse 1\n";
$rules .= implode( "\n", $context_rules ) . "\n";
$rules .= "}\n";
}
}
/**
* Returns directives plugin applies to files of specific section
* Without location
*/
public function section_rules( $section ) {
$rules = [];
$expires = $this->c->get_boolean( "browsercache.$section.expires" );
$lifetime = $this->c->get_integer( "browsercache.$section.lifetime" );
if ( $expires ) {
$rules[] = ' enableExpires 1';
$rules[] = " expiresDefault A$lifetime";
$rules[] = " ExpiresByType */*=A$lifetime";
} else {
$rules[] = ' enableExpires 0';
}
/*
if ( $this->c->get_boolean( "browsercache.$section.last_modified" ) )
lastmod support not implemented
*/
$add_header_rules = array();
if ( $this->c->get_boolean( "browsercache.$section.cache.control" ) ) {
$cache_policy = $this->c->get_string( "browsercache.$section.cache.policy" );
switch ( $cache_policy ) {
case 'cache':
$add_header_rules[] = 'unset Pragma';
$add_header_rules[] = 'set Pragma public';
$add_header_rules[] = 'set Cache-Control public';
break;
case 'cache_public_maxage':
$add_header_rules[] = 'unset Pragma';
$add_header_rules[] = 'set Pragma public';
break;
case 'cache_validation':
$add_header_rules[] = 'unset Pragma';
$add_header_rules[] = 'set Pragma public';
$add_header_rules[] = 'unset Cache-Control';
$add_header_rules[] = 'set Cache-Control "public, must-revalidate, proxy-revalidate"';
break;
case 'cache_noproxy':
$add_header_rules[] = 'unset Pragma';
$add_header_rules[] = 'set Pragma public';
$add_header_rules[] = 'unset Cache-Control';
$add_header_rules[] = 'set Cache-Control "private, must-revalidate"';
break;
case 'cache_maxage':
$add_header_rules[] = 'unset Pragma';
$add_header_rules[] = 'set Pragma "public"';
$add_header_rules[] = 'unset Cache-Control';
if ( $expires ) {
$add_header_rules[] = 'set Cache-Control "public, must-revalidate, proxy-revalidate"';
} else {
$add_header_rules[] = "set Cache-Control \"max-age=$lifetime, public, must-revalidate, proxy-revalidate\"";
}
break;
case 'no_cache':
$add_header_rules[] = 'unset Pragma';
$add_header_rules[] = 'set Pragma "no-cache";';
$add_header_rules[] = 'unset Cache-Control';
$add_header_rules[] = 'set Cache-Control "max-age=0, private, no-store, no-cache, must-revalidate"';
break;
}
}
// need htaccess for rewrites
$rewrite = $this->c->get_boolean( 'browsercache.rewrite' );
return array( 'add_header' => $add_header_rules, 'other' => $rules, 'rewrite' => $rewrite );
}
public function w3tc_cdn_rules_section( $section_rules ) {
$section_rules_bc = $this->section_rules( 'other' );
$section_rules['other'] = array_merge( $section_rules['other'], $section_rules_bc['other'] );
$section_rules['add_header'] = array_merge(
$section_rules['add_header'], $section_rules_bc['add_header'] );
return $section_rules;
}
}

View File

@ -0,0 +1,511 @@
<?php
namespace W3TC;
/**
* Rules generation for Nginx
*/
class BrowserCache_Environment_Nginx {
private $c;
public function __construct( $config ) {
$this->c = $config;
}
public function get_required_rules( $mime_types ) {
return array(
array(
'filename' => Util_Rule::get_nginx_rules_path(),
'content' => $this->generate( $mime_types ),
),
);
}
/**
* Returns cache rules
*/
public function generate( $mime_types, $cdnftp = false ) {
$cssjs_types = $mime_types['cssjs'];
$cssjs_types = array_unique( $cssjs_types );
$html_types = $mime_types['html'];
$other_types = $mime_types['other'];
$other_compression_types = $mime_types['other_compression'];
$rules = '';
$rules .= W3TC_MARKER_BEGIN_BROWSERCACHE_CACHE . "\n";
if ( $this->c->get_boolean( 'browsercache.rewrite' ) ) {
$core = Dispatcher::component( 'BrowserCache_Core' );
$extensions = $core->get_replace_extensions( $this->c );
$exts = implode( '|', $extensions );
$rules .= "set \$w3tcbc_rewrite_filename '';\n";
$rules .= "set \$w3tcbc_rewrite_uri '';\n";
$rules .= "if (\$uri ~ '^(?<w3tcbc_base>.+)\.(x[0-9]{5})" .
"(?<w3tcbc_ext>\.($exts))$') {\n";
$rules .= " set \$w3tcbc_rewrite_filename \$document_root\$w3tcbc_base\$w3tcbc_ext;\n";
$rules .= " set \$w3tcbc_rewrite_uri \$w3tcbc_base\$w3tcbc_ext;\n";
$rules .= "}\n";
if ( Util_Environment::is_wpmu() &&
!Util_Environment::is_wpmu_subdomain() ) {
// WPMU subdir extra rewrite
if ( defined( 'W3TC_HOME_URI' ) ) {
$home_uri = W3TC_HOME_URI;
} else {
$primary_blog_id = get_network()->site_id;
$home_uri = parse_url( get_home_url( $primary_blog_id ),
PHP_URL_PATH );
$home_uri = rtrim( $home_uri, '/' );
}
$rules .= "if (\$uri ~ '^$home_uri/[_0-9a-zA-Z-]+(?<w3tcbc_base>/wp-.+)\.(x[0-9]{5})(?<w3tcbc_ext>\.($exts))$') {\n";
$rules .= " set \$w3tcbc_rewrite_filename \$document_root$home_uri\$w3tcbc_base\$w3tcbc_ext;\n";
$rules .= " set \$w3tcbc_rewrite_uri $home_uri\$w3tcbc_base\$w3tcbc_ext;\n";
$rules .= "}\n";
}
$rules .= "if (-f \$w3tcbc_rewrite_filename) {\n";
$rules .= " rewrite .* \$w3tcbc_rewrite_uri;\n";
$rules .= "}\n";
}
$cssjs_brotli = $this->c->get_boolean( 'browsercache.cssjs.brotli' );
$html_brotli = $this->c->get_boolean( 'browsercache.html.brotli' );
$other_brotli = $this->c->get_boolean( 'browsercache.other.brotli' );
if ( $cssjs_brotli || $html_brotli || $other_brotli ) {
$brotli_types = array();
if ( $cssjs_brotli ) {
$brotli_types = array_merge( $brotli_types, $cssjs_types );
}
if ( $html_brotli ) {
$brotli_types = array_merge( $brotli_types, $html_types );
}
if ( $other_brotli ) {
$brotli_types = array_merge( $brotli_types,
$other_compression_types );
}
unset( $brotli_types['html|htm'] );
// some nginx cant handle values longer than 47 chars
unset( $brotli_types['odp'] );
$rules .= "brotli on;\n";
$rules .= 'brotli_types ' .
implode( ' ', array_unique( $brotli_types ) ) . ";\n";
}
$cssjs_compression = $this->c->get_boolean( 'browsercache.cssjs.compression' );
$html_compression = $this->c->get_boolean( 'browsercache.html.compression' );
$other_compression = $this->c->get_boolean( 'browsercache.other.compression' );
if ( $cssjs_compression || $html_compression || $other_compression ) {
$compression_types = array();
if ( $cssjs_compression ) {
$compression_types = array_merge( $compression_types, $cssjs_types );
}
if ( $html_compression ) {
$compression_types = array_merge( $compression_types, $html_types );
}
if ( $other_compression ) {
$compression_types = array_merge( $compression_types,
$other_compression_types );
}
unset( $compression_types['html|htm'] );
// some nginx cant handle values longer than 47 chars
unset( $compression_types['odp'] );
$rules .= "gzip on;\n";
$rules .= "gzip_types " .
implode( ' ', array_unique( $compression_types ) ) . ";\n";
}
if ( $this->c->get_boolean( 'browsercache.no404wp' ) ) {
$exceptions = $this->c->get_array( 'browsercache.no404wp.exceptions' );
$impoloded = implode( '|', $exceptions );
if ( !empty( $impoloded ) ) {
$wp_uri = network_home_url( '', 'relative' );
$wp_uri = rtrim( $wp_uri, '/' );
$rules .= "location ~ (" . $impoloded . ") {\n";
$rules .= ' try_files $uri $uri/ ' . $wp_uri .
'/index.php?$args;' . "\n";
$rules .= "}\n";
}
}
$this->generate_section( $rules, $mime_types['cssjs'], 'cssjs' );
$this->generate_section( $rules, $mime_types['html'], 'html' );
$this->generate_section( $rules, $mime_types['other'], 'other' );
$rules .= implode( "\n", $this->security_rules() ) . "\n";
$rules .= W3TC_MARKER_END_BROWSERCACHE_CACHE . "\n";
return $rules;
}
/**
* Returns security header directives
*/
private function security_rules() {
$rules = [];
if ( $this->c->get_boolean( 'browsercache.hsts' ) ||
$this->c->get_boolean( 'browsercache.security.xfo' ) ||
$this->c->get_boolean( 'browsercache.security.xss' ) ||
$this->c->get_boolean( 'browsercache.security.xcto' ) ||
$this->c->get_boolean( 'browsercache.security.pkp' ) ||
$this->c->get_boolean( 'browsercache.security.referrer.policy' ) ||
$this->c->get_boolean( 'browsercache.security.csp' ) ||
$this->c->get_boolean( 'browsercache.security.cspro' ) ||
$this->c->get_boolean( 'browsercache.security.fp' )
) {
$lifetime = $this->c->get_integer( 'browsercache.other.lifetime' );
if ( $this->c->get_boolean( 'browsercache.hsts' ) ) {
$dir = $this->c->get_string( 'browsercache.security.hsts.directive' );
$rules[] = "add_header Strict-Transport-Security \"max-age=$lifetime" . ( strpos( $dir,"inc" ) ? "; includeSubDomains" : "" ) . ( strpos( $dir, "pre" ) ? "; preload" : "" ) . "\";";
}
if ( $this->c->get_boolean( 'browsercache.security.xfo' ) ) {
$dir = $this->c->get_string( 'browsercache.security.xfo.directive' );
$url = trim( $this->c->get_string( 'browsercache.security.xfo.allow' ) );
if ( empty( $url ) ) {
$url = Util_Environment::home_url_maybe_https();
}
$rules[] = "add_header X-Frame-Options \"" . ( $dir == "same" ? "SAMEORIGIN" : ( $dir == "deny" ? "DENY" : "ALLOW-FROM $url" ) ) . "\";";
}
if ( $this->c->get_boolean( 'browsercache.security.xss' ) ) {
$dir = $this->c->get_string( 'browsercache.security.xss.directive' );
$rules[] = "add_header X-XSS-Protection \"" . ( $dir == "block" ? "1; mode=block" : $dir ) . "\";";
}
if ( $this->c->get_boolean( 'browsercache.security.xcto' ) ) {
$rules[] = "add_header X-Content-Type-Options \"nosniff\";";
}
if ( $this->c->get_boolean( 'browsercache.security.pkp' ) ) {
$pin = trim( $this->c->get_string( 'browsercache.security.pkp.pin' ) );
$pinbak = trim( $this->c->get_string( 'browsercache.security.pkp.pin.backup' ) );
$extra = $this->c->get_string( 'browsercache.security.pkp.extra' );
$url = trim( $this->c->get_string( 'browsercache.security.pkp.report.url' ) );
$rep_only = $this->c->get_string( 'browsercache.security.pkp.report.only' ) == '1' ? true : false;
$rules[] = "add_header " . ( $rep_only ? "Public-Key-Pins-Report-Only" : "Public-Key-Pins" ) . " 'pin-sha256=\"$pin\"; pin-sha256=\"$pinbak\"; max-age=$lifetime" . ( strpos( $extra,"inc" ) ? "; includeSubDomains" : "" ) . ( !empty( $url ) ? "; report-uri=\"$url\"" : "" ) . "';";
}
if ( $this->c->get_boolean( 'browsercache.security.referrer.policy' ) ) {
$dir = $this->c->get_string( 'browsercache.security.referrer.policy.directive' );
$rules[] = "add_header Referrer-Policy \"" . ( $dir == "0" ? "" : $dir ) . "\";";
}
if ( $this->c->get_boolean( 'browsercache.security.csp' ) ) {
$base = trim( $this->c->get_string( 'browsercache.security.csp.base' ) );
$frame = trim( $this->c->get_string( 'browsercache.security.csp.frame' ) );
$connect = trim( $this->c->get_string( 'browsercache.security.csp.connect' ) );
$font = trim( $this->c->get_string( 'browsercache.security.csp.font' ) );
$script = trim( $this->c->get_string( 'browsercache.security.csp.script' ) );
$style = trim( $this->c->get_string( 'browsercache.security.csp.style' ) );
$img = trim( $this->c->get_string( 'browsercache.security.csp.img' ) );
$media = trim( $this->c->get_string( 'browsercache.security.csp.media' ) );
$object = trim( $this->c->get_string( 'browsercache.security.csp.object' ) );
$plugin = trim( $this->c->get_string( 'browsercache.security.csp.plugin' ) );
$form = trim( $this->c->get_string( 'browsercache.security.csp.form' ) );
$frame_ancestors = trim( $this->c->get_string( 'browsercache.security.csp.frame.ancestors' ) );
$sandbox = trim( $this->c->get_string( 'browsercache.security.csp.sandbox' ) );
$child = trim( $this->c->get_string( 'browsercache.security.csp.child' ) );
$manifest = trim( $this->c->get_string( 'browsercache.security.csp.manifest' ) );
$scriptelem = trim( $this->c->get_string( 'browsercache.security.csp.scriptelem' ) );
$scriptattr = trim( $this->c->get_string( 'browsercache.security.csp.scriptattr' ) );
$styleelem = trim( $this->c->get_string( 'browsercache.security.csp.styleelem' ) );
$scriptelem = trim( $this->c->get_string( 'browsercache.security.csp.styleattr' ) );
$worker = trim( $this->c->get_string( 'browsercache.security.csp.worker' ) );
$default = trim( $this->c->get_string( 'browsercache.security.csp.default' ) );
$dir = rtrim(
( ! empty( $base ) ? "base-uri $base; " : '' ) .
( ! empty( $frame ) ? "frame-src $frame; " : '' ) .
( ! empty( $connect ) ? "connect-src $connect; " : '' ) .
( ! empty( $font ) ? "font-src $font; " : '' ) .
( ! empty( $script ) ? "script-src $script; " : '' ) .
( ! empty( $style ) ? "style-src $style; " : '' ) .
( ! empty( $img ) ? "img-src $img; " : '' ) .
( ! empty( $media ) ? "media-src $media; " : '' ) .
( ! empty( $object ) ? "object-src $object; " : '' ) .
( ! empty( $plugin ) ? "plugin-types $plugin; " : '' ) .
( ! empty( $form ) ? "form-action $form; " : '' ) .
( ! empty( $frame_ancestors ) ? "frame-ancestors $frame_ancestors; " : '' ) .
( ! empty( $sandbox ) ? "sandbox $sandbox; " : '' ) .
( ! empty( $child ) ? "child-src $child; " : '' ) .
( ! empty( $manifest ) ? "manifest-src $manifest; " : '' ) .
( ! empty( $scriptelem ) ? "script-src-elem $scriptelem; " : '' ) .
( ! empty( $scriptattr ) ? "script-src-attr $scriptattr; " : '' ) .
( ! empty( $styleelem ) ? "style-src-elem $styleelem; " : '' ) .
( ! empty( $styleattr ) ? "style-src-attr $styleattr; " : '' ) .
( ! empty( $worker ) ? "worker-src $worker; " : '' ) .
( ! empty( $default ) ? "default-src $default;" : '' ),
'; '
);
if ( ! empty( $dir ) ) {
$rules[] = "add_header Content-Security-Policy \"$dir\";";
}
}
if ( $this->c->get_boolean( 'browsercache.security.cspro' ) && ( ! empty( $this->c->get_string( 'browsercache.security.cspro.reporturi' ) ) || ! empty( $this->c->get_string( 'browsercache.security.cspro.reportto' ) ) ) ) {
$base = trim( $this->c->get_string( 'browsercache.security.cspro.base' ) );
$reporturi = trim( $this->c->get_string( 'browsercache.security.cspro.reporturi' ) );
$reportto = trim( $this->c->get_string( 'browsercache.security.cspro.reportto' ) );
$frame = trim( $this->c->get_string( 'browsercache.security.cspro.frame' ) );
$connect = trim( $this->c->get_string( 'browsercache.security.cspro.connect' ) );
$font = trim( $this->c->get_string( 'browsercache.security.cspro.font' ) );
$script = trim( $this->c->get_string( 'browsercache.security.cspro.script' ) );
$style = trim( $this->c->get_string( 'browsercache.security.cspro.style' ) );
$img = trim( $this->c->get_string( 'browsercache.security.cspro.img' ) );
$media = trim( $this->c->get_string( 'browsercache.security.cspro.media' ) );
$object = trim( $this->c->get_string( 'browsercache.security.cspro.object' ) );
$plugin = trim( $this->c->get_string( 'browsercache.security.cspro.plugin' ) );
$form = trim( $this->c->get_string( 'browsercache.security.cspro.form' ) );
$frame_ancestors = trim( $this->c->get_string( 'browsercache.security.cspro.frame.ancestors' ) );
$sandbox = trim( $this->c->get_string( 'browsercache.security.cspro.sandbox' ) );
$child = trim( $this->c->get_string( 'browsercache.security.csp.child' ) );
$manifest = trim( $this->c->get_string( 'browsercache.security.csp.manifest' ) );
$scriptelem = trim( $this->c->get_string( 'browsercache.security.csp.scriptelem' ) );
$scriptattr = trim( $this->c->get_string( 'browsercache.security.csp.scriptattr' ) );
$styleelem = trim( $this->c->get_string( 'browsercache.security.csp.styleelem' ) );
$scriptelem = trim( $this->c->get_string( 'browsercache.security.csp.styleattr' ) );
$worker = trim( $this->c->get_string( 'browsercache.security.csp.worker' ) );
$default = trim( $this->c->get_string( 'browsercache.security.cspro.default' ) );
$dir = rtrim(
( ! empty( $base ) ? "base-uri $base; " : '' ) .
( ! empty( $reporturi ) ? "report-uri $reporturi; " : '' ) .
( ! empty( $reportto ) ? "report-to $reportto; " : '' ) .
( ! empty( $frame ) ? "frame-src $frame; " : '' ) .
( ! empty( $connect ) ? "connect-src $connect; " : '' ) .
( ! empty( $font ) ? "font-src $font; " : '' ) .
( ! empty( $script ) ? "script-src $script; " : '' ) .
( ! empty( $style ) ? "style-src $style; " : '' ) .
( ! empty( $img ) ? "img-src $img; " : '' ) .
( ! empty( $media ) ? "media-src $media; " : '' ) .
( ! empty( $object ) ? "object-src $object; " : '' ) .
( ! empty( $plugin ) ? "plugin-types $plugin; " : '' ) .
( ! empty( $form ) ? "form-action $form; " : '' ) .
( ! empty( $frame_ancestors ) ? "frame-ancestors $frame_ancestors; " : '' ) .
( ! empty( $sandbox ) ? "sandbox $sandbox; " : '' ) .
( ! empty( $child ) ? "child-src $child; " : '' ) .
( ! empty( $manifest ) ? "manifest-src $manifest; " : '' ) .
( ! empty( $scriptelem ) ? "script-src-elem $scriptelem; " : '' ) .
( ! empty( $scriptattr ) ? "script-src-attr $scriptattr; " : '' ) .
( ! empty( $styleelem ) ? "style-src-elem $styleelem; " : '' ) .
( ! empty( $styleattr ) ? "style-src-attr $styleattr; " : '' ) .
( ! empty( $worker ) ? "worker-src $worker; " : '' ) .
( ! empty( $default ) ? "default-src $default;" : '' ),
'; '
);
if ( ! empty( $dir ) ) {
$rules[] = "add_header Content-Security-Policy-Report-Only \"$dir\";";
}
}
if ( $this->c->get_boolean( 'browsercache.security.fp' ) ) {
$fp_values = $this->c->get_array( 'browsercache.security.fp.values' );
$feature_v = array();
$permission_v = array();
foreach ( $fp_values as $key => $value ) {
if ( ! empty( $value ) ) {
$value = str_replace( array( '"', "'" ), '', $value );
$feature_v[] = "$key '$value'";
$permission_v[] = "$key=($value)";
}
}
if ( ! empty( $feature_v ) ) {
$rules[] = 'add_header Feature-Policy "' . implode( ';', $feature_v ) . "\"\n";
}
if ( ! empty( $permission_v ) ) {
$rules[] = 'add_header Permissions-Policy "' . implode( ',', $permission_v ) . "\"\n";
}
}
}
return $rules;
}
/**
* Adds cache rules for type to &$rules.
*
* @param string $rules Rules.
* @param array $mime_types MIME types.
* @param string $section Section.
* @return void
*/
private function generate_section( &$rules, $mime_types, $section ) {
$expires = $this->c->get_boolean( 'browsercache.' . $section . '.expires' );
$etag = $this->c->get_boolean( 'browsercache.' . $section . '.etag' );
$cache_control = $this->c->get_boolean( 'browsercache.' . $section . '.cache.control' );
$w3tc = $this->c->get_boolean( 'browsercache.' . $section . '.w3tc' );
$last_modified = $this->c->get_boolean( 'browsercache.' . $section . '.last_modified' );
if ( $etag || $expires || $cache_control || $w3tc || ! $last_modified ) {
$mime_types2 = apply_filters(
'w3tc_browsercache_rules_section_extensions',
$mime_types,
$this->c,
$section
);
$extensions = array_keys( $mime_types2 );
// Remove ext from filesmatch if its the same as permalink extension.
$pext = strtolower( pathinfo( get_option( 'permalink_structure' ), PATHINFO_EXTENSION ) );
if ( $pext ) {
$extensions = Util_Rule::remove_extension_from_list( $extensions, $pext );
}
$rules .= 'location ~ \\.(' . implode( '|', $extensions ) . ')$ {' . "\n";
$subrules = Dispatcher::nginx_rules_for_browsercache_section( $this->c, $section );
$rules .= ' ' . implode( "\n ", $subrules ) . "\n";
// Add rules for the Image Service extension, if active.
if ( 'other' === $section && array_key_exists( 'imageservice', $this->c->get_array( 'extensions.active' ) ) ) {
$rules .= "\n" . ' location ~* ^(?<path>.+)\.(jpe?g|png|gif)$ {' . "\n" .
' if ( $http_accept !~* "webp|\*/\*" ) {' . "\n" .
' break;' . "\n" .
' }' . "\n\n" .
' ' . implode( "\n ", Dispatcher::nginx_rules_for_browsercache_section( $this->c, $section, true ) ) . "\n" .
' add_header Vary Accept;' . "\n";
if ( $this->c->get_boolean( 'browsercache.no404wp' ) ) {
$rules .= ' try_files ${path}.webp $uri =404;';
} else {
$rules .= ' try_files ${path}.webp $uri /index.php?$args;';
}
$rules .= "\n" . ' }' . "\n\n";
}
if ( ! $this->c->get_boolean( 'browsercache.no404wp' ) ) {
$wp_uri = network_home_url( '', 'relative' );
$wp_uri = rtrim( $wp_uri, '/' );
$rules .= ' try_files $uri $uri/ ' . $wp_uri . '/index.php?$args;' . "\n";
}
$rules .= '}' . "\n";
}
}
/**
* Returns directives plugin applies to files of specific section
* Without location
*
* $extra_add_headers_set specifies if other add_header directives will
* be added to location block generated
*/
public function section_rules( $section, $extra_add_headers_set = false ) {
$rules = array();
$expires = $this->c->get_boolean( "browsercache.$section.expires" );
$lifetime = $this->c->get_integer( "browsercache.$section.lifetime" );
if ( $expires ) {
$rules[] = 'expires ' . $lifetime . 's;';
}
if ( version_compare( Util_Environment::get_server_version(), '1.3.3', '>=' ) ) {
if ( $this->c->get_boolean( "browsercache.$section.etag" ) ) {
$rules[] = 'etag on;';
} else {
$rules[] = 'etag off;';
}
}
if ( $this->c->get_boolean( "browsercache.$section.last_modified" ) ) {
$rules[] = 'if_modified_since exact;';
} else {
$rules[] = 'if_modified_since off;';
}
$add_header_rules = array();
if ( $this->c->get_boolean( "browsercache.$section.cache.control" ) ) {
$cache_policy = $this->c->get_string( "browsercache.$section.cache.policy" );
switch ( $cache_policy ) {
case 'cache':
$add_header_rules[] = 'add_header Pragma "public";';
$add_header_rules[] = 'add_header Cache-Control "public";';
break;
case 'cache_public_maxage':
$add_header_rules[] = 'add_header Pragma "public";';
if ( $expires ) {
$add_header_rules[] = 'add_header Cache-Control "public";';
} else {
$add_header_rules[] = "add_header Cache-Control \"max-age=$lifetime, public\";";
}
break;
case 'cache_validation':
$add_header_rules[] = 'add_header Pragma "public";';
$add_header_rules[] = 'add_header Cache-Control "public, must-revalidate, proxy-revalidate";';
break;
case 'cache_noproxy':
$add_header_rules[] = 'add_header Pragma "public";';
$add_header_rules[] = 'add_header Cache-Control "private, must-revalidate";';
break;
case 'cache_maxage':
$add_header_rules[] = 'add_header Pragma "public";';
if ( $expires ) {
$add_header_rules[] = 'add_header Cache-Control "public, must-revalidate, proxy-revalidate";';
} else {
$add_header_rules[] = "add_header Cache-Control \"max-age=$lifetime, public, must-revalidate, proxy-revalidate\";";
}
break;
case 'no_cache':
$add_header_rules[] = 'add_header Pragma "no-cache";';
$add_header_rules[] = 'add_header Cache-Control "max-age=0, private, no-store, no-cache, must-revalidate";';
break;
}
}
if ( $this->c->get_boolean( "browsercache.$section.w3tc" ) ) {
$add_header_rules[] = 'add_header X-Powered-By "' .
Util_Environment::w3tc_header() . '";';
}
if ( !empty( $add_header_rules ) || $extra_add_headers_set ) {
$add_header_rules = array_merge( $add_header_rules,
$this->security_rules() );
}
return array( 'add_header' => $add_header_rules, 'other' => $rules );
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace W3TC;
class BrowserCache_Page extends Base_Page_Settings {
protected $_page = 'w3tc_browsercache';
public static function w3tc_ajax() {
add_action( 'w3tc_ajax_browsercache_quick_reference', array(
'\W3TC\BrowserCache_Page',
'w3tc_ajax_browsercache_quick_reference' ) );
}
public static function w3tc_ajax_browsercache_quick_reference() {
include W3TC_DIR . '/BrowserCache_Page_View_QuickReference.php';
exit();
}
function view() {
$browsercache_enabled = $this->_config->get_boolean( 'browsercache.enabled' );
$browsercache_last_modified = ( $this->_config->get_boolean( 'browsercache.cssjs.last_modified' ) && $this->_config->get_boolean( 'browsercache.html.last_modified' ) && $this->_config->get_boolean( 'browsercache.other.last_modified' ) );
$browsercache_expires = ( $this->_config->get_boolean( 'browsercache.cssjs.expires' ) && $this->_config->get_boolean( 'browsercache.html.expires' ) && $this->_config->get_boolean( 'browsercache.other.expires' ) );
$browsercache_cache_control = ( $this->_config->get_boolean( 'browsercache.cssjs.cache.control' ) && $this->_config->get_boolean( 'browsercache.html.cache.control' ) && $this->_config->get_boolean( 'browsercache.other.cache.control' ) );
$browsercache_etag = ( $this->_config->get_boolean( 'browsercache.cssjs.etag' ) && $this->_config->get_boolean( 'browsercache.html.etag' ) && $this->_config->get_boolean( 'browsercache.other.etag' ) );
$browsercache_w3tc = ( $this->_config->get_boolean( 'browsercache.cssjs.w3tc' ) && $this->_config->get_boolean( 'browsercache.html.w3tc' ) && $this->_config->get_boolean( 'browsercache.other.w3tc' ) );
$browsercache_compression = ( $this->_config->get_boolean( 'browsercache.cssjs.compression' ) && $this->_config->get_boolean( 'browsercache.html.compression' ) && $this->_config->get_boolean( 'browsercache.other.compression' ) );
$browsercache_brotli = ( $this->_config->get_boolean( 'browsercache.cssjs.brotli' ) && $this->_config->get_boolean( 'browsercache.html.brotli' ) && $this->_config->get_boolean( 'browsercache.other.brotli' ) );
$browsercache_replace = ( $this->_config->get_boolean( 'browsercache.cssjs.replace' ) && $this->_config->get_boolean( 'browsercache.other.replace' ) );
$browsercache_querystring = ( $this->_config->get_boolean( 'browsercache.cssjs.querystring' ) && $this->_config->get_boolean( 'browsercache.other.querystring' ) );
$browsercache_update_media_qs = ( $this->_config->get_boolean( 'browsercache.cssjs.replace' ) || $this->_config->get_boolean( 'browsercache.other.replace' ) );
$browsercache_nocookies =
( $this->_config->get_boolean( 'browsercache.cssjs.nocookies' ) &&
$this->_config->get_boolean( 'browsercache.other.nocookies' ) );
$is_nginx = Util_Environment::is_nginx();
include W3TC_INC_DIR . '/options/browsercache.php';
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace W3TC;
if ( ! defined( 'W3TC' ) ) {
die();
}
?>
<div class="lightbox-content-padded">
<h3><?php esc_html_e( 'Security Headers: Quick Reference', 'w3-total-cache' ); ?></h3>
<fieldset>
<legend><?php esc_html_e( 'Legend', 'w3-total-cache' ); ?></legend>
<p>
All of the directives that end with -src support similar values known as
a source list. Multiple source list values can be space separated with the exception of
'none' which should be the only value.
</p>
</fieldset>
<table class="w3tcbc_qrf">
<tr>
<th>Source Value</th>
<th>Example</th>
<th>Description</th>
</tr>
<tr>
<td><code>*</code></td>
<td><code>img-src *</code></td>
<td>Wildcard, allows any URL except data: blob: filesystem: schemes</td>
</tr>
<tr>
<td><code>'none'</code></td>
<td><code>object-src 'none'</code></td>
<td>Prevents loading resources from any source</td>
</tr>
<tr>
<td><code>'self'</code></td>
<td><code>script-src 'self'</code></td>
<td>Allows loading resources from the same origin (same scheme, host and port)</td>
</tr>
<tr>
<td><code>data:</code></td>
<td><code>img-src 'self' data:</code></td>
<td>Allows loading resources via the data scheme (e.g. Base64 encoded images)</td>
</tr>
<tr>
<td><code>domain.example.com</code></td>
<td><code>img-src domain.example.com</code></td>
<td>Allows loading resources from the specified domain name</td>
</tr>
<tr>
<td><code>*.example.com</code></td>
<td><code>img-src *.example.com</code></td>
<td>Allows loading resources from any subdomain under example.com</td>
</tr>
<tr>
<td><code>https://cdn.com</code></td>
<td><code>img-src https://cdn.com</code></td>
<td>Allows loading resources only over <acronym title="HyperText Transfer Protocol over SSL">HTTPS</acronym> matching the given domain</td>
</tr>
<tr>
<td><code>https:</code></td>
<td><code>img-src https:</code></td>
<td>Allows loading resources only over <acronym title="HyperText Transfer Protocol over SSL">HTTPS</acronym> on any domain</td>
</tr>
<tr>
<td><code>'unsafe-inline'</code></td>
<td><code>script-src 'unsafe-inline'</code></td>
<td>Allows use of inline source elements such as style attribute, onclick, or script tag bodies (depends on the context of the source it is applied to)</td>
</tr>
<tr>
<td><code>'unsafe-eval'</code></td>
<td><code>script-src 'unsafe-eval'</code></td>
<td>Allows unsafe dynamic code evaluation such as Javascript eval()</td>
</tr>
</table>
</div>

View File

@ -0,0 +1,494 @@
<?php
namespace W3TC;
/**
* W3 ObjectCache plugin
*/
class BrowserCache_Plugin {
private $_config = null;
private $browsercache_rewrite;
function __construct() {
$this->_config = Dispatcher::config();
}
/**
* Runs plugin
*/
function run() {
add_filter( 'w3tc_admin_bar_menu',
array( $this, 'w3tc_admin_bar_menu' ) );
if ( $this->_config->get_boolean( 'browsercache.html.w3tc' ) ) {
add_action( 'send_headers',
array( $this, 'send_headers' ) );
}
if ( !$this->_config->get_boolean( 'browsercache.html.etag' ) ) {
add_filter( 'wp_headers',
array( $this, 'filter_wp_headers' ),
0, 2 );
}
$url_uniqualize_enabled = $this->url_uniqualize_enabled();
if ( $this->url_clean_enabled() || $url_uniqualize_enabled ) {
$this->browsercache_rewrite =
$this->_config->get_boolean( 'browsercache.rewrite' );
// modify CDN urls
add_filter( 'w3tc_cdn_url',
array( $this, 'w3tc_cdn_url' ), 0, 3 );
if ( $url_uniqualize_enabled ) {
add_action( 'w3tc_flush_all',
array( $this, 'w3tc_flush_all' ), 1050, 1 );
}
if ( $this->can_ob() ) {
Util_Bus::add_ob_callback( 'browsercache',
array( $this, 'ob_callback' ) );
}
}
$v = $this->_config->get_string( 'browsercache.security.session.cookie_httponly' );
if ( !empty( $v ) ) {
@ini_set( 'session.cookie_httponly', $v == 'on' ? '1': '0' );
}
$v = $this->_config->get_string( 'browsercache.security.session.cookie_secure' );
if ( !empty( $v ) ) {
@ini_set( 'session.cookie_secure', $v == 'on' ? '1': '0' );
}
$v = $this->_config->get_string( 'browsercache.security.session.use_only_cookies' );
if ( !empty( $v ) ) {
@ini_set( 'session.use_only_cookies', $v == 'on' ? '1': '0' );
}
add_filter( 'w3tc_minify_http2_preload_url',
array( $this, 'w3tc_minify_http2_preload_url' ), 4000 );
add_filter( 'w3tc_cdn_config_headers',
array( $this, 'w3tc_cdn_config_headers' ) );
if ( Util_Admin::is_w3tc_admin_page() ) {
add_action( 'admin_notices', array( $this, 'admin_notices' ) );
}
}
private function url_clean_enabled() {
return
$this->_config->get_boolean( 'browsercache.cssjs.querystring' ) ||
$this->_config->get_boolean( 'browsercache.html.querystring' ) ||
$this->_config->get_boolean( 'browsercache.other.querystring' );
}
private function url_uniqualize_enabled() {
return $this->_config->get_boolean( 'browsercache.cssjs.replace' ) ||
$this->_config->get_boolean( 'browsercache.html.replace' ) ||
$this->_config->get_boolean( 'browsercache.other.replace' );
}
public function w3tc_flush_all( $extras = array() ) {
if ( isset( $extras['only'] ) && $extras['only'] != 'browsercache' )
return;
update_option( 'w3tc_browsercache_flush_timestamp',
rand( 10000, 99999 ) . '' );
}
/**
* Check if we can start OB
*
* @return boolean
*/
function can_ob() {
/**
* Skip if admin
*/
if ( defined( 'WP_ADMIN' ) ) {
return false;
}
/**
* Skip if doing AJAX
*/
if ( defined( 'DOING_AJAX' ) ) {
return false;
}
/**
* Skip if doing cron
*/
if ( defined( 'DOING_CRON' ) ) {
return false;
}
/**
* Skip if APP request
*/
if ( defined( 'APP_REQUEST' ) ) {
return false;
}
/**
* Skip if XMLRPC request
*/
if ( defined( 'XMLRPC_REQUEST' ) ) {
return false;
}
/**
* Check for WPMU's and WP's 3.0 short init
*/
if ( defined( 'SHORTINIT' ) && SHORTINIT ) {
return false;
}
/**
* Check User Agent
*/
$http_user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
if ( stristr( $http_user_agent, W3TC_POWERED_BY ) !== false ) {
return false;
}
return true;
}
/**
* Output buffer callback
*
* @param string $buffer
* @return mixed
*/
function ob_callback( $buffer ) {
if ( $buffer != '' && Util_Content::is_html_xml( $buffer ) ) {
$domain_url_regexp = Util_Environment::home_domain_root_url_regexp();
$buffer = preg_replace_callback(
'~(href|src|action|extsrc|asyncsrc|w3tc_load_js\()=?([\'"])((' .
$domain_url_regexp .
')?(/[^\'"/][^\'"]*\.([a-z-_]+)([\?#][^\'"]*)?))[\'"]~Ui', array(
$this,
'link_replace_callback'
), $buffer );
// without quotes
$buffer = preg_replace_callback(
'~(href|src|action|extsrc|asyncsrc)=((' .
$domain_url_regexp .
')?(/[^\\s>][^\\s>]*\.([a-z-_]+)([\?#][^\\s>]*)?))([\\s>])~Ui', array(
$this,
'link_replace_callback_noquote'
), $buffer );
}
return $buffer;
}
/**
* Link replace callback
*
* @param string $matches
* @return string
*/
function link_replace_callback( $matches ) {
list ( $match, $attr, $quote, $url, , , , , $extension ) = $matches;
$ops = $this->_get_url_mutation_operations( $url, $extension );
if ( is_null( $ops ) )
return $match;
$url = $this->mutate_url( $url, $ops, !$this->browsercache_rewrite );
if ( $attr != 'w3tc_load_js(' )
return $attr . '=' . $quote . $url . $quote;
return sprintf( '%s\'%s\'', $attr, $url );
}
/**
* Link replace callback when no quote arount attribute value
*
* @param string $matches
* @return string
*/
function link_replace_callback_noquote( $matches ) {
list ( $match, $attr, $url, , , , , $extension, , $delimiter ) = $matches;
$ops = $this->_get_url_mutation_operations( $url, $extension );
if ( is_null( $ops ) )
return $match;
$url = $this->mutate_url( $url, $ops, !$this->browsercache_rewrite );
return $attr . '=' . $url . $delimiter;
}
/**
* Mutate http/2 header links
*/
public function w3tc_minify_http2_preload_url( $data ) {
if ( isset( $data['browsercache_processed'] ) ) {
return $data;
}
$data['browsercache_processed'] = '*';
$url = $data['result_link'];
// decouple extension
$matches = array();
if ( !preg_match( '/\.([a-zA-Z0-9]+)($|[\?])/', $url, $matches ) ) {
return $data;
}
$extension = $matches[1];
$ops = $this->_get_url_mutation_operations( $url, $extension );
if ( is_null( $ops ) ) {
return $data;
}
$mutate_by_querystring = !$this->browsercache_rewrite;
$url = $this->mutate_url( $url, $ops, $mutate_by_querystring );
$data['result_link'] = $url;
return $data;
}
/**
* Link replace for CDN url
*
* @param string $matches
* @return string
*/
function w3tc_cdn_url( $url, $original_url, $is_cdn_mirror ) {
// decouple extension
$matches = array();
if ( !preg_match( '/\.([a-zA-Z0-9]+)($|[\?])/', $original_url, $matches ) )
return $url;
$extension = $matches[1];
$ops = $this->_get_url_mutation_operations( $original_url, $extension );
if ( is_null( $ops ) )
return $url;
// for push cdns each flush would require manual reupload of files
$mutate_by_querystring = !$this->browsercache_rewrite || !$is_cdn_mirror;
$url = $this->mutate_url( $url, $ops, $mutate_by_querystring );
return $url;
}
private function mutate_url( $url, $ops, $mutate_by_querystring ) {
$query_pos = strpos( $url, '?' );
if ( isset( $ops['querystring'] ) && $query_pos !== false ) {
$url = substr( $url, 0, $query_pos );
$query_pos = false;
}
if ( isset( $ops['replace'] ) ) {
$id = $this->get_filename_uniqualizator();
if ( $mutate_by_querystring ) {
if ( $query_pos !== false ) {
$url = substr( $url, 0, $query_pos + 1 ) . $id . '&amp;' .
substr( $url, $query_pos + 1 );
} else {
$tag_pos = strpos( $url, '#' );
if ( $tag_pos === false ) {
$url .= '?' . $id;
} else {
$url = substr( $url, 0, $tag_pos ) . '?' . $id .
substr( $url, $tag_pos );
}
}
} else {
// add $id to url before extension
$url_query = '';
if ( $query_pos !== false ) {
$url_query = substr( $url, $query_pos );
$url = substr( $url, 0, $query_pos );
}
$ext_pos = strrpos( $url, '.' );
$extension = substr( $url, $ext_pos );
$url = substr( $url, 0, strlen( $url ) - strlen( $extension ) ) .
'.' . $id . $extension . $url_query;
}
}
return $url;
}
function _get_url_mutation_operations( $url, $extension ) {
static $extensions = null;
if ( $extensions === null ) {
$core = Dispatcher::component( 'BrowserCache_Core' );
$extensions = $core->get_replace_querystring_extensions( $this->_config );
}
static $exceptions = null;
if ( $exceptions === null )
$exceptions = $this->_config->get_array( 'browsercache.replace.exceptions' );
if ( !isset( $extensions[$extension] ) )
return null;
$test_url = Util_Environment::remove_query( $url );
foreach ( $exceptions as $exception ) {
$escaped = str_replace( '~', '\~', $exception );
if ( trim( $exception ) && preg_match( '~' . $escaped . '~', $test_url ) )
return null;
}
return $extensions[$extension];
}
/**
* Returns replace ID
*
* @return string
*/
function get_filename_uniqualizator() {
static $cache_id = null;
if ( $cache_id === null ) {
$value = get_option( 'w3tc_browsercache_flush_timestamp' );
if ( empty( $value ) ) {
$value = rand( 10000, 99999 ) . '';
update_option( 'w3tc_browsercache_flush_timestamp', $value );
}
$cache_id = substr( $value, 0, 5 );
}
return 'x' . $cache_id;
}
public function w3tc_admin_bar_menu( $menu_items ) {
$browsercache_update_media_qs =
( $this->_config->get_boolean( 'browsercache.cssjs.replace' ) ||
$this->_config->get_boolean( 'browsercache.other.replace' ) );
if ( $browsercache_update_media_qs ) {
$menu_items['20190.browsercache'] = array(
'id' => 'w3tc_flush_browsercache',
'parent' => 'w3tc_flush',
'title' => __( 'Browser Cache', 'w3-total-cache' ),
'href' => wp_nonce_url( admin_url(
'admin.php?page=w3tc_dashboard&amp;w3tc_flush_browser_cache' ),
'w3tc' )
);
}
return $menu_items;
}
/**
* Send headers
*/
function send_headers() {
@header( 'X-Powered-By: ' . Util_Environment::w3tc_header() );
}
/**
* Returns headers config for CDN
*/
function w3tc_cdn_config_headers( $config ) {
$sections = Util_Mime::sections_to_mime_types_map();
foreach ( $sections as $section => $v ) {
$config[$section] = $this->w3tc_cdn_config_headers_section( $section );
}
return $config;
}
private function w3tc_cdn_config_headers_section( $section ) {
$c = $this->_config;
$prefix = 'browsercache.' . $section;
$lifetime = $c->get_integer( $prefix . '.lifetime' );
$headers = array();
if ( $c->get_boolean( $prefix . '.w3tc' ) ) {
$headers['X-Powered-By'] = Util_Environment::w3tc_header();
}
if ( $c->get_boolean( $prefix . '.cache.control' ) ) {
switch ( $c->get_string( $prefix . '.cache.policy' ) ) {
case 'cache':
$headers['Pragma'] = 'public';
$headers['Cache-Control'] = 'public';
break;
case 'cache_public_maxage':
$headers['Pragma'] = 'public';
$headers['Cache-Control'] = "max-age=$lifetime, public";
break;
case 'cache_validation':
$headers['Pragma'] = 'public';
$headers['Cache-Control'] = 'public, must-revalidate, proxy-revalidate';
break;
case 'cache_noproxy':
$headers['Pragma'] = 'public';
$headers['Cache-Control'] = 'private, must-revalidate';
break;
case 'cache_maxage':
$headers['Pragma'] = 'public';
$headers['Cache-Control'] = "max-age=$lifetime, public, must-revalidate, proxy-revalidate";
break;
case 'no_cache':
$headers['Pragma'] = 'no-cache';
$headers['Cache-Control'] = 'max-age=0, private, no-store, no-cache, must-revalidate';
break;
}
}
return array(
'etag' => $c->get_boolean( $prefix . 'etag' ),
'expires' => $c->get_boolean( $prefix . '.expires' ),
'lifetime' => $lifetime,
'static' => $headers
);
}
/**
* Filters headers set by WordPress
*
* @param unknown $headers
* @param unknown $wp
* @return
*/
function filter_wp_headers( $headers, $wp ) {
if ( !empty( $wp->query_vars['feed'] ) )
unset( $headers['ETag'] );
return $headers;
}
/**
* Admin notice for Content-Security-Policy-Report-Only that displays if the feature is enabled and the report-uri/to isn't defined.
*
* @since 2.2.13
*/
public function admin_notices() {
if ( $this->_config->get_boolean( 'browsercache.security.cspro' ) && empty( $this->_config->get_string( 'browsercache.security.cspro.reporturi' ) ) && empty( $this->_config->get_string( 'browsercache.security.cspro.reportto' ) ) ) {
$message = '<p>' . sprintf(
// translators: 1 opening HTML a tag to Browser Cache CSP-Report-Only settings, 2 closing HTML a tag.
esc_html__(
'The Content Security Policy - Report Only requires the "report-uri" and/or "report-to" directives. Please define one or both of these directives %1$shere%2$s.',
'w3-total-cache'
),
'<a href="' . Util_Ui::admin_url( 'admin.php?page=w3tc_browsercache#browsercache__security__cspro' ) . '" target="_blank" alt="' . esc_attr__( 'Browser Cache Content-Security-Policy-Report-Only Settings', 'w3-total-cache' ) . '">',
'</a>'
);
Util_Ui::error_box( $message );
}
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace W3TC;
class BrowserCache_Plugin_Admin {
function run() {
$config_labels = new BrowserCache_ConfigLabels();
add_filter( 'w3tc_config_labels', array(
$config_labels, 'config_labels' ) );
add_action( 'w3tc_ajax',
array( '\W3TC\BrowserCache_Page', 'w3tc_ajax' ) );
add_action( 'w3tc_config_ui_save-w3tc_browsercache',
array( $this, 'w3tc_config_ui_save_w3tc_browsercache' ),
10, 2 );
}
public function w3tc_config_ui_save_w3tc_browsercache( $config, $old_config ) {
$prefix = 'browsercache__security__fp__values__keyvalues__';
$prefixl = strlen( $prefix );
$fp_values = array();
foreach ( $_REQUEST as $key => $value ) {
$value = Util_Request::get_string( $key );
if ( substr( $key, 0, $prefixl ) == $prefix ) {
$k = substr( $key, $prefixl );
if ( !empty( $value ) ) {
$fp_values[$k] = $value;
}
}
}
$config->set( 'browsercache.security.fp.values', $fp_values );
}
}

View File

@ -0,0 +1,216 @@
<?php
/**
* File: Cache.php
*
* @package W3TC
*/
namespace W3TC;
/**
* W3 Cache class
*/
/**
* Class: Cache
*/
class Cache {
/**
* Returns cache engine instance
*
* @param string $engine Engine key code.
* @param array $config Configuration.
* @return W3_Cache_Base
*/
public static function instance( $engine, $config = array() ) {
static $instances = array();
// Common configuration data.
if ( ! isset( $config['blog_id'] ) ) {
$config['blog_id'] = Util_Environment::blog_id();
}
$instance_key = sprintf( '%s_%s', $engine, md5( serialize( $config ) ) );
if ( ! isset( $instances[ $instance_key ] ) ) {
switch ( $engine ) {
case 'apc':
if ( function_exists( 'apcu_store' ) ) {
$instances[ $instance_key ] = new Cache_Apcu( $config );
} else if ( function_exists( 'apc_store' ) ) {
$instances[ $instance_key ] = new Cache_Apc( $config );
}
break;
case 'eaccelerator':
$instances[ $instance_key ] = new Cache_Eaccelerator( $config );
break;
case 'file':
$instances[ $instance_key ] = new Cache_File( $config );
break;
case 'file_generic':
$instances[ $instance_key ] = new Cache_File_Generic( $config );
break;
case 'memcached':
if ( class_exists( '\Memcached' ) ) {
$instances[ $instance_key ] = new Cache_Memcached( $config );
} elseif ( class_exists( '\Memcache' ) ) {
$instances[ $instance_key ] = new Cache_Memcache( $config );
}
break;
case 'nginx_memcached':
$instances[ $instance_key ] = new Cache_Nginx_Memcached( $config );
break;
case 'redis':
$instances[ $instance_key ] = new Cache_Redis( $config );
break;
case 'wincache':
$instances[ $instance_key ] = new Cache_Wincache( $config );
break;
case 'xcache':
$instances[ $instance_key ] = new Cache_Xcache( $config );
break;
default:
trigger_error( 'Incorrect cache engine ' . esc_html( $engine ), E_USER_WARNING );
$instances[ $instance_key ] = new Cache_Base( $config );
break;
}
if ( ! isset( $instances[ $instance_key ] ) || ! $instances[ $instance_key ]->available() ) {
$instances[ $instance_key ] = new Cache_Base( $config );
}
}
return $instances[ $instance_key ];
}
/**
* Returns caching engine name
*
* @param string $engine Engine key code.
* @param string $module Module.
*
* @return string
*/
public static function engine_name( $engine, $module = '' ) {
switch ( $engine ) {
case 'memcached':
if ( class_exists( 'Memcached' ) ) {
$engine_name = 'Memcached';
} else {
$engine_name = 'Memcache';
}
break;
case 'nginx_memcached':
$engine_name = 'Nginx + Memcached';
break;
case 'apc':
$engine_name = 'APC';
break;
case 'eaccelerator':
$engine_name = 'EAccelerator';
break;
case 'redis':
$engine_name = 'Redis';
break;
case 'xcache':
$engine_name = 'XCache';
break;
case 'wincache':
$engine_name = 'WinCache';
break;
case 'file':
if ( 'pgcache' === $module ) {
$engine_name = 'Disk: Basic';
} else {
$engine_name = 'Disk';
}
break;
case 'file_generic':
$engine_name = 'Disk: Enhanced';
break;
case 'ftp':
$engine_name = 'Self-hosted / file transfer protocol upload';
break;
case 's3':
$engine_name = 'Amazon Simple Storage Service (S3)';
break;
case 's3_compatible':
$engine_name = 'S3 compatible';
break;
case 'cf':
$engine_name = 'Amazon Cloudfront';
break;
case 'google_drive':
$engine_name = 'Google Drive';
break;
case 'highwinds':
$engine_name = 'Highwinds';
break;
case 'cf2':
$engine_name = 'Amazon Cloudfront';
break;
case 'rscf':
$engine_name = 'Rackspace Cloud Files';
break;
case 'azure':
$engine_name = 'Microsoft Azure Storage';
break;
case 'edgecast':
$engine_name = 'Media Template ProCDN / EdgeCast';
break;
case 'att':
$engine_name = 'AT&amp;T';
break;
case 'rackspace_cdn':
$engine_name = 'Rackspace';
break;
case 'stackpath2':
$engine_name = 'StackPath';
break;
case 'bunnycdn':
$engine_name = 'Bunny CDN';
break;
case '':
$engine_name = __( 'None', 'w3-total-cache' );
break;
default:
$engine_name = $engine;
break;
}
return $engine_name;
}
}

View File

@ -0,0 +1,204 @@
<?php
namespace W3TC;
/**
* W3 Cache flushing
*/
class CacheFlush {
private $_config;
private $_executor;
/**
* PHP5 Constructor
*/
function __construct() {
$this->_config = Dispatcher::config();
$sns = $this->_config->get_boolean( 'cluster.messagebus.enabled' );
if ( $sns )
$this->_executor = new Enterprise_CacheFlush_MakeSnsEvent();
else
$this->_executor = new CacheFlush_Locally();
if ( function_exists( 'add_action' ) ) {
add_action( 'w3tc_redirect', array(
$this,
'execute_delayed_operations'
), 100000, 0 );
add_filter( 'wp_redirect', array(
$this,
'execute_delayed_operations_filter'
), 100000, 1 );
add_action( 'w3tc_messagebus_message_processed', array(
$this,
'execute_delayed_operations'
), 0 );
add_action( 'shutdown', array(
$this,
'execute_delayed_operations'
), 100000, 0 );
}
}
/**
* Flushes database cache
*/
function dbcache_flush() {
if ( $this->_config->get_boolean( 'dbcache.enabled' ) ) {
$this->_executor->dbcache_flush();
}
}
/**
* Flushes minify cache
*/
function minifycache_flush() {
if ( $this->_config->get_boolean( 'minify.enabled' ) ) {
$this->_executor->minifycache_flush();
}
}
/**
* Flushes object cache
*/
function objectcache_flush() {
if ( $this->_config->getf_boolean( 'objectcache.enabled' ) ) {
$this->_executor->objectcache_flush();
}
}
/**
* Flushes fragment cache
*/
function fragmentcache_flush() {
$this->_executor->fragmentcache_flush();
}
/**
* Flushes fragment cache based on group
*/
function fragmentcache_flush_group( $group ) {
$this->_executor->fragmentcache_flush_group( $group );
}
/**
* Updates Browser Query String
*/
function browsercache_flush() {
if ( $this->_config->get_boolean( 'browsercache.enabled' ) ) {
$this->_executor->browsercache_flush();
}
}
/**
* Purge CDN mirror cache.
*
* @param array $extras Extra configuration.
*/
public function cdn_purge_all( $extras = array() ) {
if ( $this->_config->get_boolean( 'cdn.enabled' ) || $this->_config->get_boolean( 'cdnfsd.enabled' ) ) {
return $this->_executor->cdn_purge_all( $extras );
}
return false;
}
/**
* Purges CDN files
*/
function cdn_purge_files( $purgefiles ) {
$this->_executor->cdn_purge_files( $purgefiles );
}
/**
* Clears the system APC
*
* @return mixed
*/
function opcache_flush() {
return $this->_executor->opcache_flush();
}
/**
* Purges/Flushes post page
*/
function flush_post( $post_id, $extras = null ) {
return $this->_executor->flush_post( $post_id, $extras );
}
/**
* Checks if page contents can be flushed (i.e. cached at all)
*/
function flushable_posts( $extras = null ) {
$flushable_posts = apply_filters( 'w3tc_flushable_posts', false,
$extras );
return $flushable_posts;
}
/**
* Purges/Flushes all posts
*/
function flush_posts( $extras = null ) {
return $this->_executor->flush_posts( $extras );
}
/**
* Purges/Flushes all enabled caches
*/
function flush_all( $extras = null ) {
static $flushed = false;
if ( !$flushed ) {
$flushed = true;
$this->_executor->flush_all( $extras );
}
}
/**
* Purges/Flushes cache group
*/
function flush_group( $group, $extras = null ) {
static $flushed_groups = array();
if ( !isset( $flushed_groups[$group] ) ) {
$flushed_groups[$group] = '*';
$this->_executor->flush_group( $group, $extras );
}
}
/**
* Purges/Flushes url
*/
function flush_url( $url, $extras = null ) {
static $flushed_urls = array();
if ( !in_array( $url, $flushed_urls ) ) {
$flushed_urls[] = $url;
return $this->_executor->flush_url( $url, $extras );
}
return true;
}
/**
* Makes get request to url specific to post, ie permalinks
*
* @param unknown $post_id
* @return boolean
*/
function prime_post( $post_id ) {
return $this->_executor->prime_post( $post_id );
}
function execute_delayed_operations() {
return $this->_executor->execute_delayed_operations();
}
function execute_delayed_operations_filter( $v ) {
$this->execute_delayed_operations();
return $v;
}
}

View File

@ -0,0 +1,276 @@
<?php
namespace W3TC;
/**
* W3 Cache flushing
*
* priorities are very important for actions here.
* if e.g. CDN is flushed before local page cache - CDN can cache again
* still not flushed pages from local page cache.
* 100 - db
* 200 - 999 local objects, like object cache
* 1000 - 1999 local files (minify, pagecache)
* 2000 - 2999 local reverse proxies varnish, nginx
* 3000 - external caches like cdn, cloudflare
*/
class CacheFlush_Locally {
/**
* Cleans db cache
*/
function dbcache_flush( $extras = array() ) {
if ( isset( $extras['only'] ) && $extras['only'] != 'dbcache' )
return;
do_action( 'w3tc_flush_dbcache' );
if ( !method_exists( $GLOBALS['wpdb'], 'flush_cache' ) )
return false;
return $GLOBALS['wpdb']->flush_cache( $extras );
}
/**
* Cleans object cache
*/
function objectcache_flush( $extras = array() ) {
if ( isset( $extras['only'] ) && $extras['only'] != 'objectcache' )
return;
do_action( 'w3tc_flush_objectcache' );
$objectcache = Dispatcher::component( 'ObjectCache_WpObjectCache_Regular' );
$v = $objectcache->flush();
do_action( 'w3tc_flush_after_objectcache' );
return $v;
}
/**
* Cleans fragment cache
*/
function fragmentcache_flush( $extras = array() ) {
if ( isset( $extras['only'] ) && $extras['only'] != 'fragment' )
return;
do_action( 'w3tc_flush_fragmentcache' );
do_action( 'w3tc_flush_after_fragmentcache' );
return true;
}
/**
* Cleans fragment cache
*/
function fragmentcache_flush_group( $group ) {
do_action( 'w3tc_flush_fragmentcache_group', $group );
do_action( 'w3tc_flush_after_fragmentcache_group', $group );
return true;
}
function minifycache_flush( $extras = array() ) {
if ( isset( $extras['only'] ) && $extras['only'] != 'minify' )
return;
do_action( 'w3tc_flush_minify' );
$minifycache = Dispatcher::component( 'Minify_MinifiedFileRequestHandler' );
$v = $minifycache->flush( $extras );
do_action( 'w3tc_flush_after_minify' );
return $v;
}
function minifycache_flush_all( $extras = array() ) {
$this->minifycache_flush( $extras );
}
/**
* Updates Query String
*/
function browsercache_flush( $extras = array() ) {
if ( isset( $extras['only'] ) && $extras['only'] != 'browsercache' )
return;
do_action( 'w3tc_flush_browsercache' );
update_option( 'w3tc_browsercache_flush_timestamp',
rand( 10000, 99999 ) . '' );
do_action( 'w3tc_flush_after_browsercache' );
}
/**
* Purge CDN mirror cache
*/
function cdn_purge_all( $extras = array() ) {
$do_flush = apply_filters( 'w3tc_preflush_cdn_all', true, $extras );
$v = false;
if ( $do_flush ) {
do_action( 'w3tc_cdn_purge_all' );
$cdn_core = Dispatcher::component( 'Cdn_Core' );
$cdn = $cdn_core->get_cdn();
$results = array();
$v = $cdn->purge_all( $results );
do_action( 'w3tc_cdn_purge_all_after' );
}
return $v;
}
/**
* Purges Files from Varnish (If enabled) and CDN
*
* @param array $purgefiles array consisting of CdnCommon file descriptors
* array(array('local_path'=>'', 'remote_path'=> ''))
* @return boolean
*/
function cdn_purge_files( $purgefiles ) {
do_action( 'w3tc_cdn_purge_files', $purgefiles );
$common = Dispatcher::component( 'Cdn_Core' );
$results = array();
$v = $common->purge( $purgefiles, $results );
do_action( 'w3tc_cdn_purge_files_after', $purgefiles );
return $v;
}
/**
* Flushes the system APC
*
* @return bool
*/
function opcache_flush() {
$o = Dispatcher::component( 'SystemOpCache_Core' );
return $o->flush();
}
/**
* Purges/Flushes post from page cache, varnish and cdn cache
*
* @param integer $post_id Post ID.
* @param boolean $force Force flag (optional).
* @param array $extras Extras.
*/
function flush_post( $post_id, $force = false, $extras = null ) {
$do_flush = apply_filters( 'w3tc_preflush_post', true, $extras );
if ( $do_flush )
do_action( 'w3tc_flush_post', $post_id, $force, $extras );
}
/**
* Purges/Flushes page contents - page cache, varnish and cdn cache
* When global changes affect whole content but not internal data structures
*/
function flush_posts( $extras = null ) {
$do_flush = apply_filters( 'w3tc_preflush_posts', true, $extras );
if ( $do_flush )
do_action( 'w3tc_flush_posts', $extras );
}
/**
* Flushes all enabled caches.
*/
function flush_all( $extras ) {
static $default_actions_added = false;
if ( !$default_actions_added ) {
$config = Dispatcher::config();
$opcache = Dispatcher::component( 'SystemOpCache_Core' );
if ( $opcache->is_enabled() )
add_action( 'w3tc_flush_all',
array( $this, 'opcache_flush' ),
50, 1 );
if ( $config->get_boolean( 'dbcache.enabled' ) )
add_action( 'w3tc_flush_all',
array( $this, 'dbcache_flush' ),
100, 2 );
if ( $config->getf_boolean( 'objectcache.enabled' ) )
add_action( 'w3tc_flush_all',
array( $this, 'objectcache_flush' ),
200, 1 );
if ( $config->get_boolean( 'minify.enabled' ) )
add_action( 'w3tc_flush_all',
array( $this, 'minifycache_flush_all' ),
1000, 1 );
$default_actions_added = true;
}
$do_flush = apply_filters( 'w3tc_preflush_all', true, $extras );
if ( $do_flush )
do_action( 'w3tc_flush_all', $extras );
}
function flush_group( $group, $extras ) {
$do_flush = apply_filters( 'w3tc_preflush_group', true, $group, $extras );
if ( $do_flush )
do_action( 'w3tc_flush_group', $group, $extras );
}
/**
* Purges/Flushes url from page cache, varnish and cdn cache
*/
function flush_url( $url, $extras = null ) {
$do_flush = apply_filters( 'w3tc_preflush_url', true, $extras );
if ( $do_flush )
do_action( 'w3tc_flush_url', $url, $extras );
}
/**
* Makes get request to url specific to post, ie permalinks
*
* @param unknown $post_id
* @return mixed
*/
function prime_post( $post_id ) {
$pgcache = Dispatcher::component( 'PgCache_Plugin_Admin' );
return $pgcache->prime_post( $post_id );
}
/**
* Called at the end of http request processing
* so that flushers can finish something they've decided to delay
*/
public function execute_delayed_operations() {
static $default_actions_added = false;
if ( !$default_actions_added ) {
$config = Dispatcher::config();
if ( $config->get_boolean( 'pgcache.enabled' ) )
add_filter( 'w3tc_flush_execute_delayed_operations',
array( $this, '_execute_delayed_operations_pgcache' ),
1100 );
if ( $config->get_boolean( 'varnish.enabled' ) )
add_filter( 'w3tc_flush_execute_delayed_operations',
array( $this, '_execute_delayed_operations_varnish' ),
2000 );
$default_actions_added = true;
}
// build response in a form 'module' => 'error message' (empty if no error)
$actions_made = array();
$actions_made = apply_filters( 'w3tc_flush_execute_delayed_operations',
$actions_made );
return $actions_made;
}
public function _execute_delayed_operations_pgcache( $actions_made ) {
$o = Dispatcher::component( 'PgCache_Flush' );
$count_flushed = $o->flush_post_cleanup();
if ( $count_flushed > 0 )
$actions_made[] = array( 'module' => 'pgcache' );
return $actions_made;
}
public function _execute_delayed_operations_varnish( $actions_made ) {
$o = Dispatcher::component( 'Varnish_Flush' );
$count_flushed = $o->flush_post_cleanup();
if ( $count_flushed > 0 )
$actions_made[] = array( 'module' => 'varnish' );
return $actions_made;
}
}

View File

@ -0,0 +1,281 @@
<?php
/**
* File: CacheGroups_Plugin_Admin.php
*
* @since 2.1.0
*
* @package W3TC
*/
namespace W3TC;
/**
* Class: CacheGroups_Plugin_Admin
*
* @since 2.1.0
*/
class CacheGroups_Plugin_Admin extends Base_Page_Settings {
/**
* Current page.
*
* @var string
*/
protected $_page = 'w3tc_cachegroups'; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
/**
* Cache groups settings view.
*
* @since 2.1.0
*/
public function view() {
$c = Dispatcher::config();
// Header.
require W3TC_INC_DIR . '/options/common/header.php';
// User agent groups.
$useragent_groups = array(
'value' => $c->get_array( 'mobile.rgroups' ),
'disabled' => $c->is_sealed( 'mobile.rgroups' ),
'description' =>
'<li>' .
__(
'Enabling even a single user agent group will set a cookie called "w3tc_referrer." It is used to ensure a consistent user experience across page views. Make sure any reverse proxy servers etc respect this cookie for proper operation.',
'w3-total-cache'
) .
'</li>' .
'<li>' .
__(
'Per the above, make sure that visitors are notified about the cookie as per any regulations in your market.',
'w3-total-cache'
) .
'</li>',
);
$useragent_groups = apply_filters( 'w3tc_ui_config_item_mobile.rgroups', $useragent_groups ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
$w3_mobile = Dispatcher::component( 'Mobile_UserAgent' );
$useragent_themes = $w3_mobile->get_themes();
// Referrer groups.
$referrer_groups = $this->_config->get_array( 'referrer.rgroups' );
$w3_referrer = Dispatcher::component( 'Mobile_Referrer' );
$referrer_themes = $w3_referrer->get_themes();
// Cookie groups.
$cookie_groups = array(
'value' => $c->get_array( 'pgcache.cookiegroups.groups' ),
'disabled' => $c->is_sealed( 'pgcache.cookiegroups.groups' ),
);
$cookie_groups = apply_filters( 'w3tc_ui_config_item_pgcache.cookiegroups.groups', $cookie_groups ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
// Load view.
require W3TC_DIR . '/CacheGroups_Plugin_Admin_View.php';
}
/**
* Save settings.
*
* @since 2.1.0
*
* @static
*
* @param array $config Config.
*/
public static function w3tc_config_ui_save_w3tc_cachegroups( $config ) {
// * User agent groups.
$useragent_groups = Util_Request::get_array( 'mobile_groups' );
$mobile_groups = array();
$cached_mobile_groups = array();
foreach ( $useragent_groups as $group => $group_config ) {
$group = strtolower( $group );
$group = preg_replace( '~[^0-9a-z_]+~', '_', $group );
$group = trim( $group, '_' );
if ( $group ) {
$theme = isset( $group_config['theme'] ) ? trim( $group_config['theme'] ) : 'default';
$enabled = isset( $group_config['enabled'] ) ? (bool) $group_config['enabled'] : true;
$redirect = isset( $group_config['redirect'] ) ? trim( $group_config['redirect'] ) : '';
$agents = isset( $group_config['agents'] ) ? Util_Environment::textarea_to_array( $group_config['agents'] ) : array();
$mobile_groups[ $group ] = array(
'theme' => $theme,
'enabled' => $enabled,
'redirect' => $redirect,
'agents' => $agents,
);
$cached_mobile_groups[ $group ] = $agents;
}
}
// Allow plugins modify WPSC mobile groups.
$cached_mobile_groups = apply_filters( 'cached_mobile_groups', $cached_mobile_groups );
// Merge existent and delete removed groups.
foreach ( $mobile_groups as $group => $group_config ) {
if ( isset( $cached_mobile_groups[ $group ] ) ) {
$mobile_groups[ $group ]['agents'] = (array) $cached_mobile_groups[ $group ];
} else {
unset( $mobile_groups[ $group ] );
}
}
// Add new groups.
foreach ( $cached_mobile_groups as $group => $agents ) {
if ( ! isset( $mobile_groups[ $group ] ) ) {
$mobile_groups[ $group ] = array(
'theme' => '',
'enabled' => true,
'redirect' => '',
'agents' => $agents,
);
}
}
// Allow plugins modify W3TC mobile groups.
$mobile_groups = apply_filters( 'w3tc_mobile_groups', $mobile_groups );
// Sanitize mobile groups.
foreach ( $mobile_groups as $group => $group_config ) {
$mobile_groups[ $group ] = array_merge(
array(
'theme' => '',
'enabled' => true,
'redirect' => '',
'agents' => array(),
),
$group_config
);
$mobile_groups[ $group ]['agents'] = self::clean_values( $mobile_groups[ $group ]['agents'] );
sort( $mobile_groups[ $group ]['agents'] );
}
$enable_mobile = false;
foreach ( $mobile_groups as $group_config ) {
if ( $group_config['enabled'] ) {
$enable_mobile = true;
break;
}
}
$config->set( 'mobile.enabled', $enable_mobile );
$config->set( 'mobile.rgroups', $mobile_groups );
// * Referrer groups.
$ref_groups = Util_Request::get_array( 'referrer_groups' );
$referrer_groups = array();
foreach ( $ref_groups as $group => $group_config ) {
$group = strtolower( $group );
$group = preg_replace( '~[^0-9a-z_]+~', '_', $group );
$group = trim( $group, '_' );
if ( $group ) {
$theme = isset( $group_config['theme'] ) ? trim( $group_config['theme'] ) : 'default';
$enabled = isset( $group_config['enabled'] ) ? (bool) $group_config['enabled'] : true;
$redirect = isset( $group_config['redirect'] ) ? trim( $group_config['redirect'] ) : '';
$referrers = isset( $group_config['referrers'] ) ? Util_Environment::textarea_to_array( $group_config['referrers'] ) : array();
$referrer_groups[ $group ] = array(
'theme' => $theme,
'enabled' => $enabled,
'redirect' => $redirect,
'referrers' => $referrers,
);
}
}
// Allow plugins modify W3TC referrer groups.
$referrer_groups = apply_filters( 'w3tc_referrer_groups', $referrer_groups );
// Sanitize mobile groups.
foreach ( $referrer_groups as $group => $group_config ) {
$referrer_groups[ $group ] = array_merge(
array(
'theme' => '',
'enabled' => true,
'redirect' => '',
'referrers' => array(),
),
$group_config
);
$referrer_groups[ $group ]['referrers'] = self::clean_values( $referrer_groups[ $group ]['referrers'] );
sort( $referrer_groups[ $group ]['referrers'] );
}
$enable_referrer = false;
foreach ( $referrer_groups as $group_config ) {
if ( $group_config['enabled'] ) {
$enable_referrer = true;
break;
}
}
$config->set( 'referrer.enabled', $enable_referrer );
$config->set( 'referrer.rgroups', $referrer_groups );
// * Cookie groups.
$mobile_groups = array();
$cached_mobile_groups = array();
$cookie_groups = Util_Request::get_array( 'cookiegroups' );
foreach ( $cookie_groups as $group => $group_config ) {
$group = strtolower( $group );
$group = preg_replace( '~[^0-9a-z_]+~', '_', $group );
$group = trim( $group, '_' );
if ( $group ) {
$enabled = isset( $group_config['enabled'] ) ? (bool) $group_config['enabled'] : false;
$cache = isset( $group_config['cache'] ) ? (bool) $group_config['cache'] : false;
$cookies = isset( $group_config['cookies'] ) ? Util_Environment::textarea_to_array( $group_config['cookies'] ) : array();
$cookiegroups[ $group ] = array(
'enabled' => $enabled,
'cache' => $cache,
'cookies' => $cookies,
);
}
}
// Allow plugins modify W3TC cookie groups.
$cookiegroups = apply_filters( 'w3tc_pgcache_cookiegroups', $cookiegroups );
$enabled = false;
foreach ( $cookiegroups as $group_config ) {
if ( $group_config['enabled'] ) {
$enabled = true;
break;
}
}
$config->set( 'pgcache.cookiegroups.enabled', $enabled );
$config->set( 'pgcache.cookiegroups.groups', $cookiegroups );
}
/**
* Clean entries.
*
* @static
*
* @param array $values Values.
*/
public static function clean_values( $values ) {
return array_unique(
array_map(
function ( $value ) {
return preg_replace( '/(?<!\\\\)' . wp_spaces_regexp() . '/', '\ ', strtolower( $value ) );
},
$values
)
);
}
}

View File

@ -0,0 +1,425 @@
/**
* File: CacheGroups_Plugin_Admin_View.js
*
* @since 2.1.0
*
* @package W3TC
*/
jQuery(function() {
jQuery(document).on( 'submit', '#cachegroups_form', function() {
var error = [];
var mobile_groups = jQuery('#mobile_groups li');
mobile_groups.each(function(index, mobile_group) {
var $mobile_group = jQuery(mobile_group);
if ($mobile_group.find('.mobile_group_enabled:checked').length) {
var name = $mobile_group.find('.mobile_group').text();
var theme = $mobile_group.find('select').val();
var redirect = $mobile_group.find('input[type=text]').val();
var agents = $mobile_group.find('textarea').val().split("\n").filter(function(line){return line.trim()!==''}).map(function(line){return line.trim();});
mobile_groups.not($mobile_group).each(function(index, compare_mobile_group) {
var $compare_mobile_group = jQuery(compare_mobile_group);
if ($compare_mobile_group.find('.mobile_group_enabled:checked').length) {
var compare_name = $compare_mobile_group.find('.mobile_group').text();
var compare_theme = $compare_mobile_group.find('select').val();
var compare_redirect = $compare_mobile_group.find('input[type=text]').val();
var compare_agents = $compare_mobile_group.find('textarea').val().split("\n").filter(function(line){return line.trim()!==''}).map(function(line){return line.trim();});
var groups = sort_array([name, compare_name]);
if (compare_redirect === '' && redirect === '' && compare_theme !== '' && compare_theme === theme) {
error.push('Duplicate theme "' + compare_theme + '" found in the user agent groups "' + groups[0] + '" and "' + groups[1] + '"');
}
if (compare_redirect !== '' && compare_redirect === redirect) {
error.push('Duplicate redirect "' + compare_redirect + '" found in the user agent groups "' + groups[0] + '" and "' + groups[1] + '"');
}
jQuery.each(compare_agents, function(index, value) {
if (jQuery.inArray(value, agents) !== -1) {
error.push('Duplicate stem "' + value + '" found in the user agent groups "' + groups[0] + '" and "' + groups[1] + '"');
}
});
}
});
}
});
var referrer_groups = jQuery('#referrer_groups li');
referrer_groups.each(function(index, referrer_group) {
var $referrer_group = jQuery(referrer_group);
if ($referrer_group.find('.referrer_group_enabled:checked').length) {
var name = $referrer_group.find('.referrer_group').text();
var theme = $referrer_group.find('select').val();
var redirect = $referrer_group.find('input[type=text]').val();
var agents = $referrer_group.find('textarea').val().split("\n").filter(function(line){return line.trim()!==''}).map(function(line){return line.trim();});
referrer_groups.not($referrer_group).each(function(index, compare_referrer_group) {
var $compare_referrer_group = jQuery(compare_referrer_group);
if ($compare_referrer_group.find('.referrer_group_enabled:checked').length) {
var compare_name = $compare_referrer_group.find('.referrer_group').text();
var compare_theme = $compare_referrer_group.find('select').val();
var compare_redirect = $compare_referrer_group.find('input[type=text]').val();
var compare_agents = $compare_referrer_group.find('textarea').val().split("\n").filter(function(line){return line.trim()!==''}).map(function(line){return line.trim();});
var groups = sort_array([name, compare_name]);
if (compare_redirect === '' && redirect === '' && compare_theme !== '' && compare_theme === theme) {
error.push('Duplicate theme "' + compare_theme + '" found in the referrer groups "' + groups[0] + '" and "' + groups[1] + '"');
}
if (compare_redirect !== '' && compare_redirect === redirect) {
error.push('Duplicate redirect "' + compare_redirect + '" found in the referrer groups "' + groups[0] + '" and "' + groups[1] + '"');
}
jQuery.each(compare_agents, function(index, value) {
if (jQuery.inArray(value, agents) !== -1) {
error.push('Duplicate stem "' + value + '" found in the referrer groups "' + groups[0] + '" and "' + groups[1] + '"');
}
});
}
});
}
});
var cookiegroups = jQuery('#cookiegroups li');
cookiegroups.each(function(index, cookiegroup) {
var $cookiegroup = jQuery(cookiegroup);
if ($cookiegroup.find('.cookiegroup_enabled:checked').length) {
var name = $cookiegroup.find('.cookiegroup_name').text();
var agents = $cookiegroup.find('textarea').val().split("\n").filter(function(line){return line.trim()!==''}).map(function(line){return line.trim();});
console.log(agents);
cookiegroups.not($cookiegroup).each(function(index, compare_cookiegroup) {
var $compare_cookiegroup = jQuery(compare_cookiegroup);
if ($compare_cookiegroup.find('.cookiegroup_enabled:checked').length) {
var compare_name = $compare_cookiegroup.find('.cookiegroup_name').text();
var compare_agents = $compare_cookiegroup.find('textarea').val().split("\n").filter(function(line){return line.trim()!==''}).map(function(line){return line.trim();});
console.log(compare_agents);
var groups = sort_array([name, compare_name]);
jQuery.each(compare_agents, function(index, value) {
if (jQuery.inArray(value, agents) !== -1) {
error.push('Duplicate stem "' + value + '" found in the cookie groups "' + groups[0] + '" and "' + groups[1] + '"');
}
});
}
});
}
});
if (error.length !== 0) {
alert(unique_array(error).join('\n'));
return false;
}
return true;
});
jQuery(document).on( 'click', '#mobile_add', function() {
var group = prompt('Enter group name (only "0-9", "a-z", "_" symbols are allowed).');
if (group !== null) {
group = group.toLowerCase();
group = group.replace(/[^0-9a-z_]+/g, '_');
group = group.replace(/^_+/, '');
group = group.replace(/_+$/, '');
if (group) {
var exists = false;
jQuery('.mobile_group').each(function() {
if (jQuery(this).html() == group) {
alert('Group already exists!');
exists = true;
return false;
}
});
if (!exists) {
var li = jQuery('<li id="mobile_group_' + group + '">' +
'<table class="form-table">' +
'<tr>' +
'<th>Group name:</th>' +
'<td>' +
'<span class="mobile_group_number">' + (jQuery('#mobile_groups li').length + 1) + '.</span> ' +
'<span class="mobile_group">' + group + '</span> ' +
'<input type="button" class="button mobile_delete" value="Delete group" />' +
'</td>' +
'</tr>' +
'<tr>' +
'<th><label for="mobile_groups_' + group + '_enabled">Enabled:</label></th>' +
'<td>' +
'<input type="hidden" name="mobile_groups[' + group + '][enabled]" value="0" />' +
'<input id="mobile_groups_' + group + '_enabled" class="mobile_group_enabled" type="checkbox" name="mobile_groups[' + group + '][enabled]" value="1" checked="checked" />' +
'</td>' +
'</tr>' +
'<tr>' +
'<th><label for="mobile_groups_' + group + '_theme">Theme:</label></th>' +
'<td>' +
'<select id="mobile_groups_' + group + '_theme" name="mobile_groups[' + group + '][theme]"><option value="">-- Pass-through --</option></select>' +
'<p class="description">Assign this group of user agents to a specific them. Leaving this option "Active Theme" allows any plugins you have (e.g. mobile plugins) to properly handle requests for these user agents. If the "redirect users to" field is not empty, this setting is ignored.</p>' +
'</td>' +
'</tr>' +
'<tr>' +
'<th><label for="mobile_groups_' + group + '_redirect">Redirect users to:</label></th>' +
'<td>' +
'<input id="mobile_groups_' + group + '_redirect" type="text" name="mobile_groups[' + group + '][redirect]" value="" size="60" />' +
'<p class="description">A 302 redirect is used to send this group of users to another hostname (domain); recommended if a 3rd party service provides a mobile version of your site.</p>' +
'</td>' +
'</tr>' +
'<tr>' +
'<th><label for="mobile_groups_' + group + '_agents">User agents:</label></th>' +
'<td>' +
'<textarea id="mobile_groups_' + group + '_agents" name="mobile_groups[' + group + '][agents]" rows="10" cols="50"></textarea>' +
'<p class="description">Specify the user agents for this group.</p>' +
'</td>' +
'</tr>' +
'</table>' +
'</li>');
var select = li.find('select');
jQuery.each(mobile_themes, function(index, value) {
select.append(jQuery('<option />').val(index).html(value));
});
jQuery('#mobile_groups').append(li);
w3tc_mobile_groups_clear();
window.location.hash = '#mobile_group_' + group;
li.find('textarea').focus();
}
} else {
alert('Empty group name!');
}
}
});
jQuery(document).on('click', '.mobile_delete', function () {
if (confirm('Are you sure want to delete this group?')) {
jQuery(this).parents('#mobile_groups li').remove();
w3tc_mobile_groups_clear();
w3tc_beforeupload_bind();
}
});
w3tc_mobile_groups_clear();
// Referrer groups.
jQuery(document).on( 'click', '#referrer_add', function() {
var group = prompt('Enter group name (only "0-9", "a-z", "_" symbols are allowed).');
if (group !== null) {
group = group.toLowerCase();
group = group.replace(/[^0-9a-z_]+/g, '_');
group = group.replace(/^_+/, '');
group = group.replace(/_+$/, '');
if (group) {
var exists = false;
jQuery('.referrer_group').each(function() {
if (jQuery(this).html() == group) {
alert('Group already exists!');
exists = true;
return false;
}
});
if (!exists) {
var li = jQuery('<li id="referrer_group_' + group + '">' +
'<table class="form-table">' +
'<tr>' +
'<th>Group name:</th>' +
'<td>' +
'<span class="referrer_group_number">' + (jQuery('#referrer_groups li').length + 1) + '.</span> ' +
'<span class="referrer_group">' + group + '</span> ' +
'<input type="button" class="button referrer_delete" value="Delete group" />' +
'</td>' +
'</tr>' +
'<tr>' +
'<th>' +
'<label for="referrer_groups_' + group + '_enabled">Enabled:</label>' +
'</th>' +
'<td>' +
'<input type="hidden" name="referrer_groups[' + group + '][enabled]" value="0" />' +
'<input id="referrer_groups_' + group + '_enabled" class="referrer_group_enabled" type="checkbox" name="referrer_groups[' + group + '][enabled]" value="1" checked="checked" />' +
'</td>' +
'</tr>' +
'<tr>' +
'<th><label for="referrer_groups_' + group + '_theme">Theme:</label></th>' +
'<td>' +
'<select id="referrer_groups_' + group + '_theme" name="referrer_groups[' + group + '][theme]"><option value="">-- Pass-through --</option></select>' +
'<p class="description">Assign this group of referrers to a specific them. Leaving this option "Active Theme" allows any plugins you have (e.g. referrer plugins) to properly handle requests for these referrers. If the "redirect users to" field is not empty, this setting is ignored.</p>' +
'</td>' +
'</tr>' +
'<tr>' +
'<th><label for="referrer_groups_' + group + '_redirect">Redirect users to:</label></th>' +
'<td>' +
'<input id="referrer_groups_' + group + '_redirect" type="text" name="referrer_groups[' + group + '][redirect]" value="" size="60" />' +
'<p class="description">A 302 redirect is used to send this group of users to another hostname (domain); recommended if a 3rd party service provides a referrer version of your site.</p>' +
'</td>' +
'</tr>' +
'<tr>' +
'<th><label for="referrer_groups_' + group + '_referrers">Referrers:</label></th>' +
'<td>' +
'<textarea id="referrer_groups_' + group + '_referrers" name="referrer_groups[' + group + '][referrers]" rows="10" cols="50"></textarea>' +
'<p class="description">Specify the referrers for this group.</p>' +
'</td>' +
'</tr>' +
'</table>' +
'</li>');
var select = li.find('select');
jQuery.each(referrer_themes, function(index, value) {
select.append(jQuery('<option />').val(index).html(value));
});
jQuery('#referrer_groups').append(li);
w3tc_referrer_groups_clear();
window.location.hash = '#referrer_group_' + group;
li.find('textarea').focus();
}
} else {
alert('Empty group name!');
}
}
});
jQuery(document).on('click', '.referrer_delete', function () {
if (confirm('Are you sure want to delete this group?')) {
jQuery(this).parents('#referrer_groups li').remove();
w3tc_referrer_groups_clear();
w3tc_beforeupload_bind();
}
});
w3tc_referrer_groups_clear();
// Cookie groups.
jQuery(document).on( 'click', '#w3tc_cookiegroup_add', function() {
var group = prompt('Enter group name (only "0-9", "a-z", "_" symbols are allowed).');
if (group !== null) {
group = group.toLowerCase();
group = group.replace(/[^0-9a-z_]+/g, '_');
group = group.replace(/^_+/, '');
group = group.replace(/_+$/, '');
if (group) {
var exists = false;
jQuery('.cookiegroup_name').each(function() {
if (jQuery(this).html() == group) {
alert('Group already exists!');
exists = true;
return false;
}
});
if (!exists) {
var li = jQuery('<li id="cookiegroup_' + group + '">' +
'<table class="form-table">' +
'<tr>' +
'<th>Group name:</th>' +
'<td>' +
'<span class="cookiegroup_number">' + (jQuery('#cookiegroups li').length + 1) + '.</span> ' +
'<span class="cookiegroup_name">' + group + '</span> ' +
'<input type="button" class="button w3tc_cookiegroup_delete" value="Delete group" />' +
'</td>' +
'</tr>' +
'<tr>' +
'<th><label for="cookiegroup_' + group + '_enabled">Enabled:</label></th>' +
'<td>' +
'<input id="cookiegroup_' + group + '_enabled" class="cookiegroup_enabled" type="checkbox" name="cookiegroups[' + group + '][enabled]" value="1" checked="checked" />' +
'</td>' +
'</tr>' +
'<tr>' +
'<th><label for="cookiegroup_' + group + '_cache">Cache:</label></th>' +
'<td>' +
'<input id="cookiegroup_' + group + '_cache" type="checkbox" name="cookiegroups[' + group + '][cache]" value="1" checked="checked" /></td>' +
'</tr>' +
'<tr>' +
'<th><label for="cookiegroups_' + group + '_cookies">Cookies:</label></th>' +
'<td>' +
'<textarea id="cookiegroups_' + group + '_cookies" name="cookiegroups[' + group + '][cookies]" rows="10" cols="50"></textarea>' +
'<p class="description">Specify the cookies for this group. Values like \'cookie\', \'cookie=value\', and cookie[a-z]+=value[a-z]+are supported. Remember to escape special characters like spaces, dots or dashes with a backslash. Regular expressions are also supported.</p>' +
'</td>' +
'</tr>' +
'</table>' +
'</li>');
var select = li.find('select');
jQuery('#cookiegroups').append(li);
w3tc_cookiegroups_clear();
window.location.hash = '#cookiegroup_' + group;
li.find('textarea').focus();
}
} else {
alert('Empty group name!');
}
}
});
jQuery(document).on( 'click', '.w3tc_cookiegroup_delete', function () {
if (confirm('Are you sure want to delete this group?')) {
jQuery(this).parents('#cookiegroups li').remove();
w3tc_cookiegroups_clear();
w3tc_beforeupload_bind();
}
});
w3tc_cookiegroups_clear();
// Add sortable.
if (jQuery.ui && jQuery.ui.sortable) {
jQuery('#cookiegroups').sortable({
axis: 'y',
stop: function() {
jQuery('#cookiegroups').find('.cookiegroup_number').each(function(index) {
jQuery(this).html((index + 1) + '.');
});
}
});
}
});
function w3tc_mobile_groups_clear() {
if (!jQuery('#mobile_groups li').length) {
jQuery('#mobile_groups_empty').show();
} else {
jQuery('#mobile_groups_empty').hide();
}
}
function w3tc_referrer_groups_clear() {
if (!jQuery('#referrer_groups li').length) {
jQuery('#referrer_groups_empty').show();
} else {
jQuery('#referrer_groups_empty').hide();
}
}
function w3tc_cookiegroups_clear() {
if (!jQuery('#cookiegroups li').length) {
jQuery('#cookiegroups_empty').show();
} else {
jQuery('#cookiegroups_empty').hide();
}
}
function unique_array(array) {
return jQuery.grep(array,function(el,i){return i === jQuery.inArray(el,array)});
}
function sort_array(array) {
return array.sort();
}

View File

@ -0,0 +1,341 @@
<?php
/**
* File: CacheGroups_Plugin_Admin_View.php
*
* @since 2.1.0
*
* @package W3TC
*
* @uses $useragent_groups
* @uses $useragent_themes
* @uses $referrer_groups
* @uses $referrer_themes
* @uses $cookie_groups
*/
namespace W3TC;
if ( ! defined( 'W3TC' ) ) {
die();
}
?>
<form id="cachegroups_form" action="admin.php?page=<?php echo esc_attr( $this->_page ); ?>" method="post">
<?php Util_UI::print_control_bar( 'cachegroups_form_control' ); ?>
<!-- User Agenet Groups -->
<script type="text/javascript">/*<![CDATA[*/
var mobile_themes = {};
<?php foreach ( $useragent_themes as $theme_key => $theme_name ) : ?>
mobile_themes['<?php echo esc_attr( addslashes( $theme_key ) ); ?>'] = '<?php echo esc_html( addslashes( $theme_name ) ); ?>';
<?php endforeach; ?>
/*]]>*/</script>
<div class="metabox-holder">
<?php Util_Ui::postbox_header( esc_html__( 'Manage User Agent Groups', 'w3-total-cache' ), '', 'manage-uag' ); ?>
<p>
<input id="mobile_add" type="button" class="button"
<?php disabled( $useragent_groups['disabled'] ); ?>
value="<?php esc_html_e( 'Create a group', 'w3-total-cache' ); ?>" />
<?php esc_html_e( 'of user agents by specifying names in the user agents field. Assign a set of user agents to use a specific theme, redirect them to another domain or if an existing mobile plugin is active, create user agent groups to ensure that a unique cache is created for each user agent group. Drag and drop groups into order (if needed) to determine their priority (top -&gt; down).', 'w3-total-cache' ); ?>
</p>
<ul id="mobile_groups">
<?php
$index = 0;
foreach ( $useragent_groups['value'] as $group => $group_config ) :
$index++;
?>
<li id="mobile_group_<?php echo esc_attr( $group ); ?>">
<table class="form-table">
<tr>
<th>
<?php esc_html_e( 'Group name:', 'w3-total-cache' ); ?>
</th>
<td>
<span class="mobile_group_number"><?php echo esc_attr( $index ); ?>.</span> <span class="mobile_group"><?php echo esc_html( $group ); // phpcs:ignore ?></span>
<input type="button" class="button mobile_delete"
value="<?php esc_html_e( 'Delete group', 'w3-total-cache' ); ?>"
<?php disabled( $useragent_groups['disabled'] ); ?> />
</td>
</tr>
<tr>
<th>
<label for="mobile_groups_<?php echo esc_attr( $group ); ?>_enabled"><?php esc_html_e( 'Enabled:', 'w3-total-cache' ); ?></label>
</th>
<td>
<input type="hidden" name="mobile_groups[<?php echo esc_attr( $group ); ?>][enabled]" value="0" />
<input id="mobile_groups_<?php echo esc_attr( $group ); ?>_enabled"
class="mobile_group_enabled" type="checkbox"
name="mobile_groups[<?php echo esc_attr( $group ); ?>][enabled]"
<?php disabled( $useragent_groups['disabled'] ); ?> value="1"
<?php checked( $group_config['enabled'], true ); ?> />
</td>
</tr>
<tr>
<th>
<label for="mobile_groups_<?php echo esc_attr( $group ); ?>_theme"><?php esc_html_e( 'Theme:', 'w3-total-cache' ); ?></label>
</th>
<td>
<select id="mobile_groups_<?php echo esc_attr( $group ); ?>_theme"
name="mobile_groups[<?php echo esc_attr( $group ); ?>][theme]"
<?php disabled( $useragent_groups['disabled'] ); ?> >
<option value=""><?php esc_html_e( '-- Pass-through --', 'w3-total-cache' ); ?></option>
<?php foreach ( $useragent_themes as $theme_key => $theme_name ) : ?>
<option value="<?php echo esc_attr( $theme_key ); ?>"<?php selected( $theme_key, $group_config['theme'] ); ?>><?php echo esc_html( $theme_name ); ?></option>
<?php endforeach; ?>
</select>
<p class="description">
<?php esc_html_e( 'Assign this group of user agents to a specific theme. Selecting "Pass-through" allows any plugin(s) (e.g. mobile plugins) to properly handle requests for these user agents. If the "redirect users to" field is not empty, this setting is ignored.', 'w3-total-cache' ); ?>
</p>
</td>
</tr>
<tr>
<th>
<label for="mobile_groups_<?php echo esc_attr( $group ); ?>_redirect"><?php esc_html_e( 'Redirect users to:', 'w3-total-cache' ); ?></label>
</th>
<td>
<input id="mobile_groups_<?php echo esc_attr( $group ); ?>_redirect"
type="text" name="mobile_groups[<?php echo esc_attr( $group ); ?>][redirect]"
value="<?php echo esc_attr( $group_config['redirect'] ); ?>"
<?php disabled( $useragent_groups['disabled'] ); ?>
size="60" />
<p class="description"><?php esc_html_e( 'A 302 redirect is used to send this group of users to another hostname (domain); recommended if a 3rd party service provides a mobile version of your site.', 'w3-total-cache' ); ?></p>
</td>
</tr>
<tr>
<th>
<label for="mobile_groups_<?php echo esc_attr( $group ); ?>_agents"><?php esc_html_e( 'User agents:', 'w3-total-cache' ); ?></label>
</th>
<td>
<textarea id="mobile_groups_<?php echo esc_attr( $group ); ?>_agents"
name="mobile_groups[<?php echo esc_attr( $group ); ?>][agents]"
rows="10" cols="50" <?php disabled( $useragent_groups['disabled'] ); ?>><?php echo esc_textarea( implode( "\r\n", (array) $group_config['agents'] ) ); ?></textarea>
<p class="description">
<?php esc_html_e( 'Specify the user agents for this group. Remember to escape special characters like spaces, dots or dashes with a backslash. Regular expressions are also supported.', 'w3-total-cache' ); ?>
</p>
</td>
</tr>
</table>
</li>
<?php endforeach; ?>
</ul>
<div id="mobile_groups_empty" style="display: none;"><?php esc_html_e( 'No groups added. All user agents receive the same page and minify cache results.', 'w3-total-cache' ); ?></div>
<?php
Util_Ui::postbox_footer();
Util_Ui::postbox_header(
__( 'Note(s):', 'w3-total-cache' ),
'',
'notes'
);
?>
<table class="form-table">
<tr>
<th colspan="2">
<ul>
<?php echo $useragent_groups['description']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</ul>
</th>
</tr>
</table>
<?php Util_Ui::postbox_footer(); ?>
</div>
<!-- Referrer Groups -->
<script type="text/javascript">/*<![CDATA[*/
var referrer_themes = {};
<?php foreach ( $referrer_themes as $theme_key => $theme_name ) : ?>
referrer_themes['<?php echo esc_attr( $theme_key ); ?>'] = '<?php echo esc_html( $theme_name ); ?>';
<?php endforeach; ?>
/*]]>*/</script>
<div class="metabox-holder">
<?php Util_Ui::postbox_header( esc_html__( 'Manage Referrer Groups', 'w3-total-cache' ), '', 'manage-rg' ); ?>
<p>
<input id="referrer_add" type="button" class="button" value="<?php esc_html_e( 'Create a group', 'w3-total-cache' ); ?>" /> <?php esc_html_e( 'of referrers by specifying names in the referrers field. Assign a set of referrers to use a specific theme, redirect them to another domain, create referrer groups to ensure that a unique cache is created for each referrer group. Drag and drop groups into order (if needed) to determine their priority (top -&gt; down).', 'w3-total-cache' ); ?>
</p>
<ul id="referrer_groups">
<?php
$index = 0;
foreach ( $referrer_groups as $group => $group_config ) :
$index++;
?>
<li id="referrer_group_<?php echo esc_attr( $group ); ?>">
<table class="form-table">
<tr>
<th>
<?php esc_html_e( 'Group name:', 'w3-total-cache' ); ?>
</th>
<td>
<span class="referrer_group_number"><?php echo esc_attr( $index ); ?>.</span> <span class="referrer_group"><?php echo esc_html( $group ); ?></span> <input type="button" class="button referrer_delete" value="<?php esc_html_e( 'Delete group', 'w3-total-cache' ); ?>" />
</td>
</tr>
<tr>
<th>
<label for="referrer_groups_<?php echo esc_attr( $group ); ?>_enabled"><?php esc_html_e( 'Enabled:', 'w3-total-cache' ); ?></label>
</th>
<td>
<input type="hidden" name="referrer_groups[<?php echo esc_attr( $group ); ?>][enabled]" value="0" />
<input id="referrer_groups_<?php echo esc_attr( $group ); ?>_enabled"
class="referrer_group_enabled" type="checkbox"
name="referrer_groups[<?php echo esc_attr( $group ); ?>][enabled]"
value="1"<?php checked( $group_config['enabled'], true ); ?> />
</td>
</tr>
<tr>
<th>
<label for="referrer_groups_<?php echo esc_attr( $group ); ?>_theme"><?php esc_html_e( 'Theme:', 'w3-total-cache' ); ?></label>
</th>
<td>
<select id="referrer_groups_<?php echo esc_attr( $group ); ?>_theme" name="referrer_groups[<?php echo esc_attr( $group ); ?>][theme]">
<option value=""><?php esc_html_e( '-- Pass-through --', 'w3-total-cache' ); ?></option>
<?php foreach ( $referrer_themes as $theme_key => $theme_name ) : ?>
<option value="<?php echo esc_attr( $theme_key ); ?>"<?php selected( $theme_key, $group_config['theme'] ); ?>><?php echo esc_html( $theme_name ); ?></option>
<?php endforeach; ?>
</select>
<p class="description"><?php esc_html_e( 'Assign this group of referrers to a specific theme. Selecting "Pass-through" allows any plugin(s) (e.g. referrer plugins) to properly handle requests for these referrers. If the "redirect users to" field is not empty, this setting is ignored.', 'w3-total-cache' ); ?></p>
</td>
</tr>
<tr>
<th>
<label for="referrer_groups_<?php echo esc_attr( $group ); ?>_redirect"><?php esc_html_e( 'Redirect users to:', 'w3-total-cache' ); ?></label>
</th>
<td>
<input id="referrer_groups_<?php echo esc_attr( $group ); ?>_redirect" type="text" name="referrer_groups[<?php echo esc_attr( $group ); ?>][redirect]" value="<?php echo esc_attr( $group_config['redirect'] ); ?>" size="60" />
<p class="description"><?php esc_html_e( 'A 302 redirect is used to send this group of referrers to another hostname (domain).', 'w3-total-cache' ); ?></p>
</td>
</tr>
<tr>
<th>
<label for="referrer_groups_<?php echo esc_attr( $group ); ?>_referrers"><?php esc_html_e( 'Referrers:', 'w3-total-cache' ); ?></label>
</th>
<td>
<textarea id="referrer_groups_<?php echo esc_attr( $group ); ?>_referrers" name="referrer_groups[<?php echo esc_attr( $group ); ?>][referrers]" rows="10" cols="50"><?php echo esc_textarea( implode( "\r\n", (array) $group_config['referrers'] ) ); ?></textarea>
<p class="description"><?php esc_html_e( 'Specify the referrers for this group. Remember to escape special characters like spaces, dots or dashes with a backslash. Regular expressions are also supported.', 'w3-total-cache' ); ?></p>
</td>
</tr>
</table>
</li>
<?php endforeach; ?>
</ul>
<div id="referrer_groups_empty" style="display: none;"><?php esc_html_e( 'No groups added. All referrers receive the same page and minify cache results.', 'w3-total-cache' ); ?></div>
<?php Util_Ui::postbox_footer(); ?>
</div>
<!-- Cookie Groups -->
<div class="metabox-holder">
<?php Util_Ui::postbox_header( esc_html__( 'Manage Cookie Groups', 'w3-total-cache' ), '', 'manage-cg' ); ?>
<p>
<input id="w3tc_cookiegroup_add" type="button" class="button"
<?php disabled( $cookie_groups['disabled'] ); ?>
value="<?php esc_html_e( 'Create a group', 'w3-total-cache' ); ?>" />
<?php esc_html_e( 'of Cookies by specifying names in the Cookies field. Assign a set of Cookies to ensure that a unique cache is created for each Cookie group. Drag and drop groups into order (if needed) to determine their priority (top -&gt; down).', 'w3-total-cache' ); ?>
</p>
<ul id="cookiegroups" class="w3tc_cachegroups">
<?php
$index = 0;
foreach ( $cookie_groups['value'] as $group => $group_config ) :
$index++;
?>
<li id="cookiegroup_<?php echo esc_attr( $group ); ?>">
<table class="form-table">
<tr>
<th>
<?php esc_html_e( 'Group name:', 'w3-total-cache' ); ?>
</th>
<td>
<span class="cookiegroup_number"><?php echo esc_attr( $index ); ?>.</span>
<span class="cookiegroup_name"><?php echo htmlspecialchars( $group ); // phpcs:ignore ?></span>
<input type="button" class="button w3tc_cookiegroup_delete"
value="<?php esc_html_e( 'Delete group', 'w3-total-cache' ); ?>"
<?php disabled( $cookie_groups['disabled'] ); ?> />
</td>
</tr>
<tr>
<th>
<label for="cookiegroup_<?php echo esc_attr( $group ); ?>_enabled">
<?php esc_html_e( 'Enabled:', 'w3-total-cache' ); ?>
</label>
</th>
<td>
<input id="cookiegroup_<?php echo esc_attr( $group ); ?>_enabled"
class="cookiegroup_enabled" type="checkbox"
name="cookiegroups[<?php echo esc_attr( $group ); ?>][enabled]"
<?php disabled( $cookie_groups['disabled'] ); ?> value="1"
<?php checked( $group_config['enabled'], true ); ?> />
</td>
</tr>
<tr>
<th>
<label for="cookiegroup_<?php echo esc_attr( $group ); ?>_cache">
<?php esc_html_e( 'Cache:', 'w3-total-cache' ); ?>
</label>
</th>
<td>
<input id="cookiegroup_<?php echo esc_attr( $group ); ?>_cache"
type="checkbox"
name="cookiegroups[<?php echo esc_attr( $group ); ?>][cache]"
<?php disabled( $cookie_groups['disabled'] ); ?> value="1"
<?php checked( $group_config['cache'], true ); ?> /> <?php esc_html_e( 'Enable', 'w3-total-cache' ); ?>
<p class="description"><?php esc_html_e( 'Controls whether web pages can be cached or not when cookies from this group are detected.', 'w3-total-cache' ); ?></p>
</td>
</tr>
<tr>
<th>
<label for="cookiegroup_<?php echo esc_attr( $group ); ?>_cookies">
<?php esc_html_e( 'Cookies:', 'w3-total-cache' ); ?>
</label>
</th>
<td>
<textarea id="cookiegroup_<?php echo esc_attr( $group ); ?>_cookies"
name="cookiegroups[<?php echo esc_attr( $group ); ?>][cookies]"
rows="10" cols="50" <?php disabled( $cookie_groups['disabled'] ); ?>><?php echo esc_textarea( implode( "\r\n", (array) $group_config['cookies'] ) ); ?></textarea>
<p class="description">
<?php esc_html_e( 'Specify the cookies for this group. Values like \'cookie\', \'cookie=value\', and cookie[a-z]+=value[a-z]+ are supported. Remember to escape special characters like spaces, dots or dashes with a backslash. Regular expressions are also supported.', 'w3-total-cache' ); ?>
</p>
</td>
</tr>
</table>
</li>
<?php endforeach; ?>
</ul>
<div id="cookiegroups_empty" style="display: none;"><?php esc_html_e( 'No groups added. All Cookies receive the same page and minify cache results.', 'w3-total-cache' ); ?></div>
<?php
Util_Ui::postbox_footer();
Util_Ui::postbox_header(
__( 'Note(s):', 'w3-total-cache' ),
'',
'notes'
);
?>
<table class="form-table">
<tr>
<th colspan="2">
<ul>
<li>
<?php esc_html_e( 'Content is cached for each group separately.', 'w3-total-cache' ); ?>
</li>
<li>
<?php esc_html_e( 'Per the above, make sure that visitors are notified about the cookie as per any regulations in your market.', 'w3-total-cache' ); ?>
</li>
</ul>
</th>
</tr>
</table>
<?php Util_Ui::postbox_footer(); ?>
</div>
</form>

View File

@ -0,0 +1,239 @@
<?php
namespace W3TC;
/**
* APC class
*/
class Cache_Apc extends Cache_Base {
/*
* Used for faster flushing
*
* @var integer $_key_version
*/
private $_key_version = array();
/**
* Adds data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function add( $key, &$var, $expire = 0, $group = '' ) {
if ( $this->get( $key, $group ) === false ) {
return $this->set( $key, $var, $expire, $group );
}
return false;
}
/**
* Sets data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function set( $key, $var, $expire = 0, $group = '' ) {
$var['key_version'] = $this->_get_key_version( $group );
$storage_key = $this->get_item_key( $key );
return apc_store( $storage_key, serialize( $var ), $expire );
}
/**
* Returns data
*
* @param string $key
* @param string $group Used to differentiate between groups of cache values
* @return mixed
*/
function get_with_old( $key, $group = '' ) {
$has_old_data = false;
$storage_key = $this->get_item_key( $key );
$v = @unserialize( apc_fetch( $storage_key ) );
if ( !is_array( $v ) || !isset( $v['key_version'] ) )
return array( null, $has_old_data );
$key_version = $this->_get_key_version( $group );
if ( $v['key_version'] == $key_version )
return array( $v, $has_old_data );
if ( $v['key_version'] > $key_version ) {
$this->_set_key_version( $v['key_version'], $group );
return array( $v, $has_old_data );
}
// key version is old
if ( !$this->_use_expired_data )
return array( null, $has_old_data );
// if we have expired data - update it for future use and let
// current process recalculate it
$expires_at = isset( $v['expires_at'] ) ? $v['expires_at'] : null;
if ( $expires_at == null || time() > $expires_at ) {
$v['expires_at'] = time() + 30;
apc_store( $storage_key, serialize( $v ), 0 );
$has_old_data = true;
return array( null, $has_old_data );
}
// return old version
return array( $v, $has_old_data );
}
/**
* Replaces data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function replace( $key, &$var, $expire = 0, $group ='' ) {
if ( $this->get( $key, $group ) !== false ) {
return $this->set( $key, $var, $expire, $group );
}
return false;
}
/**
* Deletes data
*
* @param string $key
* @param string $group
* @return boolean
*/
function delete( $key, $group = '' ) {
$storage_key = $this->get_item_key( $key );
if ( $this->_use_expired_data ) {
$v = @unserialize( apc_fetch( $storage_key ) );
if ( is_array( $v ) ) {
$v['key_version'] = 0;
apc_store( $storage_key, serialize( $v ), 0 );
return true;
}
}
return apc_delete( $storage_key );
}
/**
* Deletes _old and primary if exists.
*
* @param unknown $key
* @return bool
*/
function hard_delete( $key, $group = '' ) {
$storage_key = $this->get_item_key( $key );
return apc_delete( $storage_key );
}
/**
* Flushes all data
*
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function flush( $group = '' ) {
$this->_get_key_version( $group ); // initialize $this->_key_version
$this->_key_version[$group]++;
$this->_set_key_version( $this->_key_version[$group], $group );
return true;
}
/**
* Checks if engine can function properly in this environment
*
* @return bool
*/
public function available() {
return function_exists( 'apc_store' );
}
/**
* Returns key postfix
*
* @param string $group Used to differentiate between groups of cache values
* @return integer
*/
private function _get_key_version( $group = '' ) {
if ( !isset( $this->_key_version[$group] ) || $this->_key_version[$group] <= 0 ) {
$v = apc_fetch( $this->_get_key_version_key( $group ) );
$v = intval( $v );
$this->_key_version[$group] = ( $v > 0 ? $v : 1 );
}
return $this->_key_version[$group];
}
/**
* Sets new key version
*
* @param unknown $v
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
private function _set_key_version( $v, $group = '' ) {
apc_store( $this->_get_key_version_key( $group ), $v, 0 );
}
/**
* Used to replace as atomically as possible known value to new one
*/
public function set_if_maybe_equals( $key, $old_value, $new_value ) {
// apc_cas doesnt fit here, since we are float but it works with
// int only
// cant guarantee atomic action here, filelocks fail often
$value = $this->get( $key );
if ( isset( $old_value['content'] ) &&
$value['content'] != $old_value['content'] )
return false;
return $this->set( $key, $new_value );
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_add( $key, $value ) {
if ( $value == 0 )
return true;
$storage_key = $this->get_item_key( $key );
$r = apc_inc( $storage_key, $value );
if ( !$r ) // it doesnt initialize counter by itself
$this->counter_set( $key, 0 );
return $r;
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_set( $key, $value ) {
$storage_key = $this->get_item_key( $key );
return apc_store( $storage_key, $value );
}
/**
* Get counter's value
*/
public function counter_get( $key ) {
$storage_key = $this->get_item_key( $key );
$v = (int)apc_fetch( $storage_key );
return $v;
}
}

View File

@ -0,0 +1,238 @@
<?php
namespace W3TC;
/**
* APC class
*/
class Cache_Apcu extends Cache_Base {
/*
* Used for faster flushing
*
* @var integer $_key_version
*/
private $_key_version = array();
/**
* Adds data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function add( $key, &$var, $expire = 0, $group = '' ) {
if ( $this->get( $key, $group ) === false ) {
return $this->set( $key, $var, $expire, $group );
}
return false;
}
/**
* Sets data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function set( $key, $var, $expire = 0, $group = '' ) {
$var['key_version'] = $this->_get_key_version( $group );
$storage_key = $this->get_item_key( $key );
return apcu_store( $storage_key, serialize( $var ), $expire );
}
/**
* Returns data
*
* @param string $key
* @param string $group Used to differentiate between groups of cache values
* @return mixed
*/
function get_with_old( $key, $group = '' ) {
$has_old_data = false;
$storage_key = $this->get_item_key( $key );
$v = @unserialize( apcu_fetch( $storage_key ) );
if ( !is_array( $v ) || !isset( $v['key_version'] ) )
return array( null, $has_old_data );
$key_version = $this->_get_key_version( $group );
if ( $v['key_version'] == $key_version )
return array( $v, $has_old_data );
if ( $v['key_version'] > $key_version ) {
$this->_set_key_version( $v['key_version'], $group );
return array( $v, $has_old_data );
}
// key version is old
if ( !$this->_use_expired_data )
return array( null, $has_old_data );
// if we have expired data - update it for future use and let
// current process recalculate it
$expires_at = isset( $v['expires_at'] ) ? $v['expires_at'] : null;
if ( $expires_at == null || time() > $expires_at ) {
$v['expires_at'] = time() + 30;
apcu_store( $storage_key, serialize( $v ), 0 );
$has_old_data = true;
return array( null, $has_old_data );
}
// return old version
return array( $v, $has_old_data );
}
/**
* Replaces data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function replace( $key, &$var, $expire = 0, $group ='' ) {
if ( $this->get( $key, $group ) !== false ) {
return $this->set( $key, $var, $expire, $group );
}
return false;
}
/**
* Deletes data
*
* @param string $key
* @param string $group
* @return boolean
*/
function delete( $key, $group = '' ) {
$storage_key = $this->get_item_key( $key );
if ( $this->_use_expired_data ) {
$v = @unserialize( apcu_fetch( $storage_key ) );
if ( is_array( $v ) ) {
$v['key_version'] = 0;
apcu_store( $storage_key, serialize( $v ), 0 );
return true;
}
}
return apcu_delete( $storage_key );
}
/**
* Deletes _old and primary if exists.
*
* @param unknown $key
* @return bool
*/
function hard_delete( $key, $group = '' ) {
$storage_key = $this->get_item_key( $key );
return apcu_delete( $storage_key );
}
/**
* Flushes all data
*
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function flush( $group = '' ) {
$this->_get_key_version( $group ); // initialize $this->_key_version
$this->_key_version[$group]++;
$this->_set_key_version( $this->_key_version[$group], $group );
return true;
}
/**
* Checks if engine can function properly in this environment
*
* @return bool
*/
public function available() {
return function_exists( 'apcu_store' );
}
/**
* Returns key postfix
*
* @param string $group Used to differentiate between groups of cache values
* @return integer
*/
private function _get_key_version( $group = '' ) {
if ( !isset( $this->_key_version[$group] ) || $this->_key_version[$group] <= 0 ) {
$v = apcu_fetch( $this->_get_key_version_key( $group ) );
$v = intval( $v );
$this->_key_version[$group] = ( $v > 0 ? $v : 1 );
}
return $this->_key_version[$group];
}
/**
* Sets new key version
*
* @param unknown $v
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
private function _set_key_version( $v, $group = '' ) {
apcu_store( $this->_get_key_version_key( $group ), $v, 0 );
}
/**
* Used to replace as atomically as possible known value to new one
*/
public function set_if_maybe_equals( $key, $old_value, $new_value ) {
// apc_cas doesnt fit here, since we are float but it works with
// int only
// cant guarantee atomic action here, filelocks fail often
$value = $this->get( $key );
if ( isset( $old_value['content'] ) &&
$value['content'] != $old_value['content'] )
return false;
return $this->set( $key, $new_value );
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_add( $key, $value ) {
if ( $value == 0 )
return true;
$storage_key = $this->get_item_key( $key );
$r = apcu_inc( $storage_key, $value );
if ( !$r ) // it doesnt initialize counter by itself
$this->counter_set( $key, 0 );
return $r;
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_set( $key, $value ) {
$storage_key = $this->get_item_key( $key );
return apcu_store( $storage_key, $value );
}
/**
* Get counter's value
*/
public function counter_get( $key ) {
$storage_key = $this->get_item_key( $key );
$v = (int)apcu_fetch( $storage_key );
return $v;
}
}

View File

@ -0,0 +1,226 @@
<?php
namespace W3TC;
/**
* Base cache class
*/
/**
* class Cache_Base
*/
class Cache_Base {
/*
* Blog id
*
* @var integer
*/
protected $_blog_id = 0;
/**
* To separate the caching for different modules
*
* @var string
*/
protected $_module = '';
/**
* Host
*
* @var string
*/
protected $_host = '';
/**
* Host
*
* @var int
*/
protected $_instance_id = 0;
/*
* If we are going to return expired data when some other process
* is working on new data calculation
*
* @var boolean
*/
protected $_use_expired_data = false;
/**
* Constructor
*
* @param array $config
*/
public function __construct( $config = array() ) {
$this->_blog_id = $config['blog_id'];
$this->_use_expired_data = isset( $config['use_expired_data'] )?$config['use_expired_data']:false;
$this->_module = isset( $config['module'] ) ? $config['module'] : 'default';
$this->_host = isset( $config['host'] ) ? $config['host'] : '';
$this->_instance_id = isset( $config['instance_id'] ) ? $config['instance_id'] : 0;
}
/**
* Adds data
*
* @abstract
* @param string $key
* @param mixed $data
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function add( $key, &$data, $expire = 0, $group = '' ) {
return false;
}
/**
* Sets data
*
* @abstract
* @param string $key
* @param mixed $data
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function set( $key, $data, $expire = 0, $group = '' ) {
return false;
}
/**
* Returns data
*
* @param string $key
* @param string $group Used to differentiate between groups of cache values
* @return mixed
*/
function get( $key, $group = '' ) {
list( $data, $has_old ) = $this->get_with_old( $key, $group );
return $data;
}
/**
* Return primary data and if old exists
*
* @abstract
* @param string $key
* @param string $group Used to differentiate between groups of cache values
* @return array|mixed
*/
function get_with_old( $key, $group = '' ) {
return array( null, false );
}
/**
* Alias for get for minify cache
*
* @param string $key
* @param string $group Used to differentiate between groups of cache values
* @return mixed
*/
function fetch( $key, $group = '' ) {
return $this->get( $key, $group = '' );
}
/**
* Replaces data
*
* @abstract
* @param string $key
* @param mixed $data
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function replace( $key, &$data, $expire = 0, $group = '' ) {
return false;
}
/**
* Deletes data
*
* @abstract
* @param string $key
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function delete( $key, $group = '' ) {
return false;
}
/**
* Deletes primary data and old data
*
* @abstract
* @param string $key
* @return boolean
*/
function hard_delete( $key, $group = '' ) {
return false;
}
/**
* Flushes all data
*
* @abstract
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function flush( $group = '' ) {
return false;
}
/**
* Checks if engine can function properly in this environment
*
* @return bool
*/
public function available() {
return true;
}
/**
* Constructs key version key
*
* @param unknown $group
* @return string
*/
protected function _get_key_version_key( $group = '' ) {
return sprintf( 'w3tc_%d_%d_%s_%s_key_version',
$this->_instance_id, $this->_blog_id,
$this->_module, $group );
}
/**
* Constructs item key
*
* @param unknown $name
* @return string
*/
public function get_item_key( $name ) {
$key = sprintf( 'w3tc_%d_%s_%d_%s_%s',
$this->_instance_id, $this->_host, $this->_blog_id,
$this->_module, $name );
return $key;
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_add( $key, $value ) {
return false;
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_set( $key, $value ) {
return false;
}
/**
* Get counter's value
*/
public function counter_get( $key ) {
return false;
}
}

View File

@ -0,0 +1,229 @@
<?php
namespace W3TC;
/**
* eAccelerator class
*/
class Cache_Eaccelerator extends Cache_Base {
/*
* Used for faster flushing
*
* @var integer $_key_postfix
*/
private $_key_version = array();
/**
* Adds data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function add( $key, &$var, $expire = 0, $group = '' ) {
if ( $this->get( $key, $group ) === false ) {
return $this->set( $key, $var, $expire, $group );
}
return false;
}
/**
* Sets data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function set( $key, $var, $expire = 0, $group = '' ) {
$var['key_version'] = $this->_get_key_version( $group );
$storage_key = $this->get_item_key( $key );
return eaccelerator_put( $storage_key, serialize( $var ), $expire );
}
/**
* Returns data
*
* @param string $key
* @param string $group Used to differentiate between groups of cache values
* @return mixed
*/
function get_with_old( $key, $group = '' ) {
$has_old_data = false;
$storage_key = $this->get_item_key( $key );
$v = @unserialize( eaccelerator_get( $storage_key ) );
if ( !is_array( $v ) || !isset( $v['key_version'] ) )
return array( null, $has_old_data );
$key_version = $this->_get_key_version( $group );
if ( $v['key_version'] == $key_version )
return array( $v, $has_old_data );
if ( $v['key_version'] > $key_version ) {
$this->_set_key_version( $v['key_version'], $group );
return array( $v, $has_old_data );
}
// key version is old
if ( !$this->_use_expired_data )
return array( null, $has_old_data );
// if we have expired data - update it for future use and let
// current process recalculate it
$expires_at = isset( $v['expires_at'] ) ? $v['expires_at'] : null;
if ( $expires_at == null || time() > $expires_at ) {
$v['expires_at'] = time() + 30;
eaccelerator_put( $storage_key, serialize( $v ), 0 );
$has_old_data = true;
return array( null, $has_old_data );
}
// return old version
return array( $v, $has_old_data );
}
/**
* Replaces data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function replace( $key, &$var, $expire = 0, $group = '' ) {
if ( $this->get( $key, $group ) !== false ) {
return $this->set( $key, $var, $expire, $group );
}
return false;
}
/**
* Deletes data
*
* @param string $key
* @param string $group
* @return boolean
*/
function delete( $key, $group = '' ) {
$storage_key = $this->get_item_key( $key );
if ( $this->_use_expired_data ) {
$v = @unserialize( eaccelerator_get( $storage_key ) );
if ( is_array( $v ) ) {
$v['key_version'] = 0;
eaccelerator_put( $storage_key, serialize( $v ), 0 );
return true;
}
}
return eaccelerator_rm( $key . '_' . $this->_blog_id );
}
/**
* Deletes _old and primary if exists.
*
* @param unknown $key
* @return bool
*/
function hard_delete( $key, $group = '' ) {
$storage_key = $this->get_item_key( $key );
return eaccelerator_rm( $storage_key );
}
/**
* Flushes all data
*
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function flush( $group = '' ) {
$this->_get_key_version( $group ); // initialize $this->_key_version
$this->_key_version[$group]++;
$this->_set_key_version( $this->_key_version[$group], $group );
return true;
}
/**
* Checks if engine can function properly in this environment
*
* @return bool
*/
public function available() {
return function_exists( 'eaccelerator_put' );
}
/**
* Returns key postfix
*
* @param string $group Used to differentiate between groups of cache values
* @return integer
*/
private function _get_key_version( $group = '' ) {
if ( !isset( $this->_key_version[$group] ) || $this->_key_version[$group] <= 0 ) {
$v = eaccelerator_get( $this->_get_key_version_key( $group ) );
$v = intval( $v );
$this->_key_version[$group] = ( $v > 0 ? $v : 1 );
}
return $this->_key_version[$group];
}
/**
* Sets new key version
*
* @param unknown $v
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
private function _set_key_version( $v, $group = '' ) {
// cant guarantee atomic action here, filelocks fail often
$value = $this->get( $key );
if ( isset( $old_value['content'] ) &&
$value['content'] != $old_value['content'] )
return false;
return $this->set( $key, $new_value );
}
/**
* Used to replace as atomically as possible known value to new one
*/
public function set_if_maybe_equals( $key, $old_value, $new_value ) {
// eaccelerator cache not supported anymore by its authors
return false;
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_add( $key, $value ) {
// eaccelerator cache not supported anymore by its authors
return false;
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_set( $key, $value ) {
// eaccelerator cache not supported anymore by its authors
return false;
}
/**
* Get counter's value
*/
public function counter_get( $key ) {
// eaccelerator cache not supported anymore by its authors
return false;
}
}

View File

@ -0,0 +1,456 @@
<?php
namespace W3TC;
/**
* class Cache_File
*/
class Cache_File extends Cache_Base {
/**
* Path to cache dir
*
* @var string
*/
protected $_cache_dir = '';
/**
* Directory to flush
*
* @var string
*/
protected $_flush_dir = '';
/**
* Exclude files
*
* @var array
*/
protected $_exclude = array();
/**
* Flush time limit
*
* @var int
*/
protected $_flush_timelimit = 0;
/**
* File locking
*
* @var boolean
*/
protected $_locking = false;
/**
* If path should be generated based on wp_hash
*
* @var bool
*/
protected $_use_wp_hash = false;
/**
* Constructor
*
* @param array $config
*/
function __construct( $config = array() ) {
parent::__construct( $config );
if ( isset( $config['cache_dir'] ) )
$this->_cache_dir = trim( $config['cache_dir'] );
else
$this->_cache_dir = Util_Environment::cache_blog_dir( $config['section'], $config['blog_id'] );
$this->_exclude = isset( $config['exclude'] ) ? (array) $config['exclude'] : array();
$this->_flush_timelimit = isset( $config['flush_timelimit'] ) ? (int) $config['flush_timelimit'] : 180;
$this->_locking = isset( $config['locking'] ) ? (boolean) $config['locking'] : false;
if ( isset( $config['flush_dir'] ) )
$this->_flush_dir = $config['flush_dir'];
else {
if ( $config['blog_id'] <= 0 && !isset( $config['cache_dir'] ) ) {
// clear whole section if we operate on master cache
// and in a mode when cache_dir not strictly specified
$this->_flush_dir = Util_Environment::cache_dir( $config['section'] );
} else
$this->_flush_dir = $this->_cache_dir;
}
if ( isset( $config['use_wp_hash'] ) && $config['use_wp_hash'] )
$this->_use_wp_hash = true;
}
/**
* Adds data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function add( $key, &$var, $expire = 0, $group = '' ) {
if ( $this->get( $key, $group ) === false ) {
return $this->set( $key, $var, $expire, $group );
}
return false;
}
/**
* Sets data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function set( $key, $var, $expire = 0, $group = '' ) {
$fp = $this->fopen_write( $key, $group, 'wb' );
if ( !$fp )
return false;
if ( $this->_locking )
@flock( $fp, LOCK_EX );
if ( $expire <= 0 || $expire > W3TC_CACHE_FILE_EXPIRE_MAX )
$expire = W3TC_CACHE_FILE_EXPIRE_MAX;
$expires_at = time() + $expire;
@fputs( $fp, pack( 'L', $expires_at ) );
@fputs( $fp, '<?php exit; ?>' );
@fputs( $fp, @serialize( $var ) );
@fclose( $fp );
if ( $this->_locking )
@flock( $fp, LOCK_UN );
return true;
}
/**
* Returns data
*
* @param string $key
* @param string $group Used to differentiate between groups of cache values
* @return mixed
*/
function get_with_old( $key, $group = '' ) {
list( $data, $has_old_data ) = $this->_get_with_old_raw( $key, $group );
if ( !empty( $data ) )
$data_unserialized = @unserialize( $data );
else
$data_unserialized = $data;
return array( $data_unserialized, $has_old_data );
}
private function _get_with_old_raw( $key, $group = '' ) {
$has_old_data = false;
$storage_key = $this->get_item_key( $key );
$path = $this->_cache_dir . DIRECTORY_SEPARATOR .
( $group ? $group . DIRECTORY_SEPARATOR : '' ) .
$this->_get_path( $storage_key, $group );
if ( !is_readable( $path ) )
return array( null, $has_old_data );
$fp = @fopen( $path, 'rb' );
if ( ! $fp || 4 > filesize( $path ) ) {
return array( null, $has_old_data );
}
if ( $this->_locking )
@flock( $fp, LOCK_SH );
$expires_at = @fread( $fp, 4 );
$data = null;
if ( $expires_at !== false ) {
list( , $expires_at ) = @unpack( 'L', $expires_at );
if ( time() > $expires_at ) {
if ( $this->_use_expired_data ) {
// update expiration so other threads will use old data
$fp2 = @fopen( $path, 'cb' );
if ( $fp2 ) {
@fputs( $fp2, pack( 'L', time() + 30 ) );
@fclose( $fp2 );
}
$has_old_data = true;
}
} else {
$data = '';
while ( !@feof( $fp ) ) {
$data .= @fread( $fp, 4096 );
}
$data = substr( $data, 14 );
}
}
if ( $this->_locking )
@flock( $fp, LOCK_UN );
@fclose( $fp );
return array( $data, $has_old_data );
}
/**
* Replaces data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function replace( $key, &$var, $expire = 0, $group = '' ) {
if ( $this->get( $key, $group ) !== false ) {
return $this->set( $key, $var, $expire, $group );
}
return false;
}
/**
* Deletes data
*
* @param string $key
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function delete( $key, $group = '' ) {
$storage_key = $this->get_item_key( $key );
$path = $this->_cache_dir . DIRECTORY_SEPARATOR .
( $group ? $group . DIRECTORY_SEPARATOR : '' ) .
$this->_get_path( $storage_key, $group );
if ( !file_exists( $path ) )
return true;
if ( $this->_use_expired_data ) {
$fp = @fopen( $path, 'cb' );
if ( $fp ) {
if ( $this->_locking )
@flock( $fp, LOCK_EX );
@fputs( $fp, pack( 'L', 0 ) ); // make it expired
@fclose( $fp );
if ( $this->_locking )
@flock( $fp, LOCK_UN );
return true;
}
}
return @unlink( $path );
}
/**
* Deletes _old and primary if exists.
*
* @param string $key
*
* @return bool
*/
function hard_delete( $key, $group = '' ) {
$key = $this->get_item_key( $key );
$path = $this->_cache_dir . DIRECTORY_SEPARATOR . $this->_get_path( $key, $group );
return @unlink( $path );
}
/**
* Flushes all data
*
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function flush( $group = '' ) {
@set_time_limit( $this->_flush_timelimit );
$flush_dir = $group ?
$this->_cache_dir . DIRECTORY_SEPARATOR . $group .
DIRECTORY_SEPARATOR :
$this->_flush_dir;
Util_File::emptydir( $flush_dir, $this->_exclude );
return true;
}
/**
* Returns modification time of cache file
*
* @param integer $key
* @param string $group Used to differentiate between groups of cache values
* @return boolean|string
*/
function mtime( $key, $group = '' ) {
$path =
$this->_cache_dir . DIRECTORY_SEPARATOR .
( $group ? $group . DIRECTORY_SEPARATOR : '' ) .
$this->_get_path( $key, $group );
if ( file_exists( $path ) ) {
return @filemtime( $path );
}
return false;
}
/**
* Returns file path for key
*
* @param string $key
* @return string
*/
function _get_path( $key, $group = '' ) {
if ( $this->_use_wp_hash && function_exists( 'wp_hash' ) )
$hash = wp_hash( $key );
else
$hash = md5( $key );
$path = sprintf( '%s/%s/%s.php', substr( $hash, 0, 3 ), substr( $hash, 3, 3 ), $hash );
return $path;
}
public function get_stats_size( $timeout_time ) {
$size = array(
'bytes' => 0,
'items' => 0,
'timeout_occurred' => false
);
$size = $this->dirsize( $this->_cache_dir, $size, $timeout_time );
return $size;
}
private function dirsize( $path, $size, $timeout_time ) {
$dir = @opendir( $path );
if ( $dir ) {
while ( !$size['timeout_occurred'] && ( $entry = @readdir( $dir ) ) !== false ) {
if ( $entry == '.' || $entry == '..' ) {
continue;
}
$full_path = $path . DIRECTORY_SEPARATOR . $entry;
if ( @is_dir( $full_path ) ) {
$size = $this->dirsize( $full_path, $size, $timeout_time );
} else {
$size['bytes'] += @filesize( $full_path );
// dont check time() for each file, quite expensive
$size['items']++;
if ( $size['items'] % 1000 == 0 )
$size['timeout_occurred'] |= ( time() > $timeout_time );
}
}
@closedir( $dir );
}
return $size;
}
/**
* Used to replace as atomically as possible known value to new one
*/
public function set_if_maybe_equals( $key, $old_value, $new_value ) {
// cant guarantee atomic action here, filelocks fail often
$value = $this->get( $key );
if ( isset( $old_value['content'] ) &&
$value['content'] != $old_value['content'] )
return false;
return $this->set( $key, $new_value );
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_add( $key, $value ) {
if ( $value == 0 )
return true;
$fp = $this->fopen_write( $key, '', 'a' );
if ( !$fp )
return false;
// use "x" to store increment, since it's most often case
// and it will save 50% of size if only increments are used
if ( $value == 1 )
@fputs( $fp, 'x' );
else
@fputs( $fp, ' ' . (int)$value );
@fclose( $fp );
return true;
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_set( $key, $value ) {
$fp = $this->fopen_write( $key, '', 'wb' );
if ( !$fp )
return false;
$expire = W3TC_CACHE_FILE_EXPIRE_MAX;
$expires_at = time() + $expire;
@fputs( $fp, pack( 'L', $expires_at ) );
@fputs( $fp, '<?php exit; ?>' );
@fputs( $fp, (int)$value );
@fclose( $fp );
return true;
}
/**
* Get counter's value
*/
public function counter_get( $key ) {
list( $value, $old_data ) = $this->_get_with_old_raw( $key );
if ( empty( $value ) )
return 0;
$original_length = strlen( $value );
$cut_value = str_replace( 'x', '', $value );
$count = $original_length - strlen( $cut_value );
// values more than 1 are stored as <space>value
$a = explode( ' ', $cut_value );
foreach ( $a as $counter_value )
$count += (int)$counter_value;
return $count;
}
private function fopen_write( $key, $group, $mode ) {
$storage_key = $this->get_item_key( $key );
$sub_path = $this->_get_path( $storage_key, $group );
$path = $this->_cache_dir . DIRECTORY_SEPARATOR .
( $group ? $group . DIRECTORY_SEPARATOR : '' ) . $sub_path;
$dir = dirname( $path );
if ( !@is_dir( $dir ) ) {
if ( !Util_File::mkdir_from( $dir, dirname( W3TC_CACHE_DIR ) ) )
return false;
}
$fp = @fopen( $path, $mode );
return $fp;
}
}

View File

@ -0,0 +1,116 @@
<?php
namespace W3TC;
/**
* File cache cleaner class
*/
class Cache_File_Cleaner {
/**
* Cache directory
*
* @var string
*/
var $_cache_dir = '';
/**
* Clean operation time limit
*
* @var int
*/
var $_clean_timelimit = 0;
/**
* Exclude files
*
* @var array
*/
var $_exclude = array();
/**
* PHP5-style constructor
*
* @param array $config
*/
function __construct( $config = array() ) {
$this->_cache_dir = ( isset( $config['cache_dir'] ) ? trim( $config['cache_dir'] ) : 'cache' );
$this->_clean_timelimit = ( isset( $config['clean_timelimit'] ) ? (int) $config['clean_timelimit'] : 180 );
$this->_exclude = ( isset( $config['exclude'] ) ? (array) $config['exclude'] : array() );
}
/**
* Run clean operation
*
* @return boolean
*/
function clean() {
@set_time_limit( $this->_clean_timelimit );
$this->_clean( $this->_cache_dir, false );
}
/**
* Clean
*
* @param string $path
* @param bool $remove
* @return void
*/
function _clean( $path, $remove = true ) {
$dir = @opendir( $path );
if ( $dir ) {
while ( ( $entry = @readdir( $dir ) ) !== false ) {
if ( $entry == '.' || $entry == '..' ) {
continue;
}
foreach ( $this->_exclude as $mask ) {
if ( fnmatch( $mask, basename( $entry ) ) ) {
continue 2;
}
}
$full_path = $path . DIRECTORY_SEPARATOR . $entry;
if ( @is_dir( $full_path ) ) {
$this->_clean( $full_path );
} elseif ( !$this->is_valid( $full_path ) ) {
@unlink( $full_path );
}
}
@closedir( $dir );
if ( $remove ) {
@rmdir( $path );
}
}
}
/**
* Check if file is valid
*
* @param string $file
* @return bool
*/
function is_valid( $file ) {
$valid = false;
if ( file_exists( $file ) ) {
$fp = @fopen( $file, 'rb' );
if ( $fp ) {
$expires = @fread( $fp, 4 );
if ( $expires !== false ) {
list( , $expires_at ) = @unpack( 'L', $expires );
$valid = ( time() < $expires_at );
}
@fclose( $fp );
}
}
return $valid;
}
}

View File

@ -0,0 +1,130 @@
<?php
namespace W3TC;
/**
* Generic file cache cleaner class
*/
class Cache_File_Cleaner_Generic extends Cache_File_Cleaner {
/**
* Number of items processed
*
* @var integer
*/
var $processed_count = 0;
/**
* Cache expire time
*
* @var int
*/
var $_expire = 0;
private $hard_delete = false;
/**
* PHP5-style constructor
*
* @param array $config
*/
function __construct( $config = array() ) {
parent::__construct( $config );
$this->_expire = ( isset( $config['expire'] ) ? (int) $config['expire'] : 0 );
if ( !$this->_expire || $this->_expire > W3TC_CACHE_FILE_EXPIRE_MAX ) {
$this->_expire = 0;
}
}
function _clean( $path, $remove = false ) {
$dir = false;
if ( is_dir( $path ) ) {
$dir = @opendir( $path );
}
if ( $dir ) {
while ( ( $entry = @readdir( $dir ) ) !== false ) {
if ( $entry == '.' || $entry == '..' ) {
continue;
}
$full_path = $path . DIRECTORY_SEPARATOR . $entry;
if ( substr( $entry, -4 ) === '_old' &&
!$this->is_old_file_expired( $full_path ) ) {
continue;
}
foreach ( $this->_exclude as $mask ) {
if ( fnmatch( $mask, basename( $entry ) ) ) {
continue 2;
}
}
if ( @is_dir( $full_path ) ) {
$this->_clean( $full_path );
} else {
$this->_clean_file( $entry, $full_path );
}
}
@closedir( $dir );
if ( $this->is_empty_dir( $path ) )
@rmdir( $path );
}
}
function _clean_file( $entry, $full_path ) {
if ( substr( $entry, -4 ) === '_old' ) {
$this->processed_count++;
@unlink( $full_path );
} elseif ( !$this->is_valid( $full_path ) ) {
$old_entry_path = $full_path . '_old';
$this->processed_count++;
if ( !@rename( $full_path, $old_entry_path ) ) {
// if we can delete old entry -
// do second attempt to store in old-entry file
if ( @unlink( $old_entry_path ) ) {
if ( !@rename( $full_path, $old_entry_path ) ) {
// last attempt - just remove entry
@unlink( $full_path );
}
}
}
}
}
/**
* Checks if file is valid
*
* @param string $file
* @return bool
*/
function is_valid( $file ) {
if ( $this->_expire <= 0 )
return false;
if ( file_exists( $file ) ) {
$ftime = @filemtime( $file );
if ( $ftime && $ftime > ( time() - $this->_expire ) ) {
return true;
}
}
return false;
}
function is_old_file_expired( $file ) {
$ftime = @filemtime( $file );
$expire = $this->_expire ? $this->_expire * 5 : W3TC_CACHE_FILE_EXPIRE_MAX;
if ( $ftime && $ftime < ( time() - $expire ) ) {
return true;
}
return false;
}
function is_empty_dir( $dir ) {
return ( $files = @scandir( $dir ) ) && count( $files ) <= 2;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace W3TC;
/**
* Disk-enhanced file cache cleaner
*/
class Cache_File_Cleaner_Generic_HardDelete extends Cache_File_Cleaner_Generic {
function __construct( $config = array() ) {
parent::__construct( $config );
}
function _clean_file( $entry, $full_path ) {
if ( substr( $entry, -4 ) === '_old' ) {
$this->processed_count++;
@unlink( $full_path );
} elseif ( !$this->is_valid( $full_path ) ) {
@unlink( $full_path );
}
}
}

View File

@ -0,0 +1,379 @@
<?php
namespace W3TC;
/**
* Disk:Enhanced file cache
*/
class Cache_File_Generic extends Cache_File {
/**
* Expire
*
* @var integer
*/
var $_expire = 0;
/**
* PHP5-style constructor
*
* @param array $config
*/
function __construct( $config = array() ) {
parent::__construct( $config );
$this->_expire = ( isset( $config['expire'] ) ? (int) $config['expire'] : 0 );
if ( !$this->_expire || $this->_expire > W3TC_CACHE_FILE_EXPIRE_MAX ) {
$this->_expire = W3TC_CACHE_FILE_EXPIRE_MAX;
}
}
/**
* Sets data
*
* @param string $key
* @param string $var
* @param int $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function set( $key, $var, $expire = 0, $group = '' ) {
$key = $this->get_item_key( $key );
$sub_path = $this->_get_path( $key, $group );
$path = $this->_cache_dir . DIRECTORY_SEPARATOR . $sub_path;
$dir = dirname( $path );
if ( !@is_dir( $dir ) ) {
if ( !Util_File::mkdir_from_safe( $dir, dirname( W3TC_CACHE_DIR ) ) )
return false;
}
$tmppath = $path . '.' . getmypid();
$fp = @fopen( $tmppath, 'wb' );
if ( !$fp )
return false;
if ( $this->_locking )
@flock( $fp, LOCK_EX );
@fputs( $fp, $var['content'] );
@fclose( $fp );
if ( $this->_locking )
@flock( $fp, LOCK_UN );
// some hostings create files with restrictive permissions
// not allowing apache to read it later
@chmod( $path, 0644 );
if ( @filesize( $tmppath ) > 0 ) {
@unlink( $path );
@rename( $tmppath, $path );
}
@unlink( $tmppath );
$old_entry_path = $path . '_old';
@unlink( $old_entry_path );
if ( Util_Environment::is_apache() && isset( $var['headers'] ) ) {
$rules = '';
if ( isset( $var['headers']['Content-Type'] ) &&
substr( $var['headers']['Content-Type'], 0, 8 ) == 'text/xml' ) {
$rules .= "<IfModule mod_mime.c>\n";
$rules .= " RemoveType .html_gzip\n";
$rules .= " AddType text/xml .html_gzip\n";
$rules .= " RemoveType .html\n";
$rules .= " AddType text/xml .html\n";
$rules .= "</IfModule>\n";
}
if ( isset( $var['headers'] ) ) {
$headers = array();
foreach ( $var['headers'] as $h ) {
if ( isset( $h['n'] ) && isset( $h['v'] ) ) {
$h2 = apply_filters( 'w3tc_pagecache_set_header', $h, $h,
'file_generic' );
if ( !empty( $h2 ) ) {
$name_escaped = $this->escape_header_name( $h2['n'] );
if ( !isset( $headers[$name_escaped] ) ) {
$headers[$name_escaped] = array(
'values' => array(),
'files_match' => $h2['files_match']
);
}
$value_escaped = $this->escape_header_value( $h2['v'] );
if ( !empty( $value_escaped ) ) {
$headers[$name_escaped]['values'][] =
" Header add " .
$name_escaped .
" '" . $value_escaped . "'\n";
}
}
}
}
$header_rules = '';
foreach ( $headers as $name_escaped => $value ) {
// Link header doesnt apply to .xml assets
$header_rules .= ' <FilesMatch "' . $value['files_match'] . "\">\n";
$header_rules .= " Header unset $name_escaped\n";
$header_rules .= implode( "\n", $value['values'] );
$header_rules .= " </FilesMatch>\n";
}
if ( !empty( $header_rules ) ) {
$rules .= "<IfModule mod_headers.c>\n";
$rules .= $header_rules;
$rules .= "</IfModule>\n";
}
}
if ( !empty($rules) ) {
@file_put_contents( dirname( $path ) .
DIRECTORY_SEPARATOR . '.htaccess', $rules );
}
}
return true;
}
private function escape_header_name( $v ) {
return preg_replace( '~[^0-9A-Za-z\-]~m', '_', $v );
}
private function escape_header_value( $v ) {
return str_replace( "'", "\\'",
str_replace( "\\", "\\\\\\", // htaccess need escape of \ to \\\
preg_replace( '~[\r\n]~m', '_', trim( $v ) ) ) );
}
/**
* Returns data
*
* @param string $key
* @param string $group Used to differentiate between groups of cache values
* @return array
*/
function get_with_old( $key, $group = '' ) {
$has_old_data = false;
$key = $this->get_item_key( $key );
$path = $this->_cache_dir . DIRECTORY_SEPARATOR .
$this->_get_path( $key, $group );
$data = $this->_read( $path );
if ( $data != null )
return array( $data, $has_old_data );
$path_old = $path . '_old';
$too_old_time = time() - 30;
if ( $exists = file_exists( $path_old ) ) {
$file_time = @filemtime( $path_old );
if ( $file_time ) {
if ( $file_time > $too_old_time ) {
// return old data
$has_old_data = true;
return array( $this->_read( $path_old ), $has_old_data );
}
// use old enough time to cause recalculation on next call
@touch( $path_old, 1479904835 );
}
}
$has_old_data = $exists;
return array( null, $has_old_data );
}
/**
* Reads file
*
* @param string $path
* @return array
*/
private function _read( $path ) {
if ( !is_readable( $path ) )
return null;
// make sure reading from cache folder
// canonicalize to avoid unexpected variants
$base_path = realpath( $this->_cache_dir );
$path = realpath( $path );
if ( strlen( $base_path ) <= 0 ||
substr( $path, 0, strlen( $base_path ) ) != $base_path ) {
return null;
}
$fp = @fopen( $path, 'rb' );
if ( !$fp )
return null;
if ( $this->_locking )
@flock( $fp, LOCK_SH );
$var = '';
while ( !@feof( $fp ) )
$var .= @fread( $fp, 4096 );
@fclose( $fp );
if ( $this->_locking )
@flock( $fp, LOCK_UN );
$headers = array();
if ( substr( $path, -4 ) == '.xml' ) {
$headers['Content-type'] = 'text/xml';
}
return array(
'404' => false,
'headers' => $headers,
'time' => null,
'content' => $var
);
}
/**
* Deletes data
*
* @param string $key
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function delete( $key, $group = '' ) {
$key = $this->get_item_key( $key );
$path = $this->_cache_dir . DIRECTORY_SEPARATOR . $this->_get_path( $key, $group );
if ( !file_exists( $path ) )
return true;
$dir = dirname( $path );
if ( file_exists( $dir . DIRECTORY_SEPARATOR . '.htaccess' ) ) {
@unlink( $dir . DIRECTORY_SEPARATOR . '.htaccess' );
}
$old_entry_path = $path . '_old';
if ( ! @rename( $path, $old_entry_path ) ) {
// if we can delete old entry - do second attempt to store in old-entry file
if ( ! @unlink( $old_entry_path ) || ! @rename( $path, $old_entry_path ) ) {
return @unlink( $path );
}
}
/**
* Disabling this as we don't want to immediately hard-expire _old cache files as there is a
* 30 second window where they are still served via get_with_old calls. During AWS testing on
* WP 5.9/6.3 this was resulting in the _old file immediately being removed during the clean
* operation, resulting in failed automated tests (8/1/2023)
*/
// @touch( $old_entry_path, 1479904835 );
return true;
}
/**
* Key to delete, deletes _old and primary if exists.
*
* @param unknown $key
* @return bool
*/
function hard_delete( $key, $group = '' ) {
$key = $this->get_item_key( $key );
$path = $this->_cache_dir . DIRECTORY_SEPARATOR . $this->_get_path( $key, $group );
$old_entry_path = $path . '_old';
@unlink( $old_entry_path );
if ( !file_exists( $path ) )
return true;
@unlink( $path );
return true;
}
/**
* Flushes all data
*
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function flush( $group = '' ) {
if ( $group == 'sitemaps' ) {
$config = Dispatcher::config();
$sitemap_regex = $config->get_string( 'pgcache.purge.sitemap_regex' );
$this->_flush_based_on_regex( $sitemap_regex );
} else {
$dir = $this->_flush_dir;
if ( !empty( $group ) ) {
$c = new Cache_File_Cleaner_Generic_HardDelete( array(
'cache_dir' => $this->_flush_dir .
DIRECTORY_SEPARATOR . $group,
'exclude' => $this->_exclude,
'clean_timelimit' => $this->_flush_timelimit
) );
} else {
$c = new Cache_File_Cleaner_Generic( array(
'cache_dir' => $this->_flush_dir,
'exclude' => $this->_exclude,
'clean_timelimit' => $this->_flush_timelimit
) );
}
$c->clean();
}
}
/**
* Returns cache file path by key
*
* @param string $key
* @return string
*/
function _get_path( $key, $group = '' ) {
return ( empty( $group ) ? '' : $group . DIRECTORY_SEPARATOR ) . $key;
}
function get_item_key( $key ) {
return $key;
}
/**
* Flush cache based on regex
*
* @param string $regex
*/
private function _flush_based_on_regex( $regex ) {
if ( Util_Environment::is_wpmu() && !Util_Environment::is_wpmu_subdomain() ) {
$domain = get_home_url();
$parsed = parse_url( $domain );
$host = $parsed['host'];
$path = isset( $parsed['path'] ) ? '/' . trim( $parsed['path'], '/' ) : '';
$flush_dir = W3TC_CACHE_PAGE_ENHANCED_DIR .
DIRECTORY_SEPARATOR . $host . $path;
} else
$flush_dir = W3TC_CACHE_PAGE_ENHANCED_DIR .
DIRECTORY_SEPARATOR . Util_Environment::host();
$dir = @opendir( $flush_dir );
if ( $dir ) {
while ( ( $entry = @readdir( $dir ) ) !== false ) {
if ( $entry == '.' || $entry == '..' ) {
continue;
}
if ( preg_match( '~' . $regex . '~', basename( $entry ) ) ) {
Util_File::rmdir( $flush_dir . DIRECTORY_SEPARATOR . $entry );
}
}
@closedir( $dir );
}
}
}

View File

@ -0,0 +1,346 @@
<?php
namespace W3TC;
/**
* PECL Memcache class
*/
class Cache_Memcache extends Cache_Base {
/**
* Memcache object
*
* @var Memcache
*/
private $_memcache = null;
/*
* Used for faster flushing
*
* @var integer $_key_version
*/
private $_key_version = array();
/**
* constructor
*
* @param array $config
*/
function __construct( $config ) {
parent::__construct( $config );
$this->_memcache = new \Memcache();
if ( !empty( $config['servers'] ) ) {
$persistent = isset( $config['persistent'] ) ? (boolean) $config['persistent'] : false;
foreach ( (array) $config['servers'] as $server ) {
list( $ip, $port ) = Util_Content::endpoint_to_host_port( $server );
$this->_memcache->addServer( $ip, $port, $persistent );
}
} else {
return false;
}
// when disabled - no extra requests are made to obtain key version,
// but flush operations not supported as a result
// group should be always empty
if ( isset( $config['key_version_mode'] ) &&
$config['key_version_mode'] == 'disabled' ) {
$this->_key_version[''] = 1;
}
return true;
}
/**
* Adds data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function add( $key, &$var, $expire = 0, $group = '' ) {
return $this->set( $key, $var, $expire, $group );
}
/**
* Sets data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function set( $key, $var, $expire = 0, $group = '' ) {
$var['key_version'] = $this->_get_key_version( $group );
$storage_key = $this->get_item_key( $key );
return @$this->_memcache->set( $storage_key, $var, false, $expire );
}
/**
* Returns data
*
* @param string $key
* @param string $group Used to differentiate between groups of cache values
* @return mixed
*/
function get_with_old( $key, $group = '' ) {
$has_old_data = false;
$storage_key = $this->get_item_key( $key );
$v = @$this->_memcache->get( $storage_key );
if ( !is_array( $v ) || !isset( $v['key_version'] ) )
return array( null, $has_old_data );
$key_version = $this->_get_key_version( $group );
if ( $v['key_version'] == $key_version )
return array( $v, $has_old_data );
if ( $v['key_version'] > $key_version ) {
$this->_set_key_version( $v['key_version'], $group );
return array( $v, $has_old_data );
}
// key version is old
if ( !$this->_use_expired_data )
return array( null, $has_old_data );
// if we have expired data - update it for future use and let
// current process recalculate it
$expires_at = isset( $v['expires_at'] ) ? $v['expires_at'] : null;
if ( $expires_at == null || time() > $expires_at ) {
$v['expires_at'] = time() + 30;
@$this->_memcache->set( $storage_key, $v, false, 0 );
$has_old_data = true;
return array( null, $has_old_data );
}
// return old version
return array( $v, $has_old_data );
}
/**
* Replaces data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function replace( $key, &$var, $expire = 0, $group = '' ) {
return $this->set( $key, $var, $expire, $group );
}
/**
* Deletes data
*
* @param string $key
* @param string $group
* @return boolean
*/
function delete( $key, $group = '' ) {
$storage_key = $this->get_item_key( $key );
if ( $this->_use_expired_data ) {
$v = @$this->_memcache->get( $storage_key );
if ( is_array( $v ) ) {
$v['key_version'] = 0;
@$this->_memcache->set( $storage_key, $v, false, 0 );
return true;
}
}
return @$this->_memcache->delete( $storage_key, 0 );
}
/**
* Key to delete, deletes _old and primary if exists.
*
* @param unknown $key
* @return bool
*/
function hard_delete( $key, $group = '' ) {
$storage_key = $this->get_item_key( $key );
return @$this->_memcache->delete( $storage_key, 0 );
}
/**
* Flushes all data
*
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function flush( $group = '' ) {
$this->_increment_key_version( $group );
return true;
}
/**
* Checks if engine can function properly in this environment
*
* @return bool
*/
public function available() {
return class_exists( 'Memcache' );
}
public function get_statistics() {
return $this->_memcache->getStats();
}
/**
* Returns key version
*
* @param string $group Used to differentiate between groups of cache values
* @return integer
*/
private function _get_key_version( $group = '' ) {
if ( !isset( $this->_key_version[$group] ) || $this->_key_version[$group] <= 0 ) {
$v = @$this->_memcache->get( $this->_get_key_version_key( $group ) );
$v = intval( $v );
$this->_key_version[$group] = ( $v > 0 ? $v : 1 );
}
return $this->_key_version[$group];
}
/**
* Sets new key version
*
* @param unknown $v
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
private function _set_key_version( $v, $group = '' ) {
// expiration has to be as long as possible since
// all cache data expires when key version expires
@$this->_memcache->set( $this->_get_key_version_key( $group ), $v, false, 0 );
$this->_key_version[$group] = $v;
}
/**
* Increments key version.
*
* @since 0.14.5
*
* @param string $group Used to differentiate between groups of cache values.
*/
private function _increment_key_version( $group = '' ) {
$r = @$this->_memcache->increment( $this->_get_key_version_key( $group ), 1 );
if ( $r ) {
$this->_key_version[$group] = $r;
} else {
// it doesn't initialize the key if it doesn't exist.
$this->_set_key_version( 2, $group );
}
}
/**
* Returns size used by cache
*/
public function get_stats_size( $timeout_time ) {
$size = array(
'bytes' => 0,
'items' => 0,
'timeout_occurred' => false
);
$key_prefix = $this->get_item_key( '' );
$slabs = @$this->_memcache->getExtendedStats( 'slabs' );
$slabs_plain = array();
if ( is_array( $slabs ) ) {
foreach ( $slabs as $server => $server_slabs ) {
foreach ( $server_slabs as $slab_id => $slab_meta ) {
if ( (int)$slab_id > 0 )
$slabs_plain[(int)$slab_id] = '*';
}
}
}
foreach ( $slabs_plain as $slab_id => $nothing ) {
$cdump = @$this->_memcache->getExtendedStats( 'cachedump',
(int)$slab_id );
if ( !is_array( $cdump ) )
continue;
foreach ( $cdump as $server => $keys_data ) {
if ( !is_array( $keys_data ) )
continue;
foreach ( $keys_data as $key => $size_expiration ) {
if ( substr( $key, 0, strlen( $key_prefix ) ) == $key_prefix ) {
if ( count( $size_expiration ) > 0 ) {
$size['bytes'] += $size_expiration[0];
$size['items']++;
}
}
}
}
}
return $size;
}
/**
* Used to replace as atomically as possible known value to new one
*/
public function set_if_maybe_equals( $key, $old_value, $new_value ) {
// cant guarantee atomic action here, memcache doesnt support CAS
$value = $this->get( $key );
if ( isset( $old_value['content'] ) &&
$value['content'] != $old_value['content'] )
return false;
return $this->set( $key, $new_value );
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_add( $key, $value ) {
if ( $value == 0 )
return true;
$storage_key = $this->get_item_key( $key );
$r = @$this->_memcache->increment( $storage_key, $value );
if ( !$r ) // it doesnt initialize counter by itself
$this->counter_set( $key, 0 );
return $r;
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_set( $key, $value ) {
$storage_key = $this->get_item_key( $key );
return @$this->_memcache->set( $storage_key, $value );
}
/**
* Get counter's value
*/
public function counter_get( $key ) {
$storage_key = $this->get_item_key( $key );
$v = (int)@$this->_memcache->get( $storage_key );
return $v;
}
public function get_item_key( $name ) {
// memcached doesn't survive spaces in a key
$key = sprintf( 'w3tc_%d_%s_%d_%s_%s',
$this->_instance_id, $this->_host, $this->_blog_id,
$this->_module, md5( $name ) );
return $key;
}
}

View File

@ -0,0 +1,425 @@
<?php
namespace W3TC;
/**
* PECL Memcached class
*/
class Cache_Memcached extends Cache_Base {
/**
* Memcache object
*
* @var Memcache
*/
private $_memcache = null;
/*
* Used for faster flushing
*
* @var integer $_key_version
*/
private $_key_version = array();
/*
* Configuration used to reinitialize persistent object
*
* @var integer $_key_version
*/
private $_config = null;
/**
* constructor
*
* @param array $config
*/
function __construct( $config ) {
parent::__construct( $config );
if ( isset( $config['persistent'] ) && $config['persistent'] ) {
$this->_config = $config;
$this->_memcache = new \Memcached( $this->_get_key_version_key( '' ) );
$server_list = $this->_memcache->getServerList();
if ( empty( $server_list ) )
return $this->initialize( $config );
else
return true;
} else {
$this->_memcache = new \Memcached();
return $this->initialize( $config );
}
}
/**
* Initializes
*/
private function initialize( $config ) {
if ( empty( $config['servers'] ) )
return false;
if ( defined( '\Memcached::OPT_REMOVE_FAILED_SERVERS' ) ) {
$this->_memcache->setOption( \Memcached::OPT_REMOVE_FAILED_SERVERS, true );
}
if ( isset( $config['binary_protocol'] ) && !empty( $config['binary_protocol'] ) && defined( '\Memcached::OPT_BINARY_PROTOCOL' ) ) {
$this->_memcache->setOption( \Memcached::OPT_BINARY_PROTOCOL, true );
}
if ( defined( '\Memcached::OPT_TCP_NODELAY' ) ) {
$this->_memcache->setOption( \Memcached::OPT_TCP_NODELAY, true );
}
if ( isset( $config['aws_autodiscovery'] ) &&
$config['aws_autodiscovery'] &&
defined( '\Memcached::OPT_CLIENT_MODE' ) &&
defined( '\Memcached::DYNAMIC_CLIENT_MODE' ) )
$this->_memcache->setOption( \Memcached::OPT_CLIENT_MODE,
\Memcached::DYNAMIC_CLIENT_MODE );
foreach ( (array)$config['servers'] as $server ) {
list( $ip, $port ) = Util_Content::endpoint_to_host_port( $server );
$this->_memcache->addServer( $ip, $port );
}
if ( isset( $config['username'] ) && !empty( $config['username'] ) &&
method_exists( $this->_memcache, 'setSaslAuthData' ) ) {
$this->_memcache->setSaslAuthData( $config['username'],
$config['password'] );
}
// when disabled - no extra requests are made to obtain key version,
// but flush operations not supported as a result
// group should be always empty
if ( isset( $config['key_version_mode'] ) &&
$config['key_version_mode'] == 'disabled' ) {
$this->_key_version[''] = 1;
}
return true;
}
/**
* Adds data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function add( $key, &$var, $expire = 0, $group = '' ) {
return $this->set( $key, $var, $expire, $group );
}
/**
* Sets data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function set( $key, $var, $expire = 0, $group = '' ) {
$var['key_version'] = $this->_get_key_version( $group );
$storage_key = $this->get_item_key( $key );
return @$this->_memcache->set( $storage_key, $var, $expire );
}
/**
* Returns data
*
* @param string $key
* @param string $group Used to differentiate between groups of cache values
* @return mixed
*/
function get_with_old( $key, $group = '' ) {
$has_old_data = false;
$storage_key = $this->get_item_key( $key );
$v = @$this->_memcache->get( $storage_key );
if ( !is_array( $v ) || !isset( $v['key_version'] ) )
return array( null, $has_old_data );
$key_version = $this->_get_key_version( $group );
if ( $v['key_version'] == $key_version )
return array( $v, $has_old_data );
if ( $v['key_version'] > $key_version ) {
$this->_set_key_version( $v['key_version'], $group );
return array( $v, $has_old_data );
}
// key version is old
if ( !$this->_use_expired_data )
return array( null, $has_old_data );
// if we have expired data - update it for future use and let
// current process recalculate it
$expires_at = isset( $v['expires_at'] ) ? $v['expires_at'] : null;
if ( $expires_at == null || time() > $expires_at ) {
$v['expires_at'] = time() + 30;
@$this->_memcache->set( $storage_key, $v, 0 );
$has_old_data = true;
return array( null, $has_old_data );
}
// return old version
return array( $v, $has_old_data );
}
/**
* Replaces data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function replace( $key, &$var, $expire = 0, $group = '' ) {
return $this->set( $key, $var, $expire, $group );
}
/**
* Deletes data
*
* @param string $key
* @param string $group
* @return boolean
*/
function delete( $key, $group = '' ) {
$storage_key = $this->get_item_key( $key );
if ( $this->_use_expired_data ) {
$v = @$this->_memcache->get( $storage_key );
if ( is_array( $v ) ) {
$v['key_version'] = 0;
@$this->_memcache->set( $storage_key, $v, 0 );
return true;
}
}
return @$this->_memcache->delete( $storage_key );
}
/**
* Key to delete, deletes _old and primary if exists.
*
* @param unknown $key
* @return bool
*/
function hard_delete( $key, $group = '' ) {
$storage_key = $this->get_item_key( $key );
return @$this->_memcache->delete( $storage_key );
}
/**
* Flushes all data
*
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function flush( $group = '' ) {
$this->_increment_key_version( $group );
// for persistent connections - apply new config to the object
// otherwise it will keep old servers list
if ( !is_null( $this->_config ) ) {
if ( method_exists( $this->_memcache, 'resetServerList' ) )
$this->_memcache->resetServerList();
$this->initialize( $this->_config );
}
return true;
}
/**
* Checks if engine can function properly in this environment
*
* @return bool
*/
public function available() {
return class_exists( 'Memcached' );
}
public function get_statistics() {
$a = $this->_memcache->getStats();
if ( ! empty( $a ) && count( $a ) > 0 ) {
$keys = array_keys( $a );
$key = $keys[0];
return $a[$key];
}
return $a;
}
/**
* Returns key version
*
* @param string $group Used to differentiate between groups of cache values
* @return integer
*/
private function _get_key_version( $group = '' ) {
if ( !isset( $this->_key_version[$group] ) || $this->_key_version[$group] <= 0 ) {
$v = @$this->_memcache->get( $this->_get_key_version_key( $group ) );
$v = intval( $v );
$this->_key_version[$group] = ( $v > 0 ? $v : 1 );
}
return $this->_key_version[$group];
}
/**
* Sets new key version
*
* @param unknown $v
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
private function _set_key_version( $v, $group = '' ) {
// expiration has to be as long as possible since
// all cache data expires when key version expires
@$this->_memcache->set( $this->_get_key_version_key( $group ), $v, 0 );
$this->_key_version[$group] = $v;
}
/**
* Increments key version.
*
* @since 0.14.5
*
* @param string $group Used to differentiate between groups of cache values.
*/
private function _increment_key_version( $group = '' ) {
$r = @$this->_memcache->increment( $this->_get_key_version_key( $group ), 1 );
if ( $r ) {
$this->_key_version[$group] = $r;
} else {
// it doesn't initialize the key if it doesn't exist.
$this->_set_key_version( 2, $group );
}
}
/**
* Returns size used by cache
*/
public function get_stats_size( $timeout_time ) {
$size = array(
'bytes' => 0,
'items' => 0,
'timeout_occurred' => false,
);
$key_prefix = $this->get_item_key( '' );
$error_occurred = false;
$server_list = $this->_memcache->getServerList();
$n = 0;
foreach ( $server_list as $server ) {
$loader = new Cache_Memcached_Stats( $server['host'], $server['port'] );
$slabs = $loader->slabs();
if ( !is_array( $slabs ) ) {
$error_occurred = true;
continue;
}
foreach ( $slabs as $slab_id ) {
$cdump = $loader->cachedump( $slab_id );
if ( !is_array( $cdump ) )
continue;
foreach ( $cdump as $line ) {
$key_data = explode( ' ', $line );
if ( !is_array( $key_data ) || count( $key_data ) < 3 )
continue;
$n++;
if ( $n % 10 == 0 ) {
$size['timeout_occurred'] = ( time() > $timeout_time );
if ( $size['timeout_occurred'] )
return $size;
}
$key = $key_data[1];
$bytes = substr( $key_data[2], 1 );
if ( substr( $key, 0, strlen( $key_prefix ) ) == $key_prefix ) {
$size['bytes'] += $bytes;
$size['items']++;
}
}
}
}
if ( $error_occurred && $size['items'] <= 0 ) {
$size['bytes'] = null;
$size['items'] = null;
}
return $size;
}
/**
* Used to replace as atomically as possible known value to new one
*/
public function set_if_maybe_equals( $key, $old_value, $new_value ) {
$storage_key = $this->get_item_key( $key );
$cas = null;
$value = @$this->_memcache->get( $storage_key, null, $cas );
if ( !is_array( $value ) )
return false;
if ( isset( $old_value['content'] ) &&
$value['content'] != $old_value['content'] )
return false;
return @$this->_memcache->cas( $cas, $storage_key, $new_value );
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_add( $key, $value ) {
if ( $value == 0 )
return true;
$storage_key = $this->get_item_key( $key );
$r = $this->_memcache->increment( $storage_key, $value, 0, 3600 );
if ( !$r ) // it doesnt initialize counter by itself
$this->counter_set( $key, 0 );
return $r;
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_set( $key, $value ) {
$storage_key = $this->get_item_key( $key );
return @$this->_memcache->set( $storage_key, $value );
}
/**
* Get counter's value
*/
public function counter_get( $key ) {
$storage_key = $this->get_item_key( $key );
$v = (int)@$this->_memcache->get( $storage_key );
return $v;
}
public function get_item_key( $name ) {
// memcached doesn't survive spaces in a key
$key = sprintf( 'w3tc_%d_%s_%d_%s_%s',
$this->_instance_id, $this->_host, $this->_blog_id,
$this->_module, md5( $name ) );
return $key;
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace W3TC;
/**
* Download extended statistics since module cant do it by itself
*/
class Cache_Memcached_Stats {
public function __construct( $host, $port ) {
$this->host = $host;
$this->port = $port;
}
public function request( $command ) {
$handle = @fsockopen( $this->host, $this->port );
if ( !$handle )
return null;
fwrite( $handle, $command . "\r\n" );
$response = array();
while ( ( !feof( $handle ) ) ) {
$line = fgets( $handle );
$response[] = $line;
if ( $this->end( $line, $command ) )
break;
}
@fclose( $handle );
return $response;
}
private function end( $buffer, $command ) {
// incr or decr also return integer
if ( ( preg_match( '/^(incr|decr)/', $command ) ) ) {
if ( preg_match(
'/^(END|ERROR|SERVER_ERROR|CLIENT_ERROR|NOT_FOUND|[0-9]*)/',
$buffer ) )
return true;
} else {
if ( preg_match( '/^(END|DELETED|OK|ERROR|SERVER_ERROR|CLIENT_ERROR|NOT_FOUND|STORED|RESET|TOUCHED)/', $buffer ) )
return true;
}
return false;
}
public function parse( $lines ) {
$return = array();
foreach ( $lines as $line ) {
$data = explode( ' ', $line );
$return[] = $data;
}
return $return;
}
public function slabs() {
$result = $this->request( 'stats slabs' );
if ( is_null( $result ) )
return null;
$result = $this->parse( $result );
$slabs = array();
foreach ( $result as $line_words ) {
if ( count( $line_words ) < 2 )
continue;
$key = explode( ':', $line_words[1] );
if ( (int)$key[0] > 0 )
$slabs[$key[0]] = '*';
}
return array_keys( $slabs );
}
public function cachedump( $slab_id ) {
$result = $this->request( 'stats cachedump ' . $slab_id . ' 0' );
if ( is_null( $result ) )
return null;
// return pure data to limit memory usage
return $result;
}
}

View File

@ -0,0 +1,306 @@
<?php
namespace W3TC;
/**
* PECL Memcached class
*/
class Cache_Nginx_Memcached extends Cache_Base {
/**
* Memcache object
*/
private $_memcache = null;
/*
* Configuration used to reinitialize persistent object
*/
private $_config = null;
/**
* constructor
*/
function __construct( $config ) {
parent::__construct( $config );
if ( isset( $config['persistent'] ) && $config['persistent'] ) {
$this->_config = $config;
$this->_memcache = new \Memcached( $this->_get_key_version_key( '' ) );
$server_list = $this->_memcache->getServerList();
if ( empty( $server_list ) )
return $this->initialize( $config );
else
return true;
} else {
$this->_memcache = new \Memcached();
return $this->initialize( $config );
}
}
/**
* Initializes
*/
private function initialize( $config ) {
if ( empty( $config['servers'] ) )
return false;
if ( defined( '\Memcached::OPT_REMOVE_FAILED_SERVERS' ) ) {
$this->_memcache->setOption( \Memcached::OPT_REMOVE_FAILED_SERVERS, true );
}
$this->_memcache->setOption( \Memcached::OPT_COMPRESSION, false );
if ( isset( $config['aws_autodiscovery'] ) &&
$config['aws_autodiscovery'] &&
defined( '\Memcached::OPT_CLIENT_MODE' ) &&
defined( '\Memcached::DYNAMIC_CLIENT_MODE' ) )
$this->_memcache->setOption( \Memcached::OPT_CLIENT_MODE,
\Memcached::DYNAMIC_CLIENT_MODE );
foreach ( (array)$config['servers'] as $server ) {
list( $ip, $port ) = Util_Content::endpoint_to_host_port( $server );
$this->_memcache->addServer( $ip, $port );
}
if ( isset( $config['username'] ) && !empty( $config['username'] ) &&
method_exists( $this->_memcache, 'setSaslAuthData' ) ) {
$this->_memcache->setSaslAuthData( $config['username'],
$config['password'] );
}
return true;
}
/**
* Adds data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function add( $key, &$var, $expire = 0, $group = '' ) {
return $this->set( $key, $var, $expire, $group );
}
/**
* Sets data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function set( $key, $var, $expire = 0, $group = '' ) {
$this->_memcache->setOption( \Memcached::OPT_USER_FLAGS,
( isset( $var['c'] ) ? 1 : 0 ) );
return @$this->_memcache->set( $key, $var['content'], $expire );
}
/**
* Returns data
*
* @param string $key
* @param string $group Used to differentiate between groups of cache values
* @return mixed
*/
function get_with_old( $key, $group = '' ) {
$has_old_data = false;
$v = @$this->_memcache->get( $key );
if ( $v === FALSE ) {
return null;
}
$data = array( 'content' => $v );
$data['compression'] = ( substr( $key, -5 ) == '_gzip' ? 'gzip' : '' );
return array( $data, false );
}
/**
* Replaces data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function replace( $key, &$var, $expire = 0, $group = '' ) {
return $this->set( $key, $var, $expire, $group );
}
/**
* Deletes data
*
* @param string $key
* @param string $group
* @return boolean
*/
function delete( $key, $group = '' ) {
return @$this->_memcache->delete( $key );
}
/**
* Key to delete, deletes _old and primary if exists.
*
* @param unknown $key
* @return bool
*/
function hard_delete( $key, $group = '' ) {
return @$this->_memcache->delete( $key );
}
/**
* Flushes all data
*
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function flush( $group = '' ) {
// can only flush everything from memcached, no way to flush only
// pgcache cache
return @$this->_memcache->flush();
}
/**
* Checks if engine can function properly in this environment
*
* @return bool
*/
public function available() {
return class_exists( 'Memcached' );
}
public function get_statistics() {
$a = $this->_memcache->getStats();
if ( count( $a ) > 0 ) {
$keys = array_keys( $a );
$key = $keys[0];
return $a[$key];
}
return $a;
}
/**
* Returns size used by cache
*/
public function get_stats_size( $timeout_time ) {
$size = array(
'bytes' => 0,
'items' => 0,
'timeout_occurred' => false,
);
$key_prefix = $this->get_item_key( '' );
$error_occurred = false;
$server_list = $this->_memcache->getServerList();
$n = 0;
foreach ( $server_list as $server ) {
$loader = new Cache_Memcached_Stats( $server['host'], $server['port'] );
$slabs = $loader->slabs();
if ( !is_array( $slabs ) ) {
$error_occurred = true;
continue;
}
foreach ( $slabs as $slab_id ) {
$cdump = $loader->cachedump( $slab_id );
if ( !is_array( $cdump ) )
continue;
foreach ( $cdump as $line ) {
$key_data = explode( ' ', $line );
if ( !is_array( $key_data ) || count( $key_data ) < 3 )
continue;
$n++;
if ( $n % 10 == 0 ) {
$size['timeout_occurred'] = ( time() > $timeout_time );
if ( $size['timeout_occurred'] )
return $size;
}
$key = $key_data[1];
$bytes = substr( $key_data[2], 1 );
if ( substr( $key, 0, strlen( $key_prefix ) ) == $key_prefix ) {
$size['bytes'] += $bytes;
$size['items']++;
}
}
}
}
if ( $error_occurred && $size['items'] <= 0 ) {
$size['bytes'] = null;
$size['items'] = null;
}
return $size;
}
/**
* Used to replace as atomically as possible known value to new one
*/
public function set_if_maybe_equals( $key, $old_value, $new_value ) {
$storage_key = $this->get_item_key( $key );
$cas = null;
$value = @$this->_memcache->get( $storage_key, null, $cas );
if ( !is_array( $value ) )
return false;
if ( isset( $old_value['content'] ) &&
$value['content'] != $old_value['content'] )
return false;
return @$this->_memcache->cas( $cas, $storage_key, $new_value );
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_add( $key, $value ) {
if ( $value == 0 )
return true;
$storage_key = $this->get_item_key( $key );
$r = @$this->_memcache->increment( $storage_key, $value );
if ( !$r ) // it doesnt initialize counter by itself
$this->counter_set( $key, 0 );
return $r;
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_set( $key, $value ) {
$storage_key = $this->get_item_key( $key );
return @$this->_memcache->set( $storage_key, $value );
}
/**
* Get counter's value
*/
public function counter_get( $key ) {
$storage_key = $this->get_item_key( $key );
$v = (int)@$this->_memcache->get( $storage_key );
return $v;
}
public function get_item_key( $name ) {
// memcached doesn't survive spaces in a key
$key = sprintf( 'w3tc_%d_%s_%d_%s_%s',
$this->_instance_id, $this->_host, $this->_blog_id,
$this->_module, md5( $name ) );
return $key;
}
}

View File

@ -0,0 +1,543 @@
<?php
/**
* File: Cache_Redis.php
*
* @package W3TC
*
* phpcs:disable PSR2.Methods.MethodDeclaration.Underscore,PSR2.Classes.PropertyDeclaration.Underscore,WordPress.PHP.DiscouragedPHPFunctions,WordPress.PHP.NoSilencedErrors
*/
namespace W3TC;
/**
* Redis cache engine.
*/
class Cache_Redis extends Cache_Base {
/**
* Accessors.
*
* @var array
*/
private $_accessors = array();
/**
* Key value.
*
* @var array
*/
private $_key_version = array();
/**
* Persistent.
*
* @var bool
*/
private $_persistent;
/**
* Password.
*
* @var string
*/
private $_password;
/**
* Servers.
*
* @var array
*/
private $_servers;
/**
* Verify TLS certificate.
*
* @var bool
*/
private $_verify_tls_certificates;
/**
* DB id.
*
* @var string
*/
private $_dbid;
/**
* Timeout.
*
* @var int.
*/
private $_timeout;
/**
* Retry interval.
*
* @var int
*/
private $_retry_interval;
/**
* Retry timeout.
*
* @var int
*/
private $_read_timeout;
/**
* Constructor.
*
* @param array $config Config.
*/
public function __construct( $config ) {
parent::__construct( $config );
$this->_persistent = ( isset( $config['persistent'] ) && $config['persistent'] );
$this->_servers = (array) $config['servers'];
$this->_verify_tls_certificates = ( isset( $config['verify_tls_certificates'] ) && $config['verify_tls_certificates'] );
$this->_password = $config['password'];
$this->_dbid = $config['dbid'];
$this->_timeout = $config['timeout'];
$this->_retry_interval = $config['retry_interval'];
$this->_read_timeout = $config['read_timeout'];
/**
* When disabled - no extra requests are made to obtain key version,
* but flush operations not supported as a result group should be always empty.
*/
if ( isset( $config['key_version_mode'] ) && 'disabled' === $config['key_version_mode'] ) {
$this->_key_version[''] = 1;
}
}
/**
* Adds data.
*
* @param string $key Key.
* @param mixed $var Var.
* @param integer $expire Expire.
* @param string $group Used to differentiate between groups of cache values.
* @return bool
*/
public function add( $key, &$var, $expire = 0, $group = '' ) {
return $this->set( $key, $var, $expire, $group );
}
/**
* Sets data.
*
* @param string $key Key.
* @param mixed $value Value.
* @param integer $expire Expire.
* @param string $group Used to differentiate between groups of cache values.
* @return bool
*/
public function set( $key, $value, $expire = 0, $group = '' ) {
$value['key_version'] = $this->_get_key_version( $group );
$storage_key = $this->get_item_key( $key );
$accessor = $this->_get_accessor( $storage_key );
if ( is_null( $accessor ) ) {
return false;
}
if ( ! $expire ) {
return $accessor->set( $storage_key, serialize( $value ) );
}
return $accessor->setex( $storage_key, $expire, serialize( $value ) );
}
/**
* Returns data
*
* @param string $key Key.
* @param string $group Used to differentiate between groups of cache values.
* @return mixed
*/
public function get_with_old( $key, $group = '' ) {
$has_old_data = false;
$storage_key = $this->get_item_key( $key );
$accessor = $this->_get_accessor( $storage_key );
if ( is_null( $accessor ) ) {
return array( null, false );
}
$v = $accessor->get( $storage_key );
$v = @unserialize( $v );
if ( ! is_array( $v ) || ! isset( $v['key_version'] ) ) {
return array( null, $has_old_data );
}
$key_version = $this->_get_key_version( $group );
if ( $v['key_version'] === $key_version ) {
return array( $v, $has_old_data );
}
if ( $v['key_version'] > $key_version ) {
$this->_set_key_version( $v['key_version'], $group );
return array( $v, $has_old_data );
}
// Key version is old.
if ( ! $this->_use_expired_data ) {
return array( null, $has_old_data );
}
// If we have expired data - update it for future use and let current process recalculate it.
$expires_at = isset( $v['expires_at'] ) ? $v['expires_at'] : null;
if ( is_null( $expires_at ) || time() > $expires_at ) {
$v['expires_at'] = time() + 30;
$accessor->setex( $storage_key, 60, serialize( $v ) );
$has_old_data = true;
return array( null, $has_old_data );
}
// Return old version.
return array( $v, $has_old_data );
}
/**
* Replaces data.
*
* @param string $key Key.
* @param mixed $value Value.
* @param integer $expire Expire.
* @param string $group Used to differentiate between groups of cache values.
* @return bool
*/
public function replace( $key, &$value, $expire = 0, $group = '' ) {
return $this->set( $key, $value, $expire, $group );
}
/**
* Deletes data.
*
* @param string $key Key.
* @param string $group Group.
* @return bool
*/
public function delete( $key, $group = '' ) {
$storage_key = $this->get_item_key( $key );
$accessor = $this->_get_accessor( $storage_key );
if ( is_null( $accessor ) ) {
return false;
}
if ( $this->_use_expired_data ) {
$v = $accessor->get( $storage_key );
$ttl = $accessor->ttl( $storage_key );
if ( is_array( $v ) ) {
$v['key_version'] = 0;
$accessor->setex( $storage_key, $ttl, $v );
return true;
}
}
return $accessor->setex( $storage_key, 1, '' );
}
/**
* Key to delete, deletes _old and primary if exists.
*
* @param string $key Key.
* @param string $group Group.
* @return bool
*/
public function hard_delete( $key, $group = '' ) {
$storage_key = $this->get_item_key( $key );
$accessor = $this->_get_accessor( $storage_key );
if ( is_null( $accessor ) ) {
return false;
}
return $accessor->setex( $storage_key, 1, '' );
}
/**
* Flushes all data.
*
* @param string $group Used to differentiate between groups of cache values.
* @return bool
*/
public function flush( $group = '' ) {
$this->_get_key_version( $group ); // Initialize $this->_key_version.
if ( isset( $this->_key_version[ $group ] ) ) {
$this->_key_version[ $group ]++;
$this->_set_key_version( $this->_key_version[ $group ], $group );
}
return true;
}
/**
* Checks if engine can function properly in this environment.
*
* @return bool
*/
public function available() {
return class_exists( 'Redis' );
}
/**
* Get statistics.
*
* @return array
*/
public function get_statistics() {
$accessor = $this->_get_accessor( '' ); // Single-server mode used for stats.
if ( is_null( $accessor ) ) {
return array();
}
$a = $accessor->info();
return $a;
}
/**
* Returns key version.
*
* @param string $group Used to differentiate between groups of cache values.
* @return int
*/
private function _get_key_version( $group = '' ) {
if ( ! isset( $this->_key_version[ $group ] ) || $this->_key_version[ $group ] <= 0 ) {
$storage_key = $this->_get_key_version_key( $group );
$accessor = $this->_get_accessor( $storage_key );
if ( is_null( $accessor ) ) {
return 0;
}
$v_original = $accessor->get( $storage_key );
$v = intval( $v_original );
$v = ( $v > 0 ? $v : 1 );
if ( (string) $v_original !== (string) $v ) {
$accessor->set( $storage_key, $v );
}
$this->_key_version[ $group ] = $v;
}
return $this->_key_version[ $group ];
}
/**
* Sets new key version.
*
* @param string $v Version.
* @param string $group Used to differentiate between groups of cache values.
* @return bool
*/
private function _set_key_version( $v, $group = '' ) {
$storage_key = $this->_get_key_version_key( $group );
$accessor = $this->_get_accessor( $storage_key );
if ( is_null( $accessor ) ) {
return false;
}
$accessor->set( $storage_key, $v );
return true;
}
/**
* Used to replace as atomically as possible known value to new one.
*
* @param string $key Key.
* @param string $old_value Old value.
* @param string $new_value New value.
*/
public function set_if_maybe_equals( $key, $old_value, $new_value ) {
$storage_key = $this->get_item_key( $key );
$accessor = $this->_get_accessor( $storage_key );
if ( is_null( $accessor ) ) {
return false;
}
$accessor->watch( $storage_key );
$value = $accessor->get( $storage_key );
$value = @unserialize( $value );
if ( ! is_array( $value ) ) {
$accessor->unwatch();
return false;
}
if ( isset( $old_value['content'] ) && $value['content'] !== $old_value['content'] ) {
$accessor->unwatch();
return false;
}
return $accessor->multi()
->set( $storage_key, $new_value )
->exec();
}
/**
* Use key as a counter and add integet value to it.
*
* @param string $key Key.
* @param int $value Value.
*/
public function counter_add( $key, $value ) {
if ( empty( $value ) ) {
return true;
}
$storage_key = $this->get_item_key( $key );
$accessor = $this->_get_accessor( $storage_key );
if ( is_null( $accessor ) ) {
return false;
}
$r = $accessor->incrBy( $storage_key, $value );
if ( ! $r ) { // It doesn't initialize counter by itself.
$this->counter_set( $key, 0 );
}
return $r;
}
/**
* Use key as a counter and add integet value to it.
*
* @param string $key Key.
* @param int $value Value.
*/
public function counter_set( $key, $value ) {
$storage_key = $this->get_item_key( $key );
$accessor = $this->_get_accessor( $storage_key );
if ( is_null( $accessor ) ) {
return false;
}
return $accessor->set( $storage_key, $value );
}
/**
* Get counter's value.
*
* @param string $key Key.
*/
public function counter_get( $key ) {
$storage_key = $this->get_item_key( $key );
$accessor = $this->_get_accessor( $storage_key );
if ( is_null( $accessor ) ) {
return 0;
}
$v = (int) $accessor->get( $storage_key );
return $v;
}
/**
* Build Redis connection arguments based on server URI
*
* @param string $server Server URI to connect to.
*/
private function build_connect_args( $server ) {
$connect_args = array();
if ( substr( $server, 0, 5 ) === 'unix:' ) {
$connect_args[] = trim( substr( $server, 5 ) );
$connect_args[] = null; // port.
} else {
list( $ip, $port ) = Util_Content::endpoint_to_host_port( $server, null );
$connect_args[] = $ip;
$connect_args[] = $port;
}
$connect_args[] = $this->_timeout;
$connect_args[] = $this->_persistent ? $this->_instance_id . '_' . $this->_dbid : null;
$connect_args[] = $this->_retry_interval;
$phpredis_version = phpversion( 'redis' );
// The read_timeout parameter was added in phpredis 3.1.3.
if ( version_compare( $phpredis_version, '3.1.3', '>=' ) ) {
$connect_args[] = $this->_read_timeout;
}
// Support for stream context was added in phpredis 5.3.2.
if ( version_compare( $phpredis_version, '5.3.2', '>=' ) ) {
$context = array();
if ( 'tls:' === substr( $server, 0, 4 ) && ! $this->_verify_tls_certificates ) {
$context['stream'] = array(
'verify_peer' => false,
'verify_peer_name' => false,
);
}
$connect_args[] = $context;
}
return $connect_args;
}
/**
* Get accessor.
*
* @param string $key Key.
* @return object
*/
private function _get_accessor( $key ) {
if ( count( $this->_servers ) <= 1 ) {
$index = 0;
} else {
$index = crc32( $key ) % count( $this->_servers );
}
if ( isset( $this->_accessors[ $index ] ) ) {
return $this->_accessors[ $index ];
}
if ( ! isset( $this->_servers[ $index ] ) ) {
$this->_accessors[ $index ] = null;
} else {
try {
$server = $this->_servers[ $index ];
$connect_args = $this->build_connect_args( $server );
$accessor = new \Redis();
if ( $this->_persistent ) {
$accessor->pconnect( ...$connect_args );
} else {
$accessor->connect( ...$connect_args );
}
if ( ! empty( $this->_password ) ) {
$accessor->auth( $this->_password );
}
$accessor->select( $this->_dbid );
} catch ( \Exception $e ) {
error_log( $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
$accessor = null;
}
$this->_accessors[ $index ] = $accessor;
}
return $this->_accessors[ $index ];
}
}

View File

@ -0,0 +1,233 @@
<?php
namespace W3TC;
/**
* Wincache class
*/
class Cache_Wincache extends Cache_Base {
/*
* Used for faster flushing
*
* @var integer $_key_version
*/
private $_key_version = array();
/**
* Adds data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function add( $key, &$var, $expire = 0, $group = '' ) {
return $this->set( $key, $var, $expire, $group );
}
/**
* Sets data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function set( $key, $var, $expire = 0, $group = '' ) {
$var['key_version'] = $this->_get_key_version( $group );
$storage_key = $this->get_item_key( $key );
return wincache_ucache_set( $storage_key, serialize( $var ), $expire );
}
/**
* Returns data
*
* @param string $key
* @param string $group Used to differentiate between groups of cache values
* @return mixed
*/
function get_with_old( $key, $group = '' ) {
$has_old_data = false;
$storage_key = $this->get_item_key( $key );
$v = @unserialize( wincache_ucache_get( $storage_key ) );
if ( !is_array( $v ) || !isset( $v['key_version'] ) )
return array( null, $has_old_data );
$key_version = $this->_get_key_version( $group );
if ( $v['key_version'] == $key_version )
return array( $v, $has_old_data );
if ( $v['key_version'] > $key_version ) {
$this->_set_key_version( $v['key_version'], $group );
return array( $v, $has_old_data );
}
// key version is old
if ( !$this->_use_expired_data )
return array( null, $has_old_data );
// if we have expired data - update it for future use and let
// current process recalculate it
$expires_at = isset( $v['expires_at'] ) ? $v['expires_at'] : null;
if ( $expires_at == null || time() > $expires_at ) {
$v['expires_at'] = time() + 30;
wincache_ucache_set( $storage_key, serialize( $v ), 0 );
$has_old_data = true;
return array( null, $has_old_data );
}
// return old version
return array( $v, $has_old_data );
}
/**
* Replaces data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function replace( $key, &$var, $expire = 0, $group = '' ) {
if ( $this->get( $key, $group ) !== false ) {
return $this->set( $key, $var, $expire, $group );
}
return false;
}
/**
* Deletes data
*
* @param string $key
* @param string $group
* @return boolean
*/
function delete( $key, $group = '' ) {
$storage_key = $this->get_item_key( $key );
if ( $this->_use_expired_data ) {
$v = @unserialize( wincache_ucache_get( $storage_key ) );
if ( is_array( $v ) ) {
$v['key_version'] = 0;
wincache_ucache_set( $storage_key, serialize( $v ), 0 );
return true;
}
}
return wincache_ucache_delete( $storage_key );
}
/**
* Key to delete, deletes _old and primary if exists.
*
* @param unknown $key
* @return bool
*/
function hard_delete( $key, $group = '' ) {
$storage_key = $this->get_item_key( $key );
return wincache_ucache_delete( $storage_key );
}
/**
* Flushes all data
*
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function flush( $group = '' ) {
$this->_get_key_version( $group ); // initialize $this->_key_version
$this->_key_version[$group]++;
$this->_set_key_version( $this->_key_version[$group], $group );
return true;
}
/**
* Checks if engine can function properly in this environment
*
* @return bool
*/
public function available() {
return function_exists( 'wincache_ucache_set' );
}
/**
* Returns key postfix
*
* @param string $group Used to differentiate between groups of cache values
* @return integer
*/
private function _get_key_version( $group = '' ) {
if ( !isset( $this->_key_version[$group] ) || $this->_key_version[$group] <= 0 ) {
$v = wincache_ucache_get( $this->_get_key_version_key( $group ) );
$v = intval( $v );
$this->_key_version[$group] = ( $v > 0 ? $v : 1 );
}
return $this->_key_version[$group];
}
/**
* Sets new key version
*
* @param unknown $v
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
private function _set_key_version( $v, $group ) {
wincache_ucache_set( $this->_get_key_version_key( $group ), $v, 0 );
}
/**
* Used to replace as atomically as possible known value to new one
*/
public function set_if_maybe_equals( $key, $old_value, $new_value ) {
// cant guarantee atomic action here, filelocks fail often
$value = $this->get( $key );
if ( isset( $old_value['content'] ) &&
$value['content'] != $old_value['content'] )
return false;
return $this->set( $key, $new_value );
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_add( $key, $value ) {
if ( $value == 0 )
return true;
$storage_key = $this->get_item_key( $key );
$r = wincache_ucache_inc( $storage_key, $value );
if ( !$r ) // it doesnt initialize counter by itself
$this->counter_set( $key, 0 );
return $r;
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_set( $key, $value ) {
$storage_key = $this->get_item_key( $key );
return wincache_ucache_set( $storage_key, $value );
}
/**
* Get counter's value
*/
public function counter_get( $key ) {
$storage_key = $this->get_item_key( $key );
$v = (int)wincache_ucache_get( $storage_key );
return $v;
}
}

View File

@ -0,0 +1,237 @@
<?php
namespace W3TC;
/**
* XCache class
*/
class Cache_Xcache extends Cache_Base {
/*
* Used for faster flushing
*
* @var integer $_key_version
*/
private $_key_version = array();
/**
* Adds data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function add( $key, &$var, $expire = 0, $group = '' ) {
if ( $this->get( $key, $group ) === false ) {
return $this->set( $key, $var, $expire, $group );
}
return false;
}
/**
* Sets data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function set( $key, $var, $expire = 0, $group = '' ) {
$var['key_version'] = $this->_get_key_version( $group );
$storage_key = $this->get_item_key( $key );
return xcache_set( $storage_key, serialize( $var ), $expire );
}
/**
* Returns data
*
* @param string $key
* @param string $group Used to differentiate between groups of cache values
* @return mixed
*/
function get_with_old( $key, $group = '' ) {
$has_old_data = false;
$storage_key = $this->get_item_key( $key );
$v = @unserialize( xcache_get( $storage_key ) );
if ( !is_array( $v ) || !isset( $v['key_version'] ) )
return array( null, $has_old_data );
$key_version = $this->_get_key_version( $group );
if ( $v['key_version'] == $key_version )
return array( $v, $has_old_data );
if ( $v['key_version'] > $key_version ) {
$this->_set_key_version( $v['key_version'], $group );
return array( $v, $has_old_data );
}
// key version is old
if ( !$this->_use_expired_data )
return array( null, $has_old_data );
// if we have expired data - update it for future use and let
// current process recalculate it
$expires_at = isset( $v['expires_at'] ) ? $v['expires_at'] : null;
if ( $expires_at == null || time() > $expires_at ) {
$v['expires_at'] = time() + 30;
xcache_set( $storage_key, serialize( $v ), 0 );
$has_old_data = true;
return array( null, $has_old_data );
}
// return old version
return array( $v, $has_old_data );
}
/**
* Replaces data
*
* @param string $key
* @param mixed $var
* @param integer $expire
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function replace( $key, &$var, $expire = 0, $group = '' ) {
if ( $this->get( $key, $group ) !== false ) {
return $this->set( $key, $var, $expire, $group );
}
return false;
}
/**
* Deletes data
*
* @param string $key
* @param string $group
* @return boolean
*/
function delete( $key, $group = '' ) {
$storage_key = $this->get_item_key( $key );
if ( $this->_use_expired_data ) {
$v = @unserialize( xcache_get( $storage_key ) );
if ( is_array( $v ) ) {
$v['key_version'] = 0;
xcache_set( $storage_key, serialize( $v ), 0 );
return true;
}
}
return xcache_unset( $storage_key );
}
/**
* Key to delete, deletes _old and primary if exists.
*
* @param unknown $key
* @return bool
*/
function hard_delete( $key, $group = '' ) {
$storage_key = $this->get_item_key( $key );
return xcache_unset( $storage_key );
}
/**
* Flushes all data
*
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
function flush( $group = '' ) {
$this->_get_key_version( $group ); // initialize $this->_key_version
$this->_key_version[$group]++;
$this->_set_key_version( $this->_key_version[$group], $group );
return true;
}
/**
* Checks if engine can function properly in this environment
*
* @return bool
*/
public function available() {
return function_exists( 'xcache_set' );
}
/**
* Returns key postfix
*
* @param string $group Used to differentiate between groups of cache values
* @return integer
*/
private function _get_key_version( $group = '' ) {
if ( !isset( $this->_key_version[$group] ) || $this->_key_version[$group] <= 0 ) {
$v = xcache_get( $this->_get_key_version_key( $group ) );
$v = intval( $v );
$this->_key_version[$group] = ( $v > 0 ? $v : 1 );
}
return $this->_key_version[$group];
}
/**
* Sets new key version
*
* @param unknown $v
* @param string $group Used to differentiate between groups of cache values
* @return boolean
*/
private function _set_key_version( $v, $group = '' ) {
xcache_set( $this->_get_key_version_key( $group ), $v, 0 );
}
/**
* Used to replace as atomically as possible known value to new one
*/
public function set_if_maybe_equals( $key, $old_value, $new_value ) {
// cant guarantee atomic action here, filelocks fail often
$value = $this->get( $key );
if ( isset( $old_value['content'] ) &&
$value['content'] != $old_value['content'] )
return false;
return $this->set( $key, $new_value );
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_add( $key, $value ) {
if ( $value == 0 )
return true;
$storage_key = $this->get_item_key( $key );
$r = xcache_inc( $storage_key, $value );
if ( !$r ) // it doesnt initialize counter by itself
$this->counter_set( $key, 0 );
return $r;
}
/**
* Use key as a counter and add integet value to it
*/
public function counter_set( $key, $value ) {
$storage_key = $this->get_item_key( $key );
return xcache_set( $storage_key, $value );
}
/**
* Get counter's value
*/
public function counter_get( $key ) {
$storage_key = $this->get_item_key( $key );
$v = (int)xcache_get( $storage_key );
return $v;
}
}

View File

@ -0,0 +1,113 @@
<?php
/**
* File: CdnEngine.php
*
* @package W3TC
*/
namespace W3TC;
/**
* Class: CdnEngine
*/
class CdnEngine {
/**
* Returns CdnEngine_Base instance.
*
* @param string $engine CDN engine.
* @param array $config Configuration.
* @return CdnEngine_Base
*/
public static function instance( $engine, array $config = array() ) {
static $instances = array();
$instance_key = sprintf( '%s_%s', $engine, md5( serialize( $config ) ) );
if ( ! isset( $instances[ $instance_key ] ) ) {
switch ( $engine ) {
case 'akamai':
$instances[ $instance_key ] = new CdnEngine_Mirror_Akamai( $config );
break;
case 'att':
$instances[ $instance_key ] = new CdnEngine_Mirror_Att( $config );
break;
case 'azure':
$instances[ $instance_key ] = new CdnEngine_Azure( $config );
break;
case 'bunnycdn':
$instances[ $instance_key ] = new CdnEngine_Mirror_BunnyCdn( $config );
break;
case 'cf':
$instances[ $instance_key ] = new CdnEngine_CloudFront( $config );
break;
case 'cf2':
$instances[ $instance_key ] = new CdnEngine_Mirror_CloudFront( $config );
break;
case 'cotendo':
$instances[ $instance_key ] = new CdnEngine_Mirror_Cotendo( $config );
break;
case 'edgecast':
$instances[ $instance_key ] = new CdnEngine_Mirror_Edgecast( $config );
break;
case 'ftp':
$instances[ $instance_key ] = new CdnEngine_Ftp( $config );
break;
case 'google_drive':
$instances[ $instance_key ] = new CdnEngine_GoogleDrive( $config );
break;
case 'highwinds':
$instances[ $instance_key ] = new CdnEngine_Mirror_Highwinds( $config );
break;
case 'limelight':
$instances[ $instance_key ] = new CdnEngine_Mirror_LimeLight( $config );
break;
case 'mirror':
$instances[ $instance_key ] = new CdnEngine_Mirror( $config );
break;
case 'rackspace_cdn':
$instances[ $instance_key ] = new CdnEngine_Mirror_RackSpaceCdn( $config );
break;
case 'rscf':
$instances[ $instance_key ] = new CdnEngine_RackSpaceCloudFiles( $config );
break;
case 's3':
$instances[ $instance_key ] = new CdnEngine_S3( $config );
break;
case 's3_compatible':
$instances[ $instance_key ] = new CdnEngine_S3_Compatible( $config );
break;
case 'stackpath':
$instances[ $instance_key ] = new CdnEngine_Mirror_StackPath( $config );
break;
case 'stackpath2':
$instances[ $instance_key ] = new CdnEngine_Mirror_StackPath2( $config );
break;
default:
empty( $engine ) || trigger_error( 'Incorrect CDN engine', E_USER_WARNING );
$instances[ $instance_key ] = new CdnEngine_Base();
break;
}
}
return $instances[ $instance_key ];
}
}

View File

@ -0,0 +1,408 @@
<?php
namespace W3TC;
/**
* Windows Azure Storage CDN engine
*/
class CdnEngine_Azure extends CdnEngine_Base {
/**
* Storage client object
*
* @var Microsoft_WindowsAzure_Storage_Blob
*/
var $_client = null;
/**
* PHP5 Constructor
*
* @param array $config
*/
function __construct( $config = array() ) {
$config = array_merge( array(
'user' => '',
'key' => '',
'container' => '',
'cname' => array(),
), $config );
parent::__construct( $config );
require_once W3TC_LIB_DIR . DIRECTORY_SEPARATOR . 'Azure' .
DIRECTORY_SEPARATOR . 'loader.php';
}
/**
* Inits storage client object
*
* @param string $error
* @return boolean
*/
function _init( &$error ) {
if ( empty( $this->_config['user'] ) ) {
$error = 'Empty account name.';
return false;
}
if ( empty( $this->_config['key'] ) ) {
$error = 'Empty account key.';
return false;
}
if ( empty( $this->_config['container'] ) ) {
$error = 'Empty container name.';
return false;
}
try {
$connectionString = 'DefaultEndpointsProtocol=https;AccountName=' .
$this->_config['user'] .
';AccountKey=' . $this->_config['key'];
$this->_client = \MicrosoftAzure\Storage\Common\ServicesBuilder::getInstance()->createBlobService(
$connectionString);
} catch ( \Exception $ex ) {
$error = $ex->getMessage();
return false;
}
return true;
}
/**
* Uploads files to S3
*
* @param array $files
* @param array $results
* @param boolean $force_rewrite
* @return boolean
*/
function upload( $files, &$results, $force_rewrite = false,
$timeout_time = NULL ) {
$error = null;
if ( !$this->_init( $error ) ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, $error );
return false;
}
foreach ( $files as $file ) {
$remote_path = $file['remote_path'];
$local_path = $file['local_path'];
// process at least one item before timeout so that progress goes on
if ( !empty( $results ) ) {
if ( !is_null( $timeout_time ) && time() > $timeout_time ) {
return 'timeout';
}
}
$results[] = $this->_upload( $file, $force_rewrite );
}
return !$this->_is_error( $results );
}
/**
* Uploads file
*
* @param string $local_path
* @param string $remote_path
* @param bool $force_rewrite
* @return array
*/
function _upload( $file, $force_rewrite = false ) {
$local_path = $file['local_path'];
$remote_path = $file['remote_path'];
if ( !file_exists( $local_path ) ) {
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR, 'Source file not found.', $file );
}
$contents = @file_get_contents( $local_path );
$md5 = md5( $contents ); // @md5_file( $local_path );
$content_md5 = $this->_get_content_md5( $md5 );
if ( !$force_rewrite ) {
try {
$propertiesResult = $this->_client->getBlobProperties( $this->_config['container'], $remote_path );
$p = $propertiesResult->getProperties();
$local_size = @filesize( $local_path );
if ( $local_size == $p->getContentLength() && $content_md5 === $p->getContentMD5() ) {
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'File up-to-date.', $file );
}
} catch ( \Exception $exception ) {
}
}
$headers = $this->get_headers_for_file( $file );
try {
// $headers
$options = new \MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions();
$options->setBlobContentMD5( $content_md5 );
if ( isset( $headers['Content-Type'] ) )
$options->setBlobContentType( $headers['Content-Type'] );
if ( isset( $headers['Cache-Control'] ) )
$options->setBlobCacheControl( $headers['Cache-Control'] );
$this->_client->createBlockBlob( $this->_config['container'],
$remote_path, $contents, $options );
} catch ( \Exception $exception ) {
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR,
sprintf( 'Unable to put blob (%s).', $exception->getMessage() ),
$file );
}
return $this->_get_result( $local_path, $remote_path, W3TC_CDN_RESULT_OK,
'OK', $file );
}
/**
* Deletes files from storage
*
* @param array $files
* @param array $results
* @return boolean
*/
function delete( $files, &$results ) {
$error = null;
if ( !$this->_init( $error ) ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, $error );
return false;
}
foreach ( $files as $file ) {
$local_path = $file['local_path'];
$remote_path = $file['remote_path'];
try {
$r = $this->_client->deleteBlob( $this->_config['container'], $remote_path );
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'OK', $file );
} catch ( \Exception $exception ) {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR,
sprintf( 'Unable to delete blob (%s).', $exception->getMessage() ),
$file );
}
}
return !$this->_is_error( $results );
}
/**
* Tests S3
*
* @param string $error
* @return boolean
*/
function test( &$error ) {
if ( !parent::test( $error ) ) {
return false;
}
$string = 'test_azure_' . md5( time() );
if ( !$this->_init( $error ) ) {
return false;
}
try {
$containers = $this->_client->listContainers();
} catch ( \Exception $exception ) {
$error = sprintf( 'Unable to list containers (%s).', $exception->getMessage() );
return false;
}
$container = null;
foreach ( $containers->getContainers() as $_container ) {
if ( $_container->getName() == $this->_config['container'] ) {
$container = $_container;
break;
}
}
if ( !$container ) {
$error = sprintf( 'Container doesn\'t exist: %s.', $this->_config['container'] );
return false;
}
try {
$this->_client->createBlockBlob( $this->_config['container'], $string, $string );
} catch ( \Exception $exception ) {
$error = sprintf( 'Unable to create blob (%s).', $exception->getMessage() );
return false;
}
try {
$propertiesResult = $this->_client->getBlobProperties( $this->_config['container'], $string );
$p = $propertiesResult->getProperties();
$size = $p->getContentLength();
$md5 = $p->getContentMD5();
} catch ( \Exception $exception ) {
$error = sprintf( 'Unable to get blob properties (%s).', $exception->getMessage() );
return false;
}
if ( $size != strlen( $string ) || $this->_get_content_md5( md5( $string ) ) != $md5 ) {
try {
$this->_client->deleteBlob( $this->_config['container'], $string );
} catch ( \Exception $exception ) {
}
$error = 'Blob data properties are not equal.';
return false;
}
try {
$getBlob = $this->_client->getBlob( $this->_config['container'], $string );
$dataStream = $getBlob->getContentStream();
$data = stream_get_contents( $dataStream );
} catch ( \Exception $exception ) {
$error = sprintf( 'Unable to get blob data (%s).', $exception->getMessage() );
return false;
}
if ( $data != $string ) {
try {
$this->_client->deleteBlob( $this->_config['container'], $string );
} catch ( \Exception $exception ) {
}
$error = 'Blob datas are not equal.';
return false;
}
try {
$this->_client->deleteBlob( $this->_config['container'], $string );
} catch ( \Exception $exception ) {
$error = sprintf( 'Unable to delete blob (%s).', $exception->getMessage() );
return false;
}
return true;
}
/**
* Returns CDN domain
*
* @return array
*/
function get_domains() {
if ( !empty( $this->_config['cname'] ) ) {
return (array) $this->_config['cname'];
} elseif ( !empty( $this->_config['user'] ) ) {
$domain = sprintf( '%s.blob.core.windows.net', $this->_config['user'] );
return array(
$domain
);
}
return array();
}
/**
* Returns via string
*
* @return string
*/
function get_via() {
return sprintf( 'Windows Azure Storage: %s', parent::get_via() );
}
/**
* Creates bucket
*
* @param string $error
* @return boolean
*/
function create_container() {
if ( !$this->_init( $error ) ) {
throw new \Exception( $error );
}
try {
$containers = $this->_client->listContainers();
} catch ( \Exception $exception ) {
$error = sprintf( 'Unable to list containers (%s).', $exception->getMessage() );
throw new \Exception( $error );
}
if ( in_array( $this->_config['container'], (array) $containers ) ) {
$error = sprintf( 'Container already exists: %s.', $this->_config['container'] );
throw new \Exception( $error );
}
try {
$createContainerOptions = new \MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions();
$createContainerOptions->setPublicAccess(
\MicrosoftAzure\Storage\Blob\Models\PublicAccessType::CONTAINER_AND_BLOBS );
$this->_client->createContainer( $this->_config['container'], $createContainerOptions );
} catch ( \Exception $exception ) {
$error = sprintf( 'Unable to create container: %s (%s)', $this->_config['container'], $exception->getMessage() );
throw new \Exception( $error );
}
}
/**
* Returns Content-MD5 header value
*
* @param string $string
* @return string
*/
function _get_content_md5( $md5 ) {
return base64_encode( pack( 'H*', $md5 ) );
}
/**
* Formats object URL
*
* @param string $path
* @return string
*/
function _format_url( $path ) {
$domain = $this->get_domain( $path );
if ( $domain && !empty( $this->_config['container'] ) ) {
$scheme = $this->_get_scheme();
$url = sprintf( '%s://%s/%s/%s', $scheme, $domain, $this->_config['container'], $path );
return $url;
}
return false;
}
/**
* How and if headers should be set
*
* @return string W3TC_CDN_HEADER_NONE, W3TC_CDN_HEADER_UPLOADABLE, W3TC_CDN_HEADER_MIRRORING
*/
function headers_support() {
return W3TC_CDN_HEADER_UPLOADABLE;
}
function get_prepend_path( $path ) {
$path = parent::get_prepend_path( $path );
$path = $this->_config['container'] ? trim( $path, '/' ) . '/' . trim( $this->_config['container'], '/' ): $path;
return $path;
}
}

View File

@ -0,0 +1,649 @@
<?php
namespace W3TC;
/**
* W3 CDN Base class
*/
define( 'W3TC_CDN_RESULT_HALT', -1 );
define( 'W3TC_CDN_RESULT_ERROR', 0 );
define( 'W3TC_CDN_RESULT_OK', 1 );
define( 'W3TC_CDN_HEADER_NONE', 'none' );
define( 'W3TC_CDN_HEADER_UPLOADABLE', 'uploadable' );
define( 'W3TC_CDN_HEADER_MIRRORING', 'mirroring' );
/**
* class CdnEngine_Base
*/
class CdnEngine_Base {
/**
* Engine configuration
*
* @var array
*/
var $_config = array();
/**
* gzip extension
*
* @var string
*/
var $_gzip_extension = '.gzip';
/**
* Last error
*
* @var string
*/
var $_last_error = '';
/**
* PHP5 Constructor
*
* @param array $config
*/
function __construct( $config = array() ) {
$this->_config = array_merge( array(
'debug' => false,
'ssl' => 'auto',
'compression' => false,
'headers' => array()
), $config );
}
/**
* Upload files to CDN
*
* @param array $files takes array consisting of array(array('local_path'=>'', 'remote_path'=>''))
* @param array $results
* @param boolean $force_rewrite
* @return boolean
*/
function upload( $files, &$results, $force_rewrite = false,
$timeout_time = NULL ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT,
'Not implemented.' );
return false;
}
/**
* Delete files from CDN
*
* @param array $files
* @param array $results
* @return boolean
*/
function delete( $files, &$results ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT,
'Not implemented.' );
return false;
}
/**
* Purge files from CDN
*
* @param array $files
* @param array $results
* @return boolean
*/
function purge( $files, &$results ) {
return $this->upload( $files, $results, true );
}
/**
* Purge CDN completely
*
* @param unknown $results
* @return bool
*/
function purge_all( &$results ) {
$results = $this->_get_results( array(), W3TC_CDN_RESULT_HALT,
'Not implemented.' );
return false;
}
/**
* Test CDN server
*
* @param string $error
* @return boolean
*/
function test( &$error ) {
if ( !$this->_test_domains( $error ) ) {
return false;
}
return true;
}
/**
* Create bucket / container for some CDN engines
*/
function create_container() {
throw new \Exception( 'Not implemented.' );
}
/**
* Returns first domain
*
* @param string $path
* @return string
*/
function get_domain( $path = '' ) {
$domains = $this->get_domains();
$count = count( $domains );
if ( $count ) {
switch ( true ) {
/**
* Reserved CSS
*/
case ( isset( $domains[0] ) && $this->_is_css( $path ) ):
$domain = $domains[0];
break;
/**
* Reserved JS after body
*/
case ( isset( $domains[2] ) && $this->_is_js_body( $path ) ):
$domain = $domains[2];
break;
/**
* Reserved JS before /body
*/
case ( isset( $domains[3] ) && $this->_is_js_footer( $path ) ):
$domain = $domains[3];
break;
/**
* Reserved JS in head, moved here due to greedy regex
*/
case ( isset( $domains[1] ) && $this->_is_js( $path ) ):
$domain = $domains[1];
break;
default:
if ( !isset( $domains[0] ) ) {
$scheme = $this->_get_scheme();
if ( 'https' == $scheme && !empty( $domains['https_default'] ) ) {
return $domains['https_default'];
} else {
return isset( $domains['http_default'] ) ? $domains['http_default'] :
$domains['https_default'];
}
} elseif ( $count > 4 ) {
$domain = $this->_get_domain( array_slice( $domains, 4 ),
$path );
} else {
$domain = $this->_get_domain( $domains, $path );
}
}
/**
* Custom host for SSL
*/
list( $domain_http, $domain_https ) = array_map( 'trim',
explode( ',', $domain . ',' ) );
$scheme = $this->_get_scheme();
switch ( $scheme ) {
case 'http':
$domain = $domain_http;
break;
case 'https':
$domain = ( $domain_https ? $domain_https : $domain_http );
break;
}
return $domain;
}
return false;
}
/**
* Returns array of CDN domains
*
* @return array
*/
function get_domains() {
return array();
}
/**
* Returns via string
*
* @return string
*/
function get_via() {
$domain = $this->get_domain();
if ( $domain ) {
return $domain;
}
return 'N/A';
}
/**
* Formats URL
*
* @param string $path
* @return string
*/
function format_url( $path ) {
$url = $this->_format_url( $path );
if ( $url && $this->_config['compression'] && ( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ? stristr( sanitize_text_field( wp_unslash( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ), 'gzip' ) !== false : false ) && $this->_may_gzip( $path ) ) {
if ( ( $qpos = strpos( $url, '?' ) ) !== false ) {
$url = substr_replace( $url, $this->_gzip_extension, $qpos, 0 );
} else {
$url .= $this->_gzip_extension;
}
}
return $url;
}
/**
* Returns prepend path
*
* @param string $path
* @return string
*/
function get_prepend_path( $path ) {
$domain = $this->get_domain( $path );
if ( $domain ) {
$scheme = $this->_get_scheme();
$url = sprintf( '%s://%s', $scheme, $domain );
return $url;
}
return false;
}
/**
* Formats URL
*
* @param string $path
* @return string
*/
function _format_url( $path ) {
$domain = $this->get_domain( $path );
if ( $domain ) {
$scheme = $this->_get_scheme();
$url = sprintf( '%s://%s/%s', $scheme, $domain, $path );
return $url;
}
return false;
}
/**
* Returns results
*
* @param array $files
* @param integer $result
* @param string $error
* @return array
*/
function _get_results( $files, $result = W3TC_CDN_RESULT_OK,
$error = 'OK' ) {
$results = array();
foreach ( $files as $key => $file ) {
if ( is_array( $file ) ) {
$local_path = $file['local_path'];
$remote_path = $file['remote_path'];
} else {
$local_path = $key;
$remote_path = $file;
}
$results[] = $this->_get_result( $local_path, $remote_path, $result,
$error, $file );
}
return $results;
}
/**
* Returns file process result
*
* @param string $local_path
* @param string $remote_path
* @param integer $result
* @param string $error
* @return array
*/
function _get_result( $local_path, $remote_path,
$result = W3TC_CDN_RESULT_OK, $error = 'OK', $descriptor = null ) {
if ( $this->_config['debug'] ) {
$this->_log( $local_path, $remote_path, $error );
}
return array(
'local_path' => $local_path,
'remote_path' => $remote_path,
'result' => $result,
'error' => $error,
'descriptor' => $descriptor
);
}
/**
* Check for errors
*
* @param array $results
* @return bool
*/
function _is_error( $results ) {
foreach ( $results as $result ) {
if ( $result['result'] != W3TC_CDN_RESULT_OK ) {
return true;
}
}
return false;
}
/**
* Returns headers for file
*
* @param array $file CDN file array
* @param array $whitelist which expensive headers to calculate
* @return array
*/
function get_headers_for_file( $file, $whitelist = array() ) {
$local_path = $file['local_path'];
$mime_type = Util_Mime::get_mime_type( $local_path );
$link = $file['original_url'];
$headers = array(
'Content-Type' => $mime_type,
'Last-Modified' => Util_Content::http_date( time() ),
'Access-Control-Allow-Origin' => '*',
'Link' => '<' . $link .'>; rel="canonical"'
);
$section = Util_Mime::mime_type_to_section( $mime_type );
if ( isset( $this->_config['headers'][$section] ) ) {
$hc = $this->_config['headers'][$section];
if ( isset( $whitelist['ETag'] ) && $hc['etag'] ) {
$headers['ETag'] = '"' . @md5_file( $local_path ) . '"';
}
if ( $hc['expires'] ) {
$headers['Expires'] = Util_Content::http_date( time() +
$hc['lifetime'] );
$expires_set = true;
}
$headers = array_merge( $headers, $hc['static'] );
}
return $headers;
}
/**
* Use gzip compression only for text-based files
*
* @param string $file
* @return boolean
*/
function _may_gzip( $file ) {
/**
* Remove query string
*/
$file = preg_replace( '~\?.*$~', '', $file );
/**
* Check by file extension
*/
if ( preg_match( '~\.(ico|js|css|xml|xsd|xsl|svg|htm|html|txt)$~i',
$file ) ) {
return true;
}
return false;
}
/**
* Test domains
*
* @param string $error
* @return boolean
*/
function _test_domains( &$error ) {
$domains = $this->get_domains();
if ( !count( $domains ) ) {
$error = 'Empty hostname / CNAME list.';
return false;
}
foreach ( $domains as $domain ) {
$_domains = array_map( 'trim', explode( ',', $domain ) );
foreach ( $_domains as $_domain ) {
$matches = null;
if ( preg_match( '~^([a-z0-9\-\.]*)~i', $_domain, $matches ) ) {
$hostname = $matches[1];
} else {
$hostname = $_domain;
}
if ( empty( $hostname ) ) {
continue;
}
if ( gethostbyname( $hostname ) === $hostname ) {
$error = sprintf( 'Unable to resolve hostname: %s.',
$hostname );
return false;
}
}
}
return true;
}
/**
* Check if css file
*
* @param string $path
* @return boolean
*/
function _is_css( $path ) {
return preg_match( '~[a-zA-Z0-9\-_]*(\.include\.[0-9]+)?\.css$~',
$path );
}
/**
* Check if JS file in heeader
*
* @param string $path
* @return boolean
*/
function _is_js( $path ) {
return preg_match( '~([a-z0-9\-_]+(\.include\.[a-z0-9]+)\.js)$~',
$path ) ||
preg_match( '~[\w\d\-_]+\.js~', $path );
}
/**
* Check if JS file after body
*
* @param string $path
* @return boolean
*/
function _is_js_body( $path ) {
return preg_match( '~[a-z0-9\-_]+(\.include-body\.[a-z0-9]+)\.js$~',
$path );
}
/**
* Check if JS file before /body
*
* @param string $path
* @return boolean
*/
function _is_js_footer( $path ) {
return preg_match( '~[a-z0-9\-_]+(\.include-footer\.[a-z0-9]+)\.js$~',
$path );
}
/**
* Returns domain for path
*
* @param array $domains
* @param string $path
* @return string
*/
function _get_domain( $domains, $path ) {
$count = count( $domains );
if ( isset( $domains['http_default'] ) )
$count--;
if ( isset( $domains['https_default'] ) )
$count--;
if ( $count ) {
/**
* Use for equal URLs same host to allow caching by browser
*/
$hash = $this->_get_hash( $path );
$domain = $domains[$hash % $count];
return $domain;
}
return false;
}
/**
* Returns integer hash for key
*
* @param string $key
* @return integer
*/
function _get_hash( $key ) {
$hash = abs( crc32( $key ) );
return $hash;
}
/**
* Returns scheme
*
* @return string
*/
function _get_scheme() {
switch ( $this->_config['ssl'] ) {
default:
case 'auto':
$scheme = ( Util_Environment::is_https() ? 'https' : 'http' );
break;
case 'enabled':
$scheme = 'https';
break;
case 'disabled':
$scheme = 'http';
break;
case 'rejected':
$scheme = 'http';
break;
}
return $scheme;
}
/**
* Write log entry
*
* @param string $local_path
* @param string $remote_path
* @param string $error
* @return bool|int
*/
function _log( $local_path, $remote_path, $error ) {
$data = sprintf( "[%s] [%s => %s] %s\n", date( 'r' ), $local_path,
$remote_path, $error );
$data = strtr( $data, '<>', '..' );
$filename = Util_Debug::log_filename( 'cdn' );
return @file_put_contents( $filename, $data, FILE_APPEND );
}
/**
* Our error handler
*
* @param integer $errno
* @param string $errstr
* @return boolean
*/
function _error_handler( $errno, $errstr ) {
$this->_last_error = $errstr;
return false;
}
/**
* Returns last error
*
* @return string
*/
function _get_last_error() {
return $this->_last_error;
}
/**
* Set our error handler
*
* @return void
*/
function _set_error_handler() {
set_error_handler( array(
$this,
'_error_handler'
) );
}
/**
* Restore prev error handler
*
* @return void
*/
function _restore_error_handler() {
restore_error_handler();
}
/**
* How and if headers should be set
*
* @return string W3TC_CDN_HEADER_NONE, W3TC_CDN_HEADER_UPLOADABLE,
* W3TC_CDN_HEADER_MIRRORING
*/
function headers_support() {
return W3TC_CDN_HEADER_NONE;
}
}

View File

@ -0,0 +1,368 @@
<?php
namespace W3TC;
if ( !defined( 'W3TC_SKIPLIB_AWS' ) ) {
require_once W3TC_DIR . '/vendor/autoload.php';
}
/**
* Amazon CloudFront (S3 origin) CDN engine
*/
class CdnEngine_CloudFront extends CdnEngine_Base {
private $s3;
private $api;
function __construct( $config = array() ) {
$config = array_merge( array(
'id' => ''
), $config );
parent::__construct( $config );
$this->s3 = new CdnEngine_S3( $config );
}
/**
* Initialize
*/
function _init() {
if ( !is_null( $this->api ) ) {
return;
}
if ( empty( $this->_config['key'] ) && empty( $this->_config['secret'] ) ) {
$credentials = \Aws\Credentials\CredentialProvider::defaultProvider();
} else {
$credentials = new \Aws\Credentials\Credentials(
$this->_config['key'],
$this->_config['secret'] );
}
$this->api = new \Aws\CloudFront\CloudFrontClient( array(
'credentials' => $credentials,
'region' => $this->_config['bucket_location'],
'version' => '2018-11-05'
)
);
return true;
}
/**
* Formats URL
*/
function _format_url( $path ) {
$domain = $this->get_domain( $path );
if ( $domain ) {
$scheme = $this->_get_scheme();
// it does not support '+', requires '%2B'
$path = str_replace( '+', '%2B', $path );
$url = sprintf( '%s://%s/%s', $scheme, $domain, $path );
return $url;
}
return false;
}
/**
* Upload files
*
* @param array $files
* @param array $results
* @param boolean $force_rewrite
* @return boolean
*/
function upload( $files, &$results, $force_rewrite = false,
$timeout_time = NULL ) {
return $this->s3->upload( $files, $results, $force_rewrite,
$timeout_time );
}
/**
* Delete files from CDN
*
* @param array $files
* @param array $results
* @return boolean
*/
function delete( $files, &$results ) {
return $this->s3->delete( $files, $results );
}
/**
* Purge files from CDN
*
* @param array $files
* @param array $results
* @return boolean
*/
function purge( $files, &$results ) {
if ( !$this->s3->upload( $files, $results, true ) ) {
return false;
}
try {
$this->_init();
$dist = $this->_get_distribution();
} catch ( \Exception $ex ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, $ex->getMessage() );
return false;
}
$paths = array();
foreach ( $files as $file ) {
$remote_file = $file['remote_path'];
$paths[] = '/' . $remote_file;
}
try {
$invalidation = $this->api->createInvalidation( array(
'DistributionId' => $dist['Id'],
'InvalidationBatch' => array(
'CallerReference' => 'w3tc-' . microtime(),
'Paths' => array(
'Items' => $paths,
'Quantity' => count( $paths ),
),
)
)
);
} catch ( \Exception $ex ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT,
sprintf( 'Unable to create invalidation batch (%s).',
$ex->getMessage() ) );
return false;
}
$results = $this->_get_results( $files, W3TC_CDN_RESULT_OK, 'OK' );
return true;
}
/**
* Returns origin
*/
function _get_origin() {
if ( $this->_config['bucket_location'] === 'us-east-1' ) {
$region = "";
} else {
$region = $this->_config['bucket_location'] . '.';
}
return sprintf( '%s.s3.%samazonaws.com', $this->_config['bucket'], $region );
}
/**
* Returns array of CDN domains
*/
public function get_domains() {
if ( !empty( $this->_config['cname'] ) ) {
return (array) $this->_config['cname'];
} elseif ( !empty( $this->_config['id'] ) ) {
$domain = sprintf( '%s.cloudfront.net', $this->_config['id'] );
return array(
$domain
);
}
return array();
}
/**
* Test CDN connectivity
*/
function test( &$error ) {
$this->_init();
if ( !$this->s3->test( $error ) ) {
return false;
}
/**
* Search active CF distribution
*/
$dists = $this->api->listDistributions();
if ( !isset( $dists['DistributionList']['Items'] ) ) {
$error = 'Unable to list distributions.';
return false;
}
if ( !count( $dists['DistributionList']['Items'] ) ) {
$error = 'No distributions found.';
return false;
}
$dist = $this->_get_distribution( $dists );
if ( $dist["Status"] != 'Deployed' ) {
$error = sprintf( 'Distribution status is not Deployed, but "%s".', $dist["Status"] );
return false;
}
if ( !$dist['Enabled'] ) {
$error = sprintf( 'Distribution for origin "%s" is disabled.', $origin );
return false;
}
if ( !empty( $this->_config['cname'] ) ) {
$domains = (array) $this->_config['cname'];
$cnames = ( isset( $dist['Aliases']['Items'] ) ? (array) $dist['Aliases']['Items'] : array() );
foreach ( $domains as $domain ) {
$_domains = array_map( 'trim', explode( ',', $domain ) );
foreach ( $_domains as $_domain ) {
if ( !in_array( $_domain, $cnames ) ) {
$error = sprintf( 'Domain name %s is not in distribution <acronym title="Canonical Name">CNAME</acronym> list.', $_domain );
return false;
}
}
}
} elseif ( !empty( $this->_config['id'] ) ) {
$domain = $this->get_domain();
if ( $domain != $dist['DomainName'] ) {
$error = sprintf( 'Distribution domain name mismatch (%s != %s).', $domain, $dist['DomainName'] );
return false;
}
}
return true;
}
/**
* Create bucket
*/
function create_container() {
$this->_init();
$this->s3->create_container();
// plugin cant set CNAMEs list since it CloudFront requires
// certificate to be specified associated with it
$cnames = array();
// make distibution
$originDomain = $this->_get_origin();
try {
$result = $this->api->createDistribution(array(
'DistributionConfig' => array(
'CallerReference' => $originDomain,
'Comment' => 'Created by W3-Total-Cache',
'DefaultCacheBehavior' => array(
'AllowedMethods' => array(
'CachedMethods' => array(
'Items' => array( 'HEAD', 'GET' ),
'Quantity' => 2,
),
'Items' => array( 'HEAD', 'GET' ),
'Quantity' => 2,
),
'Compress' => true,
'DefaultTTL' => 86400,
'FieldLevelEncryptionId' => '',
'ForwardedValues' => array(
'Cookies' => array(
'Forward' => 'none',
),
'Headers' => array(
'Quantity' => 0,
),
'QueryString' => false,
'QueryStringCacheKeys' => array(
'Quantity' => 0,
),
),
'LambdaFunctionAssociations' => array( 'Quantity' => 0),
'MinTTL' => 0,
'SmoothStreaming' => false,
'TargetOriginId' => $originDomain,
'TrustedSigners' => array(
'Enabled' => false,
'Quantity' => 0,
),
'ViewerProtocolPolicy' => 'allow-all',
),
'Enabled' => true,
'Origins' => array(
'Items' => array(
array(
'DomainName' => $originDomain,
'Id' => $originDomain,
'OriginPath' => '',
'CustomHeaders' => array( 'Quantity' => 0 ),
'S3OriginConfig' => array(
'OriginAccessIdentity' => ''
),
),
),
'Quantity' => 1,
),
'Aliases' => array(
'Items' => $cnames,
'Quantity' => count( $cnames )
)
)
));
// extract domain dynamic part stored later in a config
$domain = $result['Distribution']['DomainName'];
$container_id = '';
if ( preg_match( '~^(.+)\.cloudfront\.net$~', $domain, $matches ) ) {
$container_id = $matches[1];
}
return $container_id;
} catch ( \Exception $ex ) {
throw new \Exception( sprintf(
'Unable to create distribution for origin %s: %s', $originDomain,
$ex->getMessage() ) );
}
}
/**
* Returns via string
*
* @return string
*/
function get_via() {
$domain = $this->get_domain();
$via = ( $domain ? $domain : 'N/A' );
return sprintf( 'Amazon Web Services: CloudFront: %s', $via );
}
private function _get_distribution( $dists = null ) {
if ( is_null( $dists ) ) {
$dists = $this->api->listDistributions();
}
if ( !isset( $dists['DistributionList']['Items'] ) ||
!count( $dists['DistributionList']['Items'] ) ) {
throw new \Exception( 'No distributions found.' );
}
$dist = false;
$origin = $this->_get_origin();
$items = $dists['DistributionList']['Items'];
foreach ( $items as $dist ) {
if ( isset( $dist['Origins']['Items'] ) ) {
foreach ( $dist['Origins']['Items'] as $o ) {
if ( isset( $o['DomainName'] ) && $o['DomainName'] == $origin ) {
return $dist;
}
}
}
}
throw new \Exception( sprintf( 'Distribution for origin "%s" not found.', $origin ) );
}
}

View File

@ -0,0 +1,684 @@
<?php
namespace W3TC;
/**
* W3 CDN FTP Class
*/
define( 'W3TC_CDN_FTP_CONNECT_TIMEOUT', 30 );
/**
* class CdnEngine_Ftp
*/
class CdnEngine_Ftp extends CdnEngine_Base {
/**
* FTP resource
*
* @var resource
*/
var $_ftp = null;
/**
* PHP5 Constructor
*
* @param array $config
*/
function __construct( $config = array() ) {
$config = array_merge( array(
'host' => '',
'type' => '',
'user' => '',
'pass' => '',
'default_keys' => false,
'pubkey' => '',
'privkey' => '',
'path' => '',
'pasv' => false,
'domain' => array(),
'docroot' => ''
), $config );
list( $ip, $port ) = Util_Content::endpoint_to_host_port( $config['host'], 21 );
$config['host'] = $ip;
$config['port'] = $port;
if ( 'sftp' === $config['type'] && $config['default_keys'] ) {
$home = isset( $_SERVER['HOME'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HOME'] ) ) : '';
$config['pubkey'] = $home . '/.ssh/id_rsa.pub';
$config['privkey'] = $home . '/.ssh/id_rsa';
}
parent::__construct( $config );
}
/**
* Connects to FTP server
*
* @param string $error
* @return boolean
*/
function _connect( &$error ) {
if ( empty( $this->_config['host'] ) ) {
$error = 'Empty host.';
return false;
}
$this->_set_error_handler();
if ( $this->_config['type'] == 'sftp' ) {
if ( !function_exists( 'ssh2_connect' ) ) {
$error = sprintf('Missing required php-ssh2 extension.');
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
$this->_ftp = @ssh2_connect( $this->_config['host'], (int) $this->_config['port'] );
return $this->_connect_sftp( $error );
}
if ( $this->_config['type'] == 'ftps' )
$this->_ftp = @ftp_ssl_connect( $this->_config['host'],
(int) $this->_config['port'], W3TC_CDN_FTP_CONNECT_TIMEOUT );
else
$this->_ftp = @ftp_connect( $this->_config['host'],
(int) $this->_config['port'], W3TC_CDN_FTP_CONNECT_TIMEOUT );
if ( !$this->_ftp ) {
$error = sprintf( 'Unable to connect to %s:%d (%s).',
$this->_config['host'], $this->_config['port'],
$this->_get_last_error() );
$this->_restore_error_handler();
return false;
}
if ( !@ftp_login( $this->_ftp, $this->_config['user'], $this->_config['pass'] ) ) {
$error = sprintf( 'Incorrect login or password (%s).', $this->_get_last_error() );
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
if ( !@ftp_pasv( $this->_ftp, $this->_config['pasv'] ) ) {
$error = sprintf( 'Unable to change mode to passive (%s).', $this->_get_last_error() );
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
if ( !empty( $this->_config['path'] ) && !@ftp_chdir( $this->_ftp, $this->_config['path'] ) ) {
$error = sprintf( 'Unable to change directory to: %s (%s).', $this->_config['path'], $this->_get_last_error() );
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
$this->_restore_error_handler();
return true;
}
/**
* Connects to SFTP server
*
* @param string $error
* @return boolean
*/
function _connect_sftp( &$error ) {
if ( is_file( $this->_config['pass'] ) ) {
if ( !@ssh2_auth_pubkey_file( $this->_ftp, $this->_config['user'], $this->_config['pubkey'], $this->_config['privkey'], $this->_config['pass'] ) ) {
$error = sprintf('Public key authentication failed (%s).', $this->_get_last_error());
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
} else {
if ( !@ssh2_auth_password( $this->_ftp, $this->_config['user'], $this->_config['pass'] ) ) {
$error = sprintf('Incorrect login or password (%s).', $this->_get_last_error());
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
}
if ( !empty( $this->_config['path'] ) && !@ssh2_exec( $this->_ftp, 'cd ' . $this->_config['path'] ) ) {
$error = sprintf( 'Unable to change directory to: %s (%s).', $this->_config['path'], $this->_get_last_error() );
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
$this->_restore_error_handler();
return true;
}
/**
* Disconnects from FTP server
*/
function _disconnect() {
if ( $this->_config['type'] == 'sftp' ) {
if ( function_exists( 'ssh2_connect' ) ) {
@ssh2_exec( $this->_ftp, 'echo "EXITING" && exit;' );
$this->_ftp = null;
}
} else {
@ftp_close( $this->_ftp );
}
}
/**
* Sends MDTM command
*
* @param string $remote_file
* @param integer $mtime
* @return boolean
*/
function _mdtm( $remote_file, $mtime ) {
$command = sprintf( 'MDTM %s %s', date( 'YmdHis', $mtime ), $remote_file );
return @ftp_raw( $this->_ftp, $command );
}
/**
* Uploads files to FTP
*
* @param array $files
* @param array $results
* @param boolean $force_rewrite
* @return boolean
*/
function upload( $files, &$results, $force_rewrite = false,
$timeout_time = NULL ) {
$error = null;
if ( !$this->_connect( $error ) ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, $error );
return false;
}
$this->_set_error_handler();
if ( $this->_config['type'] == 'sftp' ) {
return $this->_upload_sftp( $files, $results, $force_rewrite, $timeout_time );
}
$home = @ftp_pwd( $this->_ftp );
if ( $home === false ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, sprintf( 'Unable to get current directory (%s).', $this->_get_last_error() ) );
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
foreach ( $files as $file ) {
$local_path = $file['local_path'];
$remote_path = $file['remote_path'];
// process at least one item before timeout so that progress goes on
if ( !empty( $results ) ) {
if ( !is_null( $timeout_time ) && time() > $timeout_time ) {
return 'timeout';
}
}
if ( !file_exists( $local_path ) ) {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR, 'Source file not found.', $file );
continue;
}
@ftp_chdir( $this->_ftp, $home );
$remote_dir = dirname( $remote_path );
$remote_dirs = preg_split( '~\\/+~', $remote_dir );
foreach ( $remote_dirs as $dir ) {
if ( !@ftp_chdir( $this->_ftp, $dir ) ) {
if ( !@ftp_mkdir( $this->_ftp, $dir ) ) {
$results[] = $this->_get_result( $local_path,
$remote_path, W3TC_CDN_RESULT_ERROR,
sprintf( 'Unable to create directory (%s).',
$this->_get_last_error() ),
$file );
continue 2;
}
if ( !@ftp_chdir( $this->_ftp, $dir ) ) {
$results[] = $this->_get_result( $local_path,
$remote_path, W3TC_CDN_RESULT_ERROR,
sprintf( 'Unable to change directory (%s).',
$this->_get_last_error() ),
$file );
continue 2;
}
}
}
// basename cannot be used, kills chinese chars and similar characters
$remote_file = substr( $remote_path, strrpos( $remote_path, '/' )+1 );
$mtime = @filemtime( $local_path );
if ( !$force_rewrite ) {
$size = @filesize( $local_path );
$ftp_size = @ftp_size( $this->_ftp, $remote_file );
$ftp_mtime = @ftp_mdtm( $this->_ftp, $remote_file );
if ( $size === $ftp_size && $mtime === $ftp_mtime ) {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'File up-to-date.', $file );
continue;
}
}
$result = @ftp_put( $this->_ftp, $remote_file, $local_path, FTP_BINARY );
if ( $result ) {
$this->_mdtm( $remote_file, $mtime );
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'OK', $file );
} else {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR,
sprintf( 'Unable to upload file (%s).',
$this->_get_last_error() ),
$file );
}
}
$this->_restore_error_handler();
$this->_disconnect();
return !$this->_is_error( $results );
}
/**
* Uploads files to SFTP
*
* @param array $files
* @param array $results
* @param boolean $force_rewrite
* @return boolean
*/
function _upload_sftp( $files, $results, $force_rewrite, $timeout_time ) {
$sftp = ssh2_sftp( $this->_ftp );
foreach ( $files as $file ) {
$local_path = $file['local_path'];
$remote_path = $file['remote_path'];
// process at least one item before timeout so that progress goes on
if ( !empty( $results ) ) {
if ( !is_null( $timeout_time ) && time() > $timeout_time ) {
return 'timeout';
}
}
if ( !file_exists( $local_path ) ) {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR, 'Source file not found.', $file );
continue;
}
$remote_dir = dirname( $remote_path );
if ( !@file_exists( 'ssh2.sftp://' . intval($sftp) . $remote_dir ) ) {
if ( !@ssh2_sftp_mkdir( $sftp, $remote_dir, null, true ) ) {
$results[] = $this->_get_result( $local_path,
$remote_path, W3TC_CDN_RESULT_ERROR,
sprintf( 'Unable to create directory (%s).',
$this->_get_last_error() ),
$file );
continue;
}
}
$mtime = @filemtime( $local_path );
if ( !$force_rewrite ) {
$size = @filesize( $local_path );
$statinfo = @ssh2_sftp_stat( $sftp, $remote_path );
if ( $size === $statinfo['size'] && $mtime === $statinfo['mtime'] ) {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'File up-to-date.', $file );
continue;
}
}
$result = @ssh2_scp_send( $this->_ftp, $local_path, $remote_path );
if ( $result ) {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'OK', $file );
} else {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR,
sprintf( 'Unable to upload file (%s).',
$this->_get_last_error() ),
$file );
}
}
$this->_restore_error_handler();
$this->_disconnect();
return !$this->_is_error( $results );
}
/**
* Deletes files from FTP
*
* @param array $files
* @param array $results
* @return boolean
*/
function delete( $files, &$results ) {
$error = null;
if ( !$this->_connect( $error ) ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, $error );
return false;
}
$this->_set_error_handler();
foreach ( $files as $file ) {
$local_path = $file['local_path'];
$remote_path = $file['remote_path'];
if ( $this->_config['type'] == 'sftp' ) {
$sftp = @ssh2_sftp( $this->_ftp );
$result = @ssh2_sftp_unlink( $sftp, $remote_path );
} else {
$result = @ftp_delete( $this->_ftp, $remote_path );
}
if ( $result ) {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'OK', $file );
} else {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR,
sprintf( 'Unable to delete file (%s).',
$this->_get_last_error() ),
$file );
}
while ( true ) {
$remote_path = dirname( $remote_path );
if ( $remote_path == '.' ) {
break;
}
if ( $this->_config['type'] == 'sftp' && !@ssh2_sftp_rmdir( $sftp, $remote_path ) ) {
break;
} else if ( !@ftp_rmdir( $this->_ftp, $remote_path ) ) {
break;
}
}
}
$this->_restore_error_handler();
$this->_disconnect();
return !$this->_is_error( $results );
}
/**
* Tests FTP server
*
* @param string $error
* @return boolean
*/
function test( &$error ) {
if ( !parent::test( $error ) ) {
return false;
}
if ( $this->_config['type'] == 'sftp' ) {
return $this->_test_sftp( $error );
}
$rand = md5( time() );
$tmp_dir = 'test_dir_' . $rand;
$tmp_file = 'test_file_' . $rand;
$tmp_path = W3TC_CACHE_TMP_DIR . '/' . $tmp_file;
if ( !@file_put_contents( $tmp_path, $rand ) ) {
$error = sprintf( 'Unable to create file: %s.', $tmp_path );
return false;
}
if ( !$this->_connect( $error ) ) {
return false;
}
$this->_set_error_handler();
if ( !@ftp_mkdir( $this->_ftp, $tmp_dir ) ) {
$error = sprintf( 'Unable to make directory: %s (%s).', $tmp_dir, $this->_get_last_error() );
@unlink( $tmp_path );
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
if ( file_exists( $this->_config['docroot'] . '/' . $tmp_dir ) ) {
$error = sprintf( 'Test directory was made in your site root, not on separate FTP host or path. Change path or FTP information: %s.', $tmp_dir );
@unlink( $tmp_path );
@ftp_rmdir( $this->_ftp, $tmp_dir );
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
if ( !@ftp_chdir( $this->_ftp, $tmp_dir ) ) {
$error = sprintf( 'Unable to change directory to: %s (%s).', $tmp_dir, $this->_get_last_error() );
@unlink( $tmp_path );
@ftp_rmdir( $this->_ftp, $tmp_dir );
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
if ( !@ftp_put( $this->_ftp, $tmp_file, $tmp_path, FTP_BINARY ) ) {
$error = sprintf( 'Unable to upload file: %s (%s).', $tmp_path, $this->_get_last_error() );
@unlink( $tmp_path );
@ftp_cdup( $this->_ftp );
@ftp_rmdir( $this->_ftp, $tmp_dir );
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
@unlink( $tmp_path );
if ( !@ftp_delete( $this->_ftp, $tmp_file ) ) {
$error = sprintf( 'Unable to delete file: %s (%s).', $tmp_path, $this->_get_last_error() );
@ftp_cdup( $this->_ftp );
@ftp_rmdir( $this->_ftp, $tmp_dir );
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
@ftp_cdup( $this->_ftp );
if ( !@ftp_rmdir( $this->_ftp, $tmp_dir ) ) {
$error = sprintf( 'Unable to remove directory: %s (%s).', $tmp_dir, $this->_get_last_error() );
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
$this->_restore_error_handler();
$this->_disconnect();
return true;
}
/**
* Tests FTP server
*
* @param string $error
* @return boolean
*/
function _test_sftp( &$error ) {
$rand = md5( time() );
$tmp_dir = 'test_dir_' . $rand;
$tmp_file = 'test_file_' . $rand;
$local_path = W3TC_CACHE_TMP_DIR . '/' . $tmp_file;
$remote_path = $tmp_dir . '/' . $tmp_file;
if ( !@file_put_contents( $local_path, $rand ) ) {
$error = sprintf( 'Unable to create file: %s.', $local_path );
return false;
}
if ( !$this->_connect( $error ) ) {
return false;
}
$sftp = @ssh2_sftp( $this->_ftp );
$this->_set_error_handler();
if ( !@ssh2_sftp_mkdir( $sftp, $tmp_dir ) ) {
$error = sprintf( 'Unable to make directory: %s (%s).', $tmp_dir, $this->_get_last_error() );
@unlink( $local_path );
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
if ( file_exists( $this->_config['docroot'] . '/' . $tmp_dir ) ) {
$error = sprintf( 'Test directory was made in your site root, not on separate FTP host or path. Change path or FTP information: %s.', $tmp_dir );
@unlink( $local_path );
@ssh2_sftp_rmdir( $sftp, $tmp_dir );
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
if ( !@ssh2_scp_send( $this->_ftp, $local_path, $remote_path ) ) {
$error = sprintf( 'Unable to upload file: %s (%s).', $local_path, $this->_get_last_error() );
@unlink( $local_path );
@ssh2_sftp_rmdir( $sftp, $tmp_dir );
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
@unlink( $local_path );
if ( !@ssh2_sftp_unlink( $sftp, $remote_path ) ) {
$error = sprintf( 'Unable to delete file: %s (%s).', $local_path, $this->_get_last_error() );
@ssh2_sftp_rmdir( $sftp, $tmp_dir );
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
if ( !@ssh2_sftp_rmdir( $sftp, $tmp_dir ) ) {
$error = sprintf( 'Unable to remove directory: %s (%s).', $tmp_dir, $this->_get_last_error() );
$this->_restore_error_handler();
$this->_disconnect();
return false;
}
$this->_restore_error_handler();
$this->_disconnect();
return true;
}
/**
* Returns array of CDN domains
*
* @return array
*/
function get_domains() {
if ( !empty( $this->_config['domain'] ) ) {
return (array) $this->_config['domain'];
}
return array();
}
/**
* How and if headers should be set
*
* @return string W3TC_CDN_HEADER_NONE, W3TC_CDN_HEADER_UPLOADABLE, W3TC_CDN_HEADER_MIRRORING
*/
function headers_support() {
return W3TC_CDN_HEADER_MIRRORING;
}
}

View File

@ -0,0 +1,617 @@
<?php
namespace W3TC;
/**
* Google drive engine
*/
class CdnEngine_GoogleDrive extends CdnEngine_Base {
private $_client_id;
private $_refresh_token;
private $_root_folder_id;
private $_root_url;
private $_service;
private $_tablename_pathmap;
/**
* PHP5 Constructor
*
* @param array $config
*/
function __construct( $config = array() ) {
parent::__construct( $config );
$this->_client_id = $config['client_id'];
$this->_refresh_token = $config['refresh_token'];
$this->_root_folder_id = $config['root_folder_id'];
$this->_root_url = rtrim( $config['root_url'], '/' ) . '/';
$this->_new_access_token_callback = $config['new_access_token_callback'];
global $wpdb;
$this->_tablename_pathmap = $wpdb->base_prefix . W3TC_CDN_TABLE_PATHMAP;
try {
$this->_init_service( $config['access_token'] );
} catch ( \Exception $e ) {
$this->_service = null;
}
}
private function _init_service( $access_token ) {
if ( empty( $this->_client_id ) || empty( $access_token ) )
throw new \Exception('service not configured');
$client = new \W3TCG_Google_Client();
$client->setClientId( $this->_client_id );
$client->setAccessToken( $access_token );
$this->_service = new \W3TCG_Google_Service_Drive( $client );
}
private function _refresh_token() {
$result = wp_remote_post( W3TC_GOOGLE_DRIVE_AUTHORIZE_URL, array(
'body' => array(
'client_id' => $this->_client_id,
'refresh_token' => $this->_refresh_token
) ) );
if ( is_wp_error( $result ) )
throw new \Exception( $result );
elseif ( $result['response']['code'] != '200' )
throw new \Exception( $result['body'] );
$access_token = $result['body'];
call_user_func( $this->_new_access_token_callback, $access_token );
$this->_init_service( $access_token );
}
/**
* Uploads files
*
* @param array $files
* @param array $results
* @param boolean $force_rewrite
* @return boolean
*/
function upload( $files, &$results, $force_rewrite = false, $timeout_time = NULL ) {
if ( is_null( $this->_service ) )
return false;
$allow_refresh_token = true;
$result = true;
$files_chunks = array_chunk( $files , 20 );
foreach ( $files_chunks as $files_chunk ) {
$r = $this->_upload_chunk( $files_chunk, $results,
$force_rewrite, $timeout_time, $allow_refresh_token );
if ( $r == 'refresh_required' ) {
$allow_refresh_token = false;
$this->_refresh_token();
$r = $this->_upload_chunk( $files_chunk, $results,
$force_rewrite, $timeout_time, $allow_refresh_token );
}
if ( $r != 'success' )
$result = false;
if ( $r == 'timeout' ) {
return 'timeout';
}
}
return $result;
}
private function _properties_to_path( $file ) {
$path_pieces = array();
foreach ( $file->properties as $p ) {
$k = ($p->key == 'path') ? 'path1' : $p->key;
if ( !preg_match( '/^path[0-9]+$/', $k ) )
continue;
$path_pieces[$k] = $p->value;
}
if ( count( $path_pieces ) == 0 )
return NULL;
ksort($path_pieces);
return join( $path_pieces );
}
private function _path_to_properties( $path ) {
// from google drive api docs:
// Maximum of 124 bytes size per property
// (including both key and value) string in UTF-8 encoding.
// Maximum of 30 private properties per file from any one application.
$chunks = str_split( $path, 55 );
$properties = array();
$i = 1;
foreach ( $chunks as $chunk ) {
$p = new \W3TCG_Google_Service_Drive_Property();
$p->key = 'path' . $i;
$p->value = $chunk;
$properties[] = $p;
$i++;
}
return $properties;
}
private function _upload_chunk( $files, &$results, $force_rewrite,
$timeout_time, $allow_refresh_token ) {
list( $result, $listed_files ) = $this->list_files_chunk( $files,
$allow_refresh_token, $timeout_time );
if ( $result != 'success' )
return $result;
$files_by_path = array();
foreach ( $listed_files as $existing_file ) {
$path = $this->_properties_to_path( $existing_file );
if ( $path ) {
$files_by_path[$path] = $existing_file;
}
}
// check update date and upload
foreach ( $files as $file_descriptor ) {
$remote_path = $file_descriptor['remote_path'];
// process at least one item before timeout so that progress goes on
if ( !empty( $results ) ) {
if ( !is_null( $timeout_time ) && time() > $timeout_time )
return 'timeout';
}
list( $parent_id, $title ) = $this->remote_path_to_title(
$file_descriptor['remote_path'] );
$properties = $this->_path_to_properties( $remote_path );
if ( isset( $file_descriptor['content'] ) ) {
// when content specified - just upload
$content = $file_descriptor['content'];
} else {
$local_path = $file_descriptor['local_path'];
if ( !file_exists( $local_path ) ) {
$results[] = $this->_get_result( $local_path,
$remote_path,
W3TC_CDN_RESULT_ERROR, 'Source file not found.',
$file_descriptor );
continue;
}
$mtime = @filemtime( $local_path );
$p = new \W3TCG_Google_Service_Drive_Property();
$p->key = 'mtime';
$p->value = $mtime;
$properties[] = $p;
if ( !$force_rewrite && isset( $files_by_path[$remote_path] ) ) {
$existing_file = $files_by_path[$remote_path];
$existing_size = $existing_file->fileSize;
$existing_mtime = 0;
if ( is_array( $existing_file->properties ) ) {
foreach ( $existing_file->properties as $p ) {
if ( $p->key == 'mtime' )
$existing_mtime = $p->value;
}
}
$size = @filesize( $local_path );
if ( $mtime == $existing_mtime && $size == $existing_size ) {
$results[] = $this->_get_result( $file_descriptor['local_path'],
$remote_path, W3TC_CDN_RESULT_OK,
'File up-to-date.', $file_descriptor );
continue;
}
}
$content = file_get_contents( $local_path );
}
$file = new \W3TCG_Google_Service_Drive_DriveFile();
$file->setTitle( $title );
$file->setProperties( $properties );
$parent = new \W3TCG_Google_Service_Drive_ParentReference();
$parent->setId( $parent_id );
$file->setParents( array( $parent ) );
try {
try {
// update file if there's one already or insert
if ( isset( $files_by_path[$remote_path] ) ) {
$existing_file = $files_by_path[$remote_path];
$created_file = $this->_service->files->update(
$existing_file->id, $file, array(
'data' => $content,
'uploadType' => 'media'
) );
} else {
$created_file = $this->_service->files->insert( $file, array(
'data' => $content,
'uploadType' => 'media'
) );
$permission = new \W3TCG_Google_Service_Drive_Permission();
$permission->setValue( '' );
$permission->setType( 'anyone' );
$permission->setRole( 'reader' );
$this->_service->permissions->insert( $created_file->id,
$permission );
}
} catch ( \W3TCG_Google_Auth_Exception $e ) {
if ( $allow_refresh_token )
return 'refresh_required';
throw $e;
}
$results[] = $this->_get_result( $file_descriptor['local_path'],
$remote_path, W3TC_CDN_RESULT_OK,
'OK', $file_descriptor );
$this->path_set_id( $remote_path, $created_file->id );
} catch ( \W3TCG_Google_Service_Exception $e ) {
$errors = $e->getErrors();
$details = '';
if ( count( $errors ) >= 1 ) {
$details = json_encode($errors);
}
delete_transient( 'w3tc_cdn_google_drive_folder_ids' );
$results[] = $this->_get_result( $file_descriptor['local_path'],
$remote_path, W3TC_CDN_RESULT_ERROR,
'Failed to upload file ' . $remote_path .
' ' . $details, $file_descriptor );
$result = 'with_errors';
continue;
} catch ( \Exception $e ) {
delete_transient( 'w3tc_cdn_google_drive_folder_ids' );
$results[] = $this->_get_result( $file_descriptor['local_path'],
$remote_path, W3TC_CDN_RESULT_ERROR,
'Failed to upload file ' . $remote_path,
$file_descriptor );
$result = 'with_errors';
continue;
}
}
return $result;
}
/**
* Deletes files
*
* @param array $files
* @param array $results
* @return boolean
*/
function delete( $files, &$results ) {
$allow_refresh_token = true;
$result = true;
$files_chunks = array_chunk( $files , 20 );
foreach ( $files_chunks as $files_chunk ) {
$r = $this->_delete_chunk( $files_chunk, $results,
$allow_refresh_token );
if ( $r == 'refresh_required' ) {
$allow_refresh_token = false;
$this->_refresh_token();
$r = $this->_delete_chunk( $files_chunk, $results,
$allow_refresh_token );
}
if ( $r != 'success' )
$result = false;
}
return $result;
}
private function _delete_chunk( $files, &$results, $allow_refresh_token ) {
list( $result, $listed_files ) = $this->list_files_chunk( $files,
$allow_refresh_token );
if ( $result != 'success' )
return $result;
foreach ( $listed_files->items as $item ) {
try {
$this->_service->files->delete( $item->id );
$results[] = $this->_get_result( $item->title,
$item->title, W3TC_CDN_RESULT_OK,
'OK' );
} catch ( \Exception $e ) {
$results[] = $this->_get_result( '',
'', W3TC_CDN_RESULT_ERROR,
'Failed to delete file ' . $item->title );
$result = 'with_errors';
continue;
}
}
return $result;
}
private function list_files_chunk( $files, $allow_refresh_token,
$timeout_time = NULL ) {
$titles_filter = array();
try {
foreach ( $files as $file_descriptor ) {
list( $parent_id, $title ) = $this->remote_path_to_title(
$file_descriptor['remote_path'] );
$titles_filter[] = '("' . $parent_id .
'" in parents and title = "' . $title . '")';
if ( !is_null( $timeout_time ) && time() > $timeout_time )
return array( 'timeout', array() );
}
} catch ( \W3TCG_Google_Auth_Exception $e ) {
if ( $allow_refresh_token )
return array( 'refresh_required', array() );
throw $e;
} catch ( \Exception $e ) {
return array( 'with_errors', array() );
}
// find files
try {
try {
$listed_files = $this->_service->files->listFiles(
array( 'q' => '(' . join( ' or ', $titles_filter ) . ') and trashed = false' )
);
} catch ( \W3TCG_Google_Auth_Exception $e ) {
if ( $allow_refresh_token )
return array( 'refresh_required', array() );
throw $e;
}
} catch ( \Exception $e ) {
return array( 'with_errors', array() );
}
return array( 'success', $listed_files );
}
private function remote_path_to_title( $remote_path ) {
$title = substr( $remote_path, 1 );
$pos = strrpos( $remote_path, '/' );
if ( $pos === false ) {
$path = '';
$title = $remote_path;
} else {
$path = substr( $remote_path, 0, $pos );
$title = substr( $remote_path, $pos + 1 );
}
$title = str_replace( '"', "'", $title );
$parent_id = $this->path_to_parent_id( $this->_root_folder_id, $path );
return array( $parent_id, $title );
}
private function path_to_parent_id( $root_id, $path ) {
if ( empty( $path ) )
return $root_id;
$path = ltrim( $path, '/' );
$pos = strpos( $path, '/' );
if ( $pos === false ) {
$top_folder = $path;
$remaining_path = '';
} else {
$top_folder = substr( $path, 0, $pos );
$remaining_path = substr( $path, $pos + 1 );
}
$new_root_id = $this->parent_id_resolve_step( $root_id, $top_folder );
return $this->path_to_parent_id( $new_root_id, $remaining_path );
}
private function parent_id_resolve_step( $root_id, $folder ) {
// decode top folder
$ids_string = get_transient( 'w3tc_cdn_google_drive_folder_ids' );
$ids = @unserialize( $ids_string );
if ( isset( $ids[$root_id . '_' . $folder] ) )
return $ids[$root_id . '_' . $folder];
// find folder
$items = $this->_service->files->listFiles( array(
'q' => '"' . $root_id . '" in parents '.
'and title = "' . $folder . '" ' .
'and mimeType = "application/vnd.google-apps.folder" ' .
'and trashed = false'
) );
if ( count( $items ) > 0 ) {
$id = $items[0]->id;
} else {
// create folder
$file = new \W3TCG_Google_Service_Drive_DriveFile( array(
'title' => $folder,
'mimeType' => 'application/vnd.google-apps.folder' ) );
$parent = new \W3TCG_Google_Service_Drive_ParentReference();
$parent->setId( $root_id );
$file->setParents( array( $parent ) );
$created_file = $this->_service->files->insert( $file );
$id = $created_file->id;
$permission = new \W3TCG_Google_Service_Drive_Permission();
$permission->setValue( '' );
$permission->setType( 'anyone' );
$permission->setRole( 'reader' );
$this->_service->permissions->insert( $id, $permission );
}
if ( !is_array( $ids ) )
$ids = array();
$ids[$root_id . '_' . $folder] = $id;
set_transient( 'w3tc_cdn_google_drive_folder_ids', serialize( $ids ) );
return $id;
}
/**
* Tests
*
* @param string $error
* @return boolean
*/
function test( &$error ) {
$test_content = '' . rand();
$file = array(
'local_path' => 'n/a',
'remote_path' => '/folder/test.txt',
'content' => $test_content
);
$results = array();
if ( !$this->upload( array( $file ), $results ) ) {
$error = sprintf( 'Unable to upload file %s', $file['remote_path'] );
return false;
}
if ( !$this->delete( array( $file ), $results ) ) {
$error = sprintf( 'Unable to delete file %s', $file['remote_path'] );
return false;
}
return true;
}
/**
* Returns array of CDN domains
*
* @return array
*/
function get_domains() {
return array();
}
/**
* How and if headers should be set
*
* @return string W3TC_CDN_HEADER_NONE, W3TC_CDN_HEADER_UPLOADABLE, W3TC_CDN_HEADER_MIRRORING
*/
function headers_support() {
return W3TC_CDN_HEADER_NONE;
}
function purge_all( &$results ) {
return false;
}
private function path_set_id( $path, $id ) {
global $wpdb;
$md5 = md5( $path );
if ( !$id ) {
$sql = "
INSERT INTO $this->_tablename_pathmap (path, path_hash, remote_id)
VALUES (%s, %s, NULL)
ON DUPLICATE KEY UPDATE remote_id = NULL";
$wpdb->query($wpdb->prepare($sql, $path, $md5));
} else {
$sql = "
INSERT INTO $this->_tablename_pathmap (path, path_hash, remote_id)
VALUES (%s, %s, %s)
ON DUPLICATE KEY UPDATE remote_id = %s";
$wpdb->query($wpdb->prepare(
$sql, $path, $md5, $id, $id));
}
}
private function path_get_id( $path, $allow_refresh_token = true ) {
global $wpdb;
$md5 = md5($path);
$sql = "SELECT remote_id FROM $this->_tablename_pathmap WHERE path_hash = %s";
$query = $wpdb->prepare( $sql, $md5 );
$results = $wpdb->get_results( $query );
if ( count( $results ) > 0 ) {
return $results[0]->remote_id;
}
$props = $this->_path_to_properties( $path );
$q = 'trashed = false';
foreach ( $props as $prop ) {
$key = $prop->key;
$value = str_replace( "'", "\\'", $prop->value );
$q .= " and properties has { key='$key' and " .
" value='$value' and visibility='PRIVATE' }";
}
try {
$items = $this->_service->files->listFiles(
array( 'q' => $q ) );
} catch ( \W3TCG_Google_Auth_Exception $e ) {
if ( $allow_refresh_token ) {
$this->_refresh_token();
return $this->path_get_id( $path, false );
}
throw $e;
}
$id = ( count( $items ) == 0 ) ? NULL : $items[0]->id;
$this->path_set_id( $path, $id );
return $id;
}
function format_url( $path, $allow_refresh_token = true ) {
$id = $this->path_get_id( Util_Environment::remove_query( $path ) );
if ( is_null( $id ) )
return NULL;
return 'https://drive.google.com/uc?id=' . $id;
}
}

View File

@ -0,0 +1,97 @@
<?php
namespace W3TC;
/**
* W3 CDN Mirror Class
*/
class CdnEngine_Mirror extends CdnEngine_Base {
/**
* PHP5 Constructor
*
* @param array $config
*/
function __construct( $config = array() ) {
$config = array_merge( array(
'domain' => array(),
), $config );
parent::__construct( $config );
}
/**
* Uploads files stub
*
* @param array $files
* @param array $results
* @param boolean $force_rewrite
* @return boolean
*/
function upload( $files, &$results, $force_rewrite = false,
$timeout_time = NULL ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_OK, 'OK' );
return true;
}
/**
* Deletes files stub
*
* @param array $files
* @param array $results
* @return boolean
*/
function delete( $files, &$results ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_OK, 'OK' );
return true;
}
/**
* Tests mirror
*
* @param string $error
* @return bool
*/
function test( &$error ) {
if ( !parent::test( $error ) ) {
return false;
}
$results = array();
$files = array(
array(
'local_path' => '',
'remote_path' => 'purge_test_' . time()
) );
if ( !$this->purge( $files, $results ) && isset( $results[0]['error'] ) ) {
$error = $results[0]['error'];
return false;
}
return true;
}
/**
* Returns array of CDN domains
*
* @return array
*/
function get_domains() {
if ( !empty( $this->_config['domain'] ) ) {
return (array) $this->_config['domain'];
}
return array();
}
/**
* How and if headers should be set
*
* @return string W3TC_CDN_HEADER_NONE, W3TC_CDN_HEADER_UPLOADABLE, W3TC_CDN_HEADER_MIRRORING
*/
function headers_support() {
return W3TC_CDN_HEADER_MIRRORING;
}
}

View File

@ -0,0 +1,109 @@
<?php
namespace W3TC;
define( 'W3TC_CDN_MIRROR_AKAMAI_WSDL', 'https://ccuapi.akamai.com/ccuapi-axis.wsdl' );
define( 'W3TC_CDN_MIRROR_AKAMAI_NAMESPACE', 'http://www.akamai.com/purge' );
class CdnEngine_Mirror_Akamai extends CdnEngine_Mirror {
/**
* PHP5 Constructor
*
* @param array $config
*/
function __construct( $config = array() ) {
$config = array_merge( array(
'username' => '',
'password' => '',
'zone' => '',
'action' => 'invalidate',
'email_notification' => array()
), $config );
parent::__construct( $config );
}
/**
* Purges remote files
*
* @param array $files
* @param array $results
* @return boolean
*/
function purge( $files, &$results ) {
if ( empty( $this->_config['username'] ) ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, __( 'Empty username.', 'w3-total-cache' ) );
return false;
}
if ( empty( $this->_config['password'] ) ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, __( 'Empty password.', 'w3-total-cache' ) );
return false;
}
require_once W3TC_LIB_DIR . '/Nusoap/nusoap.php';
$client = new \nusoap_client(
W3TC_CDN_MIRROR_AKAMAI_WSDL,
'wsdl'
);
$error = $client->getError();
if ( $error ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, sprintf( __( 'Constructor error (%s).', 'w3-total-cache' ), $error ) );
return false;
}
$zone = $this->_config['zone'];
$expressions = array();
foreach ( $files as $file ) {
$remote_path = $file['remote_path'];
$expressions[] = $this->_format_url( $remote_path );
}
$action = $this->_config['action'];
$email = $this->_config['email_notification'];
$email = implode( ',', $email );
$options = array( 'action='.$action, 'domain='.$zone, 'type=arl' );
if ( $email )
$options[] = 'email-notification='.$email;
$params = array( $this->_config['username'],
$this->_config['password'],
'',
$options,
$expressions
);
$result = $client->call( 'purgeRequest', $params, W3TC_CDN_MIRROR_AKAMAI_NAMESPACE );
if ( $client->fault ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, __( 'Invalid response.', 'w3-total-cache' ) );
return false;
}
$result_code = $result['resultCode'];
$result_message = $result['resultMsg'];
$error = $client->getError();
if ( $error ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, sprintf( __( 'Unable to purge (%s).', 'w3-total-cache' ), $error ) );
return false;
}
if ( $result_code>=300 ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, sprintf( __( 'Unable to purge (%s).', 'w3-total-cache' ), $result_message ) );
return false;
}
$results = $this->_get_results( $files, W3TC_CDN_RESULT_OK, __( 'OK', 'w3-total-cache' ) );
return true;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace W3TC;
define( 'W3TC_CDN_EDGECAST_PURGE_URL', 'http://api.acdn.att.com/v2/mcc/customers/%s/edge/purge' );
/**
* class CdnEngine_Mirror_Att
*/
class CdnEngine_Mirror_Att extends CdnEngine_Mirror_Edgecast {
}

View File

@ -0,0 +1,167 @@
<?php
/**
* File: CdnEngine_Mirror_BunnyCdn.php
*
* @since X.X.X
* @package W3TC
*/
namespace W3TC;
/**
* Class: CdnEngine_Mirror_BunnyCdn
*
* @since X.X.X
*
* @extends CdnEngine_Mirror
*/
class CdnEngine_Mirror_BunnyCdn extends CdnEngine_Mirror {
/**
* Constructor.
*
* @param array $config {
* Configuration.
*
* @type string $account_api_key Account API key.
* @type string $storage_api_key Storage API key.
* @type string $stream_api_key Steam API key.
* @type int $pull_zone_id Pull zone id.
* @type string $cdn_hostname CDN hostname.
* }
*/
public function __construct( array $config = array() ) {
$config = \array_merge(
array(
'account_api_key' => '',
'storage_api_key' => '',
'stream_api_key' => '',
'pull_zone_id' => null,
'domain' => '',
),
$config
);
parent::__construct( $config );
}
/**
* Purge remote files.
*
* @since X.X.X
*
* @param array $files Local and remote file paths.
* @param array $results Results.
* @return bool
*/
public function purge( $files, &$results ) {
if ( empty( $this->_config['account_api_key'] ) ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, \__( 'Missing account API key.', 'w3-total-cache' ) );
return false;
}
if ( empty( $this->_config['cdn_hostname'] ) ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, \__( 'Missing CDN hostname.', 'w3-total-cache' ) );
return false;
}
$url_prefixes = $this->url_prefixes();
$api = new Cdn_BunnyCdn_Api( $this->_config );
$results = array();
try {
$items = array();
foreach ( $files as $file ) {
foreach ( $url_prefixes as $prefix ) {
$items[] = array(
'url' => $prefix . '/' . $file['remote_path'],
'recursive' => true,
);
}
}
$api->purge( array( 'items' => $items ) );
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_OK, 'OK' );
} catch ( \Exception $e ) {
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_HALT, \__( 'Could not purge pull zone items: ', 'w3-total-cache' ) . $e->getMessage() );
}
return ! $this->_is_error( $results );
}
/**
* Purge CDN completely.
*
* @since X.X.X
*
* @param array $results Results.
* @return bool
*/
public function purge_all( &$results ) {
if ( empty( $this->_config['account_api_key'] ) ) {
$results = $this->_get_results( array(), W3TC_CDN_RESULT_HALT, __( 'Missing account API key.', 'w3-total-cache' ) );
return false;
}
// Purge active pull zones: CDN & CDNFSD.
$active_zone_ids = array();
$config = Dispatcher::config();
$cdn_zone_id = $config->get_integer( 'cdn.bunnycdn.pull_zone_id' );
$cdnfsd_zone_id = $config->get_integer( 'cdnfsd.bunnycdn.pull_zone_id' );
if ( $config->get_boolean( 'cdn.enabled' ) && 'bunnycdn' === $config->get_string( 'cdn.engine' ) && $cdn_zone_id ) {
$active_ids[] = $cdn_zone_id;
}
if ( $config->get_boolean( 'cdnfsd.enabled' ) && 'bunnycdn' === $config->get_string( 'cdnfsd.engine' ) && $cdnfsd_zone_id ) {
$active_ids[] = $cdnfsd_zone_id;
}
if ( empty( $active_ids ) ) {
$results = $this->_get_results( array(), W3TC_CDN_RESULT_HALT, __( 'Missing pull zone id.', 'w3-total-cache' ) );
return false;
}
$results = array();
foreach ( $active_ids as $id ) {
$api = new Cdn_BunnyCdn_Api( array_merge( $this->_config, array( 'pull_zone_id' => $id ) ) );
try {
$api->purge_pull_zone();
$results[] = $this->_get_result( '', '' ); // W3TC_CDN_RESULT_OK.
} catch ( \Exception $e ) {
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_HALT, \__( 'Could not purge pull zone', 'w3-total-cache' ) . '; ' . $e->getMessage() );
}
}
return ! $this->_is_error( $results );
}
/**
* Get URL prefixes.
*
* If set to "auto", then add URLs for both "http" and "https".
*
* @since X.X.X
*
* @return array
*/
private function url_prefixes() {
$url_prefixes = array();
if ( 'auto' === $this->_config['ssl'] || 'enabled' === $this->_config['ssl'] ) {
$url_prefixes[] = 'https://' . $this->_config['cdn_hostname'];
}
if ( 'auto' === $this->_config['ssl'] || 'enabled' !== $this->_config['ssl'] ) {
$url_prefixes[] = 'http://' . $this->_config['cdn_hostname'];
}
return $url_prefixes;
}
}

View File

@ -0,0 +1,331 @@
<?php
namespace W3TC;
if ( !defined( 'W3TC_SKIPLIB_AWS' ) ) {
require_once W3TC_DIR . '/vendor/autoload.php';
}
/**
* Amazon CloudFront (mirror) CDN engine
*/
class CdnEngine_Mirror_CloudFront extends CdnEngine_Mirror {
private $api;
/**
* Constructor
*/
function __construct( $config = array() ) {
parent::__construct( $config );
}
/**
* Initializes S3 object
*
* @param string $error
* @return bool
*/
function _init() {
if ( !is_null( $this->api ) ) {
return;
}
if ( empty( $this->_config['key'] ) && empty( $this->_config['secret'] ) ) {
$credentials = \Aws\Credentials\CredentialProvider::defaultProvider();
} else {
$credentials = new \Aws\Credentials\Credentials(
$this->_config['key'],
$this->_config['secret'] );
}
$this->api = new \Aws\CloudFront\CloudFrontClient( array(
'credentials' => $credentials,
'region' => 'us-east-1',
'version' => '2018-11-05'
)
);
return true;
}
/**
* Returns origin
*
* @return string
*/
function _get_origin() {
return Util_Environment::host_port();
}
/**
* Purge files from CDN
*
* @param array $files
* @param array $results
* @return boolean
*/
function purge( $files, &$results ) {
try {
$this->_init();
$dist = $this->_get_distribution();
} catch ( \Exception $ex ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, $ex->getMessage() );
return false;
}
$paths = array();
foreach ( $files as $file ) {
$remote_file = $file['remote_path'];
$paths[] = '/' . $remote_file;
}
try {
$invalidation = $this->api->createInvalidation( array(
'DistributionId' => $dist['Id'],
'InvalidationBatch' => array(
'CallerReference' => 'w3tc-' . microtime(),
'Paths' => array(
'Items' => $paths,
'Quantity' => count( $paths ),
),
)
)
);
} catch ( \Exception $ex ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT,
sprintf( 'Unable to create invalidation batch (%s).',
$ex->getMessage() ) );
return false;
}
$results = $this->_get_results( $files, W3TC_CDN_RESULT_OK, 'OK' );
return true;
}
/**
* Purge CDN completely
*
* @param unknown $results
* @return bool
*/
function purge_all( &$results ) {
return $this->purge( array( array( 'remote_path' => '*' ) ), $results );
}
/**
* Returns array of CDN domains
*
* @return array
*/
function get_domains() {
if ( !empty( $this->_config['cname'] ) ) {
return (array) $this->_config['cname'];
} elseif ( !empty( $this->_config['id'] ) ) {
$domain = sprintf( '%s.cloudfront.net', $this->_config['id'] );
return array(
$domain
);
}
return array();
}
/**
* Tests CF
*
* @param string $error
* @return boolean
*/
function test( &$error ) {
$this->_init();
/**
* Search active CF distribution
*/
$dists = $this->api->listDistributions();
if ( !isset( $dists['DistributionList']['Items'] ) ) {
$error = 'Unable to list distributions.';
return false;
}
if ( !count( $dists['DistributionList']['Items'] ) ) {
$error = 'No distributions found.';
return false;
}
$dist = $this->_get_distribution( $dists );
if ( $dist["Status"] != 'Deployed' ) {
$error = sprintf( 'Distribution status is not Deployed, but "%s".', $dist["Status"] );
return false;
}
if ( !$dist['Enabled'] ) {
$error = sprintf( 'Distribution for origin "%s" is disabled.', $origin );
return false;
}
if ( !empty( $this->_config['cname'] ) ) {
$domains = (array) $this->_config['cname'];
$cnames = ( isset( $dist['Aliases']['Items'] ) ? (array) $dist['Aliases']['Items'] : array() );
foreach ( $domains as $domain ) {
$_domains = array_map( 'trim', explode( ',', $domain ) );
foreach ( $_domains as $_domain ) {
if ( !in_array( $_domain, $cnames ) ) {
$error = sprintf( 'Domain name %s is not in distribution <acronym title="Canonical Name">CNAME</acronym> list.', $_domain );
return false;
}
}
}
} elseif ( !empty( $this->_config['id'] ) ) {
$domain = $this->get_domain();
if ( $domain != $dist['DomainName'] ) {
$error = sprintf( 'Distribution domain name mismatch (%s != %s).', $domain, $dist['DomainName'] );
return false;
}
}
return true;
}
/**
* Create distribution
*/
function create_container() {
$this->_init();
// plugin cant set CNAMEs list since it CloudFront requires
// certificate to be specified associated with it
$cnames = array();
// make distibution
$originDomain = $this->_get_origin();
try {
$result = $this->api->createDistribution( array(
'DistributionConfig' => array(
'CallerReference' => $originDomain,
'Comment' => 'Created by W3-Total-Cache',
'DefaultCacheBehavior' => array(
'AllowedMethods' => array(
'CachedMethods' => array(
'Items' => array( 'HEAD', 'GET' ),
'Quantity' => 2,
),
'Items' => array( 'HEAD', 'GET' ),
'Quantity' => 2,
),
'Compress' => true,
'DefaultTTL' => 86400,
'FieldLevelEncryptionId' => '',
'ForwardedValues' => array(
'Cookies' => array(
'Forward' => 'none',
),
'Headers' => array(
'Quantity' => 0,
),
'QueryString' => false,
'QueryStringCacheKeys' => array(
'Quantity' => 0,
),
),
'LambdaFunctionAssociations' => array( 'Quantity' => 0),
'MinTTL' => 0,
'SmoothStreaming' => false,
'TargetOriginId' => $originDomain,
'TrustedSigners' => array(
'Enabled' => false,
'Quantity' => 0,
),
'ViewerProtocolPolicy' => 'allow-all',
),
'Enabled' => true,
'Origins' => array(
'Items' => array(
array(
'DomainName' => $originDomain,
'Id' => $originDomain,
'OriginPath' => '',
'CustomHeaders' => array( 'Quantity' => 0 ),
'CustomOriginConfig' => array(
'HTTPPort' => 80,
'HTTPSPort' => 443,
'OriginProtocolPolicy' => 'match-viewer'
),
),
),
'Quantity' => 1,
),
'Aliases' => array(
'Items' => $cnames,
'Quantity' => count( $cnames )
)
)
));
// extract domain dynamic part stored later in a config
$domain = $result['Distribution']['DomainName'];
$container_id = '';
if ( preg_match( '~^(.+)\.cloudfront\.net$~', $domain, $matches ) ) {
$container_id = $matches[1];
}
return $container_id;
} catch ( \Aws\Exception\AwsException $ex ) {
throw new \Exception( sprintf(
'Unable to create distribution for origin %s: %s', $originDomain,
$ex->getAwsErrorMessage() ) );
} catch ( \Exception $ex ) {
throw new \Exception( sprintf(
'Unable to create distribution for origin %s: %s', $originDomain,
$ex->getMessage() ) );
}
}
/**
* Returns via string
*/
function get_via() {
$domain = $this->get_domain();
$via = ( $domain ? $domain : 'N/A' );
return sprintf( 'Amazon Web Services: CloudFront: %s', $via );
}
private function _get_distribution( $dists = null ) {
if ( is_null( $dists ) ) {
$dists = $this->api->listDistributions();
}
if ( !isset( $dists['DistributionList']['Items'] ) ||
!count( $dists['DistributionList']['Items'] ) ) {
throw new \Exception( 'No distributions found.' );
}
$dist = false;
$origin = $this->_get_origin();
$items = $dists['DistributionList']['Items'];
foreach ( $items as $dist ) {
if ( isset( $dist['Origins']['Items'] ) ) {
foreach ( $dist['Origins']['Items'] as $o ) {
if ( isset( $o['DomainName'] ) && $o['DomainName'] == $origin ) {
return $dist;
}
}
}
}
throw new \Exception( sprintf( 'Distribution for origin "%s" not found.', $origin ) );
}
}

View File

@ -0,0 +1,120 @@
<?php
namespace W3TC;
define( 'W3TC_CDN_MIRROR_COTENDO_WSDL', 'https://api.cotendo.net/cws?wsdl' );
define( 'W3TC_CDN_MIRROR_COTENDO_ENDPOINT', 'http://api.cotendo.net/cws?ver=1.0' );
define( 'W3TC_CDN_MIRROR_COTENDO_NAMESPACE', 'http://api.cotendo.net/' );
/**
* class CdnEngine_Mirror_Cotendo
*/
class CdnEngine_Mirror_Cotendo extends CdnEngine_Mirror {
/**
* PHP5 Constructor
*
* @param array $config
*/
function __construct( $config = array() ) {
$config = array_merge( array(
'username' => '',
'password' => '',
'zones' => array(),
), $config );
parent::__construct( $config );
}
/**
* Purges remote files
*
* @param array $files
* @param array $results
* @return boolean
*/
function purge( $files, &$results ) {
if ( empty( $this->_config['username'] ) ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, __( 'Empty username.', 'w3-total-cache' ) );
return false;
}
if ( empty( $this->_config['password'] ) ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, __( 'Empty password.', 'w3-total-cache' ) );
return false;
}
if ( empty( $this->_config['zones'] ) ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, __( 'Empty zones list.', 'w3-total-cache' ) );
return false;
}
require_once W3TC_LIB_DIR . '/Nusoap/nusoap.php';
$client = new \nusoap_client(
W3TC_CDN_MIRROR_COTENDO_WSDL,
'wsdl'
);
$error = $client->getError();
if ( $error ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, sprintf( __( 'Constructor error (%s).', 'w3-total-cache' ), $error ) );
return false;
}
$client->authtype = 'basic';
$client->username = $this->_config['username'];
$client->password = $this->_config['password'];
$client->forceEndpoint = W3TC_CDN_MIRROR_COTENDO_ENDPOINT;
foreach ( (array) $this->_config['zones'] as $zone ) {
$expressions = array();
foreach ( $files as $file ) {
$remote_path = $file['remote_path'];
$expressions[] = '/' . $remote_path;
}
$expression = implode( "\n", $expressions );
$params = array(
'cname' => $zone,
'flushExpression' => $expression,
'flushType' => 'hard',
);
$client->call( 'doFlush', $params, W3TC_CDN_MIRROR_COTENDO_NAMESPACE );
if ( $client->fault ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, __( 'Invalid response.', 'w3-total-cache' ) );
return false;
}
$error = $client->getError();
if ( $error ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, sprintf( __( 'Unable to purge (%s).', 'w3-total-cache' ), $error ) );
return false;
}
}
$results = $this->_get_results( $files, W3TC_CDN_RESULT_OK, __( 'OK', 'w3-total-cache' ) );
return true;
}
/**
* Purges CDN completely
*
* @param unknown $results
* @return bool
*/
function purge_all( &$results ) {
return $this->purge( array( array( 'local_path'=>'*', 'remote_path'=> '*' ) ), $results );
}
}

View File

@ -0,0 +1,142 @@
<?php
namespace W3TC;
if ( !defined( 'W3TC_CDN_EDGECAST_PURGE_URL' ) ) define( 'W3TC_CDN_EDGECAST_PURGE_URL', 'http://api.edgecast.com/v2/mcc/customers/%s/edge/purge' );
define( 'W3TC_CDN_EDGECAST_MEDIATYPE_WINDOWS_MEDIA_STREAMING', 1 );
define( 'W3TC_CDN_EDGECAST_MEDIATYPE_FLASH_MEDIA_STREAMING', 2 );
define( 'W3TC_CDN_EDGECAST_MEDIATYPE_HTTP_LARGE_OBJECT', 3 );
define( 'W3TC_CDN_EDGECAST_MEDIATYPE_HTTP_SMALL_OBJECT', 8 );
define( 'W3TC_CDN_EDGECAST_MEDIATYPE_APPLICATION_DELIVERY_NETWORK', 14 );
/**
* class CdnEngine_Mirror_Edgecast
*/
class CdnEngine_Mirror_Edgecast extends CdnEngine_Mirror {
/**
* PHP5 Constructor
*
* @param array $config
*/
function __construct( $config = array() ) {
$config = array_merge( array(
'apiid' => '',
'apikey' => ''
), $config );
parent::__construct( $config );
}
/**
* Purges remote files
*
* @param array $files
* @param array $results
* @return boolean
*/
function purge( $files, &$results ) {
if ( empty( $this->_config['account'] ) ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, __( 'Empty account #.', 'w3-total-cache' ) );
return false;
}
if ( empty( $this->_config['token'] ) ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, __( 'Empty token.', 'w3-total-cache' ) );
return false;
}
foreach ( $files as $file ) {
$local_path = $file['local_path'];
$remote_path = $file['remote_path'];
$url = $this->format_url( $remote_path );
$error = null;
if ( $this->_purge_content( $url, W3TC_CDN_EDGECAST_MEDIATYPE_HTTP_SMALL_OBJECT, $error ) ) {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, __( 'OK', 'w3-total-cache' ), $file );
} else {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR,
sprintf( __( 'Unable to purge (%s).', 'w3-total-cache' ), $error ),
$file );
}
}
return !$this->_is_error( $results );
}
/**
* Purges CDN completely
*
* @param unknown $results
* @return bool
*/
function purge_all( &$results ) {
return $this->purge( array( array( 'local_path'=>'*', 'remote_path'=> '*' ) ), $results );
}
/**
* Purge content
*
* @param string $path
* @param int $type
* @param string $error
* @return boolean
*/
function _purge_content( $path, $type, &$error ) {
$url = sprintf( W3TC_CDN_EDGECAST_PURGE_URL, $this->_config['account'] );
$args = array(
'method' => 'PUT',
'user-agent' => W3TC_POWERED_BY,
'headers' => array(
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => sprintf( 'TOK:%s', $this->_config['token'] )
),
'body' => json_encode( array(
'MediaPath' => $path,
'MediaType' => $type
) )
);
$response = wp_remote_request( $url, $args );
if ( is_wp_error( $response ) ) {
$error = implode( '; ', $response->get_error_messages() );
return false;
}
switch ( $response['response']['code'] ) {
case 200:
return true;
case 400:
$error = __( 'Invalid Request Parameter', 'w3-total-cache' );
return false;
case 403:
$error = __( 'Authentication Failure or Insufficient Access Rights', 'w3-total-cache' );
return false;
case 404:
$error = __( 'Invalid Request URI', 'w3-total-cache' );
return false;
case 405:
$error = __( 'Invalid Request', 'w3-total-cache' );
return false;
case 500:
$error = __( 'Server Error', 'w3-total-cache' );
return false;
}
$error = 'Unknown error';
return false;
}
}

View File

@ -0,0 +1,169 @@
<?php
namespace W3TC;
/**
* class CdnEngine_Mirror_Highwinds
*/
class CdnEngine_Mirror_Highwinds extends CdnEngine_Mirror {
private $api;
private $domains;
private $host_hash_code;
/**
* PHP5 Constructor
*
* @param array $config
* account_hash
* username
* password
* host_hash_code
*/
function __construct( $config = array() ) {
$this->api = new Cdn_Highwinds_Api( $config['account_hash'],
$config['api_token'] );
$this->host_hash_code = $config['host_hash_code'];
if ( !empty( $config['domains'] ) )
$this->domains = (array)$config['domains'];
else
$this->domains = array(
'cds.' . $config['host_hash_code'] . '.hwcdn.net' );
parent::__construct( $config );
}
/**
* Purges remote files
*
* @param array $files
* @param array $results
* @return boolean
*/
function purge( $files, &$results ) {
$results = array();
try {
$urls = array();
foreach ( $files as $file )
$urls[] = $this->_format_url( $file['remote_path'] );
$this->api->purge( $urls, false );
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_OK, 'OK' );
} catch ( \Exception $e ) {
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_HALT,
__( 'Failed to purge: ', 'w3-total-cache' ) . $e->getMessage() );
}
return !$this->_is_error( $results );
}
/**
* Purge CDN completely
*
* @param unknown $results
* @return bool
*/
function purge_all( &$results ) {
$results = array();
try {
$urls = array();
foreach ( $this->domains as $domain ) {
$urls[] = 'http://' . $domain . '/';
$urls[] = 'https://' . $domain . '/';
}
$this->api->purge( $urls, true );
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_OK, 'OK' );
} catch ( \Exception $e ) {
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_HALT,
__( 'Failed to purge all: ', 'w3-total-cache' ) . $e->getMessage() );
}
return !$this->_is_error( $results );
}
function get_domains() {
return $this->domains;
}
public function service_analytics_transfer() {
$start_date = gmdate( 'Y-m-d', strtotime( '-30 days', time() ) ) . 'T00:00:00Z';
$end_date = gmdate( 'Y-m-d' ) . 'T00:00:00Z';
$response = $this->api->analytics_transfer( $this->host_hash_code,
'P1D', 'CDS', $start_date, $end_date );
if ( !isset( $response['series'] ) || !is_array( $response['series'] ) ||
count( $response['series'] ) < 1 )
throw new \Exception( 'cant parse response' );
$series = $response['series'][0];
if ( !isset( $series['metrics'] ) || !is_array( $series['metrics'] ) )
throw new \Exception( 'cant parse response - no metrics' );
$metrics = $series['metrics'];
if ( !isset( $series['metrics'] ) || !is_array( $series['data'] ) )
throw new \Exception( 'cant parse response - no metrics' );
$output = array();
foreach ( $series['data'] as $data ) {
$item = array();
for ( $m = 0; $m < count( $metrics ); $m++ )
$item[$metrics[$m]] = $data[$m];
$output[] = $item;
}
return $output;
}
public function service_cnames_get() {
$scope_id = $this->_get_scope_id();
$configuration = $this->api->configure_scope_get( $this->host_hash_code,
$scope_id );
$domains = array();
if ( isset( $configuration['hostname'] ) ) {
foreach ( $configuration['hostname'] as $d )
$domains[] = $d['domain'];
}
return $domains;
}
public function service_cnames_set( $domains ) {
$scope_id = $this->_get_scope_id();
$configuration = $this->api->configure_scope_get( $this->host_hash_code,
$scope_id );
$hostname = array();
foreach ( $domains as $d )
$hostname[] = array( 'domain' => $d );
$configuration['hostname'] = $hostname;
$this->api->configure_scope_set( $this->host_hash_code,
$scope_id, $configuration );
}
private function _get_scope_id() {
$scopes_response = $this->api->configure_scopes( $this->host_hash_code );
$scope_id = 0;
foreach ( $scopes_response['list'] as $scope ) {
if ( $scope['platform'] == 'CDS' )
return $scope['id'];
}
throw new Exception( 'scope CDN hasnt been created' );
}
}

View File

@ -0,0 +1,136 @@
<?php
namespace W3TC;
/**
* class CdnEngine_Mirror_Highwinds
*/
class CdnEngine_Mirror_LimeLight extends CdnEngine_Mirror {
private $short_name;
private $username;
private $api_key;
private $debug;
private $domains;
/**
* PHP5 Constructor
*
* @param array $config
* account_hash
* username
* password
*/
function __construct( $config = array() ) {
$this->short_name = $config['short_name'];
$this->username = $config['username'];
$this->api_key = $config['api_key'];
$this->debug = $config['debug'];
$this->domains = (array)$config['domains'];
parent::__construct( $config );
}
/**
* Purges remote files
*
* @param array $files
* @param array $results
* @return boolean
*/
function purge( $files, &$results ) {
if ( empty( $this->short_name ) || empty( $this->username ) ||
empty( $this->api_key ) )
throw new \Exception( __( 'Credentials are not specified.', 'w3-total-cache' ) );
$api = new Cdnfsd_LimeLight_Api( $this->short_name, $this->username, $this->api_key );
$results = array();
try {
$items = array();
foreach ( $files as $file ) {
$url = $this->_format_url( $file['remote_path'] );
$items[] = array(
'pattern' => $url,
'exact' => true,
'evict' => false,
'incqs' => false
);
// max number of items per request based on API docs
if ( count( $items ) >= 100 ) {
if ( $this->debug ) {
Util_Debug::log( 'cdn', json_encode( $items, JSON_PRETTY_PRINT ) );
}
$api->purge( $items );
$items = array();
}
}
if ( $this->debug ) {
Util_Debug::log( 'cdn', json_encode( $items, JSON_PRETTY_PRINT ) );
}
$api->purge( $items );
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_OK, 'OK' );
} catch ( \Exception $e ) {
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_HALT,
__( 'Failed to purge: ', 'w3-total-cache' ) . $e->getMessage() );
}
return !$this->_is_error( $results );
}
/**
* Purge CDN completely
*
* @param unknown $results
* @return bool
*/
function purge_all( &$results ) {
if ( empty( $this->short_name ) || empty( $this->username ) ||
empty( $this->api_key ) )
throw new \Exception( __( 'Access key not specified.', 'w3-total-cache' ) );
$api = new Cdnfsd_LimeLight_Api( $this->short_name, $this->username, $this->api_key );
$results = array();
try {
$items = array();
foreach ( $this->domains as $domain ) {
$items[] = array(
'pattern' => 'http://' . $domain . '/*',
'exact' => false,
'evict' => false,
'incqs' => false
);
$items[] = array(
'pattern' => 'https://' . $domain . '/*',
'exact' => false,
'evict' => false,
'incqs' => false
);
}
if ( $this->debug ) {
Util_Debug::log( 'cdn', json_encode( $items, JSON_PRETTY_PRINT ) );
}
$api->purge( $items );
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_OK, 'OK' );
} catch ( \Exception $e ) {
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_HALT,
__( 'Failed to purge all: ', 'w3-total-cache' ) . $e->getMessage() );
}
return !$this->_is_error( $results );
}
function get_domains() {
return $this->domains;
}
}

View File

@ -0,0 +1,167 @@
<?php
namespace W3TC;
/**
* Rackspace CDN (pull) engine
*/
class CdnEngine_Mirror_RackSpaceCdn extends CdnEngine_Mirror {
private $_access_state;
private $_service_id;
private $_domains;
private $_api;
private $_new_access_state_callback;
function __construct( $config = array() ) {
$config = array_merge( array(
'user_name' => '',
'api_key' => '',
'region' => '',
'service_id' => '',
'service_access_url' => '',
'service_protocol' => 'http',
'domains' => array(),
'access_state' => '',
'new_access_state_callback' => ''
), $config );
$this->_service_id = $config['service_id'];
$this->_new_access_state_callback = $config['new_access_state_callback'];
// init access state
$this->_access_state = @json_decode( $config['access_state'], true );
if ( !is_array( $this->_access_state ) )
$this->_access_state = array();
$this->_access_state = array_merge( array(
'access_token' => '',
'access_region_descriptor' => array()
), $this->_access_state );
// cnames
if ( $config['service_protocol'] != 'https' && !empty( $config['domains'] ) )
$this->_domains = (array)$config['domains'];
else
$this->_domains = array( $config['service_access_url'] );
// form 'ssl' parameter based on service protocol
if ( $config['service_protocol'] == 'https' )
$config['ssl'] = 'enabled';
else
$config['ssl'] = 'disabled';
parent::__construct( $config );
$this->_create_api( array( $this, '_on_new_access_requested_api' ) );
}
private function _create_api( $new_access_required_callback_api ) {
$this->_api = new Cdn_RackSpace_Api_Cdn( array(
'access_token' => $this->_access_state['access_token'],
'access_region_descriptor' => $this->_access_state['access_region_descriptor'],
'new_access_required' => $new_access_required_callback_api ) );
}
/**
* Called when new access token issued by api objects
*/
public function _on_new_access_requested_api() {
$r = Cdn_RackSpace_Api_Tokens::authenticate( $this->_config['user_name'],
$this->_config['api_key'] );
if ( !isset( $r['access_token'] ) || !isset( $r['services'] ) )
throw new \Exception( 'Authentication failed' );
$r['regions'] = Cdn_RackSpace_Api_Tokens::cdn_services_by_region(
$r['services'] );
if ( !isset( $r['regions'][$this->_config['region']] ) )
throw new \Exception( 'Region ' . $this->_config['region'] . ' not found' );
$this->_access_state['access_token'] = $r['access_token'];
$this->_access_state['access_region_descriptor'] =
$r['regions'][$this->_config['region']];
$this->_create_api( array( $this, '_on_new_access_requested_second_time' ) );
if ( !empty( $this->_new_access_state_callback ) )
call_user_func( $this->_new_access_state_callback,
json_encode( $this->_access_state ) );
return $this->_api;
}
private function _on_new_access_requested_second_time() {
throw new \Exception( 'Authentication failed' );
}
/**
* Purges remote files
*
* @param array $files
* @param array $results
* @return boolean
*/
function purge( $files, &$results ) {
$results = array();
try {
foreach ( $files as $file ) {
$url = $this->_format_url( $file['remote_path'] );
$this->_api->purge( $this->_service_id, $url );
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_OK, 'OK' );
}
} catch ( \Exception $e ) {
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_HALT,
__( 'Failed to purge: ', 'w3-total-cache' ) . $e->getMessage() );
}
return !$this->_is_error( $results );
}
public function get_domains() {
return $this->_domains;
}
public function service_domains_get() {
$service = $this->_api->service_get( $this->_service_id );
$domains = array();
if ( isset( $service['domains'] ) ) {
foreach ( $service['domains'] as $d )
$domains[] = $d['domain'];
}
return $domains;
}
public function service_domains_set( $domains ) {
$value = array();
foreach ( $domains as $d ) {
$v = array( 'domain' => $d );
if ( $this->_config['service_protocol'] == 'https' )
$v['protocol'] = 'https';
$value[] = $v;
}
$this->_api->service_set( $this->_service_id,
array( array(
'op' => 'replace',
'path' => '/domains',
'value' => $value ) ) );
}
}

View File

@ -0,0 +1,113 @@
<?php
namespace W3TC;
class CdnEngine_Mirror_StackPath extends CdnEngine_Mirror {
/**
* PHP5 Constructor
*
* @param array $config
*/
function __construct( $config = array() ) {
$config = array_merge( array(
'authorization_key' => '',
'alias' => '',
'consumerkey' => '',
'consumersecret' => '',
'zone_id' => 0
), $config );
$split_keys = explode( '+', $config['authorization_key'] );
if ( sizeof( $split_keys )==3 )
list( $config['alias'], $config['consumerkey'], $config['consumersecret'] ) = $split_keys;
parent::__construct( $config );
}
/**
* Purges remote files
*
* @param array $files
* @param array $results
* @return boolean
*/
function purge( $files, &$results ) {
if ( empty( $this->_config['authorization_key'] ) ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, __( 'Empty Authorization Key.', 'w3-total-cache' ) );
return false;
}
if ( empty( $this->_config['alias'] ) ||
empty( $this->_config['consumerkey'] ) ||
empty( $this->_config['consumersecret'] ) ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, __( 'Malformed Authorization Key.', 'w3-total-cache' ) );
return false;
}
$api = new Cdn_StackPath_Api( $this->_config['alias'],
$this->_config['consumerkey'], $this->_config['consumersecret'] );
$results = array();
try {
$zone_id = $this->_config['zone_id'];
if ( $zone_id == 0 || is_null( $zone_id ) ) {
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_ERROR,
__( 'No zone defined', 'w3-total-cache' ) );
return !$this->_is_error( $results );
}
$files_to_pass = array();
foreach ( $files as $file )
$files_to_pass[] = '/' . $file['remote_path'];
$params = array( 'files' => $files_to_pass );
$api->delete_site_cache( $zone_id, $params );
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_OK, 'OK' );
} catch ( \Exception $e ) {
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_HALT, __( 'Failure to pull zone: ', 'w3-total-cache' ) . $e->getMessage() );
}
return !$this->_is_error( $results );
}
/**
* Purge CDN completely
*
* @param unknown $results
* @return bool
*/
function purge_all( &$results ) {
if ( empty( $this->_config['authorization_key'] ) ) {
$results = $this->_get_results( array(), W3TC_CDN_RESULT_HALT, __( 'Empty Authorization Key.', 'w3-total-cache' ) );
return false;
}
if ( empty( $this->_config['alias'] ) || empty( $this->_config['consumerkey'] ) || empty( $this->_config['consumersecret'] ) ) {
$results = $this->_get_results( array(), W3TC_CDN_RESULT_HALT, __( 'Malformed Authorization Key.', 'w3-total-cache' ) );
return false;
}
$api = new Cdn_StackPath_Api( $this->_config['alias'], $this->_config['consumerkey'], $this->_config['consumersecret'] );
$results = array();
try {
$zone_id = $this->_config['zone_id'];
if ( $zone_id == 0 || is_null( $zone_id ) ) {
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_ERROR,
__( 'No zone defined', 'w3-total-cache' ) );
return !$this->_is_error( $results );
}
$file_purge = $api->delete_site_cache( $zone_id );
} catch ( \Exception $e ) {
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_HALT, __( 'Failure to pull zone: ', 'w3-total-cache' ) . $e->getMessage() );
}
return !$this->_is_error( $results );
}
}

View File

@ -0,0 +1,109 @@
<?php
namespace W3TC;
class CdnEngine_Mirror_StackPath2 extends CdnEngine_Mirror {
/**
* PHP5 Constructor
*
* @param array $config
*/
function __construct( $config = array() ) {
$config = array_merge( array(
'client_id' => '',
'client_secret' => '',
'stack_id' => '',
'site_root_domain' => '',
'access_token' => '',
'on_new_access_token' => null
), $config );
parent::__construct( $config );
}
/**
* Purges remote files
*
* @param array $files
* @param array $results
* @return boolean
*/
function purge( $files, &$results ) {
if ( empty( $this->_config['client_id'] ) ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, __( 'Empty Authorization Key.', 'w3-total-cache' ) );
return false;
}
$url_prefixes = $this->url_prefixes();
$api = new Cdn_StackPath2_Api( $this->_config );
$results = array();
try {
$items = array();
foreach ( $files as $file ) {
foreach ( $url_prefixes as $prefix ) {
$items[] = array( 'url' => $prefix . '/' . $file['remote_path'],
'recursive' => true,
);
}
}
$api->purge( array( 'items' => $items ) );
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_OK, 'OK' );
} catch ( \Exception $e ) {
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_HALT, __( 'Failure to pull zone: ', 'w3-total-cache' ) . $e->getMessage() );
}
return !$this->_is_error( $results );
}
/**
* Purge CDN completely
*
* @param unknown $results
* @return bool
*/
function purge_all( &$results ) {
if ( empty( $this->_config['client_id'] ) ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, __( 'Empty Authorization Key.', 'w3-total-cache' ) );
return false;
}
$url_prefixes = $this->url_prefixes();
$api = new Cdn_StackPath2_Api( $this->_config );
$results = array();
try {
$items = array();
foreach ( $url_prefixes as $prefix ) {
$items[] = array( 'url' => $prefix . '/',
'recursive' => true,
);
}
$r = $api->purge( array( 'items' => $items ) );
} catch ( \Exception $e ) {
$results[] = $this->_get_result( '', '', W3TC_CDN_RESULT_HALT, __( 'Failure to pull zone: ', 'w3-total-cache' ) . $e->getMessage() );
}
return !$this->_is_error( $results );
}
private function url_prefixes() {
$url_prefixes = array();
if ( $this->_config['ssl'] == 'auto' ||
$this->_config['ssl'] == 'enabled' ) {
$url_prefixes[] = 'https://' . $this->_config['site_root_domain'];
}
if ( $this->_config['ssl'] == 'auto' ||
$this->_config['ssl'] != 'enabled' ) {
$url_prefixes[] = 'http://' . $this->_config['site_root_domain'];
}
return $url_prefixes;
}
}

View File

@ -0,0 +1,335 @@
<?php
namespace W3TC;
/**
* Rackspace Cloud Files CDN engine
*/
class CdnEngine_RackSpaceCloudFiles extends CdnEngine_Base {
private $_access_state;
private $_container;
private $_api_files;
private $_api_cdn;
function __construct( $config = array() ) {
$config = array_merge( array(
'user_name' => '',
'api_key' => '',
'region' => '',
'container' => '',
'cname' => array(),
'access_state' => ''
), $config );
$this->_container = $config['container'];
$this->_new_access_state_callback = $config['new_access_state_callback'];
// init access state
$this->_access_state = @json_decode( $config['access_state'], true );
if ( !is_array( $this->_access_state ) )
$this->_access_state = array();
$this->_access_state = array_merge( array(
'access_token' => '',
'access_region_descriptor' => array(),
'host_http' => '',
'host_https' => ''
), $this->_access_state );
parent::__construct( $config );
$this->_create_api(
array( $this, '_on_new_access_requested_api_files' ),
array( $this, '_on_new_access_requested_api_cdn' ) );
}
private function _create_api( $new_access_required_callback_api_files,
$new_access_required_callback_api_cdn ) {
$this->_api_files = new Cdn_RackSpace_Api_CloudFiles( array(
'access_token' => $this->_access_state['access_token'],
'access_region_descriptor' => $this->_access_state['access_region_descriptor'],
'new_access_required' => $new_access_required_callback_api_files ) );
$this->_api_cdn = new Cdn_RackSpace_Api_CloudFilesCdn( array(
'access_token' => $this->_access_state['access_token'],
'access_region_descriptor' => $this->_access_state['access_region_descriptor'],
'new_access_required' => $new_access_required_callback_api_cdn ) );
}
/**
* Called when new access token issued by api objects
*/
public function _on_new_access_requested_api_files() {
$this->_on_new_access_requested();
return $this->_api_files;
}
/**
* Called when new access token issued by api objects
*/
public function _on_new_access_requested_api_cdn() {
$this->_on_new_access_requested();
return $this->_api_cdn;
}
private function _on_new_access_requested() {
$r = Cdn_RackSpace_Api_Tokens::authenticate( $this->_config['user_name'],
$this->_config['api_key'] );
if ( !isset( $r['access_token'] ) || !isset( $r['services'] ) )
throw new \Exception( 'Authentication failed' );
$r['regions'] = Cdn_RackSpace_Api_Tokens::cloudfiles_services_by_region(
$r['services'] );
if ( !isset( $r['regions'][$this->_config['region']] ) )
throw new \Exception( 'Region ' . $this->_config['region'] . ' not found' );
$this->_access_state['access_token'] = $r['access_token'];
$this->_access_state['access_region_descriptor'] =
$r['regions'][$this->_config['region']];
$this->_create_api(
array( $this, '_on_new_access_requested_second_time' ),
array( $this, '_on_new_access_requested_second_time' ) );
$c = $this->_api_cdn->container_get( $this->_config['container'] );
$this->_access_state['host_http'] = substr( $c['x-cdn-uri'], 7 );
$this->_access_state['host_https'] = substr( $c['x-cdn-ssl-uri'], 8 );
call_user_func( $this->_new_access_state_callback,
json_encode( $this->_access_state ) );
}
private function _on_new_access_requested_second_time() {
throw new \Exception( 'Authentication failed' );
}
/**
* Formats URL
*/
function _format_url( $path ) {
$domain = $this->get_domain( $path );
if ( $domain ) {
$scheme = $this->_get_scheme();
// it does not support '+', requires '%2B'
$path = str_replace( '+', '%2B', $path );
$url = sprintf( '%s://%s/%s', $scheme, $domain, $path );
return $url;
}
return false;
}
/**
* Uploads files to CDN
*
* @param array $files
* @param array $results
* @param boolean $force_rewrite
* @return boolean
*/
function upload( $files, &$results, $force_rewrite = false,
$timeout_time = NULL ) {
foreach ( $files as $file ) {
$local_path = $file['local_path'];
$remote_path = $file['remote_path'];
// process at least one item before timeout so that progress goes on
if ( !empty( $results ) ) {
if ( !is_null( $timeout_time ) && time() > $timeout_time ) {
return 'timeout';
}
}
if ( !file_exists( $local_path ) ) {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR, 'Source file not found.', $file );
continue;
}
$file_content = file_get_contents( $local_path );
$do_write = true;
// rewrite is optional, check md5
if ( !$force_rewrite ) {
$object_meta = null;
try {
$object_meta = $this->_api_files->object_get_meta_or_null(
$this->_container, $remote_path );
} catch ( \Exception $exception ) {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR,
sprintf( 'Unable to check object (%s).', $exception->getMessage() ),
$file );
$do_write = false;
}
if ( is_array( $object_meta ) && isset( $object_meta['etag'] ) ) {
$md5_actual = md5( $file_content );
if ( $md5_actual == $object_meta['etag'] ) {
$results[] = $this->_get_result( $local_path,
$remote_path, W3TC_CDN_RESULT_OK,
'Object up-to-date.', $file );
$do_write = false;
}
}
}
if ( $do_write ) {
try {
$this->_api_files->object_create( array(
'container' => $this->_container,
'name' => $remote_path,
'content_type' => Util_Mime::get_mime_type( $local_path ),
'content' => $file_content ) );
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'OK', $file );
} catch ( \Exception $exception ) {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR,
sprintf( 'Unable to create object (%s).', $exception->getMessage() ),
$file );
}
}
}
return !$this->_is_error( $results );
}
/**
* Deletes files from CDN
*
* @param array $files
* @param array $results
* @return boolean
*/
function delete( $files, &$results ) {
foreach ( $files as $file ) {
$local_path = $file['local_path'];
$remote_path = $file['remote_path'];
try {
$this->_api_files->object_delete( $this->_container, $remote_path );
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'OK', $file );
} catch ( \Exception $exception ) {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR,
sprintf( 'Unable to delete object (%s).',
$exception->getMessage() ),
$file );
}
}
return !$this->_is_error( $results );
}
/**
* Test CDN connection
*
* @param string $error
* @return boolean
*/
function test( &$error ) {
$filename = 'test_rscf_' . md5( time() );
try {
$object = $this->_api_files->object_create( array(
'container' => $this->_container,
'name' => $filename,
'content_type' => 'text/plain',
'content' => $filename ) );
} catch ( \Exception $exception ) {
$error = sprintf( 'Unable to write object (%s).', $exception->getMessage() );
return false;
}
$result = true;
try {
$r = wp_remote_get( 'http://' . $this->get_host_http() . '/' . $filename );
if ( $r['body'] != $filename ) {
$error = 'Failed to retrieve object after storing.';
$result = false;
}
} catch ( \Exception $exception ) {
$error = sprintf( 'Unable to read object (%s).', $exception->getMessage() );
$result = false;
}
try {
$this->_api_files->object_delete( $this->_container, $filename );
} catch ( \Exception $exception ) {
$error = sprintf( 'Unable to delete object (%s).', $exception->getMessage() );
$result = false;
}
return $result;
}
/**
* Returns CDN domain
*
* @return array
*/
function get_domains() {
if ( Util_Environment::is_https() ) {
if ( !empty( $this->_config['cname'] ) ) {
return (array) $this->_config['cname'];
}
return array( $this->get_host_https() );
} else {
if ( !empty( $this->_config['cname'] ) ) {
return (array) $this->_config['cname'];
}
return array( $this->get_host_http() );
}
}
/**
* Returns VIA string
*
* @return string
*/
function get_via() {
return sprintf( 'Rackspace Cloud Files: %s', parent::get_via() );
}
public function get_host_http() {
if ( empty( $this->_access_state['host_http'] ) )
$this->_on_new_access_requested();
return $this->_access_state['host_http'];
}
public function get_host_https() {
if ( empty( $this->_access_state['host_https'] ) )
$this->_on_new_access_requested();
return $this->_access_state['host_https'];
}
}

View File

@ -0,0 +1,519 @@
<?php
namespace W3TC;
if ( !defined( 'W3TC_SKIPLIB_AWS' ) ) {
require_once W3TC_DIR . '/vendor/autoload.php';
}
/**
* CDN engine for S3 push type
*/
class CdnEngine_S3 extends CdnEngine_Base {
private $api;
/**
* Regions list.
*
* @link https://docs.aws.amazon.com/general/latest/gr/rande.html
*
* @return array
*/
static public function regions_list() {
return array(
'us-east-1' => __( 'US East (N. Virginia)', 'w3-total-cache' ),
'us-east-2' => __( 'US East (Ohio)', 'w3-total-cache' ),
'us-west-1' => __( 'US West (N. California)', 'w3-total-cache' ),
'us-west-2' => __( 'US West (Oregon)', 'w3-total-cache' ),
'af-south-1' => __( 'Africa (Cape Town)', 'w3-total-cache' ),
'ap-east-1' => __( 'Asia Pacific (Hong Kong)', 'w3-total-cache' ),
'ap-northeast-1' => __( 'Asia Pacific (Tokyo)', 'w3-total-cache' ),
'ap-northeast-2' => __( 'Asia Pacific (Seoul)', 'w3-total-cache' ),
'ap-northeast-3' => __( 'Asia Pacific (Osaka-Local)', 'w3-total-cache' ),
'ap-south-1' => __( 'Asia Pacific (Mumbai)', 'w3-total-cache' ),
'ap-southeast-1' => __( 'Asia Pacific (Singapore)', 'w3-total-cache' ),
'ap-southeast-2' => __( 'Asia Pacific (Sydney)', 'w3-total-cache' ),
'ca-central-1' => __( 'Canada (Central)', 'w3-total-cache' ),
'cn-north-1' => __( 'China (Beijing)', 'w3-total-cache' ),
'cn-northwest-1' => __( 'China (Ningxia)', 'w3-total-cache' ),
'eu-central-1' => __( 'Europe (Frankfurt)', 'w3-total-cache' ),
'eu-north-1' => __( 'Europe (Stockholm)', 'w3-total-cache' ),
'eu-south-1' => __( 'Europe (Milan)', 'w3-total-cache' ),
'eu-west-1' => __( 'Europe (Ireland)', 'w3-total-cache' ),
'eu-west-2' => __( 'Europe (London)', 'w3-total-cache' ),
'eu-west-3' => __( 'Europe (Paris)', 'w3-total-cache' ),
'me-south-1' => __( 'Middle East (Bahrain)', 'w3-total-cache' ),
'sa-east-1' => __( 'South America (São Paulo)', 'w3-total-cache' ),
);
}
public function __construct( $config = array() ) {
$config = array_merge( array(
'key' => '',
'secret' => '',
'bucket' => '',
'bucket_location' => '',
'cname' => array(),
), $config );
parent::__construct( $config );
}
/**
* Formats URL
*/
function _format_url( $path ) {
$domain = $this->get_domain( $path );
if ( $domain ) {
$scheme = $this->_get_scheme();
// it does not support '+', requires '%2B'
$path = str_replace( '+', '%2B', $path );
$url = sprintf( '%s://%s/%s', $scheme, $domain, $path );
return $url;
}
return false;
}
/**
* Inits S3 object
*
* @param string $error
* @return boolean
*/
public function _init() {
if ( !is_null( $this->api ) ) {
return;
}
if ( empty( $this->_config['bucket'] ) ) {
throw new \Exception( 'Empty bucket.' );
}
if ( empty( $this->_config['key'] ) && empty( $this->_config['secret'] ) ) {
$credentials = \Aws\Credentials\CredentialProvider::defaultProvider();
} else {
if ( empty( $this->_config['key'] ) ) {
throw new \Exception( 'Empty access key.' );
}
if ( empty( $this->_config['secret'] ) ) {
throw new \Exception( 'Empty secret key.' );
}
$credentials = new \Aws\Credentials\Credentials(
$this->_config['key'],
$this->_config['secret'] );
}
if ( isset( $this->_config['public_objects'] ) && 'enabled' === $this->_config['public_objects'] ) {
$this->_config['s3_acl'] = 'public-read';
}
$this->api = new \Aws\S3\S3Client( array(
'credentials' => $credentials,
'region' => $this->_config['bucket_location'],
'version' => '2006-03-01',
'use_arn_region' => true,
)
);
}
/**
* Uploads files to S3
*
* @param array $files
* @param array $results
* @param boolean $force_rewrite
* @return boolean
*/
public function upload( $files, &$results, $force_rewrite = false,
$timeout_time = NULL ) {
$error = null;
try {
$this->_init();
} catch ( \Exception $ex ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, $ex->getMessage() );
return false;
}
foreach ( $files as $file ) {
$local_path = $file['local_path'];
$remote_path = $file['remote_path'];
// process at least one item before timeout so that progress goes on
if ( !empty( $results ) ) {
if ( !is_null( $timeout_time ) && time() > $timeout_time ) {
return 'timeout';
}
}
$results[] = $this->_upload( $file, $force_rewrite );
if ( $this->_config['compression'] && $this->_may_gzip( $remote_path ) ) {
$file['remote_path_gzip'] = $remote_path . $this->_gzip_extension;
$results[] = $this->_upload_gzip( $file, $force_rewrite );
}
}
return !$this->_is_error( $results );
}
/**
* Uploads single file to S3
*
* @param array CDN file array
* @param boolean $force_rewrite
* @return array
*/
private function _upload( $file, $force_rewrite = false ) {
$local_path = $file['local_path'];
$remote_path = $file['remote_path'];
if ( !file_exists( $local_path ) ) {
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR, 'Source file not found.', $file );
}
try {
if ( !$force_rewrite ) {
try {
$info = $this->api->headObject( array(
'Bucket' => $this->_config['bucket'],
'Key' => $remote_path )
);
$hash = '"' . @md5_file( $local_path ) . '"';
$s3_hash = ( isset( $info['ETag'] ) ? $info['ETag'] : '' );
if ( $hash === $s3_hash ) {
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'Object up-to-date.', $file );
}
} catch ( \Aws\Exception\AwsException $ex ) {
if ( $ex->getAwsErrorCode() == 'NotFound' ) {
} else {
throw $ex;
}
}
}
$headers = $this->get_headers_for_file( $file );
$result = $this->_put_object( array(
'Key' => $remote_path,
'SourceFile' => $local_path,
), $headers
);
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'OK', $file );
} catch ( \Exception $ex ) {
$error = sprintf( 'Unable to put object (%s).', $ex->getMessage() );
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR, $error, $file );
}
}
/**
* Uploads gzip version of file
*/
private function _upload_gzip( $file, $force_rewrite = false ) {
$local_path = $file['local_path'];
$remote_path = $file['remote_path_gzip'];
if ( !function_exists( 'gzencode' ) ) {
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR, "GZIP library doesn't exist.", $file );
}
if ( !file_exists( $local_path ) ) {
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR, 'Source file not found.', $file );
}
$contents = @file_get_contents( $local_path );
if ( $contents === false ) {
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR, 'Unable to read file.', $file );
}
$data = gzencode( $contents );
try {
if ( !$force_rewrite ) {
try {
$info = $this->api->headObject( array(
'Bucket' => $this->_config['bucket'],
'Key' => $remote_path )
);
$hash = '"' . md5( $data ) . '"';
$s3_hash = ( isset( $info['ETag'] ) ? $info['ETag'] : '' );
if ( $hash === $s3_hash ) {
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'Object up-to-date.', $file );
}
} catch ( \Aws\Exception\AwsException $ex ) {
if ( $ex->getAwsErrorCode() == 'NotFound' ) {
} else {
throw $ex;
}
}
}
$headers = $this->get_headers_for_file( $file );
$headers['Content-Encoding'] = 'gzip';
$result = $this->_put_object( array(
'Key' => $remote_path,
'Body' => $data
), $headers
);
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'OK', $file );
} catch ( \Exception $ex ) {
$error = sprintf( 'Unable to put object (%s).', $ex->getMessage() );
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR, $error, $file );
}
}
/**
* Wrapper to set headers well
*/
private function _put_object( $data, $headers ) {
if ( ! empty( $this->_config['s3_acl'] ) ) {
$data['ACL'] = 'public-read';
}
$data['Bucket'] = $this->_config['bucket'];
$data['ContentType'] = $headers['Content-Type'];
if ( isset( $headers['Content-Encoding'] ) ) {
$data['ContentEncoding'] = $headers['Content-Encoding'];
}
if ( isset( $headers['Cache-Control'] ) ) {
$data['CacheControl'] = $headers['Cache-Control'];
}
return $this->api->putObject( $data );
}
/**
* Deletes files from S3
*
* @param array $files
* @param array $results
* @return boolean
*/
public function delete( $files, &$results ) {
$error = null;
try {
$this->_init();
} catch ( \Exception $ex ) {
$results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, $ex->getMessage() );
return false;
}
foreach ( $files as $file ) {
$local_path = $file['local_path'];
$remote_path = $file['remote_path'];
try {
$this->api->deleteObject( array(
'Bucket' => $this->_config['bucket'],
'Key' => $remote_path
) );
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'OK', $file );
} catch ( \Exception $ex ) {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR,
sprintf( 'Unable to delete object (%s).',
$ex->getMessage() ),
$file );
}
if ( $this->_config['compression'] ) {
$remote_path_gzip = $remote_path . $this->_gzip_extension;
try {
$this->api->deleteObject( array(
'Bucket' => $this->_config['bucket'],
'Key' => $remote_path_gzip
) );
$results[] = $this->_get_result( $local_path, $remote_path_gzip,
W3TC_CDN_RESULT_OK, 'OK', $file );
} catch ( \Exception $ex ) {
$results[] = $this->_get_result( $local_path, $remote_path_gzip,
W3TC_CDN_RESULT_ERROR,
sprintf( 'Unable to delete object (%s).',
$ex->getMessage() ),
$file );
}
}
}
return !$this->_is_error( $results );
}
/**
* Test CDN connectivity works
*/
public function test( &$error ) {
if ( !parent::test( $error ) ) {
return false;
}
$key = 'test_s3_' . md5( time() );
$this->_init();
$buckets = $this->api->listBuckets();
$bucket_found = false;
foreach ( $buckets['Buckets'] as $bucket ) {
if ( $bucket['Name'] == $this->_config['bucket'] ) {
$bucket_found = true;
}
}
if ( !$bucket_found ) {
throw new \Exception(
sprintf(
// translators: 1: AWS S3 bucket name.
esc_html__( 'Bucket doesn\'t exist: %1$s.', 'w3-total-cache' ),
$this->_config['bucket']
)
);
}
if ( ! empty( $this->_config['s3_acl'] ) ) {
$result = $this->api->putObject(
array(
'ACL' => $this->_config['s3_acl'],
'Bucket' => $this->_config['bucket'],
'Key' => $key,
'Body' => $key
)
);
} else {
$result = $this->api->putObject(
array(
'Bucket' => $this->_config['bucket'],
'Key' => $key,
'Body' => $key
)
);
}
$object = $this->api->getObject( array(
'Bucket' => $this->_config['bucket'],
'Key' => $key
) );
if ( $object['Body'] != $key ) {
$error = 'Objects are not equal.';
$this->api->deleteObject( array(
'Bucket' => $this->_config['bucket'],
'Key' => $key
) );
return false;
}
$this->api->deleteObject( array(
'Bucket' => $this->_config['bucket'],
'Key' => $key
) );
return true;
}
/**
* Returns CDN domain
*
* @return array
*/
public function get_domains() {
if ( !empty( $this->_config['cname'] ) ) {
return (array) $this->_config['cname'];
} elseif ( !empty( $this->_config['bucket'] ) ) {
$domain = sprintf( '%s.s3.amazonaws.com', $this->_config['bucket'] );
return array(
$domain
);
}
return array();
}
/**
* Returns via string
*
* @return string
*/
public function get_via() {
return sprintf( 'Amazon Web Services: S3: %s', parent::get_via() );
}
/**
* Creates bucket
*/
public function create_container() {
$this->_init();
try {
$buckets = $this->api->listBuckets();
} catch ( \Exception $ex ) {
throw new \Exception( 'Unable to list buckets: ' . $ex->getMessage() );
}
foreach ( $buckets['Buckets'] as $bucket ) {
if ( $bucket['Name'] == $this->_config['bucket'] ) {
throw new \Exception( 'Bucket already exists: ' . $this->_config['bucket'] );
}
}
try {
$this->api->createBucket( array(
'Bucket' => $this->_config['bucket'],
) );
$this->api->putBucketCors( array(
'Bucket' => $this->_config['bucket'],
'CORSConfiguration' => array(
'CORSRules' => array(
array(
'AllowedHeaders' => array( '*' ),
'AllowedMethods' => array( 'GET' ),
'AllowedOrigins' => array( '*' )
)
)
)
) );
} catch ( \Exception $e) {
throw new \Exception( 'Failed to create bucket: ' . $ex->getMessage() );
}
}
/**
* How and if headers should be set
*
* @return string W3TC_CDN_HEADER_NONE, W3TC_CDN_HEADER_UPLOADABLE, W3TC_CDN_HEADER_MIRRORING
*/
public function headers_support() {
return W3TC_CDN_HEADER_UPLOADABLE;
}
}

View File

@ -0,0 +1,351 @@
<?php
namespace W3TC;
/**
* Amazon S3 CDN engine
*/
if ( !class_exists( 'S3Compatible' ) ) {
require_once W3TC_LIB_DIR . '/S3Compatible.php';
}
/**
* class CdnEngine_S3
*/
class CdnEngine_S3_Compatible extends CdnEngine_Base {
/**
* S3 object
*/
private $_s3 = null;
/**
* PHP5 Constructor
*
* @param array $config
*/
function __construct( $config = array() ) {
$config = array_merge( array(
'key' => '',
'secret' => '',
'bucket' => '',
'cname' => array(),
), $config );
$this->_s3 = new \S3Compatible( $config['key'], $config['secret'], false,
$config['api_host'] );
$this->_s3->setSignatureVersion( 'v2' );
parent::__construct( $config );
}
/**
* Formats URL
*
* @param string $path
* @return string
*/
function _format_url( $path ) {
$domain = $this->get_domain( $path );
if ( $domain ) {
$scheme = $this->_get_scheme();
// it does not support '+', requires '%2B'
$path = str_replace( '+', '%2B', $path );
$url = sprintf( '%s://%s/%s', $scheme, $domain, $path );
return $url;
}
return false;
}
/**
* Uploads files to S3
*
* @param array $files
* @param array $results
* @param boolean $force_rewrite
* @return boolean
*/
function upload( $files, &$results, $force_rewrite = false,
$timeout_time = NULL ) {
$error = null;
foreach ( $files as $file ) {
$local_path = $file['local_path'];
$remote_path = $file['remote_path'];
// process at least one item before timeout so that progress goes on
if ( !empty( $results ) ) {
if ( !is_null( $timeout_time ) && time() > $timeout_time ) {
return 'timeout';
}
}
$results[] = $this->_upload( $file, $force_rewrite );
if ( $this->_config['compression'] && $this->_may_gzip( $remote_path ) ) {
$file['remote_path_gzip'] = $remote_path . $this->_gzip_extension;
$results[] = $this->_upload_gzip( $file, $force_rewrite );
}
}
return !$this->_is_error( $results );
}
/**
* Uploads single file to S3
*
* @param array CDN file array
* @param boolean $force_rewrite
* @return array
*/
function _upload( $file, $force_rewrite = false ) {
$local_path = $file['local_path'];
$remote_path = $file['remote_path'];
if ( !file_exists( $local_path ) ) {
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR, 'Source file not found.', $file );
}
if ( !$force_rewrite ) {
$this->_set_error_handler();
$info = @$this->_s3->getObjectInfo( $this->_config['bucket'],
$remote_path );
$this->_restore_error_handler();
if ( $info ) {
$hash = @md5_file( $local_path );
$s3_hash = ( isset( $info['hash'] ) ? $info['hash'] : '' );
if ( $hash === $s3_hash ) {
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'Object up-to-date.', $file );
}
}
}
$headers = $this->get_headers_for_file( $file, array( 'ETag' => '*' ) );
$this->_set_error_handler();
$result = @$this->_s3->putObjectFile( $local_path,
$this->_config['bucket'], $remote_path,
\S3Compatible::ACL_PUBLIC_READ, array(), $headers );
$this->_restore_error_handler();
if ( $result ) {
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'OK', $file );
}
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR,
sprintf( 'Unable to put object (%s).', $this->_get_last_error() ),
$file );
}
/**
* Uploads gzip version of file
*
* @param string $local_path
* @param string $remote_path
* @param boolean $force_rewrite
* @return array
*/
function _upload_gzip( $file, $force_rewrite = false ) {
$local_path = $file['local_path'];
$remote_path = $file['remote_path_gzip'];
if ( !function_exists( 'gzencode' ) )
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR, "GZIP library doesn't exist.", $file );
if ( !file_exists( $local_path ) )
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR, 'Source file not found.', $file );
$contents = @file_get_contents( $local_path );
if ( $contents === false )
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR, 'Unable to read file.', $file );
$data = gzencode( $contents );
if ( !$force_rewrite ) {
$this->_set_error_handler();
$info = @$this->_s3->getObjectInfo( $this->_config['bucket'],
$remote_path );
$this->_restore_error_handler();
if ( $info ) {
$hash = md5( $data );
$s3_hash = ( isset( $info['hash'] ) ? $info['hash'] : '' );
if ( $hash === $s3_hash ) {
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'Object up-to-date.', $file );
}
}
}
$headers = $this->get_headers_for_file( $file, array( 'ETag' => '*' ) );
$headers = array_merge( $headers, array(
'Vary' => 'Accept-Encoding',
'Content-Encoding' => 'gzip'
) );
$this->_set_error_handler();
$result = @$this->_s3->putObjectString( $data, $this->_config['bucket'],
$remote_path, \S3Compatible::ACL_PUBLIC_READ, array(), $headers );
$this->_restore_error_handler();
if ( $result )
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'OK', $file );
return $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR, sprintf( 'Unable to put object (%s).',
$this->_get_last_error() ), $file );
}
/**
* Deletes files from S3
*
* @param array $files
* @param array $results
* @return boolean
*/
function delete( $files, &$results ) {
$error = null;
foreach ( $files as $file ) {
$local_path = $file['local_path'];
$remote_path = $file['remote_path'];
$this->_set_error_handler();
$result = @$this->_s3->deleteObject( $this->_config['bucket'],
$remote_path );
$this->_restore_error_handler();
if ( $result ) {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_OK, 'OK', $file );
} else {
$results[] = $this->_get_result( $local_path, $remote_path,
W3TC_CDN_RESULT_ERROR,
sprintf( 'Unable to delete object (%s).',
$this->_get_last_error() ), $file );
}
if ( $this->_config['compression'] ) {
$remote_path_gzip = $remote_path . $this->_gzip_extension;
$this->_set_error_handler();
$result = @$this->_s3->deleteObject( $this->_config['bucket'],
$remote_path_gzip );
$this->_restore_error_handler();
if ( $result ) {
$results[] = $this->_get_result( $local_path,
$remote_path_gzip, W3TC_CDN_RESULT_OK, 'OK', $file );
} else {
$results[] = $this->_get_result( $local_path,
$remote_path_gzip, W3TC_CDN_RESULT_ERROR,
sprintf( 'Unable to delete object (%s).',
$this->_get_last_error() ),
$file );
}
}
}
return !$this->_is_error( $results );
}
/**
* Tests S3
*
* @param string $error
* @return boolean
*/
function test( &$error ) {
if ( !parent::test( $error ) ) {
return false;
}
$string = 'test_s3_' . md5( time() );
$this->_set_error_handler();
if ( !@$this->_s3->putObjectString( $string, $this->_config['bucket'],
$string, \S3Compatible::ACL_PUBLIC_READ ) ) {
$error = sprintf( 'Unable to put object (%s).',
$this->_get_last_error() );
$this->_restore_error_handler();
return false;
}
$object = @$this->_s3->getObject( $this->_config['bucket'], $string );
if ( !$object ) {
$error = sprintf( 'Unable to get object (%s).',
$this->_get_last_error() );
$this->_restore_error_handler();
return false;
}
if ( $object->body != $string ) {
$error = 'Objects are not equal.';
@$this->_s3->deleteObject( $this->_config['bucket'], $string );
$this->_restore_error_handler();
return false;
}
if ( !@$this->_s3->deleteObject( $this->_config['bucket'], $string ) ) {
$error = sprintf( 'Unable to delete object (%s).',
$this->_get_last_error() );
$this->_restore_error_handler();
return false;
}
$this->_restore_error_handler();
return true;
}
/**
* Returns CDN domain
*
* @return array
*/
function get_domains() {
return (array) $this->_config['cname'];
}
/**
* Returns via string
*
* @return string
*/
function get_via() {
return sprintf( 'S3-compatible: %s', parent::get_via() );
}
/**
* How and if headers should be set
*
* @return string W3TC_CDN_HEADER_NONE, W3TC_CDN_HEADER_UPLOADABLE,
* W3TC_CDN_HEADER_MIRRORING
*/
function headers_support() {
return W3TC_CDN_HEADER_UPLOADABLE;
}
}

View File

@ -0,0 +1,533 @@
<?php
namespace W3TC;
class Cdn_AdminActions {
private $_config = null;
function __construct() {
$this->_config = Dispatcher::config();
}
/**
* CDN queue action
*
* @return void
*/
function w3tc_cdn_queue() {
$w3_plugin_cdn = Dispatcher::component( 'Cdn_Core_Admin' );
$cdn_queue_action = Util_Request::get_string( 'cdn_queue_action' );
$cdn_queue_tab = Util_Request::get_string( 'cdn_queue_tab' );
$notes = array();
switch ( $cdn_queue_tab ) {
case 'upload':
case 'delete':
case 'purge':
break;
default:
$cdn_queue_tab = 'upload';
}
switch ( $cdn_queue_action ) {
case 'delete':
$cdn_queue_id = Util_Request::get_integer( 'cdn_queue_id' );
if ( !empty( $cdn_queue_id ) ) {
$w3_plugin_cdn->queue_delete( $cdn_queue_id );
$notes[] = __( 'File successfully deleted from the queue.', 'w3-total-cache' );
}
break;
case 'empty':
$cdn_queue_type = Util_Request::get_integer( 'cdn_queue_type' );
if ( !empty( $cdn_queue_type ) ) {
$w3_plugin_cdn->queue_empty( $cdn_queue_type );
$notes[] = __( 'Queue successfully emptied.', 'w3-total-cache' );
}
break;
case 'process':
$w3_plugin_cdn_normal = Dispatcher::component( 'Cdn_Plugin' );
$n = $w3_plugin_cdn_normal->cron_queue_process();
$notes[] = sprintf( __( 'Number of processed queue items: %d', 'w3-total-cache' ), $n );
break;
}
$nonce = wp_create_nonce( 'w3tc' );
$queue = $w3_plugin_cdn->queue_get();
$title = __( 'Unsuccessful file transfer queue.', 'w3-total-cache' );
include W3TC_INC_DIR . '/popup/cdn_queue.php';
}
/**
* CDN export library action
*
* @return void
*/
function w3tc_cdn_export_library() {
$w3_plugin_cdn = Dispatcher::component( 'Cdn_Core_Admin' );
$total = $w3_plugin_cdn->get_attachments_count();
$title = __( 'Media Library export', 'w3-total-cache' );
include W3TC_INC_DIR . '/popup/cdn_export_library.php';
}
function w3tc_cdn_flush() {
$flush = Dispatcher::component( 'CacheFlush' );
$flush->flush_all( array(
'only' => 'cdn'
) );
$status = $flush->execute_delayed_operations();
$errors = array();
foreach ( $status as $i ) {
if ( isset( $i['error'] ) )
$errors[] = $i['error'];
}
if ( empty( $errors ) ) {
Util_Admin::redirect( array(
'w3tc_note' => 'flush_cdn'
), true );
} else {
Util_Admin::redirect_with_custom_messages2( array(
'errors' => array( 'Failed to purge CDN: ' .
implode( ', ', $errors ) )
), true );
}
}
/**
* CDN export library process
*
* @return void
*/
function w3tc_cdn_export_library_process() {
$w3_plugin_cdn = Dispatcher::component( 'Cdn_Core_Admin' );
$limit = Util_Request::get_integer( 'limit' );
$offset = Util_Request::get_integer( 'offset' );
$count = null;
$total = null;
$results = array();
$w3_plugin_cdn->export_library( $limit, $offset, $count, $total,
$results, time() + 120 );
$response = array(
'limit' => $limit,
'offset' => $offset,
'count' => $count,
'total' => $total,
'results' => $results
);
echo json_encode( $response );
}
/**
* CDN import library action
*
* @return void
*/
function w3tc_cdn_import_library() {
$w3_plugin_cdn = Dispatcher::component( 'Cdn_Core_Admin' );
$common = Dispatcher::component( 'Cdn_Core' );
$cdn = $common->get_cdn();
$total = $w3_plugin_cdn->get_import_posts_count();
$cdn_host = $cdn->get_domain();
$title = __( 'Media Library import', 'w3-total-cache' );
include W3TC_INC_DIR . '/popup/cdn_import_library.php';
}
/**
* CDN import library process
*
* @return void
*/
function w3tc_cdn_import_library_process() {
$w3_plugin_cdn = Dispatcher::component( 'Cdn_Core_Admin' );
$limit = Util_Request::get_integer( 'limit' );
$offset = Util_Request::get_integer( 'offset' );
$import_external = Util_Request::get_boolean( 'cdn_import_external' );
$config_state = Dispatcher::config_state();
$config_state->set( 'cdn.import.external', $import_external );
$config_state->save();
$count = null;
$total = null;
$results = array();
@$w3_plugin_cdn->import_library( $limit, $offset, $count, $total, $results );
$response = array(
'limit' => $limit,
'offset' => $offset,
'count' => $count,
'total' => $total,
'results' => $results,
);
echo json_encode( $response );
}
/**
* CDN rename domain action
*
* @return void
*/
function w3tc_cdn_rename_domain() {
$w3_plugin_cdn = Dispatcher::component( 'Cdn_Core_Admin' );
$total = $w3_plugin_cdn->get_rename_posts_count();
$title = __( 'Modify attachment URLs', 'w3-total-cache' );
include W3TC_INC_DIR . '/popup/cdn_rename_domain.php';
}
/**
* CDN rename domain process
*
* @return void
*/
function w3tc_cdn_rename_domain_process() {
$w3_plugin_cdn = Dispatcher::component( 'Cdn_Core_Admin' );
$limit = Util_Request::get_integer( 'limit' );
$offset = Util_Request::get_integer( 'offset' );
$names = Util_Request::get_array( 'names' );
$count = null;
$total = null;
$results = array();
@$w3_plugin_cdn->rename_domain( $names, $limit, $offset, $count, $total, $results );
$response = array(
'limit' => $limit,
'offset' => $offset,
'count' => $count,
'total' => $total,
'results' => $results
);
echo json_encode( $response );
}
/**
* CDN export action
*
* @return void
*/
function w3tc_cdn_export() {
$w3_plugin_cdn = Dispatcher::component( 'Cdn_Plugin' );
$cdn_export_type = Util_Request::get_string( 'cdn_export_type', 'custom' );
switch ( $cdn_export_type ) {
case 'includes':
$title = __( 'Includes files export', 'w3-total-cache' );
$files = $w3_plugin_cdn->get_files_includes();
break;
case 'theme':
$title = __( 'Theme files export', 'w3-total-cache' );
$files = $w3_plugin_cdn->get_files_theme();
break;
case 'minify':
$title = __( 'Minify files export', 'w3-total-cache' );
$files = $w3_plugin_cdn->get_files_minify();
break;
default:
case 'custom':
$title = __( 'Custom files export', 'w3-total-cache' );
$files = $w3_plugin_cdn->get_files_custom();
break;
}
include W3TC_INC_DIR . '/popup/cdn_export_file.php';
}
/**
* CDN export process
*
* @return void
*/
function w3tc_cdn_export_process() {
$common = Dispatcher::component( 'Cdn_Core' );
$files = Util_Request::get_array( 'files' );
$upload = array();
$results = array();
foreach ( $files as $file ) {
$local_path = $common->docroot_filename_to_absolute_path( $file );
$remote_path = $common->uri_to_cdn_uri( $common->docroot_filename_to_uri( $file ) );
$d = $common->build_file_descriptor( $local_path, $remote_path );
$d['_original_id'] = $file;
$upload[] = $d;
}
$common->upload( $upload, false, $results, time() + 5 );
$output = array();
foreach ( $results as $item ) {
$file = '';
if ( isset( $item['descriptor']['_original_id'] ) )
$file = $item['descriptor']['_original_id'];
$output[] = array(
'result' => $item['result'],
'error' => $item['error'],
'file' => $file
);
}
$response = array(
'results' => $output
);
echo json_encode( $response );
}
/**
* CDN purge action
*
* @return void
*/
function w3tc_cdn_purge() {
$title = __( 'Content Delivery Network (CDN): Purge Tool', 'w3-total-cache' );
$results = array();
$path = ltrim( str_replace( get_home_url(), '', get_stylesheet_directory_uri() ), '/' );
include W3TC_INC_DIR . '/popup/cdn_purge.php';
}
/**
* CDN purge post action
*
* @return void
*/
function w3tc_cdn_purge_files() {
$title = __( 'Content Delivery Network (CDN): Purge Tool', 'w3-total-cache' );
$results = array();
$files = Util_Request::get_array( 'files' );
$purge = array();
$common = Dispatcher::component( 'Cdn_Core' );
foreach ( $files as $file ) {
$local_path = $common->docroot_filename_to_absolute_path( $file );
$remote_path = $common->uri_to_cdn_uri( $common->docroot_filename_to_uri( $file ) );
$purge[] = $common->build_file_descriptor( $local_path, $remote_path );
}
if ( count( $purge ) ) {
$common->purge( $purge, $results );
} else {
$errors[] = __( 'Empty files list.', 'w3-total-cache' );
}
$path = str_replace( get_home_url(), '', get_stylesheet_directory_uri() );
include W3TC_INC_DIR . '/popup/cdn_purge.php';
}
/**
* CDN Purge Post
*
* @return void
*/
function w3tc_cdn_purge_attachment() {
$results = array();
$attachment_id = Util_Request::get_integer( 'attachment_id' );
$w3_plugin_cdn = Dispatcher::component( 'Cdn_Core_Admin' );
if ( $w3_plugin_cdn->purge_attachment( $attachment_id, $results ) ) {
Util_Admin::redirect( array(
'w3tc_note' => 'cdn_purge_attachment'
), true );
} else {
Util_Admin::redirect( array(
'w3tc_error' => 'cdn_purge_attachment'
), true );
}
}
/**
* CDN Test action
*
* @return void
*/
function w3tc_cdn_test() {
$engine = Util_Request::get_string( 'engine' );
$config = Util_Request::get_array( 'config' );
//TODO: Workaround to support test case cdn/a04
if ( $engine == 'ftp' && !isset( $config['host'] ) ) {
$config = Util_Request::get_string( 'config' );
$config = json_decode( $config, true );
}
$config = array_merge( $config, array(
'debug' => false
) );
if ( isset( $config['domain'] ) && !is_array( $config['domain'] ) ) {
$config['domain'] = explode( ',', $config['domain'] );
}
if ( Cdn_Util::is_engine( $engine ) ) {
$result = true;
$error = null;
} else {
$result = false;
$error = __( 'Incorrect engine ' . $engine, 'w3-total-cache' );
}
if ( !isset( $config['docroot'] ) )
$config['docroot'] = Util_Environment::document_root();
if ( $result ) {
if ( $engine == 'google_drive' ||
$engine == 'highwinds' ||
$engine == 'limelight' ||
$engine == 'stackpath' ||
$engine == 'transparentcdn' ||
$engine == 'stackpath2' ||
$engine == 'rackspace_cdn' ||
$engine == 'rscf' ||
'bunnycdn' === $engine ||
$engine == 's3_compatible' ) {
// those use already stored w3tc config
$w3_cdn = Dispatcher::component( 'Cdn_Core' )->get_cdn();
} else {
// those use dynamic config from the page
$w3_cdn = CdnEngine::instance( $engine, $config );
}
@set_time_limit( $this->_config->get_integer( 'timelimit.cdn_test' ) );
try {
if ( $w3_cdn->test( $error ) ) {
$result = true;
$error = __( 'Test passed', 'w3-total-cache' );
} else {
$result = false;
$error = sprintf( __( 'Error: %s', 'w3-total-cache' ), $error );
}
} catch ( \Exception $ex ) {
$result = false;
$error = sprintf( __( 'Error: %s', 'w3-total-cache' ), $ex->getMessage() );
}
}
$response = array(
'result' => $result,
'error' => $error
);
echo json_encode( $response );
}
/**
* Create container action
*
* @return void
*/
function w3tc_cdn_create_container() {
$engine = Util_Request::get_string( 'engine' );
$config = Util_Request::get_array( 'config' );
$config = array_merge( $config, array(
'debug' => false
) );
$container_id = '';
switch ( $engine ) {
case 's3':
case 'cf':
case 'cf2':
case 'azure':
$w3_cdn = CdnEngine::instance( $engine, $config );
@set_time_limit( $this->_config->get_integer( 'timelimit.cdn_upload' ) );
$result = false;
try {
$container_id = $w3_cdn->create_container();
$result = true;
$error = __( 'Created successfully.', 'w3-total-cache' );
} catch ( \Exception $ex ) {
$error = sprintf(
__( 'Error: %s', 'w3-total-cache' ),
$ex->getMessage()
);
}
break;
default:
$result = false;
$error = __( 'Incorrect type.', 'w3-total-cache' );
}
$response = array(
'result' => $result,
'error' => $error,
'container_id' => $container_id,
);
echo json_encode( $response );
}
/**
* Redirect to the Bunny CDN signup page.
*
* @since X.X.X
*
* @return void
*/
public function w3tc_cdn_bunnycdn_signup() {
try {
$state = Dispatcher::config_state();
$state->set( 'track.bunnycdn_signup', time() );
$state->save();
} catch ( \Exception $ex ) {} // phpcs:ignore
Util_Environment::redirect( W3TC_BUNNYCDN_SIGNUP_URL );
}
/**
* Test CDN URL.
*
* @param string $url URL.
*/
private function test_cdn_url( $url ) {
$response = wp_remote_get( $url );
if ( is_wp_error( $response ) ) {
return false;
} else {
$code = wp_remote_retrieve_response_code( $response );
return 200 == $code;
}
}
}

View File

@ -0,0 +1,286 @@
<?php
namespace W3TC;
class Cdn_AdminNotes {
/**
*
*
* @param Config $config
* @return array
*/
public function w3tc_notes( $notes ) {
$config = Dispatcher::config();
$state = Dispatcher::config_state();
$cdn_engine = $config->get_string( 'cdn.engine' );
$page = Util_Request::get_string( 'page' );
if ( !Cdn_Util::is_engine_mirror( $cdn_engine ) ) {
/**
* Show notification after theme change
*/
if ( $state->get_boolean( 'cdn.show_note_theme_changed' ) ) {
$notes['cdn_theme_changed'] = sprintf(
__( 'The active theme has changed, please %s now to ensure proper operation. %s',
'w3-total-cache' ),
Util_Ui::button_popup(
__( 'upload active theme files', 'w3-total-cache' ),
'cdn_export',
'cdn_export_type=theme' ),
Util_Ui::button_hide_note2( array(
'w3tc_default_config_state' => 'y',
'key' => 'cdn.show_note_theme_changed',
'value' => 'false' ) ) );
}
/**
* Show notification after WP upgrade
*/
if ( $state->get_boolean( 'cdn.show_note_wp_upgraded' ) ) {
$notes['cdn_wp_upgraded'] = sprintf(
__( 'Upgraded WordPress? Please %s files now to ensure proper operation. %s',
'w3-total-cache' ),
Util_Ui::button_popup( 'upload wp-includes', 'cdn_export',
'cdn_export_type=includes' ),
Util_Ui::button_hide_note2( array(
'w3tc_default_config_state' => 'y',
'key' => 'cdn.show_note_wp_upgraded',
'value' => 'false' ) ) );
}
/**
* Show notification after CDN enable
*/
if ( $state->get_boolean( 'cdn.show_note_cdn_upload' ) ||
$state->get_boolean( 'cdn.show_note_cdn_reupload' ) ) {
$cdn_upload_buttons = array();
if ( $config->get_boolean( 'cdn.includes.enable' ) ) {
$cdn_upload_buttons[] = Util_Ui::button_popup(
'wp-includes', 'cdn_export', 'cdn_export_type=includes' );
}
if ( $config->get_boolean( 'cdn.theme.enable' ) ) {
$cdn_upload_buttons[] = Util_Ui::button_popup(
'theme files', 'cdn_export', 'cdn_export_type=theme' );
}
if ( $config->get_boolean( 'minify.enabled' ) &&
$config->get_boolean( 'cdn.minify.enable' ) &&
!$config->get_boolean( 'minify.auto' ) ) {
$cdn_upload_buttons[] = Util_Ui::button_popup(
'minify files', 'cdn_export',
'cdn_export_type=minify' );
}
if ( $config->get_boolean( 'cdn.custom.enable' ) ) {
$cdn_upload_buttons[] = Util_Ui::button_popup(
'custom files', 'cdn_export', 'cdn_export_type=custom' );
}
if ( $state->get_boolean( 'cdn.show_note_cdn_upload' ) ) {
$notes[] = sprintf(
__( 'Make sure to %s and upload the %s, files to the <acronym title="Content Delivery Network">CDN</acronym> to ensure proper operation. %s',
'w3-total-cache' ),
Util_Ui::button_popup( 'export the media library',
'cdn_export_library' ),
implode( ', ', $cdn_upload_buttons ),
Util_Ui::button_hide_note2( array(
'w3tc_default_config_state' => 'y',
'key' => 'cdn.show_note_cdn_upload',
'value' => 'false' ) ) );
}
if ( $state->get_boolean( 'cdn.show_note_cdn_reupload' ) ) {
$notes[] = sprintf(
__( 'Settings that affect Browser Cache settings for files hosted by the CDN have been changed. To apply the new settings %s and %s. %s',
'w3-total-cache' ),
Util_Ui::button_popup(
__( 'export the media library', 'w3-total-cache' ),
'cdn_export_library' ),
implode( ', ', $cdn_upload_buttons ),
Util_Ui::button_hide_note2( array(
'w3tc_default_config_state' => 'y',
'key' => 'cdn.show_note_cdn_reupload',
'value' => 'false' ) ) );
}
}
}
/**
* Check CURL extension
*/
if ( !$state->get_boolean( 'cdn.hide_note_no_curl' ) &&
!function_exists( 'curl_init' ) ) {
$notes[] = sprintf(
__( 'The <strong>CURL PHP</strong> extension is not available. Please install it to enable S3 or CloudFront functionality. %s',
'w3-total-cache' ),
Util_Ui::button_hide_note2( array(
'w3tc_default_config_state' => 'y',
'key' => 'cdn.hide_note_no_curl',
'value' => 'true' ) ) );
}
if ( 'maxcdn' === $cdn_engine ) {
$notes[] = sprintf(
// translators: 1: Opening anchor tag with a link to the CDN settings page, 2: closing anchor tag, 3 opening anchor tag to MaxCDN/StackPath migration guide.
__(
'MaxCDN has been replaced with StackPath CDN. As a result your configuration is now invalid and requires reconfiguration to a new %1$sCDN provider%2$s. You can migrate to StackPath using %3$sthis guide%2$s.',
'w3-total-cache'
),
'<a href="' . esc_url( admin_url( 'admin.php?page=w3tc_general#cdn' ) ) . '">',
'</a>',
'<a href="' . esc_url( 'https://support.stackpath.com/hc/en-us/articles/10408946467739-MaxCDN-Migration-to-StackPath-Instructions' ) . '" target="_blank">'
);
}
return $notes;
}
function w3tc_errors( $errors ) {
$c = Dispatcher::config();
$state = Dispatcher::config_state();
$cdn_engine = $c->get_string( 'cdn.engine' );
if ( Cdn_Util::is_engine_push( $cdn_engine ) ) {
/**
* Show notification if upload queue is not empty
*/
try {
if ( !( $error = get_transient( 'w3tc_cdn_error' ) ) &&
!$this->_is_queue_empty() ) {
$errors['cdn_unsuccessful_queue'] = sprintf(
__( 'The %s has unresolved errors. Empty the queue to restore normal operation.',
'w3-total-cache' ),
Util_Ui::button_popup(
__( 'unsuccessful transfer queue', 'w3-total-cache' ), 'cdn_queue' ) );
} elseif ( $error ) {
$errors['cdn_generic'] = $error;
}
} catch ( \Exception $ex ) {
$errors[] = $ex->getMessage();
set_transient( 'w3tc_cdn_error', $ex->getMessage(), 30 );
}
/**
* Check upload settings
*/
$upload_info = Util_Http::upload_info();
if ( !$upload_info ) {
$upload_path = get_option( 'upload_path' );
$upload_path = trim( $upload_path );
if ( empty( $upload_path ) ) {
$upload_path = WP_CONTENT_DIR . '/uploads';
$errors['cdn_uploads_folder_empty'] = sprintf(
__( 'The uploads directory is not available. Default WordPress directories will be created: <strong>%s</strong>.',
'w3-total-cache' ),
$upload_path );
}
if ( !Util_Environment::is_wpmu() ) {
$errors['cdn_uploads_folder_not_found'] = sprintf(
__( 'The uploads path found in the database (%s) is inconsistent with the actual path. Please manually adjust the upload path either in miscellaneous settings or if not using a custom path %s automatically to resolve the issue.',
'w3-total-cache' ),
$upload_path,
Util_Ui::button_link( __( 'update the path', 'w3-total-cache' ),
Util_Ui::url( array(
'w3tc_config_update_upload_path' => 'y' ) ) ) );
}
}
}
/**
* Check CDN settings
*/
$error = '';
switch ( true ) {
case ( $cdn_engine == 'ftp' && !count( $c->get_array( 'cdn.ftp.domain' ) ) ):
$errors['cdn_ftp_empty'] = __( 'A configuration issue prevents <acronym title="Content Delivery Network">CDN</acronym> from working:
The <strong>"Replace default hostname with"</strong>
field cannot be empty. Enter <acronym
title="Content Delivery Network">CDN</acronym>
provider hostname <a href="?page=w3tc_cdn#configuration">here</a>.
<em>(This is the hostname used in order to view objects
in a browser.)</em>', 'w3-total-cache' );
break;
case ( $cdn_engine == 's3' && ( $c->get_string( 'cdn.s3.key' ) == '' || $c->get_string( 'cdn.s3.secret' ) == '' || $c->get_string( 'cdn.s3.bucket' ) == '' ) ):
$error = __( 'The <strong>"Access key", "Secret key" and "Bucket"</strong> fields cannot be empty.', 'w3-total-cache' );
break;
case ( $cdn_engine == 'cf' && ( $c->get_string( 'cdn.cf.key' ) == '' || $c->get_string( 'cdn.cf.secret' ) == '' || $c->get_string( 'cdn.cf.bucket' ) == '' || ( $c->get_string( 'cdn.cf.id' ) == '' && !count( $c->get_array( 'cdn.cf.cname' ) ) ) ) ):
$error = __( 'The <strong>"Access key", "Secret key", "Bucket" and "Replace default hostname with"</strong> fields cannot be empty.', 'w3-total-cache' );
break;
case ( $cdn_engine == 'cf2' && ( $c->get_string( 'cdn.cf2.key' ) == '' || $c->get_string( 'cdn.cf2.secret' ) == '' || ( $c->get_string( 'cdn.cf2.id' ) == '' && !count( $c->get_array( 'cdn.cf2.cname' ) ) ) ) ):
$error = __( 'The <strong>"Access key", "Secret key" and "Replace default hostname with"</strong> fields cannot be empty.', 'w3-total-cache' );
break;
case ( $cdn_engine == 'rscf' && ( $c->get_string( 'cdn.rscf.user' ) == '' || $c->get_string( 'cdn.rscf.key' ) == '' || $c->get_string( 'cdn.rscf.container' ) == '' ) ):
$error = __( 'The <strong>"Username", "API key", "Container" and "Replace default hostname with"</strong> fields cannot be empty.', 'w3-total-cache' );
break;
case ( $cdn_engine == 'azure' && ( $c->get_string( 'cdn.azure.user' ) == '' || $c->get_string( 'cdn.azure.key' ) == '' || $c->get_string( 'cdn.azure.container' ) == '' ) ):
$error = __( 'The <strong>"Account name", "Account key" and "Container"</strong> fields cannot be empty.', 'w3-total-cache' );
break;
case ( $cdn_engine == 'mirror' && !count( $c->get_array( 'cdn.mirror.domain' ) ) ):
$error = __( 'The <strong>"Replace default hostname with"</strong> field cannot be empty.', 'w3-total-cache' );
break;
case ( $cdn_engine == 'cotendo' && !count( $c->get_array( 'cdn.cotendo.domain' ) ) ):
$error = __( 'The <strong>"Replace default hostname with"</strong> field cannot be empty.', 'w3-total-cache' );
break;
case ( $cdn_engine == 'edgecast' && !count( $c->get_array( 'cdn.edgecast.domain' ) ) ):
$error = __( 'The <strong>"Replace default hostname with"</strong> field cannot be empty.', 'w3-total-cache' );
break;
case ( $cdn_engine == 'att' && !count( $c->get_array( 'cdn.att.domain' ) ) ):
$error = __( 'The <strong>"Replace default hostname with"</strong> field cannot be empty.', 'w3-total-cache' );
break;
case ( $cdn_engine == 'akamai' && !count( $c->get_array( 'cdn.akamai.domain' ) ) ):
$error = 'The <strong>"Replace default hostname with"</strong> field cannot be empty.';
break;
}
if ( $error ) {
$errors['cdn_not_configured'] = __( 'A configuration issue prevents <acronym title="Content Delivery Network">CDN</acronym> from working: ', 'w3-total-cache' ) . $error . __( ' <a href="?page=w3tc_cdn#configuration">Specify it here</a>.', 'w3-total-cache' );
}
return $errors;
}
/**
* Returns true if upload queue is empty
*
* @return bool
* @throws Exception
*/
private function _is_queue_empty() {
global $wpdb;
$wpdb->hide_errors();
$sql = sprintf( 'SELECT COUNT(*) FROM %s', $wpdb->base_prefix . W3TC_CDN_TABLE_QUEUE );
$result = $wpdb->get_var( $sql );
if ( ( $error = $wpdb->last_error ) ) {
if ( strpos( $error, "doesn't exist" ) !== false ) {
$url = is_network_admin() ? network_admin_url( 'admin.php?page=w3tc_install' ) : admin_url( 'admin.php?page=w3tc_install' );
throw new \Exception( sprintf(
__( 'Encountered issue with CDN: %s. See %s for instructions of creating correct table.', 'w3-total-cache' ),
$wpdb->last_error,
'<a href="' . $url . '">' . __( 'Install page', 'w3-total-cache' ) . '</a>' ) );
}
else
throw new \Exception( sprintf( __( 'Encountered issue with CDN: %s.', 'w3-total-cache' ), $wpdb->last_error ) );
}
return $result == 0;
}
}

View File

@ -0,0 +1,552 @@
<?php
/**
* File: Cdn_BunnyCdn_Api.php
*
* @since X.X.X
* @package W3TC
*/
namespace W3TC;
/**
* Class: Cdn_BunnyCdn_Api
*
* @since X.X.X
*/
class Cdn_BunnyCdn_Api {
/**
* Account API Key.
*
* @since X.X.X
* @access private
*
* @var string
*/
private $account_api_key;
/**
* Storage API Key.
*
* @since X.X.X
* @access private
*
* @var string
*/
private $storage_api_key;
/**
* Stream API Key.
*
* @since X.X.X
* @access private
*
* @var string
*/
private $stream_api_key;
/**
* API type.
*
* One of: "account", "storage", "stream".
*
* @since X.X.X
* @access private
*
* @var string
*/
private $api_type;
/**
* Pull zone id.
*
* @since X.X.X
* @access private
*
* @var int
*/
private $pull_zone_id;
/**
* Default Edge Rules.
*
* @since X.X.X
* @access private
* @static
*
* @var array
*/
private static $default_edge_rules = array(
array(
'ActionType' => 15, // BypassPermaCache.
'TriggerMatchingType' => 0, // MatchAny.
'Enabled' => true,
'Triggers' => array(
array(
'Type' => 3, // UrlExtension.
'PatternMatchingType' => 0, // MatchAny.
'PatternMatches' => array( '.zip' ),
),
),
'Description' => 'Bypass PermaCache for ZIP files',
),
array(
'ActionType' => 3, // OverrideCacheTime.
'TriggerMatchingType' => 0, // MatchAny.
'ActionParameter1' => '0',
'ActionParameter2' => '',
'Enabled' => true,
'Triggers' => array(
array(
'Type' => 1, // RequestHeader.
'PatternMatchingType' => 0, // MatchAny.
'PatternMatches' => array(
'*wordpress_logged_in_*',
'*wordpress_sec_*',
),
'Parameter1' => 'Cookie',
),
),
'Description' => 'Override Cache Time if logged into WordPress',
),
array(
'ActionType' => 15, // BypassPermaCache.
'TriggerMatchingType' => 0, // MatchAny.
'Enabled' => true,
'Triggers' => array(
array(
'Type' => 1, // RequestHeader.
'PatternMatchingType' => 0, // MatchAny.
'PatternMatches' => array(
'*wordpress_logged_in_*',
'*wordpress_sec_*',
),
'Parameter1' => 'Cookie',
),
),
'Description' => 'Bypass PermaCache if logged into WordPress',
),
array(
'ActionType' => 16, // OverrideBrowserCacheTime.
'TriggerMatchingType' => 0, // MatchAny.
'ActionParameter1' => '0',
'Enabled' => true,
'Triggers' => array(
array(
'Type' => 1, // RequestHeader.
'PatternMatchingType' => 0, // MatchAny.
'PatternMatches' => array(
'*wordpress_logged_in_*',
'*wordpress_sec_*',
),
'Parameter1' => 'Cookie',
),
),
'Description' => 'Override Browser Cache Time if logged into WordPress',
),
);
/**
* Constructor.
*
* @since X.X.X
*
* @param array $config Configuration.
*/
public function __construct( array $config ) {
$this->account_api_key = ! empty( $config['account_api_key'] ) ? $config['account_api_key'] : '';
$this->storage_api_key = ! empty( $config['storage_api_key'] ) ? $config['storage_api_key'] : '';
$this->stream_api_key = ! empty( $config['stream_api_key'] ) ? $config['stream_api_key'] : '';
$this->pull_zone_id = ! empty( $config['pull_zone_id'] ) ? $config['pull_zone_id'] : '';
}
/**
* Increase http request timeout to 60 seconds.
*
* @since X.X.X
*
* @param int $time Timeout in seconds.
*/
public function filter_timeout_time( $time ) {
return 600;
}
/**
* Don't check certificate, some users have limited CA list
*
* @since X.X.X
*
* @param bool $verify Always false.
*/
public function https_ssl_verify( $verify = false ) {
return false;
}
/**
* List pull zones.
*
* @since X.X.X
*
* @link https://docs.bunny.net/reference/pullzonepublic_index
*
* @return array
*/
public function list_pull_zones() {
$this->api_type = 'account';
return $this->wp_remote_get( \esc_url( 'https://api.bunny.net/pullzone' ) );
}
/**
* Get pull zone details by pull zone id.
*
* @since X.X.X
*
* @link https://docs.bunny.net/reference/pullzonepublic_index2
*
* @param int $id Pull zone id.
* @return array
*/
public function get_pull_zone( $id ) {
$this->api_type = 'account';
return $this->wp_remote_get(
\esc_url( 'https://api.bunny.net/pullzone/id' . $id )
);
}
/**
* Add a pull zone.
*
* @since X.X.X
*
* @link https://docs.bunny.net/reference/pullzonepublic_add
*
* @param array $data {
* Data used to create the pull zone.
*
* @type string $Name The name/hostname for the pull zone where the files will be accessible; only letters, numbers, and dashes.
* @type string $OriginUrl Origin URL or IP (with optional port number).
* @type string $OriginHostHeader Optional: The host HTTP header that will be sent to the origin. If empty, hostname will be automatically extracted from the Origin URL.
* @type bool $AddHostHeader Optional: If enabled, the original host header of the request will be forwarded to the origin server. This should be disabled in most cases.
* }
*
* @return array
* @throws \Exception Exception.
*/
public function add_pull_zone( array $data ) {
$this->api_type = 'account';
if ( empty( $data['Name'] ) || ! \is_string( $data['Name'] ) ) { // A Name string is required, which is used for the CDN hostname.
throw new \Exception( \esc_html__( 'A pull zone name (string) is required.', 'w3-total-cache' ) );
}
if ( \preg_match( '[^\w\d-]', $data['Name'] ) ) { // Only letters, numbers, and dashes are allowed in the Name.
throw new \Exception( \esc_html__( 'A pull zone name (string) is required.', 'w3-total-cache' ) );
}
return $this->wp_remote_post(
'https://api.bunny.net/pullzone',
$data
);
}
/**
* Update a pull zone.
*
* @since X.X.X
*
* @link https://docs.bunny.net/reference/pullzonepublic_updatepullzone
*
* @param int $id Optional pull zone ID. Can be specified in the constructor configuration array parameter.
* @param array $data Data used to update the pull zone.
* @return array
* @throws \Exception Exception.
*/
public function update_pull_zone( $id, array $data ) {
$this->api_type = 'account';
$id = empty( $this->pull_zone_id ) ? $id : $this->pull_zone_id;
if ( empty( $id ) || ! \is_int( $id ) ) {
throw new \Exception( \esc_html__( 'Invalid pull zone id.', 'w3-total-cache' ) );
}
return $this->wp_remote_post(
'https://api.bunny.net/pullzone/' . $id,
$data
);
}
/**
* Delete a pull zone.
*
* @since X.X.X
*
* @link https://docs.bunny.net/reference/pullzonepublic_delete
*
* @param int $id Optional pull zone ID. Can be specified in the constructor configuration array parameter.
* @return array
* @throws \Exception Exception.
*/
public function delete_pull_zone( $id ) {
$this->api_type = 'account';
$id = empty( $this->pull_zone_id ) ? $id : $this->pull_zone_id;
if ( empty( $id ) || ! \is_int( $id ) ) {
throw new \Exception( \esc_html__( 'Invalid pull zone id.', 'w3-total-cache' ) );
}
return $this->wp_remote_post(
\esc_url( 'https://api.bunny.net/pullzone/' . $id ),
array(),
array( 'method' => 'DELETE' )
);
}
/**
* Add a custom hostname to a pull zone.
*
* @since X.X.X
*
* @link https://docs.bunny.net/reference/pullzonepublic_addhostname
*
* @param string $hostname Custom hostname.
* @param int $pull_zone_id Optional pull zone ID. Can be specified in the constructor configuration array parameter.
* @return void
* @throws \Exception Exception.
*/
public function add_custom_hostname( $hostname, $pull_zone_id = null ) {
$this->api_type = 'account';
$pull_zone_id = empty( $this->pull_zone_id ) ? $pull_zone_id : $this->pull_zone_id;
if ( empty( $pull_zone_id ) || ! \is_int( $pull_zone_id ) ) {
throw new \Exception( \esc_html__( 'Invalid pull zone id.', 'w3-total-cache' ) );
}
if ( empty( $hostname ) || ! \filter_var( $hostname, FILTER_VALIDATE_DOMAIN ) ) {
throw new \Exception( \esc_html__( 'Invalid hostname', 'w3-total-cache' ) . ' "' . \esc_html( $hostname ) . '".' );
}
$this->wp_remote_post(
\esc_url( 'https://api.bunny.net/pullzone/' . $pull_zone_id . '/addHostname' ),
array( 'Hostname' => $hostname )
);
}
/**
* Get the default edge rules.
*
* @since X.X.X
* @static
*
* @return array
*/
public static function get_default_edge_rules() {
return self::$default_edge_rules;
}
/**
* Add/Update Edge Rule.
*
* @since X.X.X
*
* @param array $data Data.
* @param int $pull_zone_id Optional pull zone ID. Can be specified in the constructor configuration array parameter.
* @return void
* @throws \Exception Exception.
*/
public function add_edge_rule( array $data, $pull_zone_id = null ) {
$this->api_type = 'account';
$pull_zone_id = empty( $this->pull_zone_id ) ? $pull_zone_id : $this->pull_zone_id;
if ( empty( $pull_zone_id ) || ! \is_int( $pull_zone_id ) ) {
throw new \Exception( \esc_html__( 'Invalid pull zone id.', 'w3-total-cache' ) );
}
if ( ! isset( $data['ActionType'] ) || ! \is_int( $data['ActionType'] ) || $data['ActionType'] < 0 ) {
throw new \Exception( \esc_html__( 'Invalid parameter "ActionType".', 'w3-total-cache' ) );
}
if ( ! isset( $data['TriggerMatchingType'] ) || ! \is_int( $data['TriggerMatchingType'] ) || $data['TriggerMatchingType'] < 0 ) {
throw new \Exception( \esc_html__( 'Invalid parameter "TriggerMatchingType".', 'w3-total-cache' ) );
}
if ( ! isset( $data['Enabled'] ) || ! \is_bool( $data['Enabled'] ) ) {
throw new \Exception( \esc_html__( 'Missing parameter "Enabled".', 'w3-total-cache' ) );
}
if ( empty( $data['Triggers'] ) ) {
throw new \Exception( \esc_html__( 'Missing parameter "Triggers".', 'w3-total-cache' ) );
}
$this->wp_remote_post(
\esc_url( 'https://api.bunny.net/pullzone/' . $pull_zone_id . '/edgerules/addOrUpdate' ),
$data
);
}
/**
* Purge.
*
* @since X.X.X
*
* @param array $data Data for the POST request.
* @return array
*/
public function purge( array $data ) {
$this->api_type = 'account';
return $this->wp_remote_get(
\esc_url( 'https://api.bunny.net/purge' ),
$data
);
}
/**
* Purge an entire pull zone.
*
* @since X.X.X
*
* @param int $pull_zone_id Optional pull zone ID. Can be specified in the constructor configuration array parameter.
* @return void
* @throws \Exception Exception.
*/
public function purge_pull_zone( $pull_zone_id = null ) {
$this->api_type = 'account';
$pull_zone_id = empty( $this->pull_zone_id ) ? $pull_zone_id : $this->pull_zone_id;
if ( empty( $pull_zone_id ) || ! \is_int( $pull_zone_id ) ) {
throw new \Exception( \esc_html__( 'Invalid pull zone id.', 'w3-total-cache' ) );
}
$this->wp_remote_post( \esc_url( 'https://api.bunny.net/pullzone/' . $pull_zone_id . '/purgeCache' ) );
}
/**
* Get the API key by API type.
*
* API type can be passed or the class property will be used.
*
* @since X.X.X
*
* @param string $type API type: One of "account", "storage", "stream" (optional).
* @return string|null
* @throws \Exception Exception.
*/
private function get_api_key( $type = null ) {
if ( empty( $type ) ) {
$type = $this->api_type;
}
if ( ! \in_array( $type, array( 'account', 'storage', 'stream' ), true ) ) {
throw new \Exception( \esc_html__( 'Invalid API type; must be one of "account", "storage", "stream".', 'w3-total-cache' ) );
}
if ( empty( $this->{$type . '_api_key'} ) ) {
throw new \Exception( \esc_html__( 'API key value is empty.', 'w3-total-cache' ) );
}
return $this->{$type . '_api_key'};
}
/**
* Decode response from a wp_remote_* call.
*
* @since X.X.X
*
* @param array|WP_Error $result Result.
* @return array
* @throws \Exception Exception.
*/
private function decode_response( $result ) {
if ( \is_wp_error( $result ) ) {
throw new \Exception( \esc_html__( 'Failed to reach API endpoint', 'w3-total-cache' ) );
}
$response_body = @\json_decode( $result['body'], true );
// Throw an exception if the response code/status is not ok.
if ( ! \in_array( $result['response']['code'], array( 200, 201, 204 ), true ) ) {
$message = isset( $response_body['Message'] ) ? $response_body['Message'] : $result['body'];
throw new \Exception(
\esc_html( \__( 'Response code ', 'w3-total-cache' ) . $result['response']['code'] . ': ' . $message )
);
}
return \is_array( $response_body ) ? $response_body : array();
}
/**
* Remote GET request.
*
* @since X.X.X
*
* @link https://developer.wordpress.org/reference/functions/wp_remote_get/
* @link https://developer.wordpress.org/reference/classes/wp_http/request/
*
* @param string $url URL address.
* @param array $data Query string data for the GET request.
* @return array
*/
private function wp_remote_get( $url, array $data = array() ) {
$api_key = $this->get_api_key();
\add_filter( 'http_request_timeout', array( $this, 'filter_timeout_time' ) );
\add_filter( 'https_ssl_verify', array( $this, 'https_ssl_verify' ) );
$result = \wp_remote_get(
$url . ( empty( $data ) ? '' : '?' . \http_build_query( $data ) ),
array(
'headers' => array(
'AccessKey' => $api_key,
'Accept' => 'application/json',
),
)
);
\remove_filter( 'https_ssl_verify', array( $this, 'https_ssl_verify' ) );
\remove_filter( 'http_request_timeout', array( $this, 'filter_timeout_time' ) );
return self::decode_response( $result );
}
/**
* Remote POST request.
*
* @since X.X.X
*
* @link https://developer.wordpress.org/reference/functions/wp_remote_post/
* @link https://developer.wordpress.org/reference/classes/wp_http/request/
*
* @param string $url URL address.
* @param array $data Optional data for the POSt request.
* @param array $args Optional additional arguments for the wp_remote_port call.
* @return string
*/
private function wp_remote_post( $url, array $data = array(), array $args = array() ) {
$api_key = $this->get_api_key();
\add_filter( 'http_request_timeout', array( $this, 'filter_timeout_time' ) );
\add_filter( 'https_ssl_verify', array( $this, 'https_ssl_verify' ) );
$result = \wp_remote_post(
$url,
\array_merge(
array(
'headers' => array(
'AccessKey' => $api_key,
'Accept' => 'application/json',
'Content-Type' => 'application/json',
),
'body' => empty( $data ) ? null : \json_encode( $data ),
),
$args
)
);
\remove_filter( 'https_ssl_verify', array( $this, 'https_ssl_verify' ) );
\remove_filter( 'http_request_timeout', array( $this, 'filter_timeout_time' ) );
return self::decode_response( $result );
}
}

View File

@ -0,0 +1,204 @@
<?php
/**
* File: Cdn_BunnyCdn_Page.php
*
* @since X.X.X
* @package W3TC
*/
namespace W3TC;
/**
* Class: Cdn_BunnyCdn_Page
*
* @since X.X.X
*/
class Cdn_BunnyCdn_Page {
/**
* W3TC AJAX.
*
* @since X.X.X
* @static
*
* @return void
*/
public static function w3tc_ajax() {
$o = new Cdn_BunnyCdn_Page();
\add_action(
'w3tc_ajax_cdn_bunnycdn_purge_url',
array( $o, 'w3tc_ajax_cdn_bunnycdn_purge_url' )
);
}
/**
* Determine if CDN or CDNFSD is active.
*
* @since X.X.X
* @static
*
* @return bool
*/
public static function is_active() {
$config = Dispatcher::config();
$cdn_enabled = $config->get_boolean( 'cdn.enabled' );
$cdn_engine = $config->get_string( 'cdn.engine' );
$cdn_zone_id = $config->get_integer( 'cdn.bunnycdn.pull_zone_id' );
$cdnfsd_enabled = $config->get_boolean( 'cdnfsd.enabled' );
$cdnfsd_engine = $config->get_string( 'cdnfsd.engine' );
$cdnfsd_zone_id = $config->get_integer( 'cdnfsd.bunnycdn.pull_zone_id' );
$account_api_key = $config->get_string( 'cdn.bunnycdn.account_api_key' );
return ( $account_api_key &&
(
( $cdn_enabled && 'bunnycdn' === $cdn_engine && $cdn_zone_id ) ||
( $cdnfsd_enabled && 'bunnycdn' === $cdnfsd_engine && $cdnfsd_zone_id )
)
);
}
/**
* Add Dashboard actions.
*
* @since X.X.X
* @static
*
* @see self::in_active()
*
* @param array $actions Actions.
* @return array
*/
public static function w3tc_dashboard_actions( array $actions ) {
if ( self::is_active() ) {
$modules = Dispatcher::component( 'ModuleStatus' );
$can_empty_memcache = $modules->can_empty_memcache();
$can_empty_opcode = $modules->can_empty_opcode();
$can_empty_file = $modules->can_empty_file();
$can_empty_varnish = $modules->can_empty_varnish();
$actions[] = sprintf(
'<input type="submit" class="dropdown-item" name="w3tc_bunnycdn_flush_all_except_bunnycdn" value="%1$s"%2$s>',
esc_attr__( 'Empty All Caches Except Bunny CDN', 'w3-total-cache' ),
( ! $can_empty_memcache && ! $can_empty_opcode && ! $can_empty_file && ! $can_empty_varnish ) ? ' disabled="disabled"' : ''
);
}
return $actions;
}
/**
* Enqueue scripts.
*
* Called from plugin-admin.
*
* @since X.X.X
* @static
*
* @return void
*/
public static function admin_print_scripts_w3tc_cdn() {
$config = Dispatcher::config();
$is_authorized = ! empty( $config->get_string( 'cdn.bunnycdn.account_api_key' ) ) &&
( $config->get_string( 'cdn.bunnycdn.pull_zone_id' ) || $config->get_string( 'cdnfsd.bunnycdn.pull_zone_id' ) );
\wp_register_script(
'w3tc_cdn_bunnycdn',
\plugins_url( 'Cdn_BunnyCdn_Page_View.js', W3TC_FILE ),
array( 'jquery' ),
W3TC_VERSION
);
\wp_localize_script(
'w3tc_cdn_bunnycdn',
'W3TC_Bunnycdn',
array(
'is_authorized' => $is_authorized,
'lang' => array(
'empty_url' => \esc_html__( 'No URL specified', 'w3-total-cache' ),
'success_purging' => \esc_html__( 'Successfully purged URL', 'w3-total-cache' ),
'error_purging' => \esc_html__( 'Error purging URL', 'w3-total-cache' ),
'error_ajax' => \esc_html__( 'Error with AJAX', 'w3-total-cache' ),
),
)
);
\wp_enqueue_script( 'w3tc_cdn_bunnycdn' );
}
/**
* CDN settings.
*
* @since X.X.X
* @static
*
* @return void
*/
public static function w3tc_settings_cdn_boxarea_configuration() {
$config = Dispatcher::config();
include W3TC_DIR . '/Cdn_BunnyCdn_Page_View.php';
}
/**
* Display purge URLs page.
*
* @since X.X.X
* @static
*/
public static function w3tc_purge_urls_box() {
$config = Dispatcher::config();
include W3TC_DIR . '/Cdn_BunnyCdn_Page_View_Purge_Urls.php';
}
/**
* W3TC AJAX: Purge a URL.
*
* Purging a URL will remove the file from the CDN cache and re-download it from your origin server.
* Please enter the exact CDN URL of each individual file.
* You can also purge folders or wildcard files using * inside of the URL path.
* Wildcard values are not supported if using Perma-Cache.
*
* @since X.X.X
*/
public function w3tc_ajax_cdn_bunnycdn_purge_url() {
$url = Util_Request::get_string( 'url' );
// Check if URL starts with "http", starts with a valid protocol, and passes a URL validation check.
if ( 0 !== \strpos( $url, 'http' ) || ! \preg_match( '~^http(s?)://(.+)~i', $url ) || ! \filter_var( $url, FILTER_VALIDATE_URL ) ) {
\wp_send_json_error(
array( 'error_message' => \esc_html__( 'Invalid URL', 'w3-total-cache' ) ),
400
);
}
$config = Dispatcher::config();
$account_api_key = $config->get_string( 'cdn.bunnycdn.account_api_key' );
$api = new Cdn_BunnyCdn_Api( array( 'account_api_key' => $account_api_key ) );
// Try to delete pull zone.
try {
$api->purge(
array(
'url' => \esc_url( $url, array( 'http', 'https' ) ),
'async' => true,
)
);
} catch ( \Exception $ex ) {
\wp_send_json_error( array( 'error_message' => $ex->getMessage() ), 422 );
}
\wp_send_json_success();
}
/**
* Flush all caches except Bunny CDN.
*
* @since X.X.X
*/
public function w3tc_bunnycdn_flush_all_except_bunnycdn() {
Dispatcher::component( 'CacheFlush' )->flush_all( array( 'bunnycdn' => 'skip' ) );
Util_Admin::redirect( array( 'w3tc_note' => 'flush_all' ), true );
}
}

View File

@ -0,0 +1,248 @@
/**
* File: Cdn_BunnyCdn_Page_View.js
*
* @since X.X.X
* @package W3TC
*
* @global W3TC_Bunnycdn Localization array for info and language.
*/
jQuery(function($) {
/**
* Resize the popup modal.
*
* @param object o W3tc_Lightbox object.
*/
function w3tc_bunnycdn_resize(o) {
o.resize();
}
// Add event handlers.
$('body')
// Load the authorization form.
.on('click', '.w3tc_cdn_bunnycdn_authorize', function() {
W3tc_Lightbox.open({
id:'w3tc-overlay',
close: '',
width: 800,
height: 300,
url: ajaxurl +
'?action=w3tc_ajax&_wpnonce=' +
w3tc_nonce +
'&w3tc_action=cdn_bunnycdn_intro',
callback: w3tc_bunnycdn_resize
});
})
// Sanitize the account API key input value.
.on('change', '#w3tc-account-api-key', function() {
var $this = $(this);
$this.val($.trim($this.val().replace(/[^a-z0-9-]/g, '')));
})
// Load the pull zone selection form.
.on('click', '.w3tc_cdn_bunnycdn_list_pull_zones', function() {
var url = ajaxurl + '?action=w3tc_ajax&_wpnonce=' + w3tc_nonce +
'&w3tc_action=cdn_bunnycdn_list_pull_zones';
W3tc_Lightbox.load_form(url, '.w3tc_cdn_bunnycdn_form', w3tc_bunnycdn_resize);
})
// Enable/disable (readonly) add pull zone form fields based on selection.
.on('change', '#w3tc-pull-zone-id', function() {
var $selected_option = $(this).find(':selected'),
$origin = $('#w3tc-origin-url'),
$name = $('#w3tc-pull-zone-name'),
$hostnames = $('#w3tc-custom-hostnames');
if ($(this).find(':selected').val() === '') {
// Enable the add pull zone fields with suggested or entered values.
$origin.val($origin.data('suggested')).prop('readonly', false);
$name.val($name.data('suggested')).prop('readonly', false);
$hostnames.val($hostnames.data('suggested')).prop('readonly', false);
} else {
// Disable the add pull zone fields and change values using the selected option.
$origin.prop('readonly', true).val($selected_option.data('origin'));
$name.prop('readonly', true).val($selected_option.data('name'));
$hostnames.prop('readonly', true).val($selected_option.data('custom-hostnames'));
}
// Update the hidden input field for the selected pull zone id from the select option value.
$('[name="pull_zone_id"]').val($selected_option.val());
// Update the hidden input field for the selected pull zone CDN hostname from the select option value.
$('[name="cdn_hostname"]').val($selected_option.data('cdn-hostname'));
})
// Sanitize the origin URL/IP input value.
.on('change', '#w3tc-origin-url', function() {
var $this = $(this);
$this.val($.trim($this.val().toLowerCase().replace(/[^a-z0-9\.:\/-]/g, '')));
})
// Sanitize the pull zone name input value.
.on('change', '#w3tc-pull-zone-name', function() {
var $this = $(this);
$this.val($.trim($this.val().toLowerCase().replace(/[^a-z0-9-]/g, '')));
})
// Sanitize the CDN hostname input value.
.on('change', '#w3tc_bunnycdn_hostname', function() {
var $this = $(this);
$this.val($.trim($this.val().toLowerCase().replace(/(^https?:|:.+$|[^a-z0-9\.-])/g, '')));
})
// Configure pull zone.
.on('click', '.w3tc_cdn_bunnycdn_configure_pull_zone', function() {
var url = ajaxurl + '?action=w3tc_ajax&_wpnonce=' + w3tc_nonce +
'&w3tc_action=cdn_bunnycdn_configure_pull_zone';
W3tc_Lightbox.load_form(url, '.w3tc_cdn_bunnycdn_form', w3tc_bunnycdn_resize);
})
// Close the popup success modal.
.on('click', '.w3tc_cdn_bunnycdn_done', function() {
window.location = window.location + '&';
})
// Load the deauthorize form.
.on('click', '.w3tc_cdn_bunnycdn_deauthorization', function() {
W3tc_Lightbox.open({
id:'w3tc-overlay',
close: '',
width: 800,
height: 300,
url: ajaxurl +
'?action=w3tc_ajax&_wpnonce=' +
w3tc_nonce +
'&w3tc_action=cdn_bunnycdn_deauthorization',
callback: w3tc_bunnycdn_resize
});
})
// Deauthorize and optionally delete the pull zone.
.on('click', '.w3tc_cdn_bunnycdn_deauthorize', function() {
var url = ajaxurl + '?action=w3tc_ajax&_wpnonce=' + w3tc_nonce +
'&w3tc_action=cdn_bunnycdn_deauthorize';
W3tc_Lightbox.load_form(url, '.w3tc_cdn_bunnycdn_form', w3tc_bunnycdn_resize);
})
// Sanitize the purge URL list.
.on('focusout', '#w3tc-purge-urls', function () {
// Abort if Bunny CDN is not authorized.
if (! W3TC_Bunnycdn.is_authorized) {
return;
}
// Declare vars.
var $this = $(this),
$button = $('.w3tc_cdn_bunnycdn_purge_urls');
// Strip whitespace, newlines, and invalid characters.
$this.val( $this.val().replace(/^(\s)*(\r\n|\n|\r)/gm, '') );
$this.val($.trim($this.val().replace(/[^a-z0-9\.:\/\r\n*-]/g, '')));
// Enable the purge button.
$button.prop('disabled', false);
})
// Purge URLs.
.on('click', '.w3tc_cdn_bunnycdn_purge_urls', function() {
// Abort if Bunny CDN is not authorized.
if (! W3TC_Bunnycdn.is_authorized) {
return;
}
// Declare vars.
var urls_processed = 0,
list = $('#w3tc-purge-urls').val().split("\n").filter((v) => v != ''),
$messages = $('#w3tc-purge-messages'),
$this = $(this);
// Disable the button clicked and show a spinner.
$this
.prop('disabled', true)
.closest('p').addClass('lightbox-loader');
// Clear the messages div.
$messages.empty();
// Abort if nothing was submitted.
if (list.length < 1) {
$('<div/>', {
class: 'error',
text: W3TC_Bunnycdn.lang.empty_url + '.'
}).appendTo($messages);
$this.closest('p').removeClass('lightbox-loader');
return;
}
list.forEach(function(url, index, array) {
$.ajax({
method: 'POST',
url: ajaxurl,
data: {
_wpnonce: w3tc_nonce[0],
action: 'w3tc_ajax',
w3tc_action: 'cdn_bunnycdn_purge_url',
url: url
}
})
.done(function(response) {
// Possible success.
if (typeof response.success !== 'undefined') {
if (response.success) {
// Successful.
$('<div/>', {
class: 'updated',
text: W3TC_Bunnycdn.lang.success_purging + ' "' + url + '".'
}).appendTo($messages);
} else {
// Unsucessful.
$('<div/>', {
class: 'error',
text: W3TC_Bunnycdn.lang.error_purging + ' "' + url + '"; ' + response.data.error_message + '.'
}).appendTo($messages);
}
} else {
// Unknown error.
$('<div/>', {
class: 'error',
text: W3TC_Bunnycdn.lang.error_ajax + '.'
}).appendTo($messages);
}
})
.fail(function(response) {
// Failure; received a non-2xx/3xx HTTP status code.
if (typeof response.responseJSON !== 'undefined' && 'data' in response.responseJSON && 'error_message' in response.responseJSON.data) {
// An error message was passed in the response data.
$('<div/>', {
class: 'error',
text: W3TC_Bunnycdn.lang.error_purging + ' "' + url + '"; ' + response.responseJSON.data.error_message + '.'
}).appendTo($messages);
} else {
// Unknown error.
$('<div/>', {
class: 'error',
text: W3TC_Bunnycdn.lang.error_ajax + '.'
}).appendTo($messages);
}
})
.complete(function() {
urls_processed++;
// When requests are all complete, then remove the spinner.
if (urls_processed === array.length) {
$this.closest('p').removeClass('lightbox-loader');
}
});
});
});
});

View File

@ -0,0 +1,130 @@
<?php
/**
* File: Cdn_BunnyCdn_Page_View.php
*
* Bunny CDN settings page section view.
*
* @since X.X.X
* @package W3TC
*
* @param array $config W3TC configuration.
*/
namespace W3TC;
defined( 'W3TC' ) || die();
$account_api_key = $config->get_string( 'cdn.bunnycdn.account_api_key' );
$is_authorized = ! empty( $account_api_key ) && $config->get_string( 'cdn.bunnycdn.pull_zone_id' );
$is_unavailable = ! empty( $account_api_key ) && $config->get_string( 'cdnfsd.bunnycdn.pull_zone_id' ); // CDN is unavailable if CDN FSD is authorized for Bunny CDN.
?>
<table class="form-table">
<tr>
<th style="width: 300px;">
<label>
<?php esc_html_e( 'Account API key authorization', 'w3-total-cache' ); ?>:
</label>
</th>
<td>
<?php if ( $is_authorized ) : ?>
<input class="w3tc_cdn_bunnycdn_deauthorization button-primary" type="button" value="<?php esc_attr_e( 'Deauthorize', 'w3-total-cache' ); ?>" />
<?php else : ?>
<input class="w3tc_cdn_bunnycdn_authorize button-primary" type="button" value="<?php esc_attr_e( 'Authorize', 'w3-total-cache' ); ?>"
<?php echo ( $is_unavailable ? 'disabled' : '' ); ?> />
<?php if ( $is_unavailable ) : ?>
<div class="notice notice-info">
<p>
<?php esc_html_e( 'CDN for objects cannot be authorized if full-site delivery is already configured.', 'w3-total-cache' ); ?>
</p>
</div>
<?php endif; ?>
<?php endif; ?>
</td>
</tr>
<?php if ( $is_authorized ) : ?>
<tr>
<th><label><?php esc_html_e( 'Pull zone name:', 'w3-total-cache' ); ?></label></th>
<td class="w3tc_config_value_text">
<?php echo esc_html( $config->get_string( 'cdn.bunnycdn.name' ) ); ?>
</td>
</tr>
<tr>
<th>
<label>
<?php
echo wp_kses(
sprintf(
// translators: 1: Opening HTML acronym tag, 2: Opening HTML acronym tag, 3: Closing HTML acronym tag.
esc_html__(
'Origin %1$sURL%3$s/%2$sIP%3$s address:',
'w3-total-cache'
),
'<acronym title="' . esc_attr__( 'Universal Resource Locator', 'w3-total-cache' ) . '">',
'<acronym title="' . esc_attr__( 'Internet Protocol', 'w3-total-cache' ) . '">',
'</acronym>'
),
array(
'acronym' => array(
'title' => array(),
),
)
);
?>
</label>
</th>
<td class="w3tc_config_value_text">
<?php echo esc_html( $config->get_string( 'cdn.bunnycdn.origin_url' ) ); ?>
</td>
</tr>
<tr>
<th>
<label>
<?php
echo wp_kses(
sprintf(
// translators: 1: Opening HTML acronym tag, 2: Closing HTML acronym tag.
esc_html__(
'%1$sCDN%2$s hostname:',
'w3-total-cache'
),
'<acronym title="' . esc_attr__( 'Content Delivery Network', 'w3-total-cache' ) . '">',
'</acronym>'
),
array(
'acronym' => array(
'title' => array(),
),
)
);
?>
</label>
</th>
<td class="w3tc_config_value_text">
<input id="w3tc_bunnycdn_hostname" type="text" name="cdn__bunnycdn__cdn_hostname"
value="<?php echo esc_html( $config->get_string( 'cdn.bunnycdn.cdn_hostname' ) ); ?>" size="100" />
<p class="description">
<?php
echo wp_kses(
sprintf(
// translators: 1: Opening HTML acronym tag, 2: Closing HTML acronym tag.
esc_html__(
'The %1$sCDN%2$s hostname is used in media links on pages. For example: example.b-cdn.net',
'w3-total-cache'
),
'<acronym title="' . esc_attr__( 'Content Delivery Network', 'w3-total-cache' ) . '">',
'</acronym>'
),
array(
'acronym' => array(
'title' => array(),
),
)
);
?>
</p>
</td>
</tr>
<?php endif; ?>
</table>

View File

@ -0,0 +1,63 @@
<?php
/**
* File: Cdn_BunnyCdn_Page_View_Purge_Urls.php
*
* Bunny CDN settings purge URLs view.
*
* @since X.X.X
* @package W3TC
*
* @param array $config W3TC configuration.
*/
namespace W3TC;
defined( 'W3TC' ) || die;
$account_api_key = $config->get_string( 'cdn.bunnycdn.account_api_key' );
$is_authorized = ! empty( $account_api_key ) &&
( $config->get_string( 'cdn.bunnycdn.pull_zone_id' ) || $config->get_string( 'cdnfsd.bunnycdn.pull_zone_id' ) );
$placeholder = \esc_url( \home_url() . '/about-us' ) . "\r\n" . \esc_url( \home_url() . '/css/*' );
?>
<table class="form-table">
<tr>
<th style="width: 300px;">
<label>
<?php \esc_html_e( 'Purge URLs', 'w3-total-cache' ); ?>:
</label>
</th>
<td>
<textarea id="w3tc-purge-urls" class="w3tc-ignore-change" cols="60" rows="5"
placeholder="<?php echo \esc_html( $placeholder ); ?>" <?php echo ( $is_authorized ? '' : 'disabled' ); ?>></textarea>
<p><?php \esc_html_e( 'Purging a URL will remove the file from the CDN cache and re-download it from your origin server. Please enter the exact CDN URL of each individual file. You can also purge folders or wildcard files using * inside of the URL path. Wildcard values are not supported if using Perma-Cache.', 'w3-total-cache' ); ?></p>
<p>
<input class="w3tc_cdn_bunnycdn_purge_urls button-primary" type="button"
value="<?php \esc_attr_e( 'Purge URLs Now', 'w3-total-cache' ); ?>"
<?php echo ( $is_authorized ? '' : 'disabled' ); ?>/>
</p>
<?php
if ( ! $is_authorized ) :
echo wp_kses(
\sprintf(
// translators: 1: Opening HTML elements, 2: Name of the CDN service, 3: Closing HTML elements.
\esc_html__( '%1$sPlease configure %2$s in order to purge URLs.%3$s', 'w3-total-cache' ),
'<div class="notice notice-info"><p>',
'Bunny CDN',
'</p></div>'
),
array(
'div' => array(
'class' => array(),
),
'p' => array(),
)
);
else :
?>
<br />
<p><div id="w3tc-purge-messages"></div></p>
<?php endif; ?>
</td>
</tr>
</table>

View File

@ -0,0 +1,279 @@
<?php
/**
* File: Cdn_BunnyCdn_Popup.php
*
* @since X.X.X
* @package W3TC
*/
namespace W3TC;
/**
* Class: Cdn_BunnyCdn_Popup
*
* @since X.X.X
*/
class Cdn_BunnyCdn_Popup {
/**
* W3TC AJAX.
*
* @since X.X.X
* @static
*
* @return void
*/
public static function w3tc_ajax() {
$o = new Cdn_BunnyCdn_Popup();
\add_action(
'w3tc_ajax_cdn_bunnycdn_intro',
array( $o, 'w3tc_ajax_cdn_bunnycdn_intro' )
);
\add_action(
'w3tc_ajax_cdn_bunnycdn_list_pull_zones',
array( $o, 'w3tc_ajax_cdn_bunnycdn_list_pull_zones' )
);
\add_action(
'w3tc_ajax_cdn_bunnycdn_configure_pull_zone',
array( $o, 'w3tc_ajax_cdn_bunnycdn_configure_pull_zone' )
);
\add_action(
'w3tc_ajax_cdn_bunnycdn_deauthorization',
array( $o, 'w3tc_ajax_cdn_bunnycdn_deauthorization' )
);
\add_action(
'w3tc_ajax_cdn_bunnycdn_deauthorize',
array( $o, 'w3tc_ajax_cdn_bunnycdn_deauthorize' )
);
}
/**
* W3TC AJAX: Render intro.
*
* @since X.X.X
*
* @return void
*/
public function w3tc_ajax_cdn_bunnycdn_intro() {
$config = Dispatcher::config();
$account_api_key = $config->get_string( 'cdn.bunnycdn.account_api_key' );
// Ask for an account API key.
$this->render_intro(
array(
'account_api_key' => empty( $account_api_key ) ? null : $account_api_key,
)
);
}
/**
* W3TC AJAX: List pull zones.
*
* @since X.X.X
*/
public function w3tc_ajax_cdn_bunnycdn_list_pull_zones() {
$account_api_key = Util_Request::get_string( 'account_api_key' );
$api = new Cdn_BunnyCdn_Api( array( 'account_api_key' => $account_api_key ) );
// Try to retrieve pull zones.
try {
$pull_zones = $api->list_pull_zones();
} catch ( \Exception $ex ) {
// Reauthorize: Ask for a new account API key.
$this->render_intro(
array(
'account_api_key' => empty( $account_api_key ) ? null : $account_api_key,
'error_message' => \esc_html( \__( 'Cannot list pull zones', 'w3-total-cache' ) . '; ' . $ex->getMessage() ),
)
);
}
// Save the account API key, if added or changed.
$config = Dispatcher::config();
if ( $config->get_string( 'cdn.bunnycdn.account_api_key' ) !== $account_api_key ) {
$config->set( 'cdn.bunnycdn.account_api_key', $account_api_key );
$config->save();
}
// Print the view.
$server_ip = ! empty( $_SERVER['SERVER_ADDR'] ) && \filter_var( \wp_unslash( $_SERVER['SERVER_ADDR'] ), FILTER_VALIDATE_IP ) ?
\filter_var( \wp_unslash( $_SERVER['SERVER_ADDR'] ), FILTER_SANITIZE_URL ) : null;
$details = array(
'pull_zones' => $pull_zones,
'suggested_origin_url' => \home_url(), // Suggested origin URL or IP.
'suggested_zone_name' => \substr( \str_replace( '.', '-', \parse_url( \home_url(), PHP_URL_HOST ) ), 0, 60 ), // Suggested pull zone name.
'pull_zone_id' => $config->get_integer( 'cdn.bunnycdn.pull_zone_id' ),
);
include W3TC_DIR . '/Cdn_BunnyCdn_Popup_View_Pull_Zones.php';
\wp_die();
}
/**
* W3TC AJAX: Configure pull zone.
*
* @since X.X.X
*
* @see Cdn_BunnyCdn_Api::get_default_edge_rules()
*/
public function w3tc_ajax_cdn_bunnycdn_configure_pull_zone() {
$config = Dispatcher::config();
$account_api_key = $config->get_string( 'cdn.bunnycdn.account_api_key' );
$pull_zone_id = Util_Request::get_integer( 'pull_zone_id' );
$origin_url = Util_Request::get_string( 'origin_url' ); // Origin URL or IP.
$name = Util_Request::get_string( 'name' ); // Pull zone name.
$cdn_hostname = Util_Request::get_string( 'cdn_hostname' ); // Pull zone CDN hostname (system).
// If not selecting a pull zone. then create a new one.
if ( empty( $pull_zone_id ) ) {
$api = new Cdn_BunnyCdn_Api( array( 'account_api_key' => $account_api_key ) );
// Try to create a new pull zone.
try {
$response = $api->add_pull_zone(
array(
'Name' => $name, // The name/hostname for the pull zone where the files will be accessible; only letters, numbers, and dashes.
'OriginUrl' => $origin_url, // Origin URL or IP (with optional port number).
'CacheErrorResponses' => true, // If enabled, bunny.net will temporarily cache error responses (304+ HTTP status codes) from your servers for 5 seconds to prevent DDoS attacks on your origin. If disabled, error responses will be set to no-cache.
'DisableCookies' => false, // Determines if the Pull Zone should automatically remove cookies from the responses.
'EnableTLS1' => false, // TLS 1.0 was deprecated in 2018.
'EnableTLS1_1' => false, // TLS 1.1 was EOL's on March 31,2020.
'ErrorPageWhitelabel' => true, // Any bunny.net branding will be removed from the error page and replaced with a generic term.
'OriginHostHeader' => \parse_url( \home_url(), PHP_URL_HOST ), // Sets the host header that will be sent to the origin.
'UseStaleWhileUpdating' => true, // Serve stale content while updating. If Stale While Updating is enabled, cache will not be refreshed if the origin responds with a non-cacheable resource.
'UseStaleWhileOffline' => true, // Serve stale content if the origin is offline.
)
);
$pull_zone_id = (int) $response['Id'];
$name = $response['Name'];
$cdn_hostname = $response['Hostnames'][0]['Value'];
} catch ( \Exception $ex ) {
// Reauthorize: Ask for a new account API key.
$this->render_intro(
array(
'account_api_key' => empty( $account_api_key ) ? null : $account_api_key,
'error_message' => \esc_html( \__( 'Cannot select or add a pull zone', 'w3-total-cache' ) . '; ' . $ex->getMessage() ),
)
);
}
// Initialize an error messages array.
$error_messages = array();
// Add Edge Rules.
foreach ( Cdn_BunnyCdn_Api::get_default_edge_rules() as $edge_rule ) {
try {
$api->add_edge_rule( $edge_rule, $pull_zone_id );
} catch ( \Exception $ex ) {
$error_messages[] = sprintf(
// translators: 1: Edge Rule description/name.
\__( 'Could not add Edge Rule "%1$s".', 'w3-total-cache' ) . '; ',
\esc_html( $edge_rule['Description'] )
) . $ex->getMessage();
}
}
// Convert error messages array to a string.
$error_messages = \implode( "\r\n", $error_messages );
}
// Save configuration.
$config->set( 'cdn.bunnycdn.pull_zone_id', $pull_zone_id );
$config->set( 'cdn.bunnycdn.name', $name );
$config->set( 'cdn.bunnycdn.origin_url', $origin_url );
$config->set( 'cdn.bunnycdn.cdn_hostname', $cdn_hostname );
$config->save();
// Print success view.
include W3TC_DIR . '/Cdn_BunnyCdn_Popup_View_Configured.php';
\wp_die();
}
/**
* W3TC AJAX: Deauthorization form.
*
* @since X.X.X
*/
public function w3tc_ajax_cdn_bunnycdn_deauthorization() {
$config = Dispatcher::config();
$origin_url = $config->get_string( 'cdn.bunnycdn.origin_url' ); // Origin URL or IP.
$name = $config->get_string( 'cdn.bunnycdn.name' ); // Pull zone name.
$cdn_hostname = $config->get_string( 'cdn.bunnycdn.cdn_hostname' ); // Pull zone CDN hostname.
$cdn_pull_zone_id = $config->get_integer( 'cdn.bunnycdn.pull_zone_id' ); // CDN pull zone id.
$cdnfsd_pull_zone_id = $config->get_integer( 'cdnfsd.bunnycdn.pull_zone_id' ); // CDN FSD pull zone id.
// Present details and ask to deauthorize and optionally delete the pull zone.
include W3TC_DIR . '/Cdn_BunnyCdn_Popup_View_Deauthorize.php';
\wp_die();
}
/**
* W3TC AJAX: Deauthorize.
*
* Deauthorize and optionally delete the pull zone.
*
* @since X.X.X
*/
public function w3tc_ajax_cdn_bunnycdn_deauthorize() {
$config = Dispatcher::config();
$account_api_key = $config->get_string( 'cdn.bunnycdn.account_api_key' );
$cdn_pull_zone_id = $config->get_integer( 'cdn.bunnycdn.pull_zone_id' ); // CDN pull zone id.
$cdnfsd_pull_zone_id = $config->get_integer( 'cdnfsd.bunnycdn.pull_zone_id' ); // CDN FSD pull zone id.
$delete_pull_zone = Util_Request::get_string( 'delete_pull_zone' );
// Delete pull zone, if requested.
if ( 'yes' === $delete_pull_zone ) {
$api = new Cdn_BunnyCdn_Api( array( 'account_api_key' => $account_api_key ) );
// Try to delete pull zone.
try {
$api->delete_pull_zone( $cdn_pull_zone_id );
} catch ( \Exception $ex ) {
$delete_error_message = $ex->getMessage();
}
// If the same pull zone is used for FSD, then deauthorize that too.
if ( ! empty( $cdn_pull_zone_id ) && $cdn_pull_zone_id === $cdnfsd_pull_zone_id ) {
$config->set( 'cdnfsd.bunnycdn.pull_zone_id', null );
$config->set( 'cdnfsd.bunnycdn.name', null );
$config->set( 'cdnfsd.bunnycdn.origin_url', null );
$config->set( 'cdnfsd.bunnycdn.cdn_hostname', null );
}
}
$config->set( 'cdn.bunnycdn.pull_zone_id', null );
$config->set( 'cdn.bunnycdn.name', null );
$config->set( 'cdn.bunnycdn.origin_url', null );
$config->set( 'cdn.bunnycdn.cdn_hostname', null );
$config->save();
// Print success view.
include W3TC_DIR . '/Cdn_BunnyCdn_Popup_View_Deauthorized.php';
\wp_die();
}
/**
* Render intro.
*
* @since X.X.X
* @access private
*
* @param array $details {
* Details for the modal.
*
* @type string $account_api_key Account API key.
* @type string $error_message Error message (optional).
* }
*/
private function render_intro( array $details ) {
include W3TC_DIR . '/Cdn_BunnyCdn_Popup_View_Intro.php';
\wp_die();
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* File: Cdn_BunnyCdn_Popup_View_Configured.php
*
* @since X.X.X
* @package W3TC
*/
namespace W3TC;
defined( 'W3TC' ) || die();
?>
<?php if ( ! empty( $error_messages ) ) : ?>
<div class="error">
<?php echo $error_messages; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
<?php endif; ?>
<form class="w3tc_cdn_bunnycdn_form">
<div class="metabox-holder">
<?php Util_Ui::postbox_header( esc_html__( 'Success', 'w3-total-cache' ) ); ?>
<div style="text-align: center">
<?php esc_html_e( 'A pull zone has been configured successfully', 'w3-total-cache' ); ?>.<br />
</div>
<p class="submit">
<input type="button" class="w3tc_cdn_bunnycdn_done w3tc-button-save button-primary"
value="<?php esc_html_e( 'Done', 'w3-total-cache' ); ?>" />
</p>
<?php Util_Ui::postbox_footer(); ?>
</div>
</form>

View File

@ -0,0 +1,62 @@
<?php
/**
* File: Cdnfsd_BunnyCdn_Popup_Deauthorize.php
*
* Assists to deauthorize Bunny CDN as an objects CDN and optionally delete the pull zone.
*
* @since X.X.X
* @package W3TC
*
* @param Config $config W3TC configuration.
* @param string $origin_url Origin URL or IP.
* @param string $name Pull zone name.
* @param string $cdn_hostname CDN hostname.
* @param string $cdn_pull_zone_id CDN pull zone id.
* @param string $cdnfsd_pull_zone_id CDN FSD pull zone id.
*/
namespace W3TC;
defined( 'W3TC' ) || die;
// Determine if the same pull zone is used for CDN and CDN FSD. If so, then we'll show a message that it will deactivate both.
$is_same_zone = $cdn_pull_zone_id === $cdnfsd_pull_zone_id;
?>
<form class="w3tc_cdn_bunnycdn_form" method="post">
<input type="hidden" name="pull_zone_id" />
<div class="metabox-holder">
<?php Util_Ui::postbox_header( \esc_html__( 'Deauthorize pull zone', 'w3-total-cache' ) ); ?>
<table class="form-table">
<tr>
<td><?php \esc_html_e( 'Name', 'w3-total-cache' ); ?>:</td>
<td><?php echo \esc_html( $name ); ?></td>
</tr>
<tr>
<td><?php \esc_html_e( 'Origin URL / IP', 'w3-total-cache' ); ?>:</td>
<td><?php echo \esc_html( $origin_url ); ?></td>
</tr>
<tr>
<td><?php \esc_html_e( 'CDN hostname', 'w3-total-cache' ); ?>:</td>
<td><?php echo \esc_html( $cdn_hostname ); ?></td>
</tr>
<tr>
<td><?php \esc_html_e( 'Delete', 'w3-total-cache' ); ?>:</td>
<td>
<input id="w3tc-delete-zone" type="checkbox" name="delete_pull_zone" value="yes" /> Delete the pull zone
<?php if ( $is_same_zone ) : ?>
<p class="notice notice-warning">
<?php \esc_html_e( 'This same pull zone is used for full-site delivery. If you delete this pull zone, then full-site delivery will be deauthorized.', 'w3-total-cache' ); ?>
</p>
<?php endif; ?>
</td>
</tr>
</table>
<p class="submit">
<input type="button" class="w3tc_cdn_bunnycdn_deauthorize w3tc-button-save button-primary"
value="<?php \esc_attr_e( 'Deauthorize', 'w3-total-cache' ); ?>" />
</p>
<?php Util_Ui::postbox_footer(); ?>
</div>
</form>

View File

@ -0,0 +1,49 @@
<?php
/**
* File: Cdnfsd_BunnyCdn_Popup_View_Deauthorized.php
*
* @since X.X.X
* @package W3TC
*
* @param Config $config W3TC configuration.
* @param string $delete_pull_zone Delete pull zon choice ("yes").
* @param string $delete_error_message An error message if there was an error trying to delete the pull zone. String already escaped.
*/
namespace W3TC;
defined( 'W3TC' ) || die();
?>
<form class="w3tc_cdn_bunnycdn_form">
<?php if ( isset( $delete_error_message ) ) : ?>
<div class="error">
<?php
esc_html_e( 'An error occurred trying to delete the pull zone; ', 'w3-total-cache' );
echo $delete_error_message . '.'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
?>
</div>
<?php endif; ?>
<div class="metabox-holder">
<?php
Util_Ui::postbox_header(
esc_html__( 'Success', 'w3-total-cache' ) .
( isset( $delete_error_message ) ? esc_html__( ' (with an error)', 'w3-total-cache' ) : '' )
);
?>
<div style="text-align: center">
<p><?php esc_html_e( 'The objects CDN has been deauthorized', 'w3-total-cache' ); ?>.</p>
</div>
<?php if ( 'yes' === $delete_pull_zone && empty( $delete_error_message ) ) : ?>
<div style="text-align: center">
<p><?php esc_html_e( 'The pull zone has been deleted', 'w3-total-cache' ); ?>.</p>
</div>
<?php endif; ?>
<p class="submit">
<input type="button" class="w3tc_cdn_bunnycdn_done w3tc-button-save button-primary"
value="<?php esc_html_e( 'Done', 'w3-total-cache' ); ?>" />
</p>
<?php Util_Ui::postbox_footer(); ?>
</div>
</form>

View File

@ -0,0 +1,54 @@
<?php
/**
* File: Cdn_BunnyCdn_Popup_View_Intro.php
*
* Assists with configuring Bunny CDN as an object storage CDN.
* Asks to enter an account API key from the Bunny CDN main account.
*
* @since X.X.X
* @package W3TC
*
* @param array $details {
* Bunny CDN API configuration details.
*
* @type string $account_api_key Account API key.
* @type string $error_message Error message (optional). String already escaped.
* }
*/
namespace W3TC;
defined( 'W3TC' ) || die();
?>
<form class="w3tc_cdn_bunnycdn_form">
<?php if ( isset( $details['error_message'] ) ) : ?>
<div class="error">
<?php echo $details['error_message']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
<?php endif; ?>
<div class="metabox-holder">
<?php Util_Ui::postbox_header( esc_html__( 'Bunny CDN API Configuration', 'w3-total-cache' ) ); ?>
<table class="form-table">
<tr>
<td><?php esc_html_e( 'Account API Key', 'w3-total-cache' ); ?>:</td>
<td>
<input id="w3tc-account-api-key" name="account_api_key" type="text" class="w3tc-ignore-change"
style="width: 550px" value="<?php echo esc_attr( $details['account_api_key'] ); ?>" />
<p class="description">
<?php esc_html_e( 'To obtain your account API key,', 'w3-total-cache' ); ?>
<a target="_blank" href="<?php echo esc_url( W3TC_BUNNYCDN_SETTINGS_URL ); ?>"><?php esc_html_e( 'click here', 'w3-total-cache' ); ?></a>,
<?php esc_html_e( 'log in using the main account credentials, and paste the API key into the field above.', 'w3-total-cache' ); ?>
</p>
</td>
</tr>
</table>
<p class="submit">
<input type="button"
class="w3tc_cdn_bunnycdn_list_pull_zones w3tc-button-save button-primary"
value="<?php esc_attr_e( 'Next', 'w3-total-cache' ); ?>" />
</p>
<?php Util_Ui::postbox_footer(); ?>
</div>
</form>

View File

@ -0,0 +1,137 @@
<?php
/**
* File: Cdn_BunnyCdn_Popup_Pull_Zones.php
*
* Assists with configuring Bunny CDN as an object storage CDN.
* A pull zone selection is presented along with a form to add a new pull zone.
*
* @since X.X.X
* @package W3TC
*
* @param string $account_api_key Account PI key.
* @parm Cdn_BunnyCdn_Api $api API class object.
* @param array $details {
* Bunny CDN API configuration details.
*
* @type array $pull_zones Pull zones.
* @type string $suggested_origin_url Suggested origin URL or IP.
* @type string $suggested_zone_name Suggested pull zone name.
* @type int $pull_zone_id Pull zone id.
* @type string $error_message Error message (optional).
* }
* @param string $server_ip Server IP address.
*/
namespace W3TC;
defined( 'W3TC' ) || die();
?>
<form class="w3tc_cdn_bunnycdn_form" method="post">
<input type="hidden" name="pull_zone_id" />
<input type="hidden" name="cdn_hostname" />
<div class="metabox-holder">
<?php Util_Ui::postbox_header( esc_html__( 'Select a pull zone', 'w3-total-cache' ) ); ?>
<table class="form-table">
<tr>
<select id="w3tc-pull-zone-id">
<option value=""<?php echo empty( $details['pull_zone_id'] ) ? ' selected' : ''; ?>>Add a new pull zone</option>
<?php
if ( ! empty( $details['pull_zones'] ) ) {
// List pull zones for selection.
foreach ( $details['pull_zones'] as $pull_zone ) {
// Skip pull zones that are disabled or suspended.
if ( ! $pull_zone['Enabled'] || $pull_zone['Suspended'] ) {
continue;
}
// Get the CDN hostname and custom hostnames.
$cdn_hostname = '?';
$custom_hostnames = array();
// Get the CDN hostname. It should be the system hostname.
foreach ( $pull_zone['Hostnames'] as $hostname ) {
if ( ! empty( $hostname['Value'] ) ) {
if ( ! empty( $hostname['IsSystemHostname'] ) ) {
// CDN hostname (system); there should only be one.
$cdn_hostname = $hostname['Value'];
} else {
// Custom hostnames; 0 or more.
$custom_hostnames[] = $hostname['Value'];
}
}
}
// Determine the origin URL/IP.
$origin_url = empty( $pull_zone['OriginUrl'] ) ? $cdn_hostname : $pull_zone['OriginUrl'];
// Determine if the current option is selected.
$is_selected = isset( $details['pull_zone_id'] ) && $details['pull_zone_id'] === $pull_zone['Id'];
// Print the select option.
?>
<option value="<?php echo esc_attr( $pull_zone['Id'] ); ?>"
<?php echo $is_selected ? ' selected' : ''; ?>
data-origin="<?php echo esc_html( $origin_url ); ?>"
data-name="<?php echo esc_attr( $pull_zone['Name'] ); ?>"
data-cdn-hostname="<?php echo esc_attr( $cdn_hostname ); ?>"
data-custom-hostnames="<?php echo esc_attr( implode( ',', $custom_hostnames ) ); ?>">
<?php echo esc_attr( $pull_zone['Name'] ); ?>
(<?php echo esc_html( $origin_url ); ?>)
</option>
<?php
// If selected, then get the origin URL/IP and pull zone name.
if ( $is_selected ) {
$selected_origin_url = $origin_url;
$selected_name = $pull_zone['Name'];
$selected_custom_hostnames = implode( "\r\n", $custom_hostnames );
}
}
}
// Determine origin URL and pull zone name for the fields below.
$field_origin_url = isset( $selected_origin_url ) ? $selected_origin_url : $details['suggested_origin_url'];
$field_name = isset( $selected_name ) ? $selected_name : $details['suggested_zone_name'];
?>
</select>
</tr>
<tr>
<td><?php esc_html_e( 'Pull Zone Name', 'w3-total-cache' ); ?>:</td>
<td>
<input id="w3tc-pull-zone-name" name="name" type="text" class="w3tc-ignore-change"
style="width: 550px" value="<?php echo esc_attr( $field_name ); ?>"
<?php echo ( empty( $details['pull_zone_id'] ) ? '' : 'readonly ' ); ?>
data-suggested="<?php echo esc_attr( $details['suggested_zone_name'] ); ?>" />
<p class="description">
<?php esc_html_e( 'Name of the pull zone (letters, numbers, and dashes). If empty, one will be automatically generated.', 'w3-total-cache' ); ?>
</p>
</td>
</tr>
<tr>
<td><?php esc_html_e( 'Origin URL / IP', 'w3-total-cache' ); ?>:</td>
<td>
<input id="w3tc-origin-url" name="origin_url" type="text" class="w3tc-ignore-change"
style="width: 550px" value="<?php echo esc_attr( $field_origin_url ); ?>"
<?php echo ( empty( $details['pull_zone_id'] ) ? '' : 'readonly ' ); ?>
data-suggested="<?php echo esc_attr( $details['suggested_origin_url'] ); ?>" />
<p class="description">
<?php
esc_html_e( 'Pull origin site URL or IP address.', 'w3-total-cache' );
if ( ! empty( $server_ip ) ) {
echo esc_html( ' ' . __( 'Detected server IP address', 'w3-total-cache' ) . ':' . $server_ip );
}
?>
</p>
</td>
</tr>
</table>
<p class="submit">
<input type="button"
class="w3tc_cdn_bunnycdn_configure_pull_zone w3tc-button-save button-primary"
value="<?php esc_attr_e( 'Apply', 'w3-total-cache' ); ?>" />
</p>
<?php Util_Ui::postbox_footer(); ?>
</div>
</form>

View File

@ -0,0 +1,75 @@
<?php
/**
* File: Cdn_BunnyCdn_Widget.php
*
* @since X.X.X
* @package W3TC
*/
namespace W3TC;
/**
* Class: Cdn_BunnyCdn_Widget
*
* @since X.X.X
*/
class Cdn_BunnyCdn_Widget {
/**
* Initialize the WP Admin Dashboard.
*
* @since X.X.X
*
* @return void
*/
public static function admin_init_w3tc_dashboard() {
$o = new Cdn_BunnyCdn_Widget();
add_action( 'admin_print_styles', array( $o, 'admin_print_styles' ) );
Util_Widget::add2(
'w3tc_bunnycdn',
400,
'<div class="w3tc-widget-bunnycdn-logo"></div>',
array( $o, 'widget_form' ),
Util_Ui::admin_url( 'admin.php?page=w3tc_cdn' ),
'normal'
);
}
/**
* Print widget form.
*
* @since X.X.X
*
* return void
*/
public function widget_form() {
$c = Dispatcher::config();
$authorized = $c->get_string( 'cdn.engine' ) === 'bunnycdn' &&
( ! empty( $c->get_integer( 'cdn.bunnycdn.pull_zone_id' ) ) || ! empty( $c->get_integer( 'cdnfsd.bunnycdn.pull_zone_id' ) ) );
if ( $authorized ) {
include __DIR__ . DIRECTORY_SEPARATOR . 'Cdn_BunnyCdn_Widget_View_Authorized.php';
} else {
include __DIR__ . DIRECTORY_SEPARATOR . 'Cdn_BunnyCdn_Widget_View_Unauthorized.php';
}
}
/**
* Enqueue styles.
*
* @since X.X.X
*
* @return void
*/
public function admin_print_styles() {
wp_enqueue_style( 'w3tc-widget' );
wp_enqueue_style(
'w3tc-bunnycdn-widget',
plugins_url( 'Cdn_BunnyCdn_Widget_View.css', W3TC_FILE ),
array(),
W3TC_VERSION
);
}
}

View File

@ -0,0 +1,89 @@
.w3tc-widget-bunnycdn-logo {
width: 150px;
height: 50px;
background: url('pub/img/w3tc_bunnycdn_logo.svg') 0 8px no-repeat;
float: left;
}
.w3tc_bunnycdn_h4 {
text-align: center;
}
.w3tc_bunnycdn_summary_h4 {
text-align: center;
color: #8F8F8F;
text-align: left;
margin: 0;
}
.w3tc_bunnycdn_summary {
border-bottom: 1px solid #d2d2d2;
padding-left: 10px;
padding-right: 10px;
}
.w3tc_bunnycdn_wrapper {
margin-left: -10px;
margin-right: -10px;
}
.w3tc_bunnycdn_ul li {
margin-bottom: 3px;
padding: 0;
}
.w3tc_bunnycdn_summary_col1 {
display: block;
font-weight: bold;
width: 90px;
float:left;
}
.w3tc_bunnycdn_summary_col2 {
display: block;
font-weight: bold;
width: 80px;
float:left;
}
.w3tc_bunnycdn_tools {
margin-top:15px;
padding-left: 10px;
padding-right: 10px;
}
.w3tc_bunnycdn_tools li {
display:inline-block;
margin-right:10px;
}
.w3tc_bunnycdn_chart {
clear: both;
padding-left: 10px;
padding-right: 10px;
}
.w3tc_bunnycdn_chart p {
color:#8F8F8F;
margin:0;
}
.w3tc_bunnycdn_wrapper .button-secondary {
margin-bottom: 3px;
}
.w3tc_bunnycdn_signup_h4 {
text-align: left;
margin-bottom: 0;
padding-bottom: 0;
}
.w3tc_bunnycdn_signup p {
margin-top: 4px;
padding-top: 0;
}
.w3tc_bunnycdn_signup p span.desc {
color:#8F8F8F;
}

View File

@ -0,0 +1,59 @@
<?php
/**
* File: Cdn_BunnyCdn_Widget_View_Authorized.php
*
* @since X.X.X
* @package W3TC
*/
namespace W3TC;
defined( 'W3TC' ) || die();
?>
<div id="bunnycdn-widget" class="bunnycdn-widget-base w3tc_bunnycdn_content">
<div class="w3tc_bunnycdn_wrapper">
<div class="w3tc_bunnycdn_tools">
<p>
<?php
w3tc_e(
'cdn.bunnycdn.widget.v2.header',
\sprintf(
// translators: 1 HTML acronym for Content Delivery Network (CDN).
\__( 'Your website performance is enhanced with Bunny.Net\'s (%1$s) service.', 'w3-total-cache' ),
'<acronym title="' . \__( 'Content Delivery Network', 'w3-total-cache' ) . '">' . \__( 'CDN', 'w3-total-cache' ) . '</acronym>'
)
);
?>
</p>
</div>
<div class="w3tc_bunnycdn_tools">
<ul class="w3tc_bunnycdn_ul">
<li><a class="button" href="<?php echo \esc_url( \wp_nonce_url( Util_Ui::admin_url( 'admin.php?page=w3tc_dashboard&amp;w3tc_flush_cdn' ), 'w3tc' ) ); ?>"><?php \esc_html_e( 'Purge Cache', 'w3-total-cache' ); ?></a></li>
</ul>
<p>
<a target="_blank" href="<?php echo esc_url( W3TC_BUNNYCDN_CDN_URL ); ?>"><?php esc_html_e( 'Click here', 'w3-total-cache' ); ?></a>
<?php esc_html_e( 'to configure additional settings at Bunny.net.', 'w3-total-cache' ); ?>
</p>
<p>
<?php
w3tc_e(
'cdn.bunnycdn.widget.v2.existing',
\sprintf(
// translators: 1 HTML acronym for Content Delivery Network (CDN).
\__(
'If you need help configuring your %1$s, we also offer Premium Services to assist you.',
'w3-total-cache'
),
'<acronym title="' . \__( 'Content Delivery Network', 'w3-total-cache' ) . '">' . \__( 'CDN', 'w3-total-cache' ) . '</acronym>'
)
);
?>
</p>
<a class="button" href="<?php echo \esc_url( \wp_nonce_url( Util_Ui::admin_url( 'admin.php?page=w3tc_support' ), 'w3tc' ) ); ?>">
<?php \esc_html_e( 'Premium Services', 'w3-total-cache' ); ?>
</a>
</div>
</div>
</div>

View File

@ -0,0 +1,79 @@
<?php
/**
* File: Cdn_BunnyCdn_Widget_View_Unauthorized.php
*
* @since X.X.X
* @package W3TC
*/
namespace W3TC;
defined( 'W3TC' ) || die();
?>
<div id="bunnycdn-widget" class="w3tc_bunnycdn_signup">
<?php if ( ! $c->get_boolean( 'cdn.enabled' ) ) : ?>
<p class="notice notice-error">
<?php
w3tc_e(
'cdn.bunnycdn.widget.v2.no_cdn',
\sprintf(
// translators: 1 HTML acronym for Content Delivery Network (CDN).
\__( 'W3 Total Cache has detected that you do not have a %1$s configured', 'w3-total-cache' ),
'<acronym title="' . \__( 'Content Delivery Network', 'w3-total-cache' ) . '">' . \__( 'CDN', 'w3-total-cache' ) . '</acronym>'
)
);
?>
</p>
<?php endif ?>
<p>
<?php
w3tc_e(
'cdn.bunnycdn.widget.v2.header',
\sprintf(
// translators: 1 HTML acronym for Content Delivery Network (CDN).
\__( 'Enhance your website performance by adding Bunny.Net\'s (%1$s) service to your site.', 'w3-total-cache' ),
'<acronym title="' . \__( 'Content Delivery Network', 'w3-total-cache' ) . '">' . \__( 'CDN', 'w3-total-cache' ) . '</acronym>'
)
);
?>
</p>
<h4 class="w3tc_bunnycdn_signup_h4"><?php \esc_html_e( 'New customer? Sign up now to speed up your site!', 'w3-total-cache' ); ?></h4>
<p>
<?php
w3tc_e(
'cdn.bunnycdn.widget.v2.works_magically',
\__( 'Bunny CDN works magically with W3 Total Cache to speed up your site around the world for as little as $1 per month.', 'w3-total-cache' )
);
?>
</p>
<a class="button-primary" href="<?php echo esc_url( W3TC_BUNNYCDN_SIGNUP_URL ); ?>" target="_blank">
<?php \esc_html_e( 'Sign Up Now ', 'w3-total-cache' ); ?>
</a>
<p>
<h4 class="w3tc_bunnycdn_signup_h4"><?php esc_html_e( 'Current customers', 'w3-total-cache' ); ?></h4>
<p>
<?php
w3tc_e(
'cdn.bunnycdn.widget.v2.existing',
\sprintf(
// translators: 1 HTML acronym for Content Delivery Network (CDN).
\__(
'If you\'re an existing Bunny CDN customer, enable %1$s and authorize. If you need help configuring your %1$s, we also offer Premium Services to assist you.',
'w3-total-cache'
),
'<acronym title="' . \__( 'Content Delivery Network', 'w3-total-cache' ) . '">' . \__( 'CDN', 'w3-total-cache' ) . '</acronym>'
)
);
?>
</p>
<a class="button-primary" href="<?php echo \esc_url( \wp_nonce_url( Util_Ui::admin_url( 'admin.php?page=w3tc_cdn' ), 'w3tc' ) ); ?>">
<?php \esc_html_e( 'Authorize', 'w3-total-cache' ); ?>
</a>
<a class="button" href="<?php echo \esc_url( \wp_nonce_url( Util_Ui::admin_url( 'admin.php?page=w3tc_support' ), 'w3tc' ) ); ?>">
<?php \esc_html_e( 'Premium Services', 'w3-total-cache' ); ?>
</a>
</div>

View File

@ -0,0 +1,73 @@
<?php
namespace W3TC;
/**
* CDN cache purge object
*/
/**
* class Cdn_CacheFlush
*/
class Cdn_CacheFlush {
/**
* Advanced cache config
*/
var $_config = null;
/**
* Array of urls to flush
*
* @var array
*/
private $flush_operation_requested = false;
/**
* PHP5 Constructor
*/
function __construct() {
$this->_config = Dispatcher::config();
}
/**
* Purges everything from CDNs that supports it
*/
function purge_all() {
$this->flush_operation_requested = true;
return true;
}
/**
* Purge a single url
*
* @param unknown $url
*/
function purge_url( $url ) {
$common = Dispatcher::component( 'Cdn_Core' );
$results = array();
$files = array();
$parsed = parse_url( $url );
$local_site_path = isset( $parsed['path'] )? ltrim( $parsed['path'], '/' ) : '';
$remote_path = $common->uri_to_cdn_uri( $local_site_path );
$files[] = $common->build_file_descriptor( $local_site_path, $remote_path );
$this->_flushed_urls[] = $url;
$common->purge( $files, $results );
}
/**
* Clears global and repeated urls
*/
function purge_post_cleanup() {
if ( $this->flush_operation_requested ) {
$common = Dispatcher::component( 'Cdn_Core' );
$results = array();
$common->purge_all( $results );
$count = 999;
$this->flush_operation_requested = false;
}
return $count;
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace W3TC;
class Cdn_ConfigLabels {
public function config_labels( $config_labels ) {
return array_merge( $config_labels, array(
'cdn.enabled' => __( '<acronym title="Content Delivery Network">CDN</acronym>:', 'w3-total-cache' ),
'cdn.engine' => __( '<acronym title="Content Delivery Network">CDN</acronym> Type:', 'w3-total-cache' ),
'cdn.debug' => __( '<acronym title="Content Delivery Network">CDN</acronym>', 'w3-total-cache' ),
'cdnfsd.debug' => __( '<acronym title="Full Site Delivery">FSD</acronym> <acronym title="Content Delivery Network">CDN</acronym>', 'w3-total-cache' ),
'cdn.uploads.enable' => __( 'Host attachments', 'w3-total-cache' ),
'cdn.includes.enable' => __( 'Host wp-includes/ files', 'w3-total-cache' ),
'cdn.theme.enable' => __( 'Host theme files', 'w3-total-cache' ),
'cdn.minify.enable' => __( 'Host minified <acronym title="Cascading Style Sheet">CSS</acronym> and <acronym title="JavaScript">JS</acronym> files', 'w3-total-cache' ),
'cdn.custom.enable' => __( 'Host custom files', 'w3-total-cache' ),
'cdn.force.rewrite' => __( 'Force over-writing of existing files', 'w3-total-cache' ),
'cdn.import.external' => __( 'Import external media library attachments', 'w3-total-cache' ),
'cdn.canonical_header' => __( 'Add canonical header', 'w3-total-cache' ),
'cdn.reject.ssl' => __( 'Disable <acronym title="Content Delivery Network">CDN</acronym> on <acronym title="Secure Sockets Layer">SSL</acronym> pages', 'w3-total-cache' ),
'cdn.admin.media_library' => __( 'Use <acronym title="Content Delivery Network">CDN</acronym> links for the Media Library on admin pages', 'w3-total-cache' ),
'cdn.reject.logged_roles' => __( 'Disable <acronym title="Content Delivery Network">CDN</acronym> for the following roles', 'w3-total-cache' ),
'cdn.reject.uri' => __( 'Disable <acronym title="Content Delivery Network">CDN</acronym> on the following pages:', 'w3-total-cache' ),
'cdn.autoupload.enabled' => __( 'Export changed files automatically', 'w3-total-cache' ),
'cdn.autoupload.interval' => __( 'Auto upload interval:', 'w3-total-cache' ),
'cdn.queue.interval' => __( 'Re-transfer cycle interval:', 'w3-total-cache' ),
'cdn.queue.limit' => __( 'Re-transfer cycle limit:', 'w3-total-cache' ),
'cdn.includes.files' => __( 'wp-includes file types to upload:', 'w3-total-cache' ),
'cdn.theme.files' => __( 'Theme file types to upload:', 'w3-total-cache' ),
'cdn.import.files' => __( 'File types to import:', 'w3-total-cache' ),
'cdn.custom.files' => __( 'Custom file list:', 'w3-total-cache' ),
'cdn.rscf.location' => __( 'Location:', 'w3-total-cache' ),
'cdn.reject.ua' => __( 'Rejected user agents:', 'w3-total-cache' ),
'cdn.reject.files' => __( 'Rejected files:', 'w3-total-cache' ),
) );
}
}

View File

@ -0,0 +1,785 @@
<?php
/**
* File: Cdn_Core.php
*
* @package W3TC
*/
namespace W3TC;
/**
* Class: Cdn_Core
*/
class Cdn_Core {
/**
* Config.
*
* @var Config
*/
private $_config = null;
/**
* Debug.
*
* @var bool
*/
private $debug;
/**
* Constructor
*/
public function __construct() {
$this->_config = Dispatcher::config();
$this->debug = $this->_config->get_boolean( 'cdn.debug' );
}
/**
* Adds file to queue.
*
* @param string $local_path Local path.
* @param string $remote_path Remote path.
* @param int $command Command number.
* @param string $last_error Last error.
* @return int
*/
public function queue_add( $local_path, $remote_path, $command, $last_error ) {
global $wpdb;
$table = $wpdb->base_prefix . W3TC_CDN_TABLE_QUEUE;
$rows = $wpdb->get_results(
$wpdb->prepare(
'SELECT id, command FROM ' . $table . ' WHERE local_path = %s AND remote_path = %s', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$local_path,
$remote_path
)
);
$already_exists = false;
foreach ( $rows as $row ) {
if ( $row->command != $command ) {
$wpdb->query(
$wpdb->prepare(
'DELETE FROM ' . $table . ' WHERE id = %d', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$row->id
)
);
} else {
$already_exists = true;
}
}
if ( $already_exists ) {
return true;
}
// Insert if not yet there.
return $wpdb->query(
$wpdb->prepare(
'INSERT INTO ' . $table . ' (local_path, remote_path, command, last_error, date) VALUES (%s, %s, %d, %s, NOW())', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$local_path,
$remote_path,
$command,
$last_error
)
);
}
/**
* Returns array of array('local_path' => '', 'remote_path' => '') for specified file.
*
* @param string $file File.
* @return array
*/
public function get_files_for_upload( $file ) {
$files = array();
$upload_info = Util_Http::upload_info();
if ( $upload_info ) {
$file = $this->normalize_attachment_file( $file );
$local_file = $upload_info['basedir'] . '/' . $file;
$parsed = parse_url( rtrim( $upload_info['baseurl'], '/' ) . '/' . $file );
$local_uri = $parsed['path'];
$remote_uri = $this->uri_to_cdn_uri( $local_uri );
$remote_file = ltrim( $remote_uri, '/' );
$files[] = $this->build_file_descriptor( $local_file, $remote_file );
}
return $files;
}
/**
* Returns array of files from sizes array.
*
* @param string $attached_file Attached file.
* @param array $sizes Sizes.
* @return array
*/
public function _get_sizes_files( $attached_file, $sizes ) {
$files = array();
$base_dir = Util_File::dirname( $attached_file );
foreach ( (array) $sizes as $size ) {
if ( isset( $size['file'] ) ) {
if ( $base_dir ) {
$file = $base_dir . '/' . $size['file'];
} else {
$file = $size['file'];
}
$files = array_merge( $files, $this->get_files_for_upload( $file ) );
}
}
return $files;
}
/**
* Returns attachment files by metadata.
*
* @param array $metadata Metadata.
* @return array
*/
public function get_metadata_files( $metadata ) {
$files = array();
if ( isset( $metadata['file'] ) && isset( $metadata['sizes'] ) ) {
$files = array_merge( $files, $this->_get_sizes_files( $metadata['file'], $metadata['sizes'] ) );
}
return $files;
}
/**
* Returns attachment files by attachment id.
*
* @param int $attachment_id Attachment id.
* @return array
*/
public function get_attachment_files( $attachment_id ) {
$files = array();
// Get attached file.
$attached_file = get_post_meta( $attachment_id, '_wp_attached_file', true );
if ( ! empty( $attached_file ) ) {
$files = array_merge( $files, $this->get_files_for_upload( $attached_file ) );
// Get backup sizes files.
$attachment_backup_sizes = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
if ( is_array( $attachment_backup_sizes ) ) {
$files = array_merge( $files, $this->_get_sizes_files( $attached_file, $attachment_backup_sizes ) );
}
}
// Get files from metadata.
$attachment_metadata = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
if ( is_array( $attachment_metadata ) ) {
$files = array_merge( $files, $this->get_metadata_files( $attachment_metadata ) );
}
return $files;
}
/**
* Uploads files to CDN.
*
* @param array $files Files.
* @param bool $queue_failed Is queue failed.
* @param array $results Results.
* @param int $timeout_time Timeout time.
* @return bool
*/
public function upload( $files, $queue_failed, &$results, $timeout_time = null ) {
if ( $this->debug ) {
Util_Debug::log( 'cdn', 'upload: ' . json_encode( $files, JSON_PRETTY_PRINT ) );
}
$cdn = $this->get_cdn();
$force_rewrite = $this->_config->get_boolean( 'cdn.force.rewrite' );
@set_time_limit( $this->_config->get_integer( 'timelimit.cdn_upload' ) );
$engine = $this->_config->get_string( 'cdn.engine' );
$return = $cdn->upload( $files, $results, $force_rewrite, $timeout_time );
if ( ! $return && $queue_failed ) {
foreach ( $results as $result ) {
if ( W3TC_CDN_RESULT_OK !== $result['result'] ) {
$this->queue_add( $result['local_path'], $result['remote_path'], W3TC_CDN_COMMAND_UPLOAD, $result['error'] );
}
}
}
return $return;
}
/**
* Deletes files from CDN.
*
* @param array $files Files.
* @param bool $queue_failed Is queue failed.
* @param array $results Results.
* @return bool
*/
public function delete( $files, $queue_failed, &$results ) {
$cdn = $this->get_cdn();
@set_time_limit( $this->_config->get_integer( 'timelimit.cdn_delete' ) );
$return = $cdn->delete( $files, $results );
if ( $this->debug ) {
Util_Debug::log( 'cdn', 'delete: ' . json_encode( $files, JSON_PRETTY_PRINT ) );
}
if ( ! $return && $queue_failed ) {
foreach ( $results as $result ) {
if ( W3TC_CDN_RESULT_OK !== $result['result'] ) {
$this->queue_add( $result['local_path'], $result['remote_path'], W3TC_CDN_COMMAND_DELETE, $result['error'] );
}
}
}
return $return;
}
/**
* Purges files from CDN.
*
* @param array $files Files; consisting of array( 'local_path'=>'', 'remote_path'=>'' ).
* @param array $results Results.
* @return bool
*/
public function purge( $files, &$results ) {
if ( $this->debug ) {
Util_Debug::log( 'cdn', 'purge: ' . json_encode( $files, JSON_PRETTY_PRINT ) );
}
/**
* Purge varnish servers before mirror purging.
*/
if ( Cdn_Util::is_engine_mirror( $this->_config->get_string( 'cdn.engine' ) ) && $this->_config->get_boolean( 'varnish.enabled' ) ) {
$varnish = Dispatcher::component( 'Varnish_Flush' );
foreach ( $files as $file ) {
$remote_path = $file['remote_path'];
$varnish->flush_url( network_site_url( $remote_path ) );
}
}
/**
* Purge CDN.
*/
$cdn = $this->get_cdn();
@set_time_limit( $this->_config->get_integer( 'timelimit.cdn_purge' ) );
$return = $cdn->purge( $files, $results );
if ( ! $return ) {
foreach ( $results as $result ) {
if ( W3TC_CDN_RESULT_OK !== $result['result'] ) {
$this->queue_add( $result['local_path'], $result['remote_path'], W3TC_CDN_COMMAND_PURGE, $result['error'] );
}
}
}
return $return;
}
/**
* Purge CDN completely.
*
* @param unknown $results Results of the purge.
* @return mixed
*/
public function purge_all( &$results ) {
$cdn = $this->get_cdn();
@set_time_limit( $this->_config->get_integer( 'timelimit.cdn_purge' ) );
return $cdn->purge_all( $results );
}
/**
* Queues file upload.
*
* Links wp_cron call to do that by the end of request processing.
*
* @param string $url Url.
* @return void
*/
public function queue_upload_url( $url ) {
$docroot_filename = Util_Environment::url_to_docroot_filename( $url );
if ( is_null( $docroot_filename ) ) {
return;
}
$filename = Util_Environment::docroot_to_full_filename( $docroot_filename );
$a = \parse_url( $url );
$remote_filename = $this->uri_to_cdn_uri( $a['path'] );
$this->queue_add( $filename, $remote_filename, W3TC_CDN_COMMAND_UPLOAD, 'Pending' );
}
/**
* Normalizes attachment file.
*
* @param string $file Filename.
* @return string
*/
public function normalize_attachment_file( $file ) {
$upload_info = Util_Http::upload_info();
if ( $upload_info ) {
$file = ltrim( str_replace( $upload_info['basedir'], '', $file ), '/\\' );
$matches = null;
if ( preg_match( '~(\d{4}/\d{2}/)?[^/]+$~', $file, $matches ) ) {
$file = $matches[0];
}
}
return $file;
}
/**
* Returns CDN object.
*/
public function get_cdn() {
static $cdn = null;
if ( is_null( $cdn ) ) {
$c = $this->_config;
$engine = $c->get_string( 'cdn.engine' );
$compression = ( $c->get_boolean( 'browsercache.enabled' ) && $c->get_boolean( 'browsercache.html.compression' ) );
switch ( $engine ) {
case 'akamai':
$engine_config = array(
'username' => $c->get_string( 'cdn.akamai.username' ),
'password' => $c->get_string( 'cdn.akamai.password' ),
'zone' => $c->get_string( 'cdn.akamai.zone' ),
'domain' => $c->get_array( 'cdn.akamai.domain' ),
'ssl' => $c->get_string( 'cdn.akamai.ssl' ),
'email_notification' => $c->get_array( 'cdn.akamai.email_notification' ),
'compression' => false,
);
break;
case 'att':
$engine_config = array(
'account' => $c->get_string( 'cdn.att.account' ),
'token' => $c->get_string( 'cdn.att.token' ),
'domain' => $c->get_array( 'cdn.att.domain' ),
'ssl' => $c->get_string( 'cdn.att.ssl' ),
'compression' => false,
);
break;
case 'azure':
$engine_config = array(
'user' => $c->get_string( 'cdn.azure.user' ),
'key' => $c->get_string( 'cdn.azure.key' ),
'container' => $c->get_string( 'cdn.azure.container' ),
'cname' => $c->get_array( 'cdn.azure.cname' ),
'ssl' => $c->get_string( 'cdn.azure.ssl' ),
'compression' => false,
);
break;
case 'cf':
$engine_config = array(
'key' => $c->get_string( 'cdn.cf.key' ),
'secret' => $c->get_string( 'cdn.cf.secret' ),
'bucket' => $c->get_string( 'cdn.cf.bucket' ),
'bucket_location' => $c->get_string( 'cdn.cf.bucket.location' ),
'id' => $c->get_string( 'cdn.cf.id' ),
'cname' => $c->get_array( 'cdn.cf.cname' ),
'ssl' => $c->get_string( 'cdn.cf.ssl' ),
'public_objects' => $c->get_string( 'cdn.cf.public_objects' ),
'compression' => $compression,
);
break;
case 'cf2':
$engine_config = array(
'key' => $c->get_string( 'cdn.cf2.key' ),
'secret' => $c->get_string( 'cdn.cf2.secret' ),
'id' => $c->get_string( 'cdn.cf2.id' ),
'cname' => $c->get_array( 'cdn.cf2.cname' ),
'ssl' => $c->get_string( 'cdn.cf2.ssl' ),
'compression' => false,
);
break;
case 'cotendo':
$engine_config = array(
'username' => $c->get_string( 'cdn.cotendo.username' ),
'password' => $c->get_string( 'cdn.cotendo.password' ),
'zones' => $c->get_array( 'cdn.cotendo.zones' ),
'domain' => $c->get_array( 'cdn.cotendo.domain' ),
'ssl' => $c->get_string( 'cdn.cotendo.ssl' ),
'compression' => false,
);
break;
case 'edgecast':
$engine_config = array(
'account' => $c->get_string( 'cdn.edgecast.account' ),
'token' => $c->get_string( 'cdn.edgecast.token' ),
'domain' => $c->get_array( 'cdn.edgecast.domain' ),
'ssl' => $c->get_string( 'cdn.edgecast.ssl' ),
'compression' => false,
);
break;
case 'ftp':
$engine_config = array(
'host' => $c->get_string( 'cdn.ftp.host' ),
'type' => $c->get_string( 'cdn.ftp.type' ),
'user' => $c->get_string( 'cdn.ftp.user' ),
'pass' => $c->get_string( 'cdn.ftp.pass' ),
'path' => $c->get_string( 'cdn.ftp.path' ),
'pasv' => $c->get_boolean( 'cdn.ftp.pasv' ),
'domain' => $c->get_array( 'cdn.ftp.domain' ),
'ssl' => $c->get_string( 'cdn.ftp.ssl' ),
'compression' => false,
'docroot' => Util_Environment::document_root(),
);
break;
case 'google_drive':
$state = Dispatcher::config_state();
$engine_config = array(
'client_id' => $c->get_string( 'cdn.google_drive.client_id' ),
'access_token' => $state->get_string( 'cdn.google_drive.access_token' ),
'refresh_token' => $c->get_string( 'cdn.google_drive.refresh_token' ),
'root_url' => $c->get_string( 'cdn.google_drive.folder.url' ),
'root_folder_id' => $c->get_string( 'cdn.google_drive.folder.id' ),
'new_access_token_callback' => array( $this, 'on_google_drive_new_access_token' ),
);
break;
case 'highwinds':
$state = Dispatcher::config_state();
$engine_config = array(
'domains' => $c->get_array( 'cdn.highwinds.host.domains' ),
'ssl' => $c->get_string( 'cdn.highwinds.ssl' ),
'api_token' => $c->get_string( 'cdn.highwinds.api_token' ),
'account_hash' => $c->get_string( 'cdn.highwinds.account_hash' ),
'host_hash_code' => $c->get_string( 'cdn.highwinds.host.hash_code' ),
);
break;
case 'limelight':
$engine_config = array(
'short_name' => $c->get_string( 'cdn.limelight.short_name' ),
'username' => $c->get_string( 'cdn.limelight.username' ),
'api_key' => $c->get_string( 'cdn.limelight.api_key' ),
'domains' => $c->get_array( 'cdn.limelight.host.domains' ),
'debug' => $c->get_string( 'cdn.debug' ),
);
break;
case 'mirror':
$engine_config = array(
'domain' => $c->get_array( 'cdn.mirror.domain' ),
'ssl' => $c->get_string( 'cdn.mirror.ssl' ),
'compression' => false,
);
break;
case 'rackspace_cdn':
$state = Dispatcher::config_state();
$engine_config = array(
'user_name' => $c->get_string( 'cdn.rackspace_cdn.user_name' ),
'api_key' => $c->get_string( 'cdn.rackspace_cdn.api_key' ),
'region' => $c->get_string( 'cdn.rackspace_cdn.region' ),
'service_access_url' => $c->get_string( 'cdn.rackspace_cdn.service.access_url' ),
'service_id' => $c->get_string( 'cdn.rackspace_cdn.service.id' ),
'service_protocol' => $c->get_string( 'cdn.rackspace_cdn.service.protocol' ),
'domains' => $c->get_array( 'cdn.rackspace_cdn.domains' ),
'access_state' => $state->get_string( 'cdn.rackspace_cdn.access_state' ),
'new_access_state_callback' => array( $this, 'on_rackspace_cdn_new_access_state' ),
);
break;
case 'rscf':
$state = Dispatcher::config_state();
$engine_config = array(
'user_name' => $c->get_string( 'cdn.rscf.user' ),
'api_key' => $c->get_string( 'cdn.rscf.key' ),
'region' => $c->get_string( 'cdn.rscf.location' ),
'container' => $c->get_string( 'cdn.rscf.container' ),
'cname' => $c->get_array( 'cdn.rscf.cname' ),
'ssl' => $c->get_string( 'cdn.rscf.ssl' ),
'compression' => false,
'access_state' => $state->get_string( 'cdn.rackspace_cf.access_state' ),
'new_access_state_callback' => array( $this, 'on_rackspace_cf_new_access_state' ),
);
break;
case 's3':
$engine_config = array(
'key' => $c->get_string( 'cdn.s3.key' ),
'secret' => $c->get_string( 'cdn.s3.secret' ),
'bucket' => $c->get_string( 'cdn.s3.bucket' ),
'bucket_location' => $c->get_string( 'cdn.s3.bucket.location' ),
'cname' => $c->get_array( 'cdn.s3.cname' ),
'ssl' => $c->get_string( 'cdn.s3.ssl' ),
'public_objects' => $c->get_string( 'cdn.s3.public_objects' ),
'compression' => $compression,
);
break;
case 's3_compatible':
$engine_config = array(
'key' => $c->get_string( 'cdn.s3.key' ),
'secret' => $c->get_string( 'cdn.s3.secret' ),
'bucket' => $c->get_string( 'cdn.s3.bucket' ),
'cname' => $c->get_array( 'cdn.s3.cname' ),
'ssl' => $c->get_string( 'cdn.s3.ssl' ),
'compression' => $compression,
'api_host' => $c->get_string( 'cdn.s3_compatible.api_host' ),
);
break;
case 'stackpath':
$engine_config = array(
'authorization_key' => $c->get_string( 'cdn.stackpath.authorization_key' ),
'zone_id' => $c->get_integer( 'cdn.stackpath.zone_id' ),
'domain' => $c->get_array( 'cdn.stackpath.domain' ),
'ssl' => $c->get_string( 'cdn.stackpath.ssl' ),
'compression' => false,
);
break;
case 'stackpath2':
$state = Dispatcher::config_state();
$engine_config = array(
'client_id' => $c->get_string( 'cdn.stackpath2.client_id' ),
'client_secret' => $c->get_string( 'cdn.stackpath2.client_secret' ),
'stack_id' => $c->get_string( 'cdn.stackpath2.stack_id' ),
'site_root_domain' => $c->get_string( 'cdn.stackpath2.site_root_domain' ),
'domain' => $c->get_array( 'cdn.stackpath2.domain' ),
'ssl' => $c->get_string( 'cdn.stackpath2.ssl' ),
'access_token' => $state->get_string( 'cdn.stackpath2.access_token' ),
'on_new_access_token' => array( $this, 'on_stackpath2_new_access_token' ),
);
break;
case 'bunnycdn':
$engine_config = array(
'account_api_key' => $c->get_string( 'cdn.bunnycdn.account_api_key' ),
'storage_api_key' => $c->get_string( 'cdn.bunnycdn.storage_api_key' ),
'stream_api_key' => $c->get_string( 'cdn.bunnycdn.stream_api_key' ),
'pull_zone_id' => $c->get_integer( 'cdn.bunnycdn.pull_zone_id' ),
'domain' => $c->get_string( 'cdn.bunnycdn.cdn_hostname' ),
);
break;
default:
$engine_config = array();
break;
}
$engine_config = array_merge(
$engine_config,
array(
'debug' => $c->get_boolean( 'cdn.debug' ),
'headers' => apply_filters( 'w3tc_cdn_config_headers', array() ),
)
);
$cdn = CdnEngine::instance( $engine, $engine_config );
}
return $cdn;
}
/**
* Called when a new access token is issued by cdnengine.
*
* @param string $access_token Access token.
*/
public function on_google_drive_new_access_token( $access_token ) {
$state = Dispatcher::config_state();
$state->set( 'cdn.google_drive.access_token', $access_token );
$state->save();
}
/**
* Called when a new access state is issued by cdnengine
*
* @param string $access_state Access state.
*/
public function on_rackspace_cdn_new_access_state( $access_state ) {
$state = Dispatcher::config_state();
$state->set( 'cdn.rackspace_cdn.access_state', $access_state );
$state->save();
}
/**
* Called when a new access state is issued by cdnengine
*
* @param string $access_state Access state.
*/
public function on_rackspace_cf_new_access_state( $access_state ) {
$state = Dispatcher::config_state();
$state->set( 'cdn.rackspace_cf.access_state', $access_state );
$state->save();
}
/**
* Called when a new access token is issued by cdnengine.
*
* @param string $access_token Access token.
*/
public function on_stackpath2_new_access_token( $access_token ) {
$state = Dispatcher::config_state();
$state->set( 'cdn.stackpath2.access_token', $access_token );
$state->save();
}
/**
* Convert relative file which is relative to ABSPATH (wp folder on disc) to path uri.
*
* @param string $file Filename.
* @return string
*/
public function docroot_filename_to_uri( $file ) {
$file = ltrim( $file, '/' );
// Translate multisite subsite uploads paths.
return str_replace( basename( WP_CONTENT_DIR ) . '/blogs.dir/' . Util_Environment::blog_id() . '/', '', $file );
}
/**
* Convert a relative path (relative to ABSPATH (wp folder on disc) into a absolute path.
*
* @param string $file Filename.
* @return string
*/
public function docroot_filename_to_absolute_path( $file ) {
if ( is_file( $file ) ) {
return $file;
}
if ( DIRECTORY_SEPARATOR != '/' ) {
$file = str_replace( '/', DIRECTORY_SEPARATOR, $file );
}
return rtrim( Util_Environment::document_root(), '/\\' ) . DIRECTORY_SEPARATOR . ltrim( $file, '/\\' );
}
/**
* Convert local uri path to CDN type specific path.
*
* @param string $local_uri Local URI.
* @return string
*/
public function uri_to_cdn_uri( $local_uri ) {
$local_uri = ltrim( $local_uri, '/' );
$remote_uri = $local_uri;
if ( Util_Environment::is_wpmu() && defined( 'DOMAIN_MAPPING' ) && DOMAIN_MAPPING ) {
$remote_uri = str_replace( site_url(), '', $local_uri );
}
$engine = $this->_config->get_string( 'cdn.engine' );
if ( Cdn_Util::is_engine_mirror( $engine ) ) {
if ( Util_Environment::is_wpmu() && strpos( $local_uri, 'files' ) === 0 ) {
$upload_dir = Util_Environment::wp_upload_dir();
$remote_uri = $this->abspath_to_relative_path( dirname( $upload_dir['basedir'] ) ) . '/' . $local_uri;
}
} elseif ( Util_Environment::is_wpmu() &&
! Util_Environment::is_wpmu_subdomain() &&
Util_Environment::is_using_master_config() &&
Cdn_Util::is_engine_push( $engine ) ) {
/**
* In common config mode files are uploaded for network home url so mirror will not contain /subblog/ path in uri.
* Since upload process is not blog-specific and wp-content/plugins/../*.jpg files are common.
*/
$home = trim( home_url( '', 'relative' ), '/' ) . '/';
$network_home = trim( network_home_url( '', 'relative' ), '/' ) . '/';
if ( $home !== $network_home && substr( $local_uri, 0, strlen( $home ) ) === $home ) {
$remote_uri = $network_home . substr( $local_uri, strlen( $home ) );
}
}
return apply_filters( 'w3tc_uri_cdn_uri', ltrim( $remote_uri, '/' ) );
}
/**
* Need to pass full URL and it's URI.
* URI passed to prevent redundant parsing, normally it's available for caller.
*
* @param string $url URL.
* @param string $path Path.
* @return string|null
**/
public function url_to_cdn_url( $url, $path ) {
$cdn = $this->get_cdn();
$remote_path = $this->uri_to_cdn_uri( $path );
$new_url = $cdn->format_url( $remote_path );
if ( ! $new_url ) {
return null;
}
$is_engine_mirror = Cdn_Util::is_engine_mirror( $this->_config->get_string( 'cdn.engine' ) );
$new_url = apply_filters( 'w3tc_cdn_url', $new_url, $url, $is_engine_mirror );
return $new_url;
}
/**
* Takes an absolute path and converts to a relative path to root.
*
* @param string $path Path.
* @return mixed
*/
public function abspath_to_relative_path( $path ) {
return str_replace( Util_Environment::document_root(), '', $path );
}
/**
* Takes a root relative path and converts to a full URI.
*
* @param string $path Path.
* @return string
*/
public function relative_path_to_url( $path ) {
return rtrim( Util_Environment::home_domain_root_url(), '/' ) . '/' .
$this->docroot_filename_to_uri( ltrim( $path, '/' ) );
}
/**
* Constructs a CDN file descriptor.
*
* @param string $local_path Local path.
* @param string $remote_path Remote path.
* @return array
*/
public function build_file_descriptor( $local_path, $remote_path ) {
$file = array(
'local_path' => $local_path,
'remote_path' => $remote_path,
'original_url' => $this->relative_path_to_url( $local_path ),
);
return apply_filters( 'w3tc_build_cdn_file_array', $file );
}
}

View File

@ -0,0 +1,764 @@
<?php
namespace W3TC;
/**
* W3 Total Cache CDN Plugin
*/
/**
* class Cdn_Core_Admin
*/
class Cdn_Core_Admin {
/**
* Config
*/
private $_config = null;
/**
* Runs plugin
*/
function __construct() {
$this->_config = Dispatcher::config();
}
/**
* Purge attachment
*
* Upload _wp_attached_file, _wp_attachment_metadata, _wp_attachment_backup_sizes
*
* @param integer $attachment_id
* @param array $results
* @return boolean
*/
function purge_attachment( $attachment_id, &$results ) {
$common = Dispatcher::component( 'Cdn_Core' );
$files = $common->get_attachment_files( $attachment_id );
return $common->purge( $files, $results );
}
/**
* Updates file date in the queue
*
* @param integer $queue_id
* @param string $last_error
* @return integer
*/
function queue_update( $queue_id, $last_error ) {
global $wpdb;
$sql = sprintf( 'UPDATE %s SET last_error = "%s", date = NOW() WHERE id = %d', $wpdb->base_prefix . W3TC_CDN_TABLE_QUEUE, esc_sql( $last_error ), $queue_id );
return $wpdb->query( $sql );
}
/**
* Removes from queue
*
* @param integer $queue_id
* @return integer
*/
function queue_delete( $queue_id ) {
global $wpdb;
$sql = sprintf( 'DELETE FROM %s WHERE id = %d', $wpdb->base_prefix . W3TC_CDN_TABLE_QUEUE, $queue_id );
return $wpdb->query( $sql );
}
/**
* Empties queue
*
* @param integer $command
* @return integer
*/
function queue_empty( $command ) {
global $wpdb;
$sql = sprintf( 'DELETE FROM %s WHERE command = %d', $wpdb->base_prefix . W3TC_CDN_TABLE_QUEUE, $command );
return $wpdb->query( $sql );
}
/**
* Returns queue
*
* @param integer $limit
* @return array
*/
function queue_get( $limit = null ) {
global $wpdb;
$sql = sprintf( 'SELECT * FROM %s%s ORDER BY date', $wpdb->base_prefix, W3TC_CDN_TABLE_QUEUE );
if ( $limit ) {
$sql .= sprintf( ' LIMIT %d', $limit );
}
$results = $wpdb->get_results( $sql );
$queue = array();
if ( $results ) {
foreach ( (array) $results as $result ) {
$queue[$result->command][] = $result;
}
}
return $queue;
}
/**
* Process queue
*
* @param integer $limit
* @return integer
*/
function queue_process( $limit ) {
$items = 0;
$commands = $this->queue_get( $limit );
$force_rewrite = $this->_config->get_boolean( 'cdn.force.rewrite' );
if ( count( $commands ) ) {
$common = Dispatcher::component( 'Cdn_Core' );
$cdn = $common->get_cdn();
foreach ( $commands as $command => $queue ) {
$files = array();
$results = array();
$map = array();
foreach ( $queue as $result ) {
$files[] = $common->build_file_descriptor( $result->local_path, $result->remote_path );
$map[$result->local_path] = $result->id;
$items++;
}
switch ( $command ) {
case W3TC_CDN_COMMAND_UPLOAD:
foreach ( $files as $file ) {
$local_file_name = $file['local_path'];
$remote_file_name = $file['remote_path'];
if ( !file_exists( $local_file_name ) ) {
Dispatcher::create_file_for_cdn( $local_file_name );
}
}
$cdn->upload( $files, $results, $force_rewrite );
foreach ( $results as $result ) {
if ( $result['result'] == W3TC_CDN_RESULT_OK ) {
Dispatcher::on_cdn_file_upload( $result['local_path'] );
}
}
break;
case W3TC_CDN_COMMAND_DELETE:
$cdn->delete( $files, $results );
break;
case W3TC_CDN_COMMAND_PURGE:
$cdn->purge( $files, $results );
break;
}
foreach ( $results as $result ) {
if ( $result['result'] == W3TC_CDN_RESULT_OK ) {
$this->queue_delete( $map[$result['local_path']] );
} else {
$this->queue_update( $map[$result['local_path']], $result['error'] );
}
}
}
}
return $items;
}
/**
* Export library to CDN
*
* @param integer $limit
* @param integer $offset
* @param integer $count
* @param integer $total
* @param array $results
* @return void
*/
function export_library( $limit, $offset, &$count, &$total, &$results, $timeout_time = 0 ) {
global $wpdb;
$count = 0;
$total = 0;
$upload_info = Util_Http::upload_info();
if ( $upload_info ) {
$sql = sprintf( 'SELECT
pm.meta_value AS file,
pm2.meta_value AS metadata
FROM
%sposts AS p
LEFT JOIN
%spostmeta AS pm ON p.ID = pm.post_ID AND pm.meta_key = "_wp_attached_file"
LEFT JOIN
%spostmeta AS pm2 ON p.ID = pm2.post_ID AND pm2.meta_key = "_wp_attachment_metadata"
WHERE
p.post_type = "attachment" AND (pm.meta_value IS NOT NULL OR pm2.meta_value IS NOT NULL)
GROUP BY
p.ID
ORDER BY
p.ID', $wpdb->prefix, $wpdb->prefix, $wpdb->prefix );
if ( $limit ) {
$sql .= sprintf( ' LIMIT %d', $limit );
if ( $offset ) {
$sql .= sprintf( ' OFFSET %d', $offset );
}
}
$posts = $wpdb->get_results( $sql );
if ( $posts ) {
$count = count( $posts );
$total = $this->get_attachments_count();
$files = array();
$common = Dispatcher::component( 'Cdn_Core' );
foreach ( $posts as $post ) {
$post_files = array();
if ( $post->file ) {
$file = $common->normalize_attachment_file( $post->file );
$local_file = $upload_info['basedir'] . '/' . $file;
$remote_file = ltrim( $upload_info['baseurlpath'] . $file, '/' );
$post_files[] = $common->build_file_descriptor( $local_file, $remote_file );
}
if ( $post->metadata ) {
$metadata = @unserialize( $post->metadata );
$post_files = array_merge( $post_files, $common->get_metadata_files( $metadata ) );
}
$post_files = apply_filters( 'w3tc_cdn_add_attachment', $post_files );
$files = array_merge( $files, $post_files );
}
$common = Dispatcher::component( 'Cdn_Core' );
$common->upload( $files, false, $results, $timeout_time );
}
}
}
/**
* Imports library
*
* @param integer $limit
* @param integer $offset
* @param integer $count
* @param integer $total
* @param array $results
* @return boolean
*/
function import_library( $limit, $offset, &$count, &$total, &$results ) {
global $wpdb;
$count = 0;
$total = 0;
$results = array();
$upload_info = Util_Http::upload_info();
$uploads_use_yearmonth_folders = get_option( 'uploads_use_yearmonth_folders' );
$document_root = Util_Environment::document_root();
@set_time_limit( $this->_config->get_integer( 'timelimit.cdn_import' ) );
if ( $upload_info ) {
/**
* Search for posts with links or images
*/
$sql = sprintf( 'SELECT
ID,
post_content,
post_date
FROM
%sposts
WHERE
post_status = "publish"
AND (post_type = "post" OR post_type = "page")
AND (post_content LIKE "%%src=%%"
OR post_content LIKE "%%href=%%")
', $wpdb->prefix );
if ( $limit ) {
$sql .= sprintf( ' LIMIT %d', $limit );
if ( $offset ) {
$sql .= sprintf( ' OFFSET %d', $offset );
}
}
$posts = $wpdb->get_results( $sql );
if ( $posts ) {
$count = count( $posts );
$total = $this->get_import_posts_count();
$regexp = '~(' . $this->get_regexp_by_mask( $this->_config->get_string( 'cdn.import.files' ) ) . ')$~';
$config_state = Dispatcher::config_state();
$import_external = $config_state->get_boolean( 'cdn.import.external' );
foreach ( $posts as $post ) {
$matches = null;
$replaced = array();
$attachments = array();
$post_content = $post->post_content;
/**
* Search for all link and image sources
*/
if ( preg_match_all( '~(href|src)=[\'"]?([^\'"<>\s]+)[\'"]?~', $post_content, $matches, PREG_SET_ORDER ) ) {
foreach ( $matches as $match ) {
list( $search, $attribute, $origin ) = $match;
/**
* Check if $search is already replaced
*/
if ( isset( $replaced[$search] ) ) {
continue;
}
$error = '';
$result = false;
$src = Util_Environment::normalize_file_minify( $origin );
$dst = '';
/**
* Check if file exists in the library
*/
if ( stristr( $origin, $upload_info['baseurl'] ) === false ) {
/**
* Check file extension
*/
$check_src = $src;
if ( Util_Environment::is_url( $check_src ) ) {
$qpos = strpos( $check_src, '?' );
if ( $qpos !== false ) {
$check_src = substr( $check_src, 0, $qpos );
}
}
if ( preg_match( $regexp, $check_src ) ) {
/**
* Check for already uploaded attachment
*/
if ( isset( $attachments[$src] ) ) {
list( $dst, $dst_url ) = $attachments[$src];
$result = true;
} else {
if ( $uploads_use_yearmonth_folders ) {
$upload_subdir = date( 'Y/m', strtotime( $post->post_date ) );
$upload_dir = sprintf( '%s/%s', $upload_info['basedir'], $upload_subdir );
$upload_url = sprintf( '%s/%s', $upload_info['baseurl'], $upload_subdir );
} else {
$upload_subdir = '';
$upload_dir = $upload_info['basedir'];
$upload_url = $upload_info['baseurl'];
}
$src_filename = pathinfo( $src, PATHINFO_FILENAME );
$src_extension = pathinfo( $src, PATHINFO_EXTENSION );
/**
* Get available filename
*/
for ( $i = 0; ; $i++ ) {
$dst = sprintf( '%s/%s%s%s', $upload_dir, $src_filename, ( $i ? $i : '' ), ( $src_extension ? '.' . $src_extension : '' ) );
if ( !file_exists( $dst ) ) {
break;
}
}
$dst_basename = basename( $dst );
$dst_url = sprintf( '%s/%s', $upload_url, $dst_basename );
$dst_path = ltrim( str_replace( $document_root, '', Util_Environment::normalize_path( $dst ) ), '/' );
if ( $upload_subdir ) {
Util_File::mkdir( $upload_subdir, 0777, $upload_info['basedir'] );
}
$download_result = false;
/**
* Check if file is remote URL
*/
if ( Util_Environment::is_url( $src ) ) {
/**
* Download file
*/
if ( $import_external ) {
$download_result = Util_Http::download( $src, $dst );
if ( !$download_result ) {
$error = 'Unable to download file';
}
} else {
$error = 'External file import is disabled';
}
} else {
/**
* Otherwise copy file from local path
*/
$src_path = $document_root . '/' . urldecode( $src );
if ( file_exists( $src_path ) ) {
$download_result = @copy( $src_path, $dst );
if ( !$download_result ) {
$error = 'Unable to copy file';
}
} else {
$error = 'Source file doesn\'t exists';
}
}
/**
* Check if download or copy was successful
*/
if ( $download_result ) {
$title = $dst_basename;
$guid = ltrim( $upload_info['baseurlpath'] . $title, ',' );
$mime_type = Util_Mime::get_mime_type( $dst );
$GLOBALS['wp_rewrite'] = new \WP_Rewrite();
/**
* Insert attachment
*/
$id = wp_insert_attachment( array(
'post_mime_type' => $mime_type,
'guid' => $guid,
'post_title' => $title,
'post_content' => '',
'post_parent' => $post->ID
), $dst );
if ( !is_wp_error( $id ) ) {
/**
* Generate attachment metadata and upload to CDN
*/
require_once ABSPATH . 'wp-admin/includes/image.php';
wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $dst ) );
$attachments[$src] = array(
$dst,
$dst_url
);
$result = true;
} else {
$error = 'Unable to insert attachment';
}
}
}
/**
* If attachment was successfully created then replace links
*/
if ( $result ) {
$replace = sprintf( '%s="%s"', $attribute, $dst_url );
// replace $search with $replace
$post_content = str_replace( $search, $replace, $post_content );
$replaced[$search] = $replace;
$error = 'OK';
}
} else {
$error = 'File type rejected';
}
} else {
$error = 'File already exists in the media library';
}
/**
* Add new entry to the log file
*/
$results[] = array(
'src' => $src,
'dst' => $dst_path,
'result' => $result,
'error' => $error
);
}
}
/**
* If post content was chenged then update DB
*/
if ( $post_content != $post->post_content ) {
wp_update_post( array(
'ID' => $post->ID,
'post_content' => $post_content
) );
}
}
}
}
}
/**
* Rename domain
*
* @param array $names
* @param integer $limit
* @param integer $offset
* @param integer $count
* @param integer $total
* @param integer $results
* @return void
*/
function rename_domain( $names, $limit, $offset, &$count, &$total, &$results ) {
global $wpdb;
@set_time_limit( $this->_config->get_integer( 'timelimit.domain_rename' ) );
$count = 0;
$total = 0;
$results = array();
$upload_info = Util_Http::upload_info();
foreach ( $names as $index => $name ) {
$names[$index] = str_ireplace( 'www.', '', $name );
}
if ( $upload_info ) {
$sql = sprintf( 'SELECT
ID,
post_content,
post_date
FROM
%sposts
WHERE
post_status = "publish"
AND (post_type = "post" OR post_type = "page")
AND (post_content LIKE "%%src=%%"
OR post_content LIKE "%%href=%%")
', $wpdb->prefix );
if ( $limit ) {
$sql .= sprintf( ' LIMIT %d', $limit );
if ( $offset ) {
$sql .= sprintf( ' OFFSET %d', $offset );
}
}
$posts = $wpdb->get_results( $sql );
if ( $posts ) {
$count = count( $posts );
$total = $this->get_rename_posts_count();
$names_quoted = array_map( array( '\W3TC\Util_Environment', 'preg_quote' ), $names );
foreach ( $posts as $post ) {
$matches = null;
$post_content = $post->post_content;
$regexp = '~(href|src)=[\'"]?(https?://(www\.)?(' . implode( '|', $names_quoted ) . ')' . Util_Environment::preg_quote( $upload_info['baseurlpath'] ) . '([^\'"<>\s]+))[\'"]~';
if ( preg_match_all( $regexp, $post_content, $matches, PREG_SET_ORDER ) ) {
foreach ( $matches as $match ) {
$old_url = $match[2];
$new_url = sprintf( '%s/%s', $upload_info['baseurl'], $match[5] );
$post_content = str_replace( $old_url, $new_url, $post_content );
$results[] = array(
'old' => $old_url,
'new' => $new_url,
'result' => true,
'error' => 'OK'
);
}
}
if ( $post_content != $post->post_content ) {
wp_update_post( array(
'ID' => $post->ID,
'post_content' => $post_content
) );
}
}
}
}
}
/**
* Returns attachments count
*
* @return integer
*/
function get_attachments_count() {
global $wpdb;
$sql = sprintf( 'SELECT COUNT(DISTINCT p.ID)
FROM %sposts AS p
LEFT JOIN %spostmeta AS pm ON p.ID = pm.post_ID
AND pm.meta_key = "_wp_attached_file"
LEFT JOIN %spostmeta AS pm2 ON p.ID = pm2.post_ID
AND pm2.meta_key = "_wp_attachment_metadata"
WHERE p.post_type = "attachment" AND (pm.meta_value IS NOT NULL OR pm2.meta_value IS NOT NULL)', $wpdb->prefix, $wpdb->prefix, $wpdb->prefix );
return $wpdb->get_var( $sql );
}
/**
* Returns import posts count
*
* @return integer
*/
function get_import_posts_count() {
global $wpdb;
$sql = sprintf( 'SELECT
COUNT(*)
FROM
%sposts
WHERE
post_status = "publish"
AND (post_type = "post" OR post_type = "page")
AND (post_content LIKE "%%src=%%"
OR post_content LIKE "%%href=%%")
', $wpdb->prefix );
return $wpdb->get_var( $sql );
}
/**
* Returns rename posts count
*
* @return integer
*/
function get_rename_posts_count() {
return $this->get_import_posts_count();
}
/**
* Returns regexp by mask
*
* @param string $mask
* @return string
*/
function get_regexp_by_mask( $mask ) {
$mask = trim( $mask );
$mask = Util_Environment::preg_quote( $mask );
$mask = str_replace( array(
'\*',
'\?',
';'
), array(
'@ASTERISK@',
'@QUESTION@',
'|'
), $mask );
$regexp = str_replace( array(
'@ASTERISK@',
'@QUESTION@'
), array(
'[^\\?\\*:\\|"<>]*',
'[^\\?\\*:\\|"<>]'
), $mask );
return $regexp;
}
/**
* media_row_actions filter
*
* @param array $actions
* @param object $post
* @return array
*/
function media_row_actions( $actions, $post ) {
$actions = array_merge( $actions, array(
'cdn_purge' => sprintf( '<a href="%s">' . __( 'Purge from CDN', 'w3-total-cache' ) . '</a>', wp_nonce_url( sprintf( 'admin.php?page=w3tc_dashboard&w3tc_cdn_purge_attachment&attachment_id=%d', $post->ID ), 'w3tc' ) )
) );
return $actions;
}
function is_running() {
/**
* CDN
*/
$running = true;
/**
* Check CDN settings
*/
$cdn_engine = $this->_config->get_string( 'cdn.engine' );
switch ( true ) {
case ( $cdn_engine == 'ftp' && !count( $this->_config->get_array( 'cdn.ftp.domain' ) ) ):
$running = false;
break;
case ( $cdn_engine == 's3' && ( $this->_config->get_string( 'cdn.s3.key' ) == '' || $this->_config->get_string( 'cdn.s3.secret' ) == '' || $this->_config->get_string( 'cdn.s3.bucket' ) == '' ) ):
$running = false; break;
case ( $cdn_engine == 'cf' && ( $this->_config->get_string( 'cdn.cf.key' ) == '' || $this->_config->get_string( 'cdn.cf.secret' ) == '' || $this->_config->get_string( 'cdn.cf.bucket' ) == '' || ( $this->_config->get_string( 'cdn.cf.id' ) == '' && !count( $this->_config->get_array( 'cdn.cf.cname' ) ) ) ) ):
$running = false;
break;
case ( $cdn_engine == 'cf2' && ( $this->_config->get_string( 'cdn.cf2.key' ) == '' || $this->_config->get_string( 'cdn.cf2.secret' ) == '' || ( $this->_config->get_string( 'cdn.cf2.id' ) == '' && !count( $this->_config->get_array( 'cdn.cf2.cname' ) ) ) ) ):
$running = false;
break;
case ( $cdn_engine == 'rscf' && ( $this->_config->get_string( 'cdn.rscf.user' ) == '' || $this->_config->get_string( 'cdn.rscf.key' ) == '' || $this->_config->get_string( 'cdn.rscf.container' ) == '' || !count( $this->_config->get_array( 'cdn.rscf.cname' ) ) ) ):
$running = false;
break;
case ( $cdn_engine == 'azure' && ( $this->_config->get_string( 'cdn.azure.user' ) == '' || $this->_config->get_string( 'cdn.azure.key' ) == '' || $this->_config->get_string( 'cdn.azure.container' ) == '' ) ):
$running = false;
break;
case ( $cdn_engine == 'mirror' && !count( $this->_config->get_array( 'cdn.mirror.domain' ) ) ):
$running = false;
break;
case ( $cdn_engine == 'cotendo' && !count( $this->_config->get_array( 'cdn.cotendo.domain' ) ) ):
$running = false;
break;
case ( $cdn_engine == 'edgecast' && !count( $this->_config->get_array( 'cdn.edgecast.domain' ) ) ):
$running = false;
break;
case ( $cdn_engine == 'att' && !count( $this->_config->get_array( 'cdn.att.domain' ) ) ):
$running = false;
break;
case ( $cdn_engine == 'akamai' && !count( $this->_config->get_array( 'cdn.akamai.domain' ) ) ):
$running = false;
break;
}
return $running;
}
}

View File

@ -0,0 +1,442 @@
<?php
namespace W3TC;
/**
* class Cdn_Environment
*/
class Cdn_Environment {
public function __construct() {
add_filter( 'w3tc_browsercache_rules_section_extensions',
array( $this, 'w3tc_browsercache_rules_section_extensions' ),
10, 3 );
add_filter( 'w3tc_browsercache_rules_section',
array( $this, 'w3tc_browsercache_rules_section' ),
10, 3 );
}
/**
* Fixes environment in each wp-admin request
*
* @param Config $config
* @param bool $force_all_checks
* @throws Util_Environment_Exceptions
*/
public function fix_on_wpadmin_request( $config, $force_all_checks ) {
$exs = new Util_Environment_Exceptions();
if ( $config->get_boolean( 'config.check' ) || $force_all_checks ) {
if ( $config->get_boolean( 'cdn.enabled' ) ) {
$this->rules_add( $config, $exs );
} else {
$this->rules_remove( $exs );
}
}
if ( count( $exs->exceptions() ) > 0 )
throw $exs;
}
/**
* Fixes environment once event occurs
*
* @param Config $config
* @param string $event
* @param Config|null $old_config
* @throws Util_Environment_Exceptions
*/
public function fix_on_event( $config, $event, $old_config = null ) {
if ( $config->get_boolean( 'cdn.enabled' ) &&
!Cdn_Util::is_engine_mirror( $config->get_string( 'cdn.engine' ) ) ) {
if ( $old_config != null &&
$config->get_integer( 'cdn.queue.interval' ) !=
$old_config->get_integer( 'cdn.queue.interval' ) ) {
$this->unschedule_queue_process();
}
if ( !wp_next_scheduled( 'w3_cdn_cron_queue_process' ) ) {
wp_schedule_event( time(),
'w3_cdn_cron_queue_process', 'w3_cdn_cron_queue_process' );
}
} else {
$this->unschedule_queue_process();
}
if ( $config->get_boolean( 'cdn.enabled' ) &&
$config->get_boolean( 'cdn.autoupload.enabled' ) &&
!Cdn_Util::is_engine_mirror( $config->get_string( 'cdn.engine' ) ) ) {
if ( $old_config != null &&
$config->get_integer( 'cdn.autoupload.interval' ) !=
$old_config->get_integer( 'cdn.autoupload.interval' ) ) {
$this->unschedule_upload();
}
if ( !wp_next_scheduled( 'w3_cdn_cron_upload' ) ) {
wp_schedule_event( time(),
'w3_cdn_cron_upload', 'w3_cdn_cron_upload' );
}
} else {
$this->unschedule_upload();
}
$exs = new Util_Environment_Exceptions();
if ( $config->get_boolean( 'cdn.enabled' ) ) {
try {
$this->handle_tables(
$event == 'activate' /* drop state on activation */,
true );
} catch ( \Exception $ex ) {
$exs->push( $ex );
}
}
if ( count( $exs->exceptions() ) > 0 )
throw $exs;
}
/**
* Fixes environment after plugin deactivation
*/
public function fix_after_deactivation() {
$exs = new Util_Environment_Exceptions();
$this->rules_remove( $exs );
$this->handle_tables( true, false );
if ( count( $exs->exceptions() ) > 0 )
throw $exs;
}
/**
* Returns required rules for module
*
* @param Config $config
* @return array|null
*/
function get_required_rules( $config ) {
if ( !$config->get_boolean( 'cdn.enabled' ) )
return null;
$rewrite_rules = array();
$rules = $this->rules_generate( $config );
if ( strlen( $rules ) > 0 ) {
if ( $config->get_string( 'cdn.engine' ) == 'ftp' ) {
$common = Dispatcher::component( 'Cdn_Core' );
$domain = $common->get_cdn()->get_domain();
$cdn_rules_path = sprintf( 'ftp://%s/%s', $domain,
Util_Rule::get_cdn_rules_path() );
$rewrite_rules[] = array(
'filename' => $cdn_rules_path,
'content' => $rules
);
}
$path = Util_Rule::get_browsercache_rules_cache_path();
$rewrite_rules[] = array(
'filename' => $path,
'content' => $rules
);
}
return $rewrite_rules;
}
/**
*
*
* @param Config $config
* @return array|null
*/
function get_instructions( $config ) {
if ( !$config->get_boolean( 'cdn.enabled' ) )
return null;
$instructions = array();
$instructions[] = array( 'title'=>__( 'CDN module: Required Database SQL', 'w3-total-cache' ),
'content' => $this->generate_table_sql(), 'area' => 'database' );
return $instructions;
}
/**
* Generate rules for FTP
*/
public function rules_generate_for_ftp( $config ) {
return $this->rules_generate( $config, true );
}
/**
* Create tables
*
* @param bool $drop
* @throws Util_Environment_Exception
*/
private function handle_tables( $drop, $create ) {
global $wpdb;
$tablename_queue = $wpdb->base_prefix . W3TC_CDN_TABLE_QUEUE;
$tablename_map = $wpdb->base_prefix . W3TC_CDN_TABLE_PATHMAP;
if ( $drop ) {
$sql = "DROP TABLE IF EXISTS `$tablename_queue`;";
$wpdb->query( $sql );
$sql = "DROP TABLE IF EXISTS `$tablename_map`;";
$wpdb->query( $sql );
}
if ( !$create ) {
return;
}
$charset_collate = '';
if ( ! empty( $wpdb->charset ) )
$charset_collate = "DEFAULT CHARACTER SET $wpdb->charset";
if ( ! empty( $wpdb->collate ) )
$charset_collate .= " COLLATE $wpdb->collate";
$sql = "CREATE TABLE IF NOT EXISTS `$tablename_queue` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`local_path` varchar(500) NOT NULL DEFAULT '',
`remote_path` varchar(500) NOT NULL DEFAULT '',
`command` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '1 - Upload, 2 - Delete, 3 - Purge',
`last_error` varchar(150) NOT NULL DEFAULT '',
`date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`),
KEY `date` (`date`)
) $charset_collate;";
$wpdb->query( $sql );
if ( !$wpdb->result )
throw new Util_Environment_Exception( 'Can\'t create table ' .
$tablename_queue );
$sql = "
CREATE TABLE IF NOT EXISTS `$tablename_map` (
-- Relative file path.
-- For reference, not actually used for finding files.
path TEXT NOT NULL,
-- MD5 hash of remote path, used for finding files.
path_hash VARCHAR(32) CHARACTER SET ascii NOT NULL,
type tinyint(1) NOT NULL DEFAULT '0',
-- Google Drive: document identifier
remote_id VARCHAR(200) CHARACTER SET ascii,
PRIMARY KEY (path_hash),
KEY `remote_id` (`remote_id`)
) $charset_collate";
$wpdb->query( $sql );
if ( !$wpdb->result )
throw new Util_Environment_Exception( 'Can\'t create table ' .
$tablename_map );
}
private function generate_table_sql() {
global $wpdb;
$charset_collate = '';
if ( ! empty( $wpdb->charset ) )
$charset_collate = "DEFAULT CHARACTER SET $wpdb->charset";
if ( ! empty( $wpdb->collate ) )
$charset_collate .= " COLLATE $wpdb->collate";
$sql = sprintf( 'DROP TABLE IF EXISTS `%s%s`;', $wpdb->base_prefix, W3TC_CDN_TABLE_QUEUE );
$sql .= "\n" . sprintf( "CREATE TABLE IF NOT EXISTS `%s%s` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`local_path` varchar(500) NOT NULL DEFAULT '',
`remote_path` varchar(500) NOT NULL DEFAULT '',
`command` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '1 - Upload, 2 - Delete, 3 - Purge',
`last_error` varchar(150) NOT NULL DEFAULT '',
`date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`),
KEY `date` (`date`)
) $charset_collate;", $wpdb->base_prefix, W3TC_CDN_TABLE_QUEUE );
return $sql;
}
/**
* schedules
*/
/**
* Unschedules cron events
*/
private function unschedule_queue_process() {
if ( wp_next_scheduled( 'w3_cdn_cron_queue_process' ) ) {
wp_clear_scheduled_hook( 'w3_cdn_cron_queue_process' );
}
}
/**
* Unschedule upload event
*/
private function unschedule_upload() {
if ( wp_next_scheduled( 'w3_cdn_cron_upload' ) ) {
wp_clear_scheduled_hook( 'w3_cdn_cron_upload' );
}
}
/*
* rules core modification
*/
/**
* Writes directives to WP .htaccess
*
* @param Config $config
* @param Util_Environment_Exceptions $exs
* @throws Util_WpFile_FilesystemOperationException with S/FTP form if it can't get the required filesystem credentials
*/
private function rules_add( $config, $exs ) {
Util_Rule::add_rules( $exs, Util_Rule::get_browsercache_rules_cache_path(),
$this->rules_generate( $config ),
W3TC_MARKER_BEGIN_CDN,
W3TC_MARKER_END_CDN,
array(
W3TC_MARKER_BEGIN_MINIFY_CORE => 0,
W3TC_MARKER_BEGIN_PGCACHE_CORE => 0,
W3TC_MARKER_BEGIN_BROWSERCACHE_CACHE => 0,
W3TC_MARKER_BEGIN_WORDPRESS => 0,
W3TC_MARKER_END_PGCACHE_CACHE => strlen( W3TC_MARKER_END_PGCACHE_CACHE ) + 1,
W3TC_MARKER_END_MINIFY_CACHE => strlen( W3TC_MARKER_END_MINIFY_CACHE ) + 1
)
);
}
/**
* Removes Page Cache core directives
*
* @param Util_Environment_Exceptions $exs
* @throws Util_WpFile_FilesystemOperationException with S/FTP form if it can't get the required filesystem credentials
*/
private function rules_remove( $exs ) {
Util_Rule::remove_rules( $exs,
Util_Rule::get_browsercache_rules_cache_path(),
W3TC_MARKER_BEGIN_CDN,
W3TC_MARKER_END_CDN );
}
/**
* Generates rules for WP dir
*
* @param Config $config
* @param bool $cdnftp
* @return string
*/
private function rules_generate( $config, $cdnftp = false ) {
if ( Util_Environment::is_nginx() ) {
$o = new Cdn_Environment_Nginx( $config );
return $o->generate( $cdnftp );
} elseif ( Util_Environment::is_litespeed() ) {
$o = new Cdn_Environment_LiteSpeed( $config );
return $o->generate( $cdnftp );
} else {
return $this->rules_generate_apache( $config, $cdnftp );
}
}
private function rules_generate_apache( $config, $cdnftp ) {
$rules = '';
if ( $config->get_boolean( 'cdn.canonical_header' ) ) {
$rules .= $this->canonical( $cdnftp,
$config->get_boolean( 'cdn.cors_header') );
}
if ( $config->get_boolean( 'cdn.cors_header') ) {
$rules .= $this->allow_origin( $cdnftp );
}
if ( strlen( $rules ) > 0 ) {
$rules =
W3TC_MARKER_BEGIN_CDN . "\n" .
$rules .
W3TC_MARKER_END_CDN . "\n";
}
return $rules;
}
private function canonical( $cdnftp = false, $cors_header = true ) {
$rules = '';
$mime_types = include W3TC_INC_DIR . '/mime/other.php';
$extensions = array_keys( $mime_types );
$extensions_lowercase = array_map( 'strtolower', $extensions );
$extensions_uppercase = array_map( 'strtoupper', $extensions );
$rules .= "<FilesMatch \"\\.(" . implode( '|',
array_merge( $extensions_lowercase, $extensions_uppercase ) ) . ")$\">\n";
$host = ( $cdnftp ? Util_Environment::home_url_host() : '%{HTTP_HOST}' );
$rules .= " <IfModule mod_rewrite.c>\n";
$rules .= " RewriteEngine On\n";
$rules .= " RewriteCond %{HTTPS} !=on\n";
$rules .= " RewriteRule .* - [E=CANONICAL:http://$host%{REQUEST_URI},NE]\n";
$rules .= " RewriteCond %{HTTPS} =on\n";
$rules .= " RewriteRule .* - [E=CANONICAL:https://$host%{REQUEST_URI},NE]\n";
$rules .= " </IfModule>\n";
$rules .= " <IfModule mod_headers.c>\n";
$rules .= ' Header set Link "<%{CANONICAL}e>; rel=\"canonical\""' . "\n";
$rules .= " </IfModule>\n";
$rules .= "</FilesMatch>\n";
return $rules;
}
/**
* Returns allow-origin rules
*/
private function allow_origin( $cdnftp = false ) {
$r = "<IfModule mod_headers.c>\n";
$r .= " Header set Access-Control-Allow-Origin \"*\"\n";
$r .= "</IfModule>\n";
if ( !$cdnftp )
return $r;
else
return
"<FilesMatch \"\.(ttf|ttc|otf|eot|woff|woff2|font.css)$\">\n" .
$r .
"</FilesMatch>\n";
}
public function w3tc_browsercache_rules_section_extensions(
$extensions, $config, $section ) {
if ( Util_Environment::is_nginx() ) {
$o = new Cdn_Environment_Nginx( $config );
$extensions = $o->w3tc_browsercache_rules_section_extensions(
$extensions, $section );
} elseif ( Util_Environment::is_litespeed() ) {
$o = new Cdn_Environment_LiteSpeed( $config );
$extensions = $o->w3tc_browsercache_rules_section_extensions(
$extensions, $section );
}
return $extensions;
}
public function w3tc_browsercache_rules_section( $section_rules, $config, $section ) {
if ( Util_Environment::is_litespeed() ) {
$o = new Cdn_Environment_LiteSpeed( $config );
$section_rules = $o->w3tc_browsercache_rules_section(
$section_rules, $section );
}
return $section_rules;
}
}

View File

@ -0,0 +1,110 @@
<?php
namespace W3TC;
/**
* CDN rules generation for LiteSpeed
*/
class Cdn_Environment_LiteSpeed {
private $c;
public function __construct( $config ) {
$this->c = $config;
}
public function generate( $cdnftp ) {
$section_rules = [
'other' => [],
'add_header' => []
];
if ( $this->c->get_boolean( 'cdn.cors_header') ) {
$section_rules['add_header'][] = 'set Access-Control-Allow-Origin "*"';
}
$canonical_header = $this->generate_canonical( $cdnftp );
if ( !empty( $canonical_header ) ) {
$section_rules['add_header'][] = $canonical_header;
}
if ( empty( $section_rules['add_header'] ) ) {
return '';
}
$section_rules = apply_filters( 'w3tc_cdn_rules_section', $section_rules, $this->c );
$context_rules[] = " extraHeaders <<<END_extraHeaders";
foreach ( $section_rules['add_header'] as $line ) {
$context_rules[] = ' ' . $line;
}
$context_rules[] = " END_extraHeaders";
$rules = [];
$rules[] = 'context exp:^.*(ttf|ttc|otf|eot|woff|woff2|font.css)$ {';
$rules[] = ' location $DOC_ROOT/$0';
$rules[] = ' allowBrowse 1';
$rules[] = implode( "\n", $context_rules );
$rules[] = '}';
return
W3TC_MARKER_BEGIN_CDN . "\n" .
implode( "\n", $rules ) . "\n" .
W3TC_MARKER_END_CDN . "\n";
}
public function generate_canonical( $cdnftp = false ) {
if ( !$this->c->get_boolean( 'cdn.canonical_header' ) ) {
return null;
}
$home_url = get_home_url();
$parse_url = @parse_url( $home_url ); // phpcs:ignore
if ( !isset( $parse_url['host'] ) ) {
return null;
}
return "set Link '<" . $parse_url['scheme'] . '://' . $parse_url['host'] .
'%{REQUEST_URI}e>; rel="canonical"' . "'";
/*
$rules .= " RewriteRule .* - [E=CANONICAL:https://$host%{REQUEST_URI},NE]\n";
$rules .= " </IfModule>\n";
$rules .= " <IfModule mod_headers.c>\n";
$rules .= ' Header set Link "<%{CANONICAL}e>; rel=\"canonical\""' . "\n";
return 'set Link "<%{CANONICAL}e>; rel=\"canonical\""' . "\n";*/
}
public function w3tc_browsercache_rules_section_extensions(
$extensions, $section ) {
// CDN adds own rules for those extensions
if ( $this->c->get_boolean( 'cdn.cors_header') ) {
unset( $extensions['ttf|ttc'] );
unset( $extensions['otf'] );
unset( $extensions['eot'] );
unset( $extensions['woff'] );
unset( $extensions['woff2'] );
}
return $extensions;
}
// add canonical header to all browsercache sections, since its needed for
// assets
public function w3tc_browsercache_rules_section( $section_rules, $section ) {
$canonical_header = $this->generate_canonical();
if ( !empty( $canonical_header ) ) {
$section_rules['add_header'][] = $canonical_header;
}
return $section_rules;
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace W3TC;
/**
* CDN rules generation for Nginx
*/
class Cdn_Environment_Nginx {
private $c;
public function __construct( $config ) {
$this->c = $config;
}
public function generate( $cdnftp ) {
$rules = '';
$rule = $this->generate_canonical( $cdnftp );
if ( !empty( $rule ) ) {
$rules = $rule . "\n";
}
if ( $this->c->get_boolean( 'cdn.cors_header') ) {
$rules_a = Dispatcher::nginx_rules_for_browsercache_section(
$this->c, 'other', true );
$rules_a[] = 'add_header Access-Control-Allow-Origin "*";';
$rules .=
"location ~ \\.(ttf|ttc|otf|eot|woff|woff2|font.css)\$ {\n" .
' ' . implode( "\n ", $rules_a ) . "\n" .
"}\n";
}
if ( strlen( $rules ) > 0 ) {
$rules =
W3TC_MARKER_BEGIN_CDN . "\n" .
$rules .
W3TC_MARKER_END_CDN . "\n";
}
return $rules;
}
public function generate_canonical( $cdnftp = false ) {
if ( !$this->c->get_boolean( 'cdn.canonical_header' ) ) {
return null;
}
$home = ( $cdnftp ? Util_Environment::home_url_host() : '$host' );
return 'add_header Link "<$scheme://' . $home .
'$request_uri>; rel=\"canonical\"";';
}
public function w3tc_browsercache_rules_section_extensions(
$extensions, $section ) {
// CDN adds own rules for those extensions
if ( $this->c->get_boolean( 'cdn.cors_header') ) {
unset( $extensions['ttf|ttc'] );
unset( $extensions['otf'] );
unset( $extensions['eot'] );
unset( $extensions['woff'] );
unset( $extensions['woff2'] );
}
return $extensions;
}
}

View File

@ -0,0 +1,183 @@
<?php
/**
* File: Cdn_GeneralPage_View.php
*
* @package W3TC
*/
namespace W3TC;
defined( 'W3TC' ) || die;
Util_Ui::postbox_header_tabs(
wp_kses(
sprintf(
// translators: 1 opening HTML acronym tag, 2 closing HTML acronym tag.
__(
'%1$sCDN%2$s',
'w3-total-cache'
),
'<acronym title="' . __( 'Content Delivery Network', 'w3-total-cache' ) . '">',
'</acronym>'
),
array(
'acronym' => array(
'title' => array(),
),
)
),
esc_html__(
'Content Delivery Network (CDN) is a powerful feature that can significantly enhance the performance of
your WordPress website. By leveraging a distributed network of servers located worldwide, a CDN helps
deliver your website\'s static files, such as images, CSS, and JavaScript, to visitors more efficiently.
This reduces the latency and improves the loading speed of your website, resulting in a faster and
smoother browsing experience for your users. With W3 Total Cache\'s CDN integration, you can easily
configure and connect your website to a CDN service of your choice, unleashing the full potential of
your WordPress site\'s speed optimization.',
'w3-total-cache'
),
'',
'cdn',
Util_UI::admin_url( 'admin.php?page=w3tc_cdn' )
);
Util_Ui::config_overloading_button(
array(
'key' => 'cdn.configuration_overloaded',
)
);
?>
<p>
<?php
if ( ! $cdn_enabled ) {
echo '&nbsp;' . wp_kses(
sprintf(
// translators: 1 opening HTML acronym tag, 2 closing HTML acronym tag,
// translators: 3 opening HTML a tag, 4 closing HTML a tag.
__(
'If you do not have a %1$sCDN%2$s provider try Bunny CDN. %3$sSign up now to enjoy a special offer%4$s!',
'w3-total-cache'
),
'<acronym title="' . __( 'Content Delivery Network', 'w3-total-cache' ) . '">',
'</acronym>',
'<a href="' . esc_url( wp_nonce_url( Util_Ui::admin_url( 'admin.php?page=w3tc_dashboard&w3tc_cdn_bunnycdn_signup' ), 'w3tc' ) ) . '" target="_blank">',
'</a>'
),
array(
'acronym' => array(
'title' => array(),
),
'a' => array(
'href' => array(),
'target' => array(),
),
)
);
}
$config = Dispatcher::config();
$cdn_engine = $config->get_string( 'cdn.engine' );
$cdnfsd_engine = $config->get_string( 'cdnfsd.engine' );
$stackpaths = array( 'stackpath', 'stackpath2' );
if ( in_array( $cdn_engine, $stackpaths, true ) || in_array( $cdnfsd_engine, $stackpaths, true ) ) {
?>
<div class="notice notice-warning inline">
<p>
<?php
// StackPath sunset is 12:00 am Central (UTC-6:00) on November, 22, 2023 (1700629200).
$date_time_format = \get_option( 'date_format' ) . ' ' . \get_option( 'time_format' );
\printf(
// translators: 1 StackPath sunset datetime.
\esc_html__(
'StackPath will cease operations at %1$s.',
'w3-total-cache'
),
\wp_date( $date_time_format, '1700629200' ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
);
?>
</p>
</div>
<?php
} elseif ( 'highwinds' === $cdn_engine || 'highwinds' === $cdnfsd_engine ) {
?>
<div class="notice notice-warning inline">
<p>
<?php
// HighWinds sunset is 12:00 am Central (UTC-6:00) on November, 22, 2023 (1700629200).
$date_time_format = \get_option( 'date_format' ) . ' ' . \get_option( 'time_format' );
\printf(
// translators: 1 HighWinds sunset datetime.
\esc_html__(
'HighWinds will cease operations at %1$s.',
'w3-total-cache'
),
\wp_date( $date_time_format, '1700629200' ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
);
?>
</p>
</div>
<?php
}
?>
</p>
<table class="form-table">
<?php
Util_Ui::config_item(
array(
'key' => 'cdn.enabled',
'control' => 'checkbox',
'checkbox_label' => __( 'Enable', 'w3-total-cache' ),
'description' => wp_kses(
sprintf(
// translators: 1 opening HTML acronym tag, 2 closing HTML acronym tag,
// translators: 3 opening HTML acronym tag, 4 closing acronym tag.
__(
'Theme files, media library attachments, %1$sCSS%2$s, and %3$sJS%4$s files will load quickly for site visitors.',
'w3-total-cache'
),
'<acronym title="' . __( 'Cascading Style Sheet', 'w3-total-cache' ) . '">',
'</acronym>',
'<acronym title="' . __( 'JavaScript', 'w3-total-cache' ) . '">',
'</acronym>'
),
array(
'acronym' => array(
'title' => array(),
),
)
),
)
);
Util_Ui::config_item(
array(
'key' => 'cdn.engine',
'control' => 'selectbox',
'selectbox_values' => $engine_values,
'selectbox_optgroups' => $engine_optgroups,
'description' => wp_kses(
sprintf(
// translators: 1 opening HTML acronym tag, 2 closing HTML acronym tag.
__(
'Select the %1$sCDN%2$s type you wish to use.',
'w3-total-cache'
),
'<acronym title="' . __( 'Content Delivery Network', 'w3-total-cache' ) . '">',
'</acronym>'
),
array(
'acronym' => array(
'title' => array(),
),
)
),
)
);
?>
</table>
<?php
do_action( 'w3tc_settings_general_boxarea_cdn_footer' );
?>
<?php Util_Ui::postbox_footer(); ?>

View File

@ -0,0 +1,79 @@
<?php
namespace W3TC;
class Cdn_GoogleDrive_AdminActions {
private $_config = null;
function __construct() {
$this->_config = Dispatcher::config();
}
function w3tc_cdn_google_drive_auth_return() {
$view = new Cdn_GoogleDrive_Popup_AuthReturn();
$view->render();
exit();
}
function w3tc_cdn_google_drive_auth_set() {
// thanks wp core for wp_magic_quotes hell
$client_id = Util_Request::get_string( 'client_id' );
$access_token = Util_Request::get_string( 'access_token' );
$refresh_token = Util_Request::get_string( 'refresh_token' );
$client = new \W3TCG_Google_Client();
$client->setClientId( $client_id );
$client->setAccessToken( $access_token );
//
// get folder details
//
$service = new \W3TCG_Google_Service_Drive( $client );
if ( empty( Util_Request::get_string( 'folder' ) ) ) {
$file = new \W3TCG_Google_Service_Drive_DriveFile( array(
'title' => Util_Request::get_string( 'folder_new' ),
'mimeType' => 'application/vnd.google-apps.folder' ) );
$created_file = $service->files->insert( $file );
$used_folder_id = $created_file->id;
} else {
$used_folder_id = Util_Request::get_string( 'folder' );
}
$permission = new \W3TCG_Google_Service_Drive_Permission();
$permission->setValue( '' );
$permission->setType( 'anyone' );
$permission->setRole( 'reader' );
$service->permissions->insert( $used_folder_id, $permission );
$used_folder = $service->files->get( $used_folder_id );
//
// save new configuration
//
delete_transient( 'w3tc_cdn_google_drive_folder_ids' );
$this->_config->set( 'cdn.google_drive.client_id', $client_id );
$this->_config->set( 'cdn.google_drive.refresh_token', $refresh_token );
$this->_config->set( 'cdn.google_drive.folder.id', $used_folder->id );
$this->_config->set( 'cdn.google_drive.folder.title',
$used_folder->title );
$this->_config->set( 'cdn.google_drive.folder.url',
$used_folder->webViewLink );
$this->_config->save();
$cs = Dispatcher::config_state();
$cs->set( 'cdn.google_drive.access_token', $access_token );
$cs->save();
wp_redirect( 'admin.php?page=w3tc_cdn', false );
}
}

View File

@ -0,0 +1,65 @@
<?php
/**
* File: Cdn_GoogleDrive_Page.php
*
* @package W3TC
*/
namespace W3TC;
/**
* Class: Cdn_GoogleDrive_Page
*/
class Cdn_GoogleDrive_Page {
/**
* Print scripts.
*
* Called from plugin-admin.
*/
public static function admin_print_scripts_w3tc_cdn() {
wp_enqueue_script(
'w3tc_cdn_google_drive',
plugins_url( 'Cdn_GoogleDrive_Page_View.js', W3TC_FILE ),
array( 'jquery' ),
'1.0',
false
);
$path = 'admin.php?page=w3tc_cdn';
$return_url = self_admin_url( $path );
wp_localize_script(
'w3tc_cdn_google_drive',
'w3tc_cdn_google_drive_url',
array( W3TC_GOOGLE_DRIVE_AUTHORIZE_URL . '?return_url=' . rawurlencode( $return_url ) )
);
// it's return from google oauth.
if ( ! empty( Util_Request::get_string( 'oa_client_id' ) ) ) {
$path = wp_nonce_url( 'admin.php', 'w3tc' ) .
'&page=w3tc_cdn&w3tc_cdn_google_drive_auth_return';
foreach ( $_GET as $key => $value ) { // phpcs:ignore
if ( substr( $key, 0, 3 ) === 'oa_' ) {
$path .= '&' . rawurlencode( $key ) . '=' . rawurlencode( Util_Request::get_string( $key ) );
}
}
$popup_url = self_admin_url( $path );
wp_localize_script(
'w3tc_cdn_google_drive',
'w3tc_cdn_google_drive_popup_url',
array( $popup_url )
);
}
}
/**
* Load view.
*/
public static function w3tc_settings_cdn_boxarea_configuration() {
$config = Dispatcher::config();
require W3TC_DIR . '/Cdn_GoogleDrive_Page_View.php';
}
}

View File

@ -0,0 +1,20 @@
jQuery(function($) {
$('.w3tc_cdn_google_drive_authorize').click(function() {
window.location = w3tc_cdn_google_drive_url[0];
});
if (window.w3tc_cdn_google_drive_popup_url) {
W3tc_Lightbox.open({
id:'w3tc-overlay',
close: '',
width: 800,
height: 500,
url: w3tc_cdn_google_drive_popup_url[0],
callback: function(lightbox) {
lightbox.resize();
},
onClose: function() {
}
});
}
});

View File

@ -0,0 +1,40 @@
<?php
namespace W3TC;
if ( ! defined( 'W3TC' ) ) {
die();
}
$refresh_token = $config->get_string( 'cdn.google_drive.refresh_token' );
?>
<tr>
<th style="width: 300px;"><label><?php esc_html_e( 'Authorize:', 'w3-total-cache' ); ?></label></th>
<td>
<?php if ( empty( $refresh_token ) ) : ?>
<input class="w3tc_cdn_google_drive_authorize button" type="button"
value="<?php esc_attr_e( 'Authorize', 'w3-total-cache' ); ?>" />
<?php else : ?>
<input class="w3tc_cdn_google_drive_authorize button" type="button"
value="<?php esc_attr_e( 'Reauthorize', 'w3-total-cache' ); ?>" />
<?php endif ?>
</td>
</tr>
<?php if ( ! empty( $refresh_token ) ) : ?>
<tr>
<th><label for="cdn_s3_bucket"><?php esc_html_e( 'Folder:', 'w3-total-cache' ); ?></label></th>
<td>
<a href="<?php echo esc_url( $config->get_string( 'cdn.google_drive.folder.url' ) ); ?>">/<?php echo esc_html( $config->get_string( 'cdn.google_drive.folder.title' ) ); ?></a>
</td>
</tr>
<tr>
<th colspan="2">
<input id="cdn_test"
class="button {type: 'google_drive', nonce: '<?php echo esc_attr( wp_create_nonce( 'w3tc' ) ); ?>'}"
type="button"
value="<?php esc_attr_e( 'Test upload', 'w3-total-cache' ); ?>" />
<span id="cdn_test_status" class="w3tc-status w3tc-process"></span>
</th>
</tr>
<?php endif ?>

View File

@ -0,0 +1,38 @@
<?php
namespace W3TC;
class Cdn_GoogleDrive_Popup_AuthReturn {
function render() {
$client_id = Util_Request::get_string( 'oa_client_id' );
$refresh_token = Util_Request::get_string( 'oa_refresh_token' );
$token_array = array(
'access_token' => Util_Request::get_string( 'oa_access_token' ),
'token_type' => Util_Request::get_string( 'oa_token_type' ),
'expires_in' => Util_Request::get_string( 'oa_expires_in' ),
'created' => Util_Request::get_string( 'oa_created' )
);
$access_token = json_encode( $token_array );
$client = new \W3TCG_Google_Client();
$client->setClientId( $client_id );
$client->setAccessToken( $access_token );
$service = new \W3TCG_Google_Service_Drive( $client );
$items = $service->files->listFiles( array(
'q' => "mimeType = 'application/vnd.google-apps.folder'"
) );
$folders = array();
foreach ( $items as $item ) {
if ( count( $item->parents ) > 0 && $item->parents[0]->isRoot )
$folders[] = $item;
}
include W3TC_DIR . '/Cdn_GoogleDrive_Popup_AuthReturn_View.php';
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace W3TC;
if ( ! defined( 'W3TC' ) ) {
die();
}
?>
<form action="admin.php?page=w3tc_cdn" method="post" style="padding: 20px">
<?php
Util_Ui::hidden( '', 'client_id', $client_id );
Util_Ui::hidden( '', 'access_token', $access_token );
Util_Ui::hidden( '', 'refresh_token', $refresh_token );
echo wp_kses(
Util_Ui::nonce_field( 'w3tc' ),
array(
'input' => array(
'type' => array(),
'name' => array(),
'value' => array(),
),
)
);
?>
<br /><br />
<div class="metabox-holder">
<?php Util_Ui::postbox_header( esc_html__( 'Select folder', 'w3-total-cache' ) ); ?>
<table class="form-table">
<tr>
<td><?php esc_html_e( 'Folder:', 'w3-total-cache' ); ?></td>
<td>
<?php foreach ( $folders as $folder ) : ?>
<label>
<input name="folder" type="radio" class="w3tc-ignore-change"
value="<?php echo esc_attr( $folder->id ); ?>" />
<?php echo esc_html( $folder->title ); ?>
</label><br />
<?php endforeach ?>
<label>
<input name="folder" type="radio" class="w3tc-ignore-change" value="" />
<?php esc_html_e( 'Add new folder:', 'w3-total-cache' ); ?>
</label>
<input name="folder_new" type="text" class="w3tc-ignore-change" />
</td>
</tr>
</table>
<p class="submit">
<input type="submit" name="w3tc_cdn_google_drive_auth_set"
class="w3tc-button-save button-primary"
value="<?php esc_attr_e( 'Apply', 'w3-total-cache' ); ?>" />
</p>
<?php Util_Ui::postbox_footer(); ?>
</div>
</form>

View File

@ -0,0 +1,218 @@
<?php
namespace W3TC;
class Cdn_Highwinds_Api {
static private $root_uri = 'https://striketracker3.highwinds.com';
private $account_hash;
private $api_token;
static public function users_me( $api_token ) {
$result = wp_remote_get( self::$root_uri . '/api/v1/users/me', array(
'headers' => 'Authorization: Bearer ' . $api_token
) );
$r = self::_decode_response( $result );
if ( !$r['auth_required'] )
return $r['response_json'];
throw new \Exception( 'Authentication failed' );
}
public function __construct( $account_hash, $api_token ) {
$this->account_hash = $account_hash;
$this->api_token = $api_token;
}
public function analytics_transfer( $host, $granularity, $platforms,
$start_date, $end_date ) {
return $this->_wp_remote_get(
self::$root_uri . '/api/v1/accounts/' . $this->account_hash .
'/analytics/transfer?startDate=' . urlencode( $start_date ) .
'&endDate=' . urlencode( $end_date ) .
'&granularity=' . urlencode( $granularity ) .
'&platforms=' . urlencode( $platforms ) );
}
public function configure_scopes( $host ) {
return $this->_wp_remote_get(
self::$root_uri . '/api/v1/accounts/' . $this->account_hash .
'/hosts/' . $host . '/configuration/scopes' );
}
public function configure_scope_get( $host, $scope_id ) {
return $this->_wp_remote_get(
self::$root_uri . '/api/v1/accounts/' . $this->account_hash .
'/hosts/' . $host . '/configuration/' . $scope_id );
}
public function configure_scope_set( $host, $scope_id, $configuration ) {
return $this->_wp_remote_put(
self::$root_uri . '/api/v1/accounts/' . $this->account_hash .
'/hosts/' . $host . '/configuration/' . $scope_id,
json_encode( $configuration )
);
}
public function host_add( $host ) {
return $this->_wp_remote_post(
self::$root_uri . '/api/v1/accounts/' . $this->account_hash . '/hosts',
json_encode( $host )
);
}
public function hosts() {
return $this->_wp_remote_get(
self::$root_uri . '/api/v1/accounts/' . $this->account_hash . '/hosts' );
}
public function origin_add( $origin ) {
return $this->_wp_remote_post(
self::$root_uri . '/api/v1/accounts/' . $this->account_hash . '/origins',
json_encode( $origin )
);
}
public function origins() {
return $this->_wp_remote_get(
self::$root_uri . '/api/v1/accounts/' . $this->account_hash . '/origins' );
}
/**
* $recursive - true/false
*/
public function purge( $urls, $recursive ) {
$list = array();
foreach ( $urls as $url ) {
$list[] = array(
'url' => $url,
'recursive' => $recursive );
}
$response = $this->_wp_remote_post(
self::$root_uri . '/api/v1/accounts/' . $this->account_hash . '/purge',
json_encode( array( 'list' => $list ) )
);
}
public function services() {
return $this->_wp_remote_get(
self::$root_uri . '/api/v1/accounts/' . $this->account_hash . '/services'
);
}
private function _wp_remote_get( $url, $body = array() ) {
$result = wp_remote_get( $url, array(
'headers' => 'Authorization: Bearer ' . $this->api_token,
'body' => $body
) );
$r = self::_decode_response( $result );
if ( !$r['auth_required'] )
return $r['response_json'];
throw new \Exception( 'Authentication failed' );
}
private function _wp_remote_post( $url, $body ) {
$headers = array(
'Authorization' => 'Bearer ' . $this->api_token
);
if ( !is_array( $body ) )
$headers['Content-Type'] = 'application/json';
$result = wp_remote_post( $url, array(
'headers' => $headers,
'body' => $body
) );
$r = self::_decode_response( $result );
if ( !$r['auth_required'] )
return $r['response_json'];
throw new \Exception( 'Authentication failed' );
}
private function _wp_remote_put( $url, $body ) {
$headers = array(
'Authorization' => 'Bearer ' . $this->api_token
);
if ( !is_array( $body ) )
$headers['Content-Type'] = 'application/json';
$result = wp_remote_post( $url, array(
'headers' => $headers,
'body' => $body,
'method' => 'PUT'
) );
$r = self::_decode_response( $result );
if ( !$r['auth_required'] )
return $r['response_json'];
throw new \Exception( 'Authentication failed' );
}
static private function _decode_response( $result ) {
if ( is_wp_error( $result ) )
throw new \Exception( 'Failed to reach API endpoint' );
$response_json = @json_decode( $result['body'], true );
if ( is_null( $response_json ) ) {
if ( $result['response']['code'] == '200' && empty( $result['body'] ) )
return array(
'response_json' => array(),
'auth_required' => false
);
throw new \Exception(
'Failed to reach API endpoint, got unexpected response ' .
$result['body'] );
}
if ( isset( $response_json['error'] ) ) {
if ( isset( $response_json['code'] ) && $response_json['code'] == '203' )
return array( 'response_json' => $response_json, 'auth_required' => true );
throw new \Exception( $response_json['error'] );
}
if ( $result['response']['code'] != '200' && $result['response']['code'] != '201' )
throw new \Exception( $result['body'] );
return array( 'response_json' => $response_json, 'auth_required' => false );
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace W3TC;
class Cdn_Highwinds_Page {
// called from plugin-admin
static public function admin_print_scripts_w3tc_cdn() {
wp_enqueue_script( 'w3tc_cdn_highwinds',
plugins_url( 'Cdn_Highwinds_Page_View.js', W3TC_FILE ),
array( 'jquery' ), '1.0' );
}
static public function w3tc_settings_cdn_boxarea_configuration() {
$config = Dispatcher::config();
include W3TC_DIR . '/Cdn_Highwinds_Page_View.php';
}
}

View File

@ -0,0 +1,99 @@
jQuery(function($) {
function w3tchw_resize(o) {
o.resize();
}
$('body')
.on('click', '.w3tc_cdn_highwinds_authorize', function() {
W3tc_Lightbox.open({
id:'w3tc-overlay',
close: '',
width: 800,
height: 300,
url: ajaxurl + '?action=w3tc_ajax&_wpnonce=' + w3tc_nonce +
'&w3tc_action=cdn_highwinds_authenticate',
callback: w3tchw_resize
});
})
.on('click', '.w3tc_cdn_highwinds_select_host', function() {
var url = ajaxurl + '?action=w3tc_ajax&_wpnonce=' + w3tc_nonce +
'&w3tc_action=cdn_highwinds_select_host';
var v = $('.w3tc_cdn_highwinds_form').find('input').each(function(i) {
var name = $(this).attr('name');
if (name)
url += '&' + encodeURIComponent(name) + '=' +
encodeURIComponent($(this).val());
});
W3tc_Lightbox.load(url, w3tchw_resize);
})
.on('click', '.w3tc_cdn_highwinds_configure_host', function() {
var url = ajaxurl + '?action=w3tc_ajax&_wpnonce=' + w3tc_nonce +
'&w3tc_action=cdn_highwinds_configure_host';
var v = $('.w3tc_cdn_highwinds_form').find('input').each(function(i) {
var name = $(this).attr('name');
var type = $(this).attr('type');
if (type == 'radio') {
if (!$(this).prop('checked'))
return;
}
if (name)
url += '&' + encodeURIComponent(name) + '=' +
encodeURIComponent($(this).val());
});
W3tc_Lightbox.load(url, w3tchw_resize);
})
.on('click', '.w3tc_cdn_highwinds_configure_cnames_form', function() {
W3tc_Lightbox.open({
id:'w3tc-overlay',
close: '',
width: 1000,
height: 400,
url: ajaxurl + '?action=w3tc_ajax&_wpnonce=' + w3tc_nonce +
'&w3tc_action=cdn_highwinds_configure_cnames_form',
callback: function(o) {
w3tchw_resize(o);
w3tc_cdn_cnames_assign();
}
});
})
.on('click', '.w3tc_cdn_highwinds_configure_cnames', function() {
var url = ajaxurl + '?action=w3tc_ajax&_wpnonce=' + w3tc_nonce +
'&w3tc_action=cdn_highwinds_configure_cnames';
var v = $('.w3tc_cdn_highwinds_form').find('input').each(function(i) {
var name = $(this).attr('name');
if (name)
url += '&' + encodeURIComponent(name) + '=' +
encodeURIComponent($(this).val());
});
W3tc_Lightbox.load(url, function(o) {
w3tchw_resize(o);
w3tc_cdn_cnames_assign();
});
})
.on('size_change', '#cdn_cname_add', function() {
w3tchw_resize(W3tc_Lightbox);
})
});

View File

@ -0,0 +1,168 @@
<?php
namespace W3TC;
if ( ! defined( 'W3TC' ) ) {
die();
}
$hash_code = $config->get_string( 'cdn.highwinds.host.hash_code' );
?>
<tr>
<th style="width: 300px;"><label><?php esc_html_e( 'Authorize:', 'w3-total-cache' ); ?></label></th>
<td>
<?php if ( empty( $hash_code ) ) : ?>
<input class="w3tc_cdn_highwinds_authorize button" type="button"
value="<?php esc_html_e( 'Authorize', 'w3-total-cache' ); ?>" />
<?php else : ?>
<input class="w3tc_cdn_highwinds_authorize button" type="button"
value="<?php esc_html_e( 'Reauthorize', 'w3-total-cache' ); ?>" />
<?php endif ?>
</td>
</tr>
<?php if ( ! empty( $hash_code ) ) : ?>
<tr>
<th>
<label>
<?php
echo wp_kses(
sprintf(
// translators: 1 opening HTML acronym tag, 2 closing HTML acronym tag,
// translators: 3 opening HTML acronym tag, 4 closing HTML acronym tag.
__(
'%1CDN%2 host (%3$sCNAME%4$s target):',
'w3-total-cache'
),
'<acronym title="' . esc_attr__( 'Content Delivery Network', 'w3-total-cache' ) . '">',
'</acronym>',
'<acronym title="' . esc_attr__( 'Canonical Name', 'w3-total-cache' ) . '">',
'</acronym>'
),
array(
'acronym' => array(
'title' => array(),
),
)
);
?>
</label>
</th>
<td class="w3tc_config_value_text">
cds.<?php echo esc_html( $config->get_string( 'cdn.highwinds.host.hash_code' ) ); ?>.hwcdn.net
</td>
</tr>
<tr>
<th>
<label for="cdn_highwinds_ssl">
<?php
echo wp_kses(
sprintf(
// translators: 1 opening HTML acronym tag, 2 closing HTML acronym tag.
__(
'%1$sSSL%2$s support:',
'w3-total-cache'
),
'<acronym title="' . esc_attr__( 'Secure Sockets Layer', 'w3-total-cache' ) . '">',
'</acronym>'
),
array(
'acronym' => array(
'title' => array(),
),
)
);
?>
</label>
</th>
<td>
<select id="cdn_highwinds_ssl" name="cdn__highwinds__ssl">
<option value="auto"<?php selected( $config->get_string( 'cdn.highwinds.ssl' ), 'auto' ); ?>><?php esc_html_e( 'Auto (determine connection type automatically)', 'w3-total-cache' ); ?></option>
<option value="enabled"<?php selected( $config->get_string( 'cdn.highwinds.ssl' ), 'enabled' ); ?>><?php esc_html_e( 'Enabled (always use SSL)', 'w3-total-cache' ); ?></option>
<option value="disabled"<?php selected( $config->get_string( 'cdn.highwinds.ssl' ), 'disabled' ); ?>><?php esc_html_e( 'Disabled (always use HTTP)', 'w3-total-cache' ); ?></option>
</select>
<p class="description">
<?php
echo wp_kses(
sprintf(
// translators: 1 opening HTML acronym tag, 2 closing HTML acronym tag,
// translators: 3 opening HTML acronym tag, 4 closing HTML acronym tag.
__(
'Some %1$sCDN%2$s providers may or may not support %3$sSSL%4$s, contact your vendor for more information.',
'w3-total-cache'
),
'<acronym title="' . esc_attr__( 'Content Delivery Network', 'w3-total-cache' ) . '">',
'</acronym>',
'<acronym title="' . esc_attr__( 'Secure Sockets Layer', 'w3-total-cache' ) . '">',
'</acronym>'
),
array(
'acronym' => array(
'title' => array(),
),
)
);
?>
</p>
</td>
</tr>
<tr>
<th><?php esc_html_e( 'Replace site\'s hostname with:', 'w3-total-cache' ); ?></th>
<td>
<?php
$cnames = $config->get_array( 'cdn.highwinds.host.domains' );
include W3TC_INC_DIR . '/options/cdn/common/cnames-readonly.php';
?>
<input class="w3tc_cdn_highwinds_configure_cnames_form button" type="button" value="
<?php
echo wp_kses(
sprintf(
// translators: 1 opening HTML acronym tag, 2 closing HTML acronym tag.
__(
'Configure %1$sCNAME%2$ss',
'w3-total-cache'
),
'<acronym title="' . esc_attr__( 'Canonical Name', 'w3-total-cache' ) . '">',
'</acronym>'
),
array(
'acronym' => array(
'title' => array(),
),
)
);
?>
" />
<p class="description">
<?php
echo wp_kses(
sprintf(
// translators: 1 opening HTML acronym tag, 2 closing HTML acronym tag,
// translators: 3 opening HTML acronym tag, 4 closing HTML acronym tag.
__(
'Hostname provided by your %1$sCDN%2$s provider, this value will replace your site\'s hostname in the %3$sHTML%4$s.',
'w3-total-cache'
),
'<acronym title="' . esc_attr__( 'Content Delivery Network', 'w3-total-cache' ) . '">',
'</acronym>',
'<acronym title="' . esc_attr__( 'Hypertext Markup Language', 'w3-total-cache' ) . '">',
'</acronym>'
),
array(
'acronym' => array(
'title' => array(),
),
)
);
?>
</p>
</td>
</tr>
<tr>
<th colspan="2">
<input id="cdn_test"
class="button {type: 'highwinds', nonce: '<?php echo esc_attr( wp_create_nonce( 'w3tc' ) ); ?>'}"
type="button"
value="<?php esc_html_e( 'Test', 'w3-total-cache' ); ?>" />
<span id="cdn_test_status" class="w3tc-status w3tc-process"></span>
</th>
</tr>
<?php endif ?>

View File

@ -0,0 +1,271 @@
<?php
namespace W3TC;
class Cdn_Highwinds_Popup {
static public function w3tc_ajax() {
$o = new Cdn_Highwinds_Popup();
add_action( 'w3tc_ajax_cdn_highwinds_authenticate',
array( $o, 'w3tc_ajax_cdn_highwinds_authenticate' ) );
add_action( 'w3tc_ajax_cdn_highwinds_select_host',
array( $o, 'w3tc_ajax_cdn_highwinds_select_host' ) );
add_action( 'w3tc_ajax_cdn_highwinds_configure_host',
array( $o, 'w3tc_ajax_cdn_highwinds_configure_host' ) );
add_action( 'w3tc_ajax_cdn_highwinds_configure_cnames_form',
array( $o, 'w3tc_ajax_cdn_highwinds_configure_cnames_form' ) );
add_action( 'w3tc_ajax_cdn_highwinds_configure_cnames',
array( $o, 'w3tc_ajax_cdn_highwinds_configure_cnames' ) );
}
public function w3tc_ajax_cdn_highwinds_authenticate() {
$details = array();
include W3TC_DIR . '/Cdn_Highwinds_Popup_View_Intro.php';
exit();
}
public function w3tc_ajax_cdn_highwinds_select_host() {
$api_token = Util_Request::get_string( 'api_token' );
try {
$user = Cdn_Highwinds_Api::users_me( $api_token );
$account_hash = $user['accountHash'];
// obtain hosts
$api = new Cdn_Highwinds_Api( $account_hash, $api_token );
$hosts_response = $api->hosts();
} catch ( \Exception $ex ) {
$details = array(
'error_message' => 'Can\'t authenticate: ' . $ex->getMessage()
);
include W3TC_DIR . '/Cdn_Highwinds_Popup_View_Intro.php';
exit();
}
$details = array(
'account_hash' => $account_hash,
'api_token' => $api_token,
'hosts' => $hosts_response['list']
);
include W3TC_DIR . '/Cdn_Highwinds_Popup_View_SelectHost.php';
exit();
}
public function w3tc_ajax_cdn_highwinds_configure_host() {
$account_hash = Util_Request::get_string( 'account_hash' );
$api_token = Util_Request::get_string( 'api_token' );
$host = Util_Request::get( 'host', '' );
$details = array(
'account_hash' => $account_hash,
'api_token' => $api_token
);
$api = new Cdn_Highwinds_Api( $account_hash, $api_token );
try {
if ( empty( $host ) ) {
$host = $this->_create_host( $api, Util_Request::get_string( 'host_new' ) );
}
} catch ( \Exception $ex ) {
$api_hosts = $api->hosts();
$details['hosts'] = $api_hosts['list'];
$details['error_message'] = $ex->getMessage();
include W3TC_DIR . '/Cdn_Highwinds_Popup_View_SelectHost.php';
exit();
}
// try to obtain CNAMEs
$c = Dispatcher::config();
try {
$scopes_response = $api->configure_scopes( $host );
$scope_id = 0;
foreach ( $scopes_response['list'] as $scope ) {
if ( $scope['platform'] == 'CDS' )
$scope_id = $scope['id'];
}
if ( $scope_id <= 0 )
throw new Exception( 'scope CDN hasnt been created' );
$configuration = $api->configure_scope_get( $host, $scope_id );
if ( isset( $configuration['hostname'] ) ) {
$domains = array();
foreach ( $configuration['hostname'] as $d )
$domains[] = $d['domain'];
$c->set( 'cdn.highwinds.host.domains', $domains );
}
} catch ( \Exception $ex ) {
}
$c->set( 'cdn.highwinds.account_hash', $account_hash );
$c->set( 'cdn.highwinds.api_token', $api_token );
$c->set( 'cdn.highwinds.host.hash_code', $host );
$c->save();
$postfix = Util_Admin::custom_message_id( array(),
array(
'cdn_configuration_saved' =>
'CDN credentials are saved successfully' ) );
echo 'Location admin.php?page=w3tc_cdn&' . esc_html( $postfix );
exit();
}
private function _create_host( $api, $host_name ) {
// create simple host
$services_response = $api->services();
// select all CDS services since its going to use caching
$service_ids = array();
foreach ( $services_response['list'] as $s ) {
if ( strpos( $s['name'], 'CDS' ) >= 0 )
$service_ids[] = $s['id'];
}
$origins_response = $api->origins();
$home_domain = Util_Environment::home_url_host();
$origin_id = 0;
foreach ( $origins_response['list'] as $o ) {
if ( $o['hostname'] == $home_domain ) {
$origin_id = $o['id'];
break;
}
}
if ( $origin_id == 0 ) {
try {
$name = preg_replace( '/[^0-9a-z]/', '_', $home_domain );
$origin_response = $api->origin_add( array(
'name' => $name,
'hostname' => $home_domain,
'path' => '/',
'port' => 80
) );
$origin_id = $origin_response['id'];
} catch ( \Exception $ex ) {
throw new \Exception( 'Can\'t create origin ' . $home_domain . ': ' .
$ex->getMessage() );
}
}
try {
// create host
$host_response = $api->host_add( array(
'name' => Util_Request::get_string( 'host_new' ),
'services' => $service_ids
) );
$host = $host_response['hashCode'];
} catch ( \Exception $ex ) {
throw new \Exception( 'Can\'t create new host: ' . $ex->getMessage() );
}
// configure host
$scopes_response = $api->configure_scopes( $host );
$scope_id = 0;
foreach ( $scopes_response['list'] as $scope ) {
if ( $scope['platform'] == 'CDS' )
$scope_id = $scope['id'];
}
if ( $scope_id <= 0 )
throw new Exception( 'Cant\'t configure host - scope CDN hasnt been created' );
$configuration = $api->configure_scope_get( $host, $scope_id );
// apply usually optimal default values
$configuration['cacheControl'] = array( array( 'maxAge' => 31536000 ) );
$configuration['compression'] = array( 'gzip' => 'css,js' );
$configuration['originPullCacheExtension'] = array(
'expiredCacheExtension' => 86400 );
$configuration['originPullHost'] = array( 'primary' => $origin_id );
$configuration['originPullPolicy'] = array( array(
'expirePolicy' => 'CACHE_CONTROL',
'expireSeconds' => 86400,
'httpHeaders' => 'Access-Control-Allow-Origin'
) );
try {
$configuration_response = $api->configure_scope_set( $host,
$scope_id, $configuration );
} catch ( \Exception $ex ) {
throw new \Exception( 'Cant\'t configure host: ' . $ex->getMessage() );
}
return $host;
}
public function w3tc_ajax_cdn_highwinds_configure_cnames_form() {
$this->render_configure_cnames_form();
exit();
}
public function w3tc_ajax_cdn_highwinds_configure_cnames() {
$details = array(
'cnames' => Util_Request::get_array( 'cdn_cnames' )
);
$core = Dispatcher::component( 'Cdn_Core' );
$cdn = $core->get_cdn();
try {
// try to obtain CNAMEs
$cdn->service_cnames_set( $details['cnames'] );
$c = Dispatcher::config();
$c->set( 'cdn.highwinds.host.domains', $details['cnames'] );
$c->save();
$postfix = Util_Admin::custom_message_id( array(),
array( 'cdn_cnames_saved' => 'CNAMEs are saved successfully' ) );
echo 'Location admin.php?page=w3tc_cdn&' . esc_html( $postfix );
exit();
} catch ( \Exception $ex ) {
$details['error_message'] = $ex->getMessage();
}
$this->render_configure_cnames_form( $details );
exit();
}
private function render_configure_cnames_form( $details = array() ) {
if ( isset( $details['cnames'] ) )
$cnames = $details['cnames'];
else {
$core = Dispatcher::component( 'Cdn_Core' );
$cdn = $core->get_cdn();
try {
// try to obtain CNAMEs
$cnames = $cdn->service_cnames_get();
} catch ( \Exception $ex ) {
$details['error_message'] = $ex->getMessage();
$cnames = array();
}
}
include W3TC_DIR . '/Cdn_Highwinds_Popup_View_ConfigureCnamesForm.php';
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace W3TC;
if ( ! defined( 'W3TC' ) ) {
die();
}
?>
<form action="admin.php?page=w3tc_cdn" method="post" style="padding: 20px" class="w3tc_cdn_highwinds_form">
<?php
if ( ! empty( $details['error_message'] ) ) {
echo '<div class="error">' . esc_html( $details['error_message'] ) . '</div>';
}
?>
<div class="metabox-holder">
<?php
Util_Ui::postbox_header(
wp_kses(
sprintf(
// translators: 1 opening HTML acronym tag, 2 closing HTML acronym tag.
__(
'%1$sCNAME%2$ss to use',
'w3-total-cache'
),
'<acronym title="' . esc_attr__( 'Canonical Name', 'w3-total-cache' ) . '">',
'</acronym>'
),
array(
'acronym' => array(
'title' => array(),
),
)
)
);
?>
<?php
$cname_class = 'w3tc-ignore-change';
require W3TC_INC_DIR . '/options/cdn/common/cnames.php';
?>
<p class="description">
<?php
echo wp_kses(
sprintf(
// translators: 1 opening HTML acronym tag, 2 closing HTML acronym tag,
// translators: 3 opening HTML acronym tag, 4 closing HTML acronym tag.
__(
'Enter hostname mapped to %1$sCDN%2$s host, this value will replace your site\'s hostname in the %3$sHTML%4$s.',
'w3-total-cache'
),
'<acronym title="' . esc_attr__( 'Content Delivery Network', 'w3-total-cache' ) . '">',
'</acronym>',
'<acronym title="' . esc_attr__( 'Hypertext Markup Language', 'w3-total-cache' ) . '">',
'</acronym>'
),
array(
'acronym' => array(
'title' => array(),
),
)
);
?>
</p>
<p class="submit">
<input type="button"
class="w3tc_cdn_highwinds_configure_cnames w3tc-button-save button-primary"
value="<?php esc_attr_e( 'Apply', 'w3-total-cache' ); ?>" />
</p>
<?php Util_Ui::postbox_footer(); ?>
</div>
</form>

View File

@ -0,0 +1,33 @@
<?php
namespace W3TC;
if ( ! defined( 'W3TC' ) ) {
die();
}
?>
<form class="w3tc_cdn_highwinds_form" method="post" style="padding: 20px">
<?php
if ( isset( $details['error_message'] ) ) {
echo '<div class="error">' . esc_html( $details['error_message'] ) . '</div>';
}
?>
<div class="metabox-holder">
<?php Util_Ui::postbox_header( esc_html__( 'Your Highwinds API Token', 'w3-total-cache' ) ); ?>
<table class="form-table">
<tr>
<td><?php esc_html_e( 'API Token:', 'w3-total-cache' ); ?></td>
<td>
<input name="api_token" type="text" class="w3tc-ignore-change"
value="" style="width: 550px" />
</td>
</tr>
</table>
<p class="submit">
<input type="button"
class="w3tc_cdn_highwinds_select_host w3tc-button-save button-primary"
value="<?php esc_attr_e( 'Next', 'w3-total-cache' ); ?>" />
</p>
<?php Util_Ui::postbox_footer(); ?>
</div>
</form>

View File

@ -0,0 +1,60 @@
<?php
namespace W3TC;
if ( ! defined( 'W3TC' ) ) {
die();
}
?>
<form action="admin.php?page=w3tc_cdn" method="post" style="padding: 20px"
class="w3tc_cdn_highwinds_form">
<?php
Util_Ui::hidden( '', 'account_hash', $details['account_hash'] );
Util_Ui::hidden( '', 'api_token', $details['api_token'] );
echo wp_kses(
Util_Ui::nonce_field( 'w3tc' ),
array(
'input' => array(
'type' => array(),
'name' => array(),
'value' => array(),
),
)
);
if ( isset( $details['error_message'] ) ) {
echo '<div class="error">' . esc_html( $details['error_message'] ) . '</div>';
}
?>
<div class="metabox-holder">
<?php Util_Ui::postbox_header( esc_html__( 'Select host to use', 'w3-total-cache' ) ); ?>
<table class="form-table">
<tr>
<td>Host:</td>
<td>
<?php foreach ( $details['hosts'] as $host ) : ?>
<label>
<input name="host" type="radio" class="w3tc-ignore-change"
value="<?php echo esc_attr( $host['hashCode'] ); ?>" />
<?php echo esc_html( $host['name'] ); ?>
(<?php echo esc_html( $host['hashCode'] ); ?>)
</label><br />
<?php endforeach; ?>
<label>
<input name="host" type="radio" class="w3tc-ignore-change" value="" />
Add new host:
</label>
<input name="host_new" type="text" class="w3tc-ignore-change" />
</td>
</tr>
</table>
<p class="submit">
<input type="button"
class="w3tc_cdn_highwinds_configure_host w3tc-button-save button-primary"
value="<?php esc_attr_e( 'Apply', 'w3-total-cache' ); ?>" />
</p>
<?php Util_Ui::postbox_footer(); ?>
</div>
</form>

View File

@ -0,0 +1,18 @@
<?php
namespace W3TC;
class Cdn_LimeLight_Page {
// called from plugin-admin
static public function admin_print_scripts_w3tc_cdn() {
wp_enqueue_script( 'w3tc_cdn_limelight',
plugins_url( 'Cdn_LimeLight_Page_View.js', W3TC_FILE ),
array( 'jquery' ), '1.0' );
}
static public function w3tc_settings_cdn_boxarea_configuration() {
$config = Dispatcher::config();
include W3TC_DIR . '/Cdn_LimeLight_Page_View.php';
}
}

View File

@ -0,0 +1,41 @@
jQuery(function($) {
function w3tc_popup_resize(o) {
o.resize();
}
$('body')
.on('click', '.w3tc_cdn_limelight_authorize', function() {
W3tc_Lightbox.open({
id:'w3tc-overlay',
close: '',
width: 800,
height: 300,
url: ajaxurl + '?action=w3tc_ajax&_wpnonce=' + w3tc_nonce +
'&w3tc_action=cdn_limelight_intro',
callback: w3tc_popup_resize
});
})
.on('click', '.w3tc_cdn_limelight_save', function() {
var url = ajaxurl + '?action=w3tc_ajax&_wpnonce=' + w3tc_nonce +
'&w3tc_action=cdn_limelight_save';
var v = $('.w3tc_popup_form').find('input').each(function(i) {
var name = $(this).attr('name');
if (name)
url += '&' + encodeURIComponent(name) + '=' +
encodeURIComponent($(this).val());
});
W3tc_Lightbox.load(url, w3tc_popup_resize);
})
.on('click', '.w3tc_cdn_limelight_done', function() {
// refresh page
window.location = window.location + '&';
})
});

View File

@ -0,0 +1,118 @@
<?php
namespace W3TC;
if ( ! defined( 'W3TC' ) ) {
die();
}
$api_key = $config->get_string( 'cdn.limelight.api_key' );
?>
<tr>
<th style="width: 300px;"><label><?php esc_html_e( 'Authorize:', 'w3-total-cache' ); ?></label></th>
<td>
<?php if ( empty( $api_key ) ) : ?>
<input class="w3tc_cdn_limelight_authorize button" type="button"
value="<?php esc_attr_e( 'Authorize', 'w3-total-cache' ); ?>" />
<?php else : ?>
<input class="w3tc_cdn_limelight_authorize button" type="button"
value="<?php esc_attr_e( 'Reauthorize', 'w3-total-cache' ); ?>" />
<?php endif; ?>
</td>
</tr>
<?php if ( ! empty( $api_key ) ) : ?>
<tr>
<th>
<label for="cdn_limelight_ssl">
<?php
echo wp_kses(
sprintf(
// translators: 1 opening HTML acronym tag, 2 closing HTML acronym tag.
__(
'%1$sSSL%2$s support:',
'w3-total-cache'
),
'<acronym title="' . esc_attr__( 'Secure Sockets Layer', 'w3-total-cache' ) . '">',
'</acronym>'
),
array(
'acronym' => array(
'title' => array(),
),
)
);
?>
</label>
</th>
<td>
<select id="cdn_limelight_ssl" name="cdn__limelight__ssl">
<option value="auto"<?php selected( $config->get_string( 'cdn.limelight.ssl' ), 'auto' ); ?>><?php esc_html_e( 'Auto (determine connection type automatically)', 'w3-total-cache' ); ?></option>
<option value="enabled"<?php selected( $config->get_string( 'cdn.limelight.ssl' ), 'enabled' ); ?>><?php esc_html_e( 'Enabled (always use SSL)', 'w3-total-cache' ); ?></option>
<option value="disabled"<?php selected( $config->get_string( 'cdn.limelight.ssl' ), 'disabled' ); ?>><?php esc_html_e( 'Disabled (always use HTTP)', 'w3-total-cache' ); ?></option>
</select>
<p class="description">
<?php
echo wp_kses(
sprintf(
// translators: 1 opening HTML acronym tag, 2 closing HTML acronym tag,
// translators: 3 opening HTML acronym tag, 4 closing HTML acronym tag.
__(
'Some %1$sCDN%2$s providers may or may not support %3$sSSL%4$s, contact your vendor for more information.',
'w3-total-cache'
),
'<acronym title="' . esc_attr__( 'Content Delivery Network', 'w3-total-cache' ) . '">',
'</acronym>',
'<acronym title="' . esc_attr__( 'Secure Sockets Layer', 'w3-total-cache' ) . '">',
'</acronym>'
),
array(
'acronym' => array(
'title' => array(),
),
)
);
?>
</p>
</td>
</tr>
<tr>
<th><?php esc_html_e( 'Replace site\'s hostname with:', 'w3-total-cache' ); ?></th>
<td>
<?php
$cnames = $config->get_array( 'cdn.limelight.host.domains' );
require W3TC_INC_DIR . '/options/cdn/common/cnames-readonly.php';
?>
<p class="description">
<?php
echo wp_kses(
sprintf(
// translators: 1 opening HTML acronym tag, 2 closing HTML acronym tag,
// translators: 3 opening HTML acronym tag, 4 closing HTML acronym tag.
__(
'Hostname provided by your %1$sCDN%2$s provider, this value will replace your site\'s hostname in the %3$sHTML%4$s.',
'w3-total-cache'
),
'<acronym title="' . esc_attr__( 'Content Delivery Network', 'w3-total-cache' ) . '">',
'</acronym>',
'<acronym title="' . esc_attr__( 'Hypertext Markup Language', 'w3-total-cache' ) . '">',
'</acronym>'
),
array(
'acronym' => array(
'title' => array(),
),
)
);
?>
</p>
</td>
</tr>
<tr>
<th colspan="2">
<input id="cdn_test"
class="button {type: 'limelight', nonce: '<?php echo esc_attr( wp_create_nonce( 'w3tc' ) ); ?>'}"
type="button" value="<?php esc_attr_e( 'Test', 'w3-total-cache' ); ?>" />
<span id="cdn_test_status" class="w3tc-status w3tc-process"></span>
</th>
</tr>
<?php endif; ?>

View File

@ -0,0 +1,75 @@
<?php
namespace W3TC;
class Cdn_LimeLight_Popup {
static public function w3tc_ajax() {
$o = new Cdnfsd_LimeLight_Popup();
add_action( 'w3tc_ajax_cdn_limelight_intro',
array( $o, 'w3tc_ajax_cdn_limelight_intro' ) );
add_action( 'w3tc_ajax_cdn_limelight_save',
array( $o, 'w3tc_ajax_cdn_limelight_save' ) );
}
public function w3tc_ajax_cdn_limelight_intro() {
$this->render_intro( array() );
}
private function render_intro( $details ) {
$config = Dispatcher::config();
$domain = '';
$domains = $config->get_array('cdn.limelight.host.domains');
if ( count( $domains ) > 0 ) {
$domain = $domains[0];
}
include W3TC_DIR . '/Cdn_LimeLight_Popup_View_Intro.php';
exit();
}
public function w3tc_ajax_cdn_limelight_save() {
$short_name = Util_Request::get_string( 'short_name' );
$username = Util_Request::get_string( 'username' );
$api_key = Util_Request::get_string( 'api_key' );
$domain = Util_Request::get_string( 'domain' );
try {
$api = new Cdnfsd_LimeLight_Api( $short_name, $username, $api_key );
$url = ( Util_Environment::is_https() ? 'https://' : 'http://' ) . $domain . '/test';
$items = array(
array(
'pattern' => $url,
'exact' => true,
'evict' => false,
'incqs' => false
)
);
$api->purge( $items );
} catch ( \Exception $ex ) {
$this->render_intro( array(
'error_message' => 'Failed to make test purge request: ' . $ex->getMessage()
) );
exit();
}
$c = Dispatcher::config();
$c->set( 'cdn.limelight.short_name', $short_name );
$c->set( 'cdn.limelight.username', $username );
$c->set( 'cdn.limelight.api_key', $api_key );
$c->set( 'cdn.limelight.host.domains', array( $domain ) );
$c->save();
include W3TC_DIR . '/Cdn_LimeLight_Popup_View_Success.php';
exit();
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace W3TC;
if ( ! defined( 'W3TC' ) ) {
die();
}
?>
<form class="w3tc_popup_form">
<?php
if ( isset( $details['error_message'] ) ) {
echo '<div class="error">' . esc_html( $details['error_message'] ) . '</div>';
}
?>
<div class="metabox-holder">
<?php Util_Ui::postbox_header( esc_html__( 'Your LimeLight Account credentials', 'w3-total-cache' ) ); ?>
<table class="form-table">
<tr>
<td><?php esc_html_e( 'Account Short Name:', 'w3-total-cache' ); ?></td>
<td>
<input name="short_name" type="text" class="w3tc-ignore-change"
style="width: 550px"
value="<?php echo esc_attr( $config->get_string( 'cdn.limelight.short_name' ) ); ?>" />
</td>
</tr>
<tr>
<td><?php esc_html_e( 'Username:', 'w3-total-cache' ); ?></td>
<td>
<input name="username" type="text" class="w3tc-ignore-change"
style="width: 550px"
value="<?php echo esc_attr( $config->get_string( 'cdn.limelight.username' ) ); ?>" />
</td>
</tr>
<tr>
<td><?php esc_html_e( 'API Key:', 'w3-total-cache' ); ?></td>
<td>
<input name="api_key" type="text" class="w3tc-ignore-change"
style="width: 550px"
value="<?php echo esc_attr( $config->get_string( 'cdn.limelight.api_key' ) ); ?>" />
</td>
</tr>
<tr>
<td><?php esc_html_e( 'CDN hostname:', 'w3-total-cache' ); ?></td>
<td>
<input name="domain" type="text" class="w3tc-ignore-change"
style="width: 550px"
value="<?php echo esc_attr( $domain ); ?>" />
</td>
</tr>
</table>
<p class="submit">
<input type="button"
class="w3tc_cdn_limelight_save w3tc-button-save button-primary"
value="<?php esc_attr_e( 'Next', 'w3-total-cache' ); ?>" />
</p>
<?php Util_Ui::postbox_footer(); ?>
</div>
</form>

View File

@ -0,0 +1,36 @@
<?php
namespace W3TC;
if ( ! defined( 'W3TC' ) ) {
die();
}
?>
<form class="w3tc_popup_form">
<div class="metabox-holder">
<?php Util_Ui::postbox_header( esc_html__( 'Succeeded', 'w3-total-cache' ) ); ?>
<div style="text-align: center">
<?php
echo wp_kses(
sprintf(
// translators: 1 HTML line break tag.
__(
'Plugin was successfully configured to use this service.%1$sMake sure you have updated domain DNS records.',
'w3-total-cache'
),
'<br />'
),
array(
'br' => array(),
)
);
?>
<p class="submit">
<input type="button"
class="w3tc_cdn_limelight_done w3tc-button-save button-primary"
value="<?php esc_attr_e( 'Done', 'w3-total-cache' ); ?>" />
</p>
<?php Util_Ui::postbox_footer(); ?>
</div>
</div>
</form>

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