PluginProbe
Gutenify – Visual Site Builder Blocks & Starter Templates / 1.8.0
Gutenify – Visual Site Builder Blocks & Starter Templates v1.8.0
1.9.0 1.8.0 1.7.0 1.6.6 1.6.5 1.6.4 trunk 0.0.1 0.0.2 0.0.3 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 All 74 releases
gutenify / core / inc / helpers / class-helpers.php

class-helpers.php in Gutenify – Visual Site Builder Blocks & Starter Templates 1.8.0, at core/inc/helpers/class-helpers.php

357 lines 11.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Helpers utility class for plugin-wide common functionality.
4 *
5 * @package gutenify
6 * @subpackage Utilities
7 * @since 1.0.0
8 */
9
10 namespace gutenify;
11
12 /**
13 * Prevent direct access to the file.
14 *
15 * Ensures this file is being loaded within the WordPress environment.
16 */
17 defined( 'ABSPATH' ) || exit;
18
19 /**
20 * Class Helpers
21 *
22 * Provides commonly used helper functions across the plugin.
23 */
24 class Helpers {
25
26 /**
27 * Returns an array of active block slugs, filtered for customization.
28 *
29 * @return array List of block slugs.
30 */
31 public static function active_blocks() {
32 $plugin_constants = self::plugin_constants();
33 $function_prefix = $plugin_constants['plugin_main_function_prefix'];
34
35 $blocks = array(
36 'templates-browser',
37 'advance-slide-item',
38 'advance-slider',
39 'buttons',
40 'button',
41 'container',
42 'content-toggle-item',
43 'faqs',
44 'gallery-carousel',
45 'grid',
46 'grid-item',
47 'icon',
48 'info-box',
49 'map',
50 'post-carousel',
51 'post-list',
52 'section-title',
53 'service',
54 'slide-item',
55 'slider',
56 'star-rating',
57 'team-member',
58 'team',
59 'testimonial',
60 'testimonials',
61 'wc-product-carousel',
62 'wc-product-list',
63 'notice-bar',
64 'countup-v2',
65 'icon-v2',
66 'countdown-timer',
67 'stacking-cards',
68 'stacking-card-item',
69 'marquee',
70 'marquee-item',
71 'term-thumbnail',
72 'advanced-search',
73 'single-product-gallery',
74 // 'back-to-top',
75 // 'image-marquee',
76 // 'image-marquee-item',
77 // 'search-toggle',
78 // 'search-toggle-container',
79
80 // Optional blocks (can be enabled via filters or config).
81 /**
82 * Depricated.
83 */
84 'countup',
85 // 'link-wrapper',
86 // 'masonry',
87 // 'custom-list',
88 // 'social-share',
89 // 'slider-v2',
90 );
91
92 // advanced-group is still in development; only enable it when WP_DEBUG is on.
93 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
94 $blocks[] = 'advanced-group';
95
96 // category-list and filter-sidebar: ported from an unrelated
97 // plugin's source ("Firefly Blocks Pro" by ffthemes) that was
98 // dropped into src/blocks wholesale — namespace/category/
99 // textdomain/asset paths rewritten to this framework's own
100 // conventions, PHP registration classes added, and fatal
101 // references to a nonexistent Firefly_Blocks_Pro_* class
102 // replaced with native WooCommerce/WP calls. Still gated
103 // behind WP_DEBUG — keep here until they've had a full
104 // editorial/QA pass (copy, icons, WC edge cases) rather than
105 // shipping to every site automatically. advanced-search and
106 // single-product-gallery share this same porting origin but
107 // have moved up into the always-active list above.
108 $blocks[] = 'category-list';
109 $blocks[] = 'filter-sidebar';
110 }
111
112 // These PRO-origin blocks ship in this lite framework but stay
113 // gated (PRO active, or the active theme opts in — see
114 // Helpers::is_pro_block_active(), checked by each block's own
115 // register_block()). Always included here rather than
116 // conditionally: active_blocks() runs at plugin-file-load time,
117 // before the active theme's own after_setup_theme callback has
118 // registered its add_theme_support() calls, so a theme-support
119 // check isn't reliable this early. Including them unconditionally
120 // only means each block's class file loads (cheap — just defines
121 // the class and an `init`-hooked callback); the real gate is
122 // checked later, on `init`, once both PRO's activation state and
123 // the theme's declared support are reliably known.
124 $blocks[] = 'popup';
125 $blocks[] = 'hover-card';
126 $blocks[] = 'hover-card-normal';
127 $blocks[] = 'hover-card-hover';
128 $blocks[] = 'wc-product-images-slider';
129
130 return apply_filters( "{$function_prefix}_active_blocks", $blocks );
131 }
132
133 /**
134 * Returns plugin constants such as slugs, URLs, versions.
135 *
136 * @return array Associative array of plugin constants.
137 */
138 public static function plugin_constants() {
139 $defaults = array();
140
141 // Filters allow other components or themes to define these constants.
142 $filter_name = 'gutenify_plugin_constants';
143 return apply_filters( $filter_name, $defaults );
144 }
145
146 /**
147 * Reads the asset file generated by wp-scripts and returns dependencies/version.
148 *
149 * @param string $path Path to the asset file.
150 * @return array Array containing 'dependencies' and 'version'.
151 */
152 public static function asset_file_values( $path ) {
153 $plugin_constants = self::plugin_constants();
154 $version = $plugin_constants['plugin_main_version'];
155
156 if ( file_exists( $path ) ) {
157 return include $path;
158 }
159
160 // Fallback if asset file doesn't exist.
161 return array(
162 'dependencies' => array(),
163 'version' => $version,
164 );
165 }
166
167 /**
168 * Returns the base URL of the plugin's core directory.
169 *
170 * @return string Trailing-slashed URL.
171 */
172 public static function core_base_url() {
173 $plugin_constants = self::plugin_constants();
174 return trailingslashit( $plugin_constants['core_base_url'] );
175 }
176
177 /**
178 * Returns the base filesystem path of the plugin's core directory.
179 *
180 * @return string Trailing-slashed directory path.
181 */
182 public static function core_base_dir() {
183 $plugin_constants = self::plugin_constants();
184 return trailingslashit( $plugin_constants['core_base_dir'] );
185 }
186
187 /**
188 * Checks whether the PRO version of the plugin is active.
189 *
190 * @return bool True if PRO version is active.
191 */
192 public static function is_pro_active() {
193 $constants = self::plugin_constants();
194 $plugin_main_function_prefix = $constants['plugin_main_function_prefix'];
195 return apply_filters( $plugin_main_function_prefix . '_pro_activation_status', false );
196 }
197
198 /**
199 * Whether a PRO-origin block hosted in this lite framework should
200 * register: either the PRO plugin is loaded, or the active theme has
201 * opted in via `add_theme_support( '{plugin_main_slug}-pro-blocks' )` —
202 * one flag shared by every block that has made this PRO-to-lite move,
203 * not a per-block flag. Callers must check this on `init` or later
204 * (never from Helpers::active_blocks(), which runs at plugin-file-load
205 * time): by `init`, every active plugin has finished loading (so
206 * `function_exists()` here is reliable regardless of plugin load
207 * order) and the active theme's own `after_setup_theme` callback has
208 * already run (so its `add_theme_support()` call, if any, is visible
209 * to `current_theme_supports()`).
210 *
211 * @return bool
212 */
213 public static function is_pro_block_active() {
214 $constants = self::plugin_constants();
215 $plugin_main_function_prefix = $constants['plugin_main_function_prefix'];
216 $plugin_main_slug = $constants['plugin_main_slug'];
217 return function_exists( $plugin_main_function_prefix . '_pro' ) || current_theme_supports( $plugin_main_slug . '-pro-blocks' );
218 }
219
220 /**
221 * Finds every remote image URL in content matching either rule
222 * `sideload_remote_images()` acts on. Split out so callers that only
223 * need to know "are there any left" (e.g. the background import queue
224 * deciding whether a retry is needed) don't have to duplicate the
225 * pattern logic.
226 *
227 * @param string $content Raw content (HTML + block comments).
228 * @param string $remote_source See sideload_remote_images().
229 * @return string[] Unique matched URLs, empty array if none.
230 */
231 private static function find_remote_image_urls( $content, $remote_source ) {
232 $patterns = array(
233 // Any host, but a standard uploads/ path (offloaded media / CDN).
234 '#https?://[^\s"\'\\\\)]+/wp-content/uploads/[^\s"\'\\\\)]+\.(?:png|jpe?g|gif|webp|svg)#i',
235 );
236
237 $remote_host = wp_parse_url( $remote_source, PHP_URL_HOST );
238 $remote_host = $remote_host ? $remote_host : $remote_source;
239
240 if ( $remote_host ) {
241 $host_parts = explode( '.', $remote_host );
242 $root_domain = implode( '.', array_slice( $host_parts, -2 ) );
243
244 if ( $root_domain ) {
245 // Same registrable domain as the source, any path (theme assets, etc).
246 $patterns[] = '#https?://[a-z0-9.-]*' . preg_quote( $root_domain, '#' ) . '/[^\s"\'\\\\)]+\.(?:png|jpe?g|gif|webp|svg)#i';
247 }
248 }
249
250 $urls = array();
251 foreach ( $patterns as $pattern ) {
252 preg_match_all( $pattern, (string) $content, $matches );
253 $urls = array_merge( $urls, $matches[0] );
254 }
255
256 return array_unique( $urls );
257 }
258
259 /**
260 * Whether content still has at least one image matching
261 * sideload_remote_images()'s rules — i.e. one that either failed to
262 * download on a previous pass, or was never processed at all.
263 *
264 * @param string $content Raw content (HTML + block comments).
265 * @param string $remote_source See sideload_remote_images().
266 * @return bool
267 */
268 public static function has_remote_images( $content, $remote_source = '' ) {
269 return ! empty( self::find_remote_image_urls( $content, $remote_source ) );
270 }
271
272 /**
273 * Download every image referenced from a remote source (a single
274 * template/kit import, or a full demo-site import) into this site's own
275 * Media Library, and rewrite the content to point at the local copies
276 * instead of the remote site. Covers plain HTML (src="...",
277 * background-image:url(...)) as well as the same URL when it appears
278 * JSON-escaped inside a Gutenberg block comment's attributes (e.g.
279 * wp:gutenify/icon {"url":"https:\/\/...").
280 *
281 * Shared by the single-template importer (Gutenify_Rest::get_template_data())
282 * and the full demo-site importer (Rest_Demo_Importer_V2::create_post()) —
283 * both pull content from the same remote source and need the same fix.
284 *
285 * Two independent match rules cover both ways a demo/template source's
286 * media can be hosted, since either can appear in the same content:
287 * - Same domain as the source (any path) — a theme's own bundled
288 * assets, e.g. demo.gutenify.com/<slug>/wp-content/themes/<slug>/
289 * assets/images/main-banner.jpg, which never lives under uploads/.
290 * - Any host, but under a standard /wp-content/uploads/ path — covers
291 * media library uploads offloaded to a different host entirely via
292 * an offload plugin (e.g. WP Offload Media to S3 —
293 * demo-gutenify-com.s3.amazonaws.com/wp-content/uploads/...) or a CDN.
294 *
295 * @param string $content Raw content (HTML + block comments).
296 * @param string $remote_source A URL or bare domain identifying the
297 * remote source; only its registrable
298 * domain (last two labels) is used, so
299 * any subdomain of it (api., demo., a
300 * per-demo subdomain, etc.) is covered.
301 * @return string
302 */
303 public static function sideload_remote_images( $content, $remote_source = '' ) {
304 $urls = self::find_remote_image_urls( $content, $remote_source );
305
306 if ( empty( $urls ) ) {
307 return $content;
308 }
309
310 $current_host = wp_parse_url( home_url(), PHP_URL_HOST );
311
312 require_once ABSPATH . 'wp-admin/includes/media.php';
313 require_once ABSPATH . 'wp-admin/includes/file.php';
314 require_once ABSPATH . 'wp-admin/includes/image.php';
315
316 // Persists for the lifetime of the PHP process, so a single request
317 // that processes many posts sharing the same image (a logo/banner
318 // reused across pages during a full demo import) downloads it once.
319 static $already_sideloaded = array();
320
321 foreach ( $urls as $url ) {
322 // Already local (e.g. re-processing already-imported content) — skip.
323 if ( $current_host && wp_parse_url( $url, PHP_URL_HOST ) === $current_host ) {
324 continue;
325 }
326
327 if ( isset( $already_sideloaded[ $url ] ) ) {
328 $local_url = $already_sideloaded[ $url ];
329 } else {
330 $attachment_id = media_sideload_image( html_entity_decode( $url ), 0, null, 'id' );
331
332 if ( is_wp_error( $attachment_id ) ) {
333 continue;
334 }
335
336 $local_url = wp_get_attachment_url( $attachment_id );
337
338 if ( ! $local_url ) {
339 continue;
340 }
341
342 $already_sideloaded[ $url ] = $local_url;
343 }
344
345 $content = str_replace( $url, $local_url, $content );
346
347 $content = str_replace(
348 str_replace( '/', '\/', $url ),
349 str_replace( '/', '\/', $local_url ),
350 $content
351 );
352 }
353
354 return $content;
355 }
356 }
357