| 1 |
<?php |
| 2 |
|
| 3 |
/** |
| 4 |
* JCH Optimize - Performs several front-end optimizations for fast downloads |
| 5 |
* |
| 6 |
* @package jchoptimize/wordpress-platform |
| 7 |
* @author Samuel Marshall <samuel@jch-optimize.net> |
| 8 |
* @copyright Copyright (c) 2020 Samuel Marshall / JCH Optimize |
| 9 |
* @license GNU/GPLv3, or later. See LICENSE file |
| 10 |
* |
| 11 |
* If LICENSE file missing, see <http://www.gnu.org/licenses/>. |
| 12 |
*/ |
| 13 |
|
| 14 |
namespace JchOptimize\WordPress\Plugin; |
| 15 |
|
| 16 |
use _JchOptimizeVendor\V91\Joomla\DI\ContainerAwareInterface; |
| 17 |
use _JchOptimizeVendor\V91\Joomla\DI\ContainerAwareTrait; |
| 18 |
use _JchOptimizeVendor\V91\Joomla\Input\Input; |
| 19 |
use _JchOptimizeVendor\V91\Laminas\Cache\Exception\ExceptionInterface; |
| 20 |
use _JchOptimizeVendor\V91\Psr\Log\LoggerAwareInterface; |
| 21 |
use _JchOptimizeVendor\V91\Psr\Log\LoggerAwareTrait; |
| 22 |
use _JchOptimizeVendor\V91\Psr\Log\LogLevel; |
| 23 |
use Exception; |
| 24 |
use JchOptimize\Core\Helper; |
| 25 |
use JchOptimize\Core\Optimize; |
| 26 |
use JchOptimize\Core\PageCache\CaptureCache; |
| 27 |
use JchOptimize\Core\PageCache\PageCache; |
| 28 |
use JchOptimize\Core\Platform\UtilityInterface; |
| 29 |
use JchOptimize\Core\Registry; |
| 30 |
use JchOptimize\Core\Settings; |
| 31 |
use JchOptimize\Core\SystemUri\SystemUri; |
| 32 |
use JchOptimize\WordPress\Container\ContainerFactory; |
| 33 |
use JchOptimize\WordPress\Model\ReCache; |
| 34 |
use WP_Admin_Bar; |
| 35 |
|
| 36 |
use function add_action; |
| 37 |
use function add_filter; |
| 38 |
use function apply_filters; |
| 39 |
use function current_user_can; |
| 40 |
use function defined; |
| 41 |
use function delete_option; |
| 42 |
use function do_action; |
| 43 |
use function gmdate; |
| 44 |
use function get_option; |
| 45 |
use function http_response_code; |
| 46 |
use function is_admin; |
| 47 |
use function load_plugin_textdomain; |
| 48 |
use function max; |
| 49 |
use function plugin_basename; |
| 50 |
use function register_activation_hook; |
| 51 |
use function register_uninstall_hook; |
| 52 |
use function time; |
| 53 |
use function update_option; |
| 54 |
use function wp_is_mobile; |
| 55 |
|
| 56 |
use const JCH_PRO; |
| 57 |
use const SHORTINIT; |
| 58 |
use const WP_USE_THEMES; |
| 59 |
use const WPMU_PLUGIN_DIR; |
| 60 |
|
| 61 |
class Loader implements LoggerAwareInterface, ContainerAwareInterface |
| 62 |
{ |
| 63 |
use LoggerAwareTrait; |
| 64 |
use ContainerAwareTrait; |
| 65 |
|
| 66 |
public function __construct( |
| 67 |
private Registry $params, |
| 68 |
private Admin $admin, |
| 69 |
private Installer $installer, |
| 70 |
private PageCache $pageCache, |
| 71 |
private UtilityInterface $utility, |
| 72 |
private ?Updater $updater = null, |
| 73 |
) { |
| 74 |
} |
| 75 |
|
| 76 |
/** |
| 77 |
* Function register_uninstall_hook can only be used with a static method |
| 78 |
* |
| 79 |
* @return void |
| 80 |
*/ |
| 81 |
public static function runUninstallRoutines(): void |
| 82 |
{ |
| 83 |
/** @var Installer $installer */ |
| 84 |
$installer = ContainerFactory::create()->get(Installer::class); |
| 85 |
$installer->deactivate(); |
| 86 |
} |
| 87 |
|
| 88 |
public function preboot_init(): void |
| 89 |
{ |
| 90 |
if (false !== ($settings = get_option('jch_options'))) { |
| 91 |
update_option('jch-optimize_settings', $settings); |
| 92 |
delete_option('jch_options'); |
| 93 |
} |
| 94 |
} |
| 95 |
|
| 96 |
public function init(): void |
| 97 |
{ |
| 98 |
$this->runActivationRoutines(); |
| 99 |
|
| 100 |
if (is_admin()) { |
| 101 |
if (defined('DOING_AJAX')) { //Ajax functions |
| 102 |
add_action('wp_ajax_filetree', [$this->admin, 'doAjaxFileTree']); |
| 103 |
add_action('wp_ajax_multiselect', [$this->admin, 'doAjaxMultiSelect']); |
| 104 |
add_action('wp_ajax_optimizeimages', [$this->admin, 'doAjaxOptimizeImages']); |
| 105 |
add_action('wp_ajax_configuresettings', [$this->admin, 'doAjaxConfigureSettings']); |
| 106 |
add_action('wp_ajax_getcacheinfo', [$this->admin, 'doAjaxGetCacheInfo']); |
| 107 |
add_action('wp_ajax_onclickicon', [$this->admin, 'doAjaxOnClickIcon']); |
| 108 |
add_action('wp_ajax_jch_configure_js_table_body', [$this->admin, 'doAjaxJchConfigureJsTableBody']); |
| 109 |
add_action('wp_ajax_jch_configure_js_auto_save', [$this->admin, 'doAjaxJchConfigureJsAutoSave']); |
| 110 |
add_action('wp_ajax_jch_cf_verify', [$this->admin, 'doAjaxJchCfVerify']); |
| 111 |
} else { |
| 112 |
add_action('admin_menu', [$this->admin, 'addAdminMenu']); |
| 113 |
add_action('admin_menu', [$this->admin, 'addPageCacheMenu']); |
| 114 |
add_action('admin_init', [$this->admin, 'registerOptions']); |
| 115 |
add_filter('plugin_action_links', [$this->admin, 'loadActionLinks'], 10, 2); |
| 116 |
$this->installer->updateSettings(); |
| 117 |
$this->installer->fixMetaFileSecurity(); |
| 118 |
} |
| 119 |
} else { |
| 120 |
$url_exclude = (array)$this->params->get('menuexcludedurl', []); |
| 121 |
/** @var Input $input */ |
| 122 |
$input = $this->container->get(Input::class); |
| 123 |
$jch_backend = (string)$input->get('jchbackend'); |
| 124 |
/** @var string|null $nooptimize */ |
| 125 |
$nooptimize = $input->get('jchnooptimize'); |
| 126 |
|
| 127 |
if ( |
| 128 |
defined('WP_USE_THEMES') |
| 129 |
&& WP_USE_THEMES |
| 130 |
&& $jch_backend != '1' |
| 131 |
&& is_null($nooptimize) |
| 132 |
&& version_compare(PHP_VERSION, '7.4.0', '>=') |
| 133 |
&& !defined('DOING_AJAX') |
| 134 |
&& !defined('DOING_CRON') |
| 135 |
&& !defined('APP_REQUEST') |
| 136 |
&& !defined('XMLRPC_REQUEST') |
| 137 |
&& (!defined('SHORTINIT') || (defined('SHORTINIT') && !SHORTINIT)) |
| 138 |
&& !Helper::findExcludes($url_exclude, SystemUri::toString()) |
| 139 |
&& $input->server->getString('HTTP_SEC_FETCH_DEST') != 'iframe' |
| 140 |
) { |
| 141 |
//Disable NextGen Resource Manager; incompatible with plugin |
| 142 |
//add_filter( 'run_ngg_resource_manager', '__return_false' ); |
| 143 |
add_action('init', [$this, 'initializeCache'], 0); |
| 144 |
|
| 145 |
ob_start([$this, 'runOptimize']); |
| 146 |
|
| 147 |
if (JCH_DEBUG && !$this->params->get('disable_logged_in_users', '1')) { |
| 148 |
add_action('admin_bar_menu', [$this, 'addMenuToAdminBar'], 101); |
| 149 |
} |
| 150 |
} |
| 151 |
} |
| 152 |
|
| 153 |
add_action('plugins_loaded', [$this, 'loadPluginTextDomain']); |
| 154 |
//Function register_uninstall_hook can only be used with a static class method |
| 155 |
register_uninstall_hook(JCH_PLUGIN_FILE, [__CLASS__, 'runUninstallRoutines']); |
| 156 |
|
| 157 |
if ($this->params->get('order_plugin', '1')) { |
| 158 |
add_action('activated_plugin', [$this, 'orderPlugin']); |
| 159 |
add_action('deactivated_plugin', [$this, 'orderPlugin']); |
| 160 |
} |
| 161 |
|
| 162 |
if (JCH_PRO) { |
| 163 |
$this->loadProUpdater(); |
| 164 |
|
| 165 |
if ($this->params->get('pro_cache_platform', '0')) { |
| 166 |
add_filter('jch_optimize_get_page_cache_id', [$this, 'getPageCacheHash'], 10, 2); |
| 167 |
} |
| 168 |
|
| 169 |
$reCacheModel = $this->getContainer()->get(ReCache::class); |
| 170 |
/** @see ReCache::reCache() */ |
| 171 |
add_action(ReCache::RECACHE_CRON_HOOK, [$reCacheModel, 'reCache']); |
| 172 |
add_action('update_option_jch-optimize_settings', [$this, 'respondToOptionUpdates']); |
| 173 |
} |
| 174 |
} |
| 175 |
|
| 176 |
public function runActivationRoutines(): void |
| 177 |
{ |
| 178 |
//Handles activation routines |
| 179 |
register_activation_hook(JCH_PLUGIN_FILE, [$this->installer, 'activate']); |
| 180 |
|
| 181 |
$mu_folder = ABSPATH . 'wp-content/mu-plugins'; |
| 182 |
|
| 183 |
if (defined('WPMU_PLUGIN_DIR') && WPMU_PLUGIN_DIR) { |
| 184 |
$mu_folder = WPMU_PLUGIN_DIR; |
| 185 |
} |
| 186 |
|
| 187 |
if ( |
| 188 |
!file_exists(JCH_PLUGIN_DIR . 'dir.php') |
| 189 |
|| (JCH_PRO && is_admin() |
| 190 |
&& !file_exists($mu_folder . '/jch-optimize-mode-switcher.php')) |
| 191 |
) { |
| 192 |
$this->installer->activate(); |
| 193 |
} |
| 194 |
} |
| 195 |
|
| 196 |
public function respondToOptionUpdates($old): void |
| 197 |
{ |
| 198 |
$new = get_option('jch-optimize_settings'); |
| 199 |
$container = $this->getContainer(); |
| 200 |
|
| 201 |
if (JCH_PRO) { |
| 202 |
if ($this->params->get('recache_frequency') != '0') { |
| 203 |
/** @see ReCache::reSchedule() */ |
| 204 |
$container->get(ReCache::class)->reSchedule(); |
| 205 |
} |
| 206 |
|
| 207 |
$pageCacheSettings = [ |
| 208 |
'cache_enable', |
| 209 |
Settings::CACHE_PLATFORM, |
| 210 |
Settings::CAPTURE_CACHE_ENABLE, |
| 211 |
Settings::PAGE_CACHE_BROWSER_CACHING, |
| 212 |
Settings::PAGE_CACHE_LIFETIME |
| 213 |
]; |
| 214 |
|
| 215 |
$updateHtaccess = false; |
| 216 |
|
| 217 |
foreach ($pageCacheSettings as $pageCacheSetting) { |
| 218 |
if ($old[$pageCacheSetting] != $new[$pageCacheSetting]) { |
| 219 |
$this->params->set($pageCacheSetting, $new[$pageCacheSetting]); |
| 220 |
$updateHtaccess = true; |
| 221 |
} |
| 222 |
} |
| 223 |
|
| 224 |
if ($updateHtaccess) { |
| 225 |
$container->get(CaptureCache::class)->updateHtaccess(); |
| 226 |
} |
| 227 |
} |
| 228 |
} |
| 229 |
|
| 230 |
protected function loadProUpdater(): void |
| 231 |
{ |
| 232 |
$this->updater->load(); |
| 233 |
} |
| 234 |
|
| 235 |
/** |
| 236 |
* @throws ExceptionInterface |
| 237 |
*/ |
| 238 |
public function initializeCache(): void |
| 239 |
{ |
| 240 |
$this->pageCache->initialize(); |
| 241 |
$this->pageCache->outputPageCache(); |
| 242 |
} |
| 243 |
|
| 244 |
public function runOptimize(string $html): string |
| 245 |
{ |
| 246 |
if (!Helper::validateHtml($html)) { |
| 247 |
return $html; |
| 248 |
} |
| 249 |
|
| 250 |
try { |
| 251 |
$this->pageCache->initialize(); |
| 252 |
|
| 253 |
$disableBrowserCaching = $this->pageCache->shouldDisableBrowserCaching(); |
| 254 |
|
| 255 |
if ($disableBrowserCaching) { |
| 256 |
$this->setNoCacheHeaders(); |
| 257 |
} |
| 258 |
|
| 259 |
$disableOptimizing = apply_filters('jch_optimize_no_optimize', false); |
| 260 |
$disableLoggedInUsers = (bool)$this->params->get('disable_logged_in_users', '1'); |
| 261 |
|
| 262 |
//Need to call Utility::isGuest after init has been called |
| 263 |
if ($disableOptimizing || ($disableLoggedInUsers && !$this->utility->isGuest())) { |
| 264 |
return $html; |
| 265 |
} |
| 266 |
|
| 267 |
gc_collect_cycles(); |
| 268 |
$container = ContainerFactory::create(); |
| 269 |
$optimize = $container->get(Optimize::class); |
| 270 |
|
| 271 |
if (!$disableBrowserCaching && $this->pageCache->getCachingEnabled()) { |
| 272 |
if ($this->params->isEnabled(Settings::PAGE_CACHE_BROWSER_CACHING)) { |
| 273 |
$this->setBrowserCachingHeaders(); |
| 274 |
} else { |
| 275 |
$this->setNoCacheHeaders(); |
| 276 |
} |
| 277 |
} |
| 278 |
|
| 279 |
$optimizedHtml = $optimize->process($html); |
| 280 |
|
| 281 |
//required for compatibility with Hide My WP Ghost |
| 282 |
/** @see https://wordpress.org/support/topic/compatibility-with-hide-my-wp-ghost/ */ |
| 283 |
/** @var string $optimizedHtml */ |
| 284 |
$optimizedHtml = apply_filters('jch_optimize_save_content', $optimizedHtml); |
| 285 |
do_action('jch_optimize_send_headers'); |
| 286 |
|
| 287 |
if (http_response_code() === 200) { |
| 288 |
$this->pageCache->store($optimizedHtml); |
| 289 |
} |
| 290 |
} catch (Exception $e) { |
| 291 |
$this->logger->log(LogLevel::ERROR, (string)$e); |
| 292 |
|
| 293 |
$optimizedHtml = $html; |
| 294 |
} |
| 295 |
|
| 296 |
return $optimizedHtml; |
| 297 |
} |
| 298 |
|
| 299 |
private function setBrowserCachingHeaders(): void |
| 300 |
{ |
| 301 |
$lifetime = max(0, (int)$this->params->get(Settings::PAGE_CACHE_LIFETIME, '900')); |
| 302 |
header('Cache-Control: public, max-age=' . $lifetime, true); |
| 303 |
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $lifetime) . ' GMT', true); |
| 304 |
} |
| 305 |
|
| 306 |
private function setNoCacheHeaders(): void |
| 307 |
{ |
| 308 |
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0', true); |
| 309 |
header('Pragma: no-cache', true); |
| 310 |
header('Expires: 0', true); |
| 311 |
} |
| 312 |
|
| 313 |
public function loadPluginTextDomain(): void |
| 314 |
{ |
| 315 |
load_plugin_textdomain('jch-optimize', false, dirname(plugin_basename(JCH_PLUGIN_FILE)) . '/languages'); |
| 316 |
} |
| 317 |
|
| 318 |
public function orderPlugin(): bool |
| 319 |
{ |
| 320 |
$active_plugins = (array)get_option('active_plugins', []); |
| 321 |
$order = [ |
| 322 |
'jch-optimize/jch-optimize.php', |
| 323 |
]; |
| 324 |
|
| 325 |
//Get the plugins in $order that are currently activated |
| 326 |
$order_short_list = array_intersect($order, $active_plugins); |
| 327 |
//Remove plugins in $order_short_list from list of activated plugins |
| 328 |
$active_plugins_slist = array_diff($active_plugins, $order_short_list); |
| 329 |
//Merge $order with $active_plugins_list |
| 330 |
$ordered_active_plugins = array_merge($order_short_list, $active_plugins_slist); |
| 331 |
|
| 332 |
update_option('active_plugins', $ordered_active_plugins); |
| 333 |
|
| 334 |
return true; |
| 335 |
} |
| 336 |
|
| 337 |
public function getPageCacheHash(array $parts): array |
| 338 |
{ |
| 339 |
if (wp_is_mobile()) { |
| 340 |
$parts[] = '_MOBILE_'; |
| 341 |
} |
| 342 |
|
| 343 |
return $parts; |
| 344 |
} |
| 345 |
|
| 346 |
public function addMenuToAdminBar(WP_Admin_Bar $admin_bar): void |
| 347 |
{ |
| 348 |
if (!current_user_can('manage_options')) { |
| 349 |
return; |
| 350 |
} |
| 351 |
|
| 352 |
$nodes = $admin_bar->get_nodes(); |
| 353 |
|
| 354 |
if (!isset($nodes['jch-optimize-parent'])) { |
| 355 |
$aArgs = [ |
| 356 |
'id' => 'jch-optimize-parent', |
| 357 |
'title' => __('JCH Optimize', 'jch-optimize') |
| 358 |
]; |
| 359 |
|
| 360 |
$admin_bar->add_node($aArgs); |
| 361 |
} |
| 362 |
|
| 363 |
$aArgs = [ |
| 364 |
'parent' => 'jch-optimize-parent', |
| 365 |
'id' => 'jch-optimize-profiler', |
| 366 |
'title' => __('Profiler', 'jch-optimize'), |
| 367 |
]; |
| 368 |
|
| 369 |
$admin_bar->add_node($aArgs); |
| 370 |
} |
| 371 |
} |
| 372 |
|