Geen omschrijving

AssetCache.php 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. <?php
  2. /*******************************************************************************
  3. * Copyright (c) 2019, Code Atlantic LLC
  4. ******************************************************************************/
  5. if ( ! defined( 'ABSPATH' ) ) {
  6. exit;
  7. }
  8. class PUM_AssetCache {
  9. /**
  10. * @var
  11. */
  12. public static $cache_dir;
  13. /**
  14. * @var
  15. */
  16. public static $suffix;
  17. /**
  18. * @var
  19. */
  20. public static $asset_url;
  21. /**
  22. * @var
  23. */
  24. public static $js_url;
  25. /**
  26. * @var
  27. */
  28. public static $css_url;
  29. /**
  30. * @var bool
  31. */
  32. public static $disabled = true;
  33. /**
  34. * @var
  35. */
  36. public static $debug;
  37. public static $initialized = false;
  38. /**
  39. *
  40. */
  41. public static function init() {
  42. if ( ! self::$initialized ) {
  43. self::$cache_dir = self::get_cache_dir();
  44. self::$debug = Popup_Maker::debug_mode() || ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG );
  45. self::$suffix = self::$debug ? '' : '.min';
  46. self::$asset_url = Popup_Maker::$URL . 'assets/';
  47. self::$js_url = self::$asset_url . 'js/';
  48. self::$css_url = self::$asset_url . 'css/';
  49. if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
  50. self::$disabled = true;
  51. } else {
  52. self::$disabled = pum_get_option( 'disable_asset_caching', false );
  53. }
  54. add_action( 'pum_extension_updated', array( __CLASS__, 'reset_cache' ) );
  55. add_action( 'pum_extension_deactivated', array( __CLASS__, 'reset_cache' ) );
  56. add_action( 'pum_extension_activated', array( __CLASS__, 'reset_cache' ) );
  57. add_action( 'pum_regenerate_asset_cache', array( __CLASS__, 'reset_cache' ) );
  58. add_action( 'pum_save_settings', array( __CLASS__, 'reset_cache' ) );
  59. add_action( 'pum_save_popup', array( __CLASS__, 'reset_cache' ) );
  60. add_action( 'pum_save_theme', array( __CLASS__, 'reset_cache' ) );
  61. add_action( 'pum_update_core_version', array( __CLASS__, 'reset_cache' ) );
  62. if ( isset( $_GET['flush_popup_cache'] ) ) {
  63. add_action( 'init', array( __CLASS__, 'reset_cache' ) );
  64. }
  65. add_filter( 'pum_alert_list', array( __CLASS__, 'cache_alert' ) );
  66. add_action( 'pum_styles', array( __CLASS__, 'global_custom_styles' ) );
  67. if ( null === get_option( 'pum_files_writeable', null ) ) {
  68. add_option( 'pum_files_writeable', true );
  69. add_option( '_pum_writeable_notice_dismissed', true );
  70. pum_reset_assets();
  71. }
  72. if ( is_admin() && current_user_can( 'edit_posts' ) ) {
  73. add_action( 'init', array( __CLASS__, 'admin_notice_check' ) );
  74. }
  75. // Prevent reinitialization.
  76. self::$initialized = true;
  77. }
  78. }
  79. /**
  80. * Checks if Asset caching is possible and enabled.
  81. *
  82. * @return bool
  83. */
  84. public static function enabled() {
  85. if ( defined( 'PUM_ASSET_CACHE' ) && ! PUM_ASSET_CACHE ) {
  86. return false;
  87. }
  88. return self::writeable() && ! self::$disabled;
  89. }
  90. /**
  91. * Is the cache directory writeable?
  92. *
  93. * @return bool True if directory is writeable
  94. */
  95. public static function writeable() {
  96. if ( self::$disabled ) {
  97. return false;
  98. }
  99. // If we have already determined files to not be writeable, go ahead and return.
  100. if ( true != get_option( 'pum_files_writeable', true ) ) {
  101. return false;
  102. }
  103. global $wp_filesystem;
  104. if ( ! function_exists( 'WP_Filesystem' ) ) {
  105. require_once( ABSPATH . 'wp-admin/includes/file.php' );
  106. }
  107. $results = WP_Filesystem();
  108. if ( true !== $results ) {
  109. // Prevents this from running again and set to show the admin notice.
  110. update_option( 'pum_files_writeable', false );
  111. update_option( '_pum_writeable_notice_dismissed', false );
  112. if ( ! is_null( $results ) && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
  113. $error = $wp_filesystem->errors->get_error_message();
  114. PUM_Utils_Logging::instance()->log( sprintf( 'Cache directory is not writeable due to filesystem error. Error given: %s', esc_html( $error ) ) );
  115. } else {
  116. PUM_Utils_Logging::instance()->log( 'Cache directory is not writeable due to incorrect filesystem method.' );
  117. }
  118. return false;
  119. }
  120. // Checks and create cachedir.
  121. if ( false !== self::$cache_dir && ! is_dir( self::$cache_dir ) ) {
  122. /** @var WP_Filesystem_Base $wp_filesystem */
  123. $wp_filesystem->mkdir( self::$cache_dir );
  124. }
  125. return false !== self::$cache_dir && is_writable( self::$cache_dir ) && ! isset( $_POST['wp_customize'] );
  126. }
  127. /**
  128. * Regenerate cache on demand.
  129. */
  130. public static function regenerate_cache() {
  131. self::cache_js();
  132. self::cache_css();
  133. }
  134. /**
  135. * Gets the directory caching should be stored in.
  136. *
  137. * Accounts for various adblock bypass options.
  138. *
  139. * @return array|string
  140. */
  141. public static function get_cache_dir() {
  142. $upload_dir = PUM_Helpers::get_upload_dir_path();
  143. if ( false === $upload_dir ) {
  144. return false;
  145. }
  146. if ( ! pum_get_option( 'bypass_adblockers', false ) ) {
  147. return trailingslashit( $upload_dir ) . 'pum';
  148. }
  149. return $upload_dir;
  150. }
  151. /**
  152. * @param $filename
  153. *
  154. * @return string
  155. */
  156. public static function generate_cache_filename( $filename ) {
  157. if ( ! pum_get_option( 'bypass_adblockers', false ) ) {
  158. global $blog_id;
  159. $is_multisite = ( is_multisite() ) ? '-' . $blog_id : '';
  160. return $filename . $is_multisite;
  161. }
  162. $site_url = get_site_url();
  163. switch ( pum_get_option( 'adblock_bypass_url_method', 'random' ) ) {
  164. case 'random':
  165. $filename = md5( $site_url . $filename );
  166. break;
  167. case 'custom':
  168. $filename = preg_replace( '/[^a-z0-9]+/', '-', pum_get_option( 'adblock_bypass_custom_filename', 'pm-' . $filename ) );
  169. break;
  170. }
  171. return $filename;
  172. }
  173. /**
  174. * Generate JS cache file.
  175. */
  176. public static function cache_js() {
  177. if ( false === self::$cache_dir ) {
  178. return;
  179. }
  180. $js_file = self::generate_cache_filename( 'pum-site-scripts' ) . '.js';
  181. $js = "/**\n";
  182. $js .= " * Do not touch this file! This file created by the Popup Maker plugin using PHP\n";
  183. $js .= " * Last modified time: " . date( 'M d Y, h:i:s' ) . "\n";
  184. $js .= " */\n\n\n";
  185. $js .= self::generate_js();
  186. if ( ! self::cache_file( $js_file, $js ) ) {
  187. update_option( 'pum-has-cached-js', false );
  188. } else {
  189. update_option( 'pum-has-cached-js', strtotime( 'now' ) );
  190. }
  191. }
  192. /**
  193. * Generate CSS cache file.
  194. */
  195. public static function cache_css() {
  196. if ( false === self::$cache_dir ) {
  197. return;
  198. }
  199. $css_file = self::generate_cache_filename( 'pum-site-styles' ) . '.css';
  200. $css = "/**\n";
  201. $css .= " * Do not touch this file! This file created by the Popup Maker plugin using PHP\n";
  202. $css .= " * Last modified time: " . date( 'M d Y, h:i:s' ) . "\n";
  203. $css .= " */\n\n\n";
  204. $css .= self::generate_css();
  205. if ( ! self::cache_file( $css_file, $css ) ) {
  206. update_option( 'pum-has-cached-css', false );
  207. } else {
  208. update_option( 'pum-has-cached-css', strtotime( 'now' ) );
  209. }
  210. }
  211. /**
  212. * Generate custom JS
  213. *
  214. * @return string
  215. */
  216. public static function generate_js() {
  217. // Load core scripts so we can eliminate another stylesheet.
  218. $core_js = file_get_contents( Popup_Maker::$DIR . 'assets/js/site' . self::$suffix . '.js' );
  219. /**
  220. * 0 Core
  221. * 5 Extensions
  222. * 8 Integrations
  223. * 10 Per Popup JS
  224. */
  225. $js = array(
  226. 'core' => array(
  227. 'content' => $core_js,
  228. 'priority' => 0,
  229. ),
  230. );
  231. $popups = pum_get_all_popups();
  232. if ( ! empty( $popups ) ) {
  233. foreach ( $popups as $popup ) {
  234. // Set this popup as the global $current.
  235. pum()->current_popup = $popup;
  236. // Preprocess the content for shortcodes that need to enqueue their own assets.
  237. // PUM_Helpers::do_shortcode( $popup->post_content );
  238. ob_start();
  239. // Allow per popup JS additions.
  240. do_action( 'pum_generate_popup_js', $popup->ID );
  241. $popup_js = ob_get_clean();
  242. if ( ! empty( $popup_js ) ) {
  243. $js[ 'popup-' . $popup->ID ] = array(
  244. 'content' => $popup_js,
  245. );
  246. }
  247. }
  248. // Clear the global $current.
  249. pum()->current_popup = null;
  250. }
  251. $js = apply_filters( 'pum_generated_js', $js );
  252. foreach ( $js as $key => $code ) {
  253. $js[ $key ] = wp_parse_args( $code, array(
  254. 'content' => '',
  255. 'priority' => 10,
  256. ) );
  257. }
  258. uasort( $js, array( 'PUM_Helpers', 'sort_by_priority' ) );
  259. $js_code = '';
  260. foreach ( $js as $key => $code ) {
  261. if ( ! empty( $code['content'] ) ) {
  262. $js_code .= $code['content'] . "\n\n";
  263. }
  264. }
  265. return $js_code;
  266. }
  267. /**
  268. * Cache file contents.
  269. *
  270. * @param string $filename Filename of file to generate.
  271. * @param string $contents Contents to put into file.
  272. *
  273. * @return bool
  274. */
  275. public static function cache_file( $filename, $contents ) {
  276. if ( false === self::$cache_dir ) {
  277. return false;
  278. }
  279. if ( ! function_exists( 'WP_Filesystem' ) ) {
  280. require_once( ABSPATH . 'wp-admin/includes/file.php' );
  281. }
  282. $file = trailingslashit( self::$cache_dir ) . $filename;
  283. WP_Filesystem();
  284. /** @var WP_Filesystem_Base $wp_filesystem */
  285. global $wp_filesystem;
  286. $results = $wp_filesystem->put_contents( $file, $contents, defined( 'FS_CHMOD_FILE' ) ? FS_CHMOD_FILE : false );
  287. // If the file is generated and is accessible...
  288. if ( true === $results && self::is_file_accessible( $filename ) ) {
  289. return true;
  290. } else {
  291. // ... else, let's set our flags to prevent cache running again for now.
  292. update_option( 'pum_files_writeable', false );
  293. update_option( '_pum_writeable_notice_dismissed', false );
  294. return false;
  295. }
  296. }
  297. /**
  298. * Generate Custom Styles
  299. *
  300. * @return string
  301. */
  302. public static function generate_css() {
  303. // Include core styles so we can eliminate another stylesheet.
  304. $core_css = file_get_contents( Popup_Maker::$DIR . 'assets/css/pum-site' . (is_rtl() ? '-rtl' : '') . self::$suffix . '.css' );
  305. /**
  306. * 0 Core
  307. * 1 Popup Themes
  308. * 5 Extensions
  309. * 10 Per Popup CSS
  310. */
  311. $css = array(
  312. 'imports' => array(
  313. 'content' => self::generate_font_imports(),
  314. 'priority' => - 1,
  315. ),
  316. 'core' => array(
  317. 'content' => $core_css,
  318. 'priority' => 0,
  319. ),
  320. 'themes' => array(
  321. 'content' => self::generate_popup_theme_styles(),
  322. 'priority' => 1,
  323. ),
  324. 'popups' => array(
  325. 'content' => self::generate_popup_styles(),
  326. 'priority' => 15,
  327. ),
  328. 'custom' => array(
  329. 'content' => self::custom_css(),
  330. 'priority' => 20,
  331. ),
  332. );
  333. $css = apply_filters( 'pum_generated_css', $css );
  334. foreach ( $css as $key => $code ) {
  335. $css[ $key ] = wp_parse_args( $code, array(
  336. 'content' => '',
  337. 'priority' => 10,
  338. ) );
  339. }
  340. uasort( $css, array( 'PUM_Helpers', 'sort_by_priority' ) );
  341. $css_code = '';
  342. foreach ( $css as $key => $code ) {
  343. if ( ! empty( $code['content'] ) ) {
  344. $css_code .= $code['content'] . "\n\n";
  345. }
  346. }
  347. return $css_code;
  348. }
  349. public static function global_custom_styles() {
  350. if ( pum_get_option( 'adjust_body_padding' ) ) {
  351. echo "html.pum-open.pum-open-overlay.pum-open-scrollable body > *[aria-hidden] { padding-right: " . pum_get_option( 'body_padding_override', '15px' ) . "!important; }";
  352. }
  353. }
  354. /**
  355. * @return string
  356. */
  357. public static function generate_popup_styles() {
  358. $popup_css = '';
  359. $popups = pum_get_all_popups();
  360. if ( ! empty( $popups ) ) {
  361. foreach ( $popups as $popup ) {
  362. // Set this popup as the global $current.
  363. pum()->current_popup = $popup;
  364. // Preprocess the content for shortcodes that need to enqueue their own assets.
  365. // PUM_Helpers::do_shortcode( $popup->post_content );
  366. $popup = pum_get_popup( $popup->ID );
  367. if ( ! pum_is_popup( $popup ) ) {
  368. continue;
  369. }
  370. ob_start();
  371. if ( $popup->get_setting( 'zindex', false ) ) {
  372. $zindex = absint( $popup->get_setting( 'zindex' ) );
  373. echo "#pum-{$popup->ID} {z-index: $zindex}\r\n";
  374. }
  375. // Allow per popup CSS additions.
  376. do_action( 'pum_generate_popup_css', $popup->ID );
  377. $popup_css .= ob_get_clean();
  378. }
  379. // Clear the global $current.
  380. pum()->current_popup = null;
  381. }
  382. return $popup_css;
  383. }
  384. /**
  385. * Used when asset cache is not enabled.
  386. *
  387. * @return string
  388. */
  389. public static function inline_css() {
  390. ob_start();
  391. echo self::generate_font_imports();
  392. echo self::generate_popup_theme_styles();
  393. echo self::generate_popup_styles();
  394. // Render any extra styles globally added.
  395. if ( ! empty( $GLOBALS['pum_extra_styles'] ) ) {
  396. echo $GLOBALS['pum_extra_styles'];
  397. }
  398. // Allows rendering extra css via action.
  399. do_action( 'pum_styles' );
  400. return ob_get_clean();
  401. }
  402. /**
  403. * @return string
  404. */
  405. public static function custom_css() {
  406. // Reset ob.
  407. ob_start();
  408. // Render any extra styles globally added.
  409. if ( ! empty( $GLOBALS['pum_extra_styles'] ) ) {
  410. echo $GLOBALS['pum_extra_styles'];
  411. }
  412. // Allows rendering extra css via action.
  413. do_action( 'pum_styles' );
  414. return ob_get_clean();
  415. }
  416. /**
  417. * Generate Popup Theme Styles
  418. *
  419. * @return mixed|string
  420. */
  421. public static function generate_font_imports() {
  422. $imports = '';
  423. $google_fonts = array();
  424. foreach ( pum_get_all_themes() as $theme ) {
  425. $google_fonts = array_merge( $google_fonts, pum_get_theme( $theme->ID )->get_google_fonts_used() );
  426. }
  427. if ( ! empty( $google_fonts ) && ! pum_get_option( 'disable_google_font_loading', false ) ) {
  428. $link = "//fonts.googleapis.com/css?family=";
  429. foreach ( $google_fonts as $font_family => $variants ) {
  430. if ( $link != "//fonts.googleapis.com/css?family=" ) {
  431. $link .= "|";
  432. }
  433. $link .= $font_family;
  434. if ( is_array( $variants ) ) {
  435. if ( implode( ',', $variants ) != '' ) {
  436. $link .= ":";
  437. $link .= trim( implode( ',', $variants ), ':' );
  438. }
  439. }
  440. }
  441. $imports = "/* Popup Google Fonts */\r\n@import url('$link');\r\n\r\n" . $imports;
  442. }
  443. $imports = apply_filters( 'pum_generate_font_imports', $imports );
  444. return $imports;
  445. }
  446. /**
  447. * Generate Popup Theme Styles
  448. *
  449. * @return mixed|string
  450. */
  451. public static function generate_popup_theme_styles() {
  452. $styles = '';
  453. $themes = pum_get_all_themes();
  454. foreach ( $themes as $theme ) {
  455. $theme_styles = pum_get_rendered_theme_styles( $theme->ID );
  456. if ( $theme_styles != '' ) {
  457. $styles .= "/* Popup Theme " . $theme->ID . ": " . $theme->post_title . " */\r\n";
  458. $styles .= $theme_styles . "\r\n";
  459. }
  460. }
  461. $styles = apply_filters( 'popmake_theme_styles', $styles );
  462. $styles = apply_filters( 'pum_generate_popup_theme_styles', $styles );
  463. return $styles;
  464. }
  465. /**
  466. * Reset the cache to force regeneration.
  467. */
  468. public static function reset_cache() {
  469. update_option( 'pum-has-cached-css', false );
  470. update_option( 'pum-has-cached-js', false );
  471. }
  472. /**
  473. * Adds admin notice if the files are not writeable.
  474. *
  475. * @param array $alerts The alerts currently in the alert system.
  476. * @return array Alerts for the alert system.
  477. * @since 1.9.0
  478. */
  479. public static function cache_alert( $alerts ) {
  480. if ( self::should_not_show_alert() ) {
  481. return $alerts;
  482. }
  483. $undo_url = add_query_arg( 'pum_writeable_notice_check', 'undo' );
  484. $dismiss_url = add_query_arg( 'pum_writeable_notice_check', 'dismiss' );
  485. ob_start();
  486. ?>
  487. <ul>
  488. <li><a href="<?php echo esc_attr( $undo_url ); ?>"><strong><?php esc_html_e( 'Try to create cache again', 'popup-maker' ); ?></strong></a></li>
  489. <li><a href="<?php echo esc_attr( $dismiss_url ); ?>" class="pum-dismiss"><?php esc_html_e( 'Keep current method', 'popup-maker' ); ?></a></li>
  490. <li><a href="https://docs.wppopupmaker.com/article/521-debugging-filesystem-errors?utm_source=filesystem-error-alert&utm_medium=inline-doclink&utm_campaign=filesystem-error" target="_blank" rel="noreferrer noopener"><?php esc_html_e( 'Learn more', 'popup-maker' ); ?></a></li>
  491. </ul>
  492. <?php
  493. $html = ob_get_clean();
  494. $alerts[] = array(
  495. 'code' => 'pum_writeable_notice',
  496. 'type' => 'warning',
  497. 'message' => esc_html__( "Popup Maker detected an issue with your file system's ability and is unable to create & save cached assets for your popup styling and settings. This may lead to suboptimal performance. Please check your filesystem and contact your hosting provide to ensure Popup Maker can create and write to cache files.", 'popup-maker' ),
  498. 'html' => $html,
  499. 'priority' => 1000,
  500. 'dismissible' => '2 weeks',
  501. 'global' => true,
  502. );
  503. return $alerts;
  504. }
  505. /**
  506. * Checks if any options have been clicked from admin notices.
  507. *
  508. * @since 1.9.0
  509. */
  510. public static function admin_notice_check() {
  511. if ( isset( $_GET['pum_writeable_notice_check'] ) ) {
  512. // If either dismiss or try again button is clicked, hide the admin notice.
  513. update_option( '_pum_writeable_notice_dismissed', true );
  514. if ( 'undo' === $_GET['pum_writeable_notice_check'] ) {
  515. // If try again is clicked, remove flag.
  516. update_option( 'pum_files_writeable', true );
  517. } else {
  518. pum_update_option( 'disable_asset_caching', true );
  519. }
  520. }
  521. }
  522. /**
  523. * Whether or not we should show admin notice
  524. *
  525. * @since 1.9.0
  526. * @return bool True if notice should not be shown
  527. */
  528. public static function should_not_show_alert() {
  529. return true == get_option( 'pum_files_writeable', true ) || true == get_option( '_pum_writeable_notice_dismissed', true );
  530. }
  531. /**
  532. * Tests whether the file is accessible and returns 200 status code
  533. *
  534. * @param string $filename Filename of cache file to test.
  535. * @return bool True if file exists and is accessible
  536. */
  537. private static function is_file_accessible( $filename ) {
  538. if ( ! $filename || empty( $filename ) || ! is_string( $filename ) ) {
  539. PUM_Utils_Logging::instance()->log( 'Cannot check if file is accessible. Filename passed: ' . print_r( $filename, true ) );
  540. return false;
  541. }
  542. $cache_url = PUM_Helpers::get_cache_dir_url();
  543. if ( false === $cache_url ) {
  544. PUM_Utils_Logging::instance()->log( 'Cannot access cache file when tested. Cache URL returned false.' );
  545. }
  546. $protocol = is_ssl() ? 'https:' : 'http:';
  547. $file = $protocol . $cache_url . '/' . $filename;
  548. $results = wp_remote_request( $file, array(
  549. 'method' => 'HEAD',
  550. 'sslverify' => false,
  551. ));
  552. // If it returned a WP_Error, let's log its error message.
  553. if ( is_wp_error( $results ) ) {
  554. $error = $results->get_error_message();
  555. PUM_Utils_Logging::instance()->log( sprintf( 'Cannot access cache file when tested. Tested file: %s Error given: %s', esc_html( $file ), esc_html( $error ) ) );
  556. }
  557. // If it returned valid array...
  558. if ( is_array( $results ) && isset( $results['response'] ) ) {
  559. $status_code = $results['response']['code'];
  560. // ... then, check if it's a valid status code. Only if it is a valid 2XX code, will this method return true.
  561. if ( false !== $status_code && ( 200 <= $status_code && 300 > $status_code ) ) {
  562. return true;
  563. } else {
  564. PUM_Utils_Logging::instance()->log( sprintf( 'Cannot access cache file when tested. Status code received was: %s', esc_html( $status_code ) ) );
  565. }
  566. }
  567. return false;
  568. }
  569. }