PageRenderTime 25ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/plugins/woocommerce/src/Internal/Admin/WCAdminAssets.php

https://github.com/woothemes/woocommerce
PHP | 382 lines | 234 code | 49 blank | 99 comment | 19 complexity | 72196fbdf290f68cdcc8a7d8cf8dc6fb MD5 | raw file
  1. <?php
  2. /**
  3. * Register the scripts, and styles used within WooCommerce Admin.
  4. */
  5. namespace Automattic\WooCommerce\Internal\Admin;
  6. use \_WP_Dependency;
  7. use Automattic\WooCommerce\Admin\Features\Features;
  8. use Automattic\WooCommerce\Admin\PageController;
  9. use Automattic\WooCommerce\Internal\Admin\Loader;
  10. /**
  11. * WCAdminAssets Class.
  12. */
  13. class WCAdminAssets {
  14. /**
  15. * Class instance.
  16. *
  17. * @var WCAdminAssets instance
  18. */
  19. protected static $instance = null;
  20. /**
  21. * Get class instance.
  22. */
  23. public static function get_instance() {
  24. if ( ! self::$instance ) {
  25. self::$instance = new self();
  26. }
  27. return self::$instance;
  28. }
  29. /**
  30. * Constructor.
  31. * Hooks added here should be removed in `wc_admin_initialize` via the feature plugin.
  32. */
  33. public function __construct() {
  34. Features::get_instance();
  35. add_action( 'admin_enqueue_scripts', array( $this, 'register_scripts' ) );
  36. add_action( 'admin_enqueue_scripts', array( $this, 'inject_wc_settings_dependencies' ), 14 );
  37. add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ), 15 );
  38. }
  39. /**
  40. * Gets the path for the asset depending on file type.
  41. *
  42. * @param string $ext File extension.
  43. * @return string Folder path of asset.
  44. */
  45. public static function get_path( $ext ) {
  46. return ( 'css' === $ext ) ? WC_ADMIN_DIST_CSS_FOLDER : WC_ADMIN_DIST_JS_FOLDER;
  47. }
  48. /**
  49. * Determines if a minified JS file should be served.
  50. *
  51. * @param boolean $script_debug Only serve unminified files if script debug is on.
  52. * @return boolean If js asset should use minified version.
  53. */
  54. public static function should_use_minified_js_file( $script_debug ) {
  55. // minified files are only shipped in non-core versions of wc-admin, return false if minified files are not available.
  56. if ( ! Features::exists( 'minified-js' ) ) {
  57. return false;
  58. }
  59. // Otherwise we will serve un-minified files if SCRIPT_DEBUG is on, or if anything truthy is passed in-lieu of SCRIPT_DEBUG.
  60. return ! $script_debug;
  61. }
  62. /**
  63. * Gets the URL to an asset file.
  64. *
  65. * @param string $file File name (without extension).
  66. * @param string $ext File extension.
  67. * @return string URL to asset.
  68. */
  69. public static function get_url( $file, $ext ) {
  70. $suffix = '';
  71. // Potentially enqueue minified JavaScript.
  72. if ( 'js' === $ext ) {
  73. $script_debug = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
  74. $suffix = self::should_use_minified_js_file( $script_debug ) ? '.min' : '';
  75. }
  76. return plugins_url( self::get_path( $ext ) . $file . $suffix . '.' . $ext, WC_ADMIN_PLUGIN_FILE );
  77. }
  78. /**
  79. * Gets the file modified time as a cache buster if we're in dev mode, or the plugin version otherwise.
  80. *
  81. * @param string $ext File extension.
  82. * @return string The cache buster value to use for the given file.
  83. */
  84. public static function get_file_version( $ext ) {
  85. if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
  86. return filemtime( WC_ADMIN_ABSPATH . self::get_path( $ext ) );
  87. }
  88. return WC_VERSION;
  89. }
  90. /**
  91. * Gets a script asset registry filename. The asset registry lists dependencies for the given script.
  92. *
  93. * @param string $script_path_name Path to where the script asset registry is contained.
  94. * @param string $file File name (without extension).
  95. * @return string complete asset filename.
  96. *
  97. * @throws \Exception Throws an exception when a readable asset registry file cannot be found.
  98. */
  99. public static function get_script_asset_filename( $script_path_name, $file ) {
  100. $minification_supported = Features::exists( 'minified-js' );
  101. $script_min_filename = $file . '.min.asset.php';
  102. $script_nonmin_filename = $file . '.asset.php';
  103. $script_asset_path = WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER . $script_path_name . '/';
  104. // Check minification is supported first, to avoid multiple is_readable checks when minification is
  105. // not supported.
  106. if ( $minification_supported && is_readable( $script_asset_path . $script_min_filename ) ) {
  107. return $script_min_filename;
  108. } elseif ( is_readable( $script_asset_path . $script_nonmin_filename ) ) {
  109. return $script_nonmin_filename;
  110. } else {
  111. // could not find an asset file, throw an error.
  112. throw new \Exception( 'Could not find asset registry for ' . $script_path_name );
  113. }
  114. }
  115. /**
  116. * Render a preload link tag for a dependency, optionally
  117. * checked against a provided allowlist.
  118. *
  119. * See: https://macarthur.me/posts/preloading-javascript-in-wordpress
  120. *
  121. * @param WP_Dependency $dependency The WP_Dependency being preloaded.
  122. * @param string $type Dependency type - 'script' or 'style'.
  123. * @param array $allowlist Optional. List of allowed dependency handles.
  124. */
  125. private function maybe_output_preload_link_tag( $dependency, $type, $allowlist = array() ) {
  126. if (
  127. (
  128. ! empty( $allowlist ) &&
  129. ! in_array( $dependency->handle, $allowlist, true )
  130. ) ||
  131. ( ! empty( $this->preloaded_dependencies[ $type ] ) &&
  132. in_array( $dependency->handle, $this->preloaded_dependencies[ $type ], true ) )
  133. ) {
  134. return;
  135. }
  136. $this->preloaded_dependencies[ $type ][] = $dependency->handle;
  137. $source = $dependency->ver ? add_query_arg( 'ver', $dependency->ver, $dependency->src ) : $dependency->src;
  138. echo '<link rel="preload" href="', esc_url( $source ), '" as="', esc_attr( $type ), '" />', "\n";
  139. }
  140. /**
  141. * Output a preload link tag for dependencies (and their sub dependencies)
  142. * with an optional allowlist.
  143. *
  144. * See: https://macarthur.me/posts/preloading-javascript-in-wordpress
  145. *
  146. * @param string $type Dependency type - 'script' or 'style'.
  147. * @param array $allowlist Optional. List of allowed dependency handles.
  148. */
  149. private function output_header_preload_tags_for_type( $type, $allowlist = array() ) {
  150. if ( 'script' === $type ) {
  151. $dependencies_of_type = wp_scripts();
  152. } elseif ( 'style' === $type ) {
  153. $dependencies_of_type = wp_styles();
  154. } else {
  155. return;
  156. }
  157. foreach ( $dependencies_of_type->queue as $dependency_handle ) {
  158. $dependency = $dependencies_of_type->query( $dependency_handle, 'registered' );
  159. if ( false === $dependency ) {
  160. continue;
  161. }
  162. // Preload the subdependencies first.
  163. foreach ( $dependency->deps as $sub_dependency_handle ) {
  164. $sub_dependency = $dependencies_of_type->query( $sub_dependency_handle, 'registered' );
  165. if ( $sub_dependency ) {
  166. $this->maybe_output_preload_link_tag( $sub_dependency, $type, $allowlist );
  167. }
  168. }
  169. $this->maybe_output_preload_link_tag( $dependency, $type, $allowlist );
  170. }
  171. }
  172. /**
  173. * Output preload link tags for all enqueued stylesheets and scripts.
  174. *
  175. * See: https://macarthur.me/posts/preloading-javascript-in-wordpress
  176. */
  177. private function output_header_preload_tags() {
  178. $wc_admin_scripts = array(
  179. WC_ADMIN_APP,
  180. 'wc-components',
  181. );
  182. $wc_admin_styles = array(
  183. WC_ADMIN_APP,
  184. 'wc-components',
  185. 'wc-material-icons',
  186. );
  187. // Preload styles.
  188. $this->output_header_preload_tags_for_type( 'style', $wc_admin_styles );
  189. // Preload scripts.
  190. $this->output_header_preload_tags_for_type( 'script', $wc_admin_scripts );
  191. }
  192. /**
  193. * Loads the required scripts on the correct pages.
  194. */
  195. public function enqueue_assets() {
  196. if ( ! PageController::is_admin_or_embed_page() ) {
  197. return;
  198. }
  199. wp_enqueue_script( WC_ADMIN_APP );
  200. wp_enqueue_style( WC_ADMIN_APP );
  201. wp_enqueue_style( 'wc-material-icons' );
  202. wp_enqueue_style( 'wc-onboarding' );
  203. // Preload our assets.
  204. $this->output_header_preload_tags();
  205. }
  206. /**
  207. * Registers all the neccessary scripts and styles to show the admin experience.
  208. */
  209. public function register_scripts() {
  210. if ( ! function_exists( 'wp_set_script_translations' ) ) {
  211. return;
  212. }
  213. $js_file_version = self::get_file_version( 'js' );
  214. $css_file_version = self::get_file_version( 'css' );
  215. $scripts = array(
  216. 'wc-explat',
  217. 'wc-experimental',
  218. 'wc-customer-effort-score',
  219. // NOTE: This should be removed when Gutenberg is updated and the notices package is removed from WooCommerce Admin.
  220. 'wc-notices',
  221. 'wc-number',
  222. 'wc-tracks',
  223. 'wc-date',
  224. 'wc-components',
  225. WC_ADMIN_APP,
  226. 'wc-csv',
  227. 'wc-store-data',
  228. 'wc-currency',
  229. 'wc-navigation',
  230. );
  231. $scripts_map = array(
  232. WC_ADMIN_APP => 'app',
  233. 'wc-csv' => 'csv-export',
  234. 'wc-store-data' => 'data',
  235. );
  236. $translated_scripts = array(
  237. 'wc-currency',
  238. 'wc-date',
  239. 'wc-components',
  240. 'wc-customer-effort-score',
  241. WC_ADMIN_APP,
  242. );
  243. foreach ( $scripts as $script ) {
  244. $script_path_name = isset( $scripts_map[ $script ] ) ? $scripts_map[ $script ] : str_replace( 'wc-', '', $script );
  245. try {
  246. $script_assets_filename = self::get_script_asset_filename( $script_path_name, 'index' );
  247. $script_assets = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER . $script_path_name . '/' . $script_assets_filename;
  248. wp_register_script(
  249. $script,
  250. self::get_url( $script_path_name . '/index', 'js' ),
  251. $script_assets ['dependencies'],
  252. $js_file_version,
  253. true
  254. );
  255. if ( in_array( $script, $translated_scripts, true ) ) {
  256. wp_set_script_translations( $script, 'woocommerce' );
  257. }
  258. } catch ( \Exception $e ) {
  259. // Avoid crashing WordPress if an asset file could not be loaded.
  260. wc_caught_exception( $e, __CLASS__ . '::' . __FUNCTION__, $script_path_name );
  261. }
  262. }
  263. wp_register_style(
  264. 'wc-components',
  265. self::get_url( 'components/style', 'css' ),
  266. array(),
  267. $css_file_version
  268. );
  269. wp_style_add_data( 'wc-components', 'rtl', 'replace' );
  270. wp_register_style(
  271. 'wc-customer-effort-score',
  272. self::get_url( 'customer-effort-score/style', 'css' ),
  273. array(),
  274. $css_file_version
  275. );
  276. wp_style_add_data( 'wc-customer-effort-score', 'rtl', 'replace' );
  277. wp_register_style(
  278. 'wc-experimental',
  279. self::get_url( 'experimental/style', 'css' ),
  280. array(),
  281. $css_file_version
  282. );
  283. wp_style_add_data( 'wc-experimental', 'rtl', 'replace' );
  284. wp_localize_script(
  285. WC_ADMIN_APP,
  286. 'wcAdminAssets',
  287. array(
  288. 'path' => plugins_url( self::get_path( 'js' ), WC_ADMIN_PLUGIN_FILE ),
  289. 'version' => $js_file_version,
  290. )
  291. );
  292. wp_register_style(
  293. WC_ADMIN_APP,
  294. self::get_url( 'app/style', 'css' ),
  295. array( 'wc-components', 'wc-customer-effort-score', 'wp-components', 'wc-experimental' ),
  296. $css_file_version
  297. );
  298. wp_style_add_data( WC_ADMIN_APP, 'rtl', 'replace' );
  299. wp_register_style(
  300. 'wc-onboarding',
  301. self::get_url( 'onboarding/style', 'css' ),
  302. array(),
  303. $css_file_version
  304. );
  305. wp_style_add_data( 'wc-onboarding', 'rtl', 'replace' );
  306. }
  307. /**
  308. * Injects wp-shared-settings as a dependency if it's present.
  309. */
  310. public function inject_wc_settings_dependencies() {
  311. if ( wp_script_is( 'wc-settings', 'registered' ) ) {
  312. $handles_for_injection = [
  313. 'wc-csv',
  314. 'wc-currency',
  315. 'wc-customer-effort-score',
  316. 'wc-navigation',
  317. // NOTE: This should be removed when Gutenberg is updated and
  318. // the notices package is removed from WooCommerce Admin.
  319. 'wc-notices',
  320. 'wc-number',
  321. 'wc-date',
  322. 'wc-components',
  323. 'wc-tracks',
  324. ];
  325. foreach ( $handles_for_injection as $handle ) {
  326. $script = wp_scripts()->query( $handle, 'registered' );
  327. if ( $script instanceof _WP_Dependency ) {
  328. $script->deps[] = 'wc-settings';
  329. }
  330. }
  331. }
  332. }
  333. }