PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.3.0
ShopBuilder – WooCommerce Builder For Elementor v3.3.0
3.4.2 3.4.1 3.4.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 All 63 releases
shopbuilder / app / Models / AssetBundler.php

AssetBundler.php in ShopBuilder – WooCommerce Builder For Elementor 3.3.0, at app/Models/AssetBundler.php

252 lines 5.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * AssetBundler class.
4 *
5 * Dynamically merges and minifies CSS/JS files into a single versioned file stored in uploads.
6 *
7 * @package RadiusTheme\SB
8 * @since 1.0.0
9 */
10
11 namespace RadiusTheme\SB\Models;
12
13 use MatthiasMullie\Minify\JS;
14 use MatthiasMullie\Minify\CSS;
15
16 // Do not allow directly accessing this file.
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit( 'This script cannot be accessed directly.' );
19 }
20
21 /**
22 * AssetBundler class.
23 */
24 class AssetBundler {
25 /**
26 * Handle for this bundle (used in filename/versioning).
27 *
28 * @var string
29 */
30 protected $handle;
31
32 /**
33 * List of source file paths to bundle.
34 *
35 * @var array
36 */
37 protected $source_files = [];
38
39 /**
40 * Directory where a minified bundle will be saved.
41 *
42 * @var string
43 */
44 protected $upload_dir;
45
46 /**
47 * Generated filename of the minified JS file.
48 *
49 * @var string
50 */
51 protected $filename;
52
53 /**
54 * File type (JS or CSS).
55 *
56 * @var string
57 */
58 protected $type;
59
60 /**
61 * AssetBundler constructor.
62 *
63 * @param string $handle Unique identifier for the bundle.
64 * @param array $files List of absolute file paths to include in the bundle.
65 * @param string $type File type (JS or CSS).
66 *
67 * @return void
68 */
69 public function __construct( string $handle, array $files, string $type = 'js' ) {
70 $this->handle = sanitize_key( $handle );
71 $this->source_files = $files;
72 $this->type = in_array( $type, [ 'js', 'css' ], true ) ? $type : 'js';
73
74 $upload = wp_upload_dir();
75 $this->upload_dir = trailingslashit( $upload['basedir'] ) . "shopbuilder_uploads/cache/$this->type/";
76
77 $this->maybe_create_cache_dir();
78 }
79
80 /**
81 * Creates the cache directory if it doesn't exist.
82 *
83 * @return void
84 */
85 protected function maybe_create_cache_dir() {
86 if ( ! file_exists( $this->upload_dir ) ) {
87 wp_mkdir_p( $this->upload_dir );
88 }
89
90 if ( ! wp_is_writable( $this->upload_dir ) ) {
91 global $wp_filesystem;
92
93 if ( ! function_exists( 'WP_Filesystem' ) ) {
94 require_once ABSPATH . 'wp-admin/includes/file.php';
95 }
96
97 if ( ! $wp_filesystem ) {
98 WP_Filesystem();
99 }
100
101 if ( $wp_filesystem ) {
102 $wp_filesystem->chmod( $this->upload_dir, FS_CHMOD_DIR );
103 }
104 }
105 }
106
107 /**
108 * Generates the full path to the cached JS file based on handle and file hash.
109 *
110 * @return string
111 */
112 protected function get_cache_file_path() {
113 $this->filename = $this->generate_filename();
114
115 return $this->upload_dir . $this->filename;
116 }
117
118 /**
119 * Checks if the cached version of the JS file already exists.
120 *
121 * @return bool
122 */
123 protected function is_cached() {
124 return file_exists( $this->get_cache_file_path() );
125 }
126
127 /**
128 * Builds the minified JS bundle if not cached and returns the URL.
129 *
130 * @return string
131 */
132 public function build() {
133 $cache_path = $this->get_cache_file_path();
134
135 if ( ! $this->is_cached() ) {
136 if ( ! wp_is_writable( $this->upload_dir ) ) {
137 return '';
138 }
139
140 $minifier = ( 'css' === $this->type ) ? new CSS() : new JS();
141
142 foreach ( $this->source_files as $file ) {
143 $this->safely_add_file_to_minifier( $file, $minifier );
144 }
145
146 try {
147 $minifier->minify( $cache_path );
148 } catch ( \Exception $e ) {
149 error_log( '[ShopBuilder Minify] ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
150
151 return '';
152 }
153 }
154
155 return $this->get_url();
156 }
157
158 /**
159 * Returns the URL to the cached JS bundle.
160 *
161 * @return string
162 */
163 public function get_url() {
164 $upload = wp_upload_dir();
165
166 return trailingslashit( $upload['baseurl'] ) . "shopbuilder_uploads/cache/$this->type/" . $this->filename;
167 }
168
169 /**
170 * Clears all cached files related to this bundle handle.
171 *
172 * @return void
173 */
174 public function clear_cache() {
175 foreach ( glob( $this->upload_dir . "$this->handle-*.min.$this->type" ) as $file ) {
176 if ( file_exists( $file ) ) {
177 wp_delete_file( $file );
178 }
179 }
180 }
181
182 /**
183 * Generates the filename for the minified JS file.
184 *
185 * @return string
186 */
187 protected function generate_filename() {
188 $hash = md5( $this->handle . wp_json_encode( $this->source_files ) );
189
190 return "$this->handle-$hash.min.$this->type";
191 }
192
193 /**
194 * Rewrites relative font URLs in CSS to absolute plugin URLs.
195 *
196 * @param string $css The CSS content.
197 * @param string $base_path The base URL for resolving relative paths.
198 * @return string Modified CSS content with absolute font URLs.
199 */
200 protected function fix_font_urls( string $css, string $base_path ) {
201 $base_path = trailingslashit( $base_path );
202
203 return preg_replace_callback(
204 '/url\((["\']?)(?!https?:\/\/|\/)([^)]+?\.(?:eot|woff2?|ttf|svg|otf))(\?[^)]+)?\1\)/i',
205 function ( $matches ) use ( $base_path ) {
206 $relative_path = $matches[2];
207 $query = isset( $matches[3] ) ? $matches[3] : '';
208 $absolute_url = $base_path . ltrim( $relative_path, './\\' );
209 return 'url("' . esc_url( $absolute_url . $query ) . '")';
210 },
211 $css
212 );
213 }
214
215 /**
216 * Safely add file contents or path to the minifier.
217 *
218 * @param string $file File path.
219 * @param object $minifier Minifier instance.
220 *
221 * @return void
222 */
223 private function safely_add_file_to_minifier( $file, $minifier ) {
224 if ( ! is_string( $file ) ) {
225 return;
226 }
227
228 if ( ! is_readable( $file ) ) {
229 error_log( "[ShopBuilder Minify] Skipping unreadable file: $file" ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
230
231 return;
232 }
233
234 if ( 'css' === $this->type ) {
235 $content = @file_get_contents( $file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
236
237 if ( false === $content ) {
238 error_log( "[ShopBuilder Minify] Failed to read file: $file" ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
239
240 return;
241 }
242
243 $base_url = dirname( plugins_url( '', $file ), 2 );
244 $processed = $this->fix_font_urls( $content, $base_url );
245
246 $minifier->add( $processed );
247 } else {
248 $minifier->add( $file );
249 }
250 }
251 }
252