PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.13.0
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.13.0
2.13.0 2.13.1 2.12.0 2.11.1 2.11.0 2.10.0 2.9.0 2.7.4 2.7.5 2.7.6 2.7.7 2.8.0 2.8.1 2.9.1 trunk 1.0 1.0-beta1 1.0-beta2 1.0-beta3 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 All 80 releases
← All changes | includes/helper.php +257 -8 2.11.02.13.0 View file →
@@ -10,8 +10,11 @@
10 10 class Helper {
11 11
12 12 use Importer;
13 13
14 + /** Memoized responsive device list (see get_responsive_devices). */
15 + private static $responsive_devices_cache = null;
16 +
14 17 public static function get_time() {
15 18 return time() + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS );
16 19 }
17 20
@@ -24,8 +27,238 @@
24 27
25 28 return $default;
26 29 }
27 30
31 + /**
32 + * Responsive breakpoint widths (px) for the whole plugin. User-configurable
33 + * via Settings; defaults preserve the historical 800/480 values. Every CSS
34 + * generator and the block editor reads these so breakpoints stay in sync.
35 + */
36 + /**
37 + * Make one CSS property or declaration value safe to write into a stylesheet.
38 + *
39 + * Block attributes reach the compiled CSS verbatim and that CSS is echoed
40 + * inside a `<style>` element, so a value carrying `</style>` closes the
41 + * element and everything after it is parsed as HTML — a stored XSS
42 + * available to anyone who can set a block attribute, which includes a
43 + * Contributor editing their own draft. `{` and `}` are the same problem one
44 + * level down: they close the rule and let the value choose its own
45 + * selector.
46 + *
47 + * Only those four characters are removed. A declaration value never needs
48 + * them, and everything a real one does need survives: data URIs (which
49 + * carry `;`), gradients, `calc()` and `var()` (parentheses and commas),
50 + * font stacks (quotes), `content` escapes (backslashes), and shorthand
51 + * slashes such as `font: 12px/1.5`.
52 + *
53 + * `;` is deliberately kept. With the braces gone, an injected `;` can only
54 + * add declarations to the same rule — which targets the block's own
55 + * element, exactly what its style controls already allow — so removing it
56 + * would break data URIs to buy nothing.
57 + *
58 + * @param mixed $value A CSS property name or declaration value.
59 + * @return string The value with the escape characters removed.
60 + */
61 + public static function esc_css_value( $value ) {
62 + if ( ! is_scalar( $value ) ) {
63 + return '';
64 + }
65 + return str_replace( [ '<', '>', '{', '}' ], '', (string) $value );
66 + }
67 +
68 + public static function get_breakpoints() {
69 + $tablet = (int) self::get_settings( 'breakpoint_tablet', 800 );
70 + $mobile = (int) self::get_settings( 'breakpoint_mobile', 480 );
71 +
72 + // Guard against nonsensical config (mobile must be below tablet).
73 + if ( $tablet < 1 ) {
74 + $tablet = 800;
75 + }
76 + if ( $mobile < 1 || $mobile >= $tablet ) {
77 + $mobile = min( 480, $tablet - 1 );
78 + }
79 +
80 + return array(
81 + 'tablet' => $tablet,
82 + 'mobile' => $mobile,
83 + );
84 + }
85 +
86 + /**
87 + * The full ordered list of responsive devices the atomic style system emits
88 + * for: the base (Desktop, width 0 = no media query), the two built-in
89 + * breakpoints, then any user-registered custom breakpoints. Each entry:
90 + * id - stable identifier (also the WP device name for the built-ins)
91 + * label - shown in the editor device switcher
92 + * suffix - appended to responsive attribute keys (e.g. fontSize + suffix)
93 + * width - max-width px for the @media rule (0 = base, no media query)
94 + *
95 + * Ordering is load-bearing, not cosmetic: the base comes first and every
96 + * other device follows widest-first, so narrower breakpoints emit later and
97 + * win in `cascade` mode. Precedence therefore follows the breakpoint's own
98 + * bounds rather than the order a custom breakpoint happened to be
99 + * registered in.
100 + */
101 + public static function get_responsive_devices() {
102 + if ( null !== self::$responsive_devices_cache ) {
103 + return self::$responsive_devices_cache;
104 + }
105 +
106 + $bp = self::get_breakpoints();
107 + // Built-ins are max-width only (min 0). `width` is the sort key
108 + // (max-width, or a large value for min-only so it sorts widest).
109 + $devices = array(
110 + array( 'id' => 'Desktop', 'label' => 'Desktop', 'suffix' => '', 'width' => 0, 'min' => 0, 'max' => 0 ),
111 + array( 'id' => 'Tablet', 'label' => 'Tablet', 'suffix' => 'Tablet', 'width' => $bp['tablet'], 'min' => 0, 'max' => $bp['tablet'] ),
112 + array( 'id' => 'Mobile', 'label' => 'Mobile', 'suffix' => 'Mobile', 'width' => $bp['mobile'], 'min' => 0, 'max' => $bp['mobile'] ),
113 + );
114 +
115 + $custom = self::get_settings( 'breakpoint_custom', array() );
116 + if ( is_array( $custom ) ) {
117 + foreach ( $custom as $c ) {
118 + $c = (array) $c;
119 + // Advanced breakpoints support a min and/or max width. `width` is
120 + // kept as a legacy alias for max-width.
121 + $max = isset( $c['maxWidth'] ) ? (int) $c['maxWidth'] : ( isset( $c['width'] ) ? (int) $c['width'] : 0 );
122 + $min = isset( $c['minWidth'] ) ? (int) $c['minWidth'] : 0;
123 + if ( $max < 1 && $min < 1 ) {
124 + continue; // needs at least one bound
125 + }
126 + // Stable, alphanumeric suffix so stored values survive label edits.
127 + $key = ! empty( $c['key'] ) ? preg_replace( '/[^a-zA-Z0-9]/', '', $c['key'] ) : (string) ( $max ? $max : $min );
128 + $suffix = 'Bp' . ucfirst( $key );
129 + $label = ! empty( $c['label'] ) ? $c['label'] : self::breakpoint_auto_label( $min, $max );
130 + $devices[] = array(
131 + 'id' => $suffix,
132 + 'label' => $label,
133 + 'suffix' => $suffix,
134 + 'width' => $max > 0 ? $max : 999999, // sort key (min-only = widest)
135 + 'min' => $min,
136 + 'max' => $max,
137 + );
138 + }
139 + }
140 +
141 + self::$responsive_devices_cache = self::sort_responsive_devices( $devices );
142 + return self::$responsive_devices_cache;
143 + }
144 +
145 + /**
146 + * Base first, then widest-first. Ties break on id so the order is stable
147 + * regardless of the PHP version's sort stability.
148 + */
149 + private static function sort_responsive_devices( $devices ) {
150 + $base = array();
151 + $rest = array();
152 + foreach ( $devices as $d ) {
153 + if ( empty( $d['min'] ) && empty( $d['max'] ) ) {
154 + $base[] = $d;
155 + } else {
156 + $rest[] = $d;
157 + }
158 + }
159 +
160 + usort(
161 + $rest,
162 + function ( $a, $b ) {
163 + $cmp = (int) $b['width'] - (int) $a['width'];
164 + return 0 !== $cmp ? $cmp : strcmp( (string) $a['id'], (string) $b['id'] );
165 + }
166 + );
167 +
168 + return array_merge( $base, $rest );
169 + }
170 +
171 + /** Drop the memoized device list (settings changed mid-request). */
172 + public static function flush_responsive_devices_cache() {
173 + self::$responsive_devices_cache = null;
174 + }
175 +
176 + /** A readable fallback label for a min/max breakpoint. */
177 + public static function breakpoint_auto_label( $min, $max ) {
178 + if ( $min > 0 && $max > 0 ) {
179 + return $min . '–' . $max . 'px';
180 + }
181 + if ( $max > 0 ) {
182 + return '≤ ' . $max . 'px';
183 + }
184 + return '≥ ' . $min . 'px';
185 + }
186 +
187 + /** Compose a CSS media condition (no `@media` keyword) from min/max px. */
188 + public static function breakpoint_media_condition( $min, $max ) {
189 + $parts = array();
190 + if ( $min > 0 ) {
191 + $parts[] = '(min-width:' . (int) $min . 'px)';
192 + }
193 + if ( $max > 0 ) {
194 + $parts[] = '(max-width:' . (int) $max . 'px)';
195 + }
196 + return implode( ' and ', $parts );
197 + }
198 +
199 + /**
200 + * How breakpoint queries relate to each other, site-wide.
201 + *
202 + * cascade (default) - max-width envelopes. A Tablet value still applies at
203 + * Mobile widths unless Mobile overrides it. This is how
204 + * aBlocks v1/v2 blocks behave, so a page mixing block
205 + * versions stays consistent.
206 + * strict - exclusive bands. A Tablet value applies only between
207 + * the Mobile bound and the Tablet bound, matching
208 + * WordPress core and block themes.
209 + */
210 + public static function get_breakpoint_mode() {
211 + return 'strict' === self::get_settings( 'breakpoint_mode', 'cascade' ) ? 'strict' : 'cascade';
212 + }
213 +
214 + /**
215 + * The single place an atomic media query is built. Returns the complete
216 + * `@media …` prelude for a device entry, or '' for the base device (which
217 + * needs no query at all).
218 + *
219 + * Both bounds are honoured, so a custom breakpoint declared with only a
220 + * `minWidth` produces a real min-width query instead of being skipped —
221 + * animations already behaved this way, style rules did not.
222 + */
223 + public static function breakpoint_media_query( $device ) {
224 + $device = (array) $device;
225 + $min = isset( $device['min'] ) ? (int) $device['min'] : 0;
226 + $max = isset( $device['max'] ) ? (int) $device['max'] : 0;
227 +
228 + if ( $min < 1 && $max < 1 ) {
229 + return '';
230 + }
231 +
232 + /*
233 + * Strict mode bounds a max-width breakpoint from below with the next
234 + * narrower breakpoint, turning overlapping envelopes into exclusive
235 + * bands. A breakpoint that already declares its own min is left alone —
236 + * the author has stated the band explicitly.
237 + */
238 + if ( 'strict' === self::get_breakpoint_mode() && $max > 0 && $min < 1 ) {
239 + $narrower = self::next_narrower_max( $max );
240 + if ( $narrower > 0 ) {
241 + $min = $narrower + 1;
242 + }
243 + }
244 +
245 + $condition = self::breakpoint_media_condition( $min, $max );
246 + return '' === $condition ? '' : '@media screen and ' . $condition;
247 + }
248 +
249 + /** The largest max-width bound narrower than $max, or 0 if none. */
250 + private static function next_narrower_max( $max ) {
251 + $best = 0;
252 + foreach ( self::get_responsive_devices() as $d ) {
253 + $dmax = isset( $d['max'] ) ? (int) $d['max'] : 0;
254 + if ( $dmax > 0 && $dmax < $max && $dmax > $best ) {
255 + $best = $dmax;
256 + }
257 + }
258 + return $best;
259 + }
260 +
28 261 public static function get_page_permalink( $page, $fallback = null ) {
29 262 $page_id = self::get_settings( $page );
30 263 $permalink = 0 < $page_id ? get_permalink( $page_id ) : '';
31 264 if ( ! $permalink ) {
@@ -76,8 +309,16 @@
76 309 }
77 310 public static function is_active_quizpress() {
78 311 return class_exists( 'QuizPress' );
79 312 }
313 + public static function is_active_zencommunity() {
314 + $zencommunity = 'zencommunity/zencommunity.php';
315 + return self::is_plugin_active( $zencommunity );
316 + }
317 + public static function is_active_gemboards() {
318 + $gemboards = 'gemboards/gemboards.php';
319 + return self::is_plugin_active( $gemboards );
320 + }
80 321 public static function is_active_easy_content_manager() {
81 322 return class_exists( 'EasyContentManager' );
82 323 }
83 324
@@ -105,20 +346,28 @@
105 346 return true;
106 347 }
107 348 }
108 349
350 + /**
351 + * The aBlocks submenu.
352 + *
353 + * Each item declares the aBlocks capability that owns it rather than
354 + * manage_options, so a site can hand somebody the Theme Builder without
355 + * handing them the whole of WordPress. Administrators hold every one of
356 + * these, so nothing changes for them. See Permissions.
357 + */
109 358 public static function get_admin_menu_list() {
110 359 $menu = [];
111 360 $menu[ ABLOCKS_PLUGIN_SLUG ] = [
112 361 'parent_slug' => ABLOCKS_PLUGIN_SLUG,
113 362 'title' => __( 'Dashboard', 'ablocks' ),
114 - 'capability' => 'manage_options',
363 + 'capability' => Permissions::ACCESS,
115 364 ];
116 365 if ( self::is_enabled_block( 'form-builder' ) ) {
117 366 $menu[ ABLOCKS_PLUGIN_SLUG . '-submissions' ] = [
118 367 'parent_slug' => ABLOCKS_PLUGIN_SLUG,
119 368 'title' => __( 'Submissions', 'ablocks' ),
120 - 'capability' => 'manage_options',
369 + 'capability' => 'ablocks_view_submissions',
121 370 ];
122 371 }
123 372 if ( self::get_addon_active_status( 'theme-builder' ) ) {
124 373 $menu[ ABLOCKS_PLUGIN_SLUG . '-theme-builder' ] = [
@@ -123,31 +372,31 @@
123 372 if ( self::get_addon_active_status( 'theme-builder' ) ) {
124 373 $menu[ ABLOCKS_PLUGIN_SLUG . '-theme-builder' ] = [
125 374 'parent_slug' => ABLOCKS_PLUGIN_SLUG,
126 375 'title' => __( 'Theme Builder', 'ablocks' ),
127 - 'capability' => 'manage_options',
376 + 'capability' => 'ablocks_manage_theme_builder',
128 377 ];
129 378 }
130 379 $menu[ ABLOCKS_PLUGIN_SLUG . '-addons' ] = [
131 380 'parent_slug' => ABLOCKS_PLUGIN_SLUG,
132 381 'title' => __( 'Add-ons', 'ablocks' ),
133 - 'capability' => 'manage_options',
382 + 'capability' => 'ablocks_manage_addons',
134 383 ];
135 384 $menu[ ABLOCKS_PLUGIN_SLUG . '-scanner' ] = [
136 385 'parent_slug' => ABLOCKS_PLUGIN_SLUG,
137 386 'title' => __( 'Site Scanner', 'ablocks' ),
138 - 'capability' => 'manage_options',
387 + 'capability' => 'ablocks_run_scanner',
139 388 ];
140 389 $menu[ ABLOCKS_PLUGIN_SLUG . '-settings' ] = [
141 390 'parent_slug' => ABLOCKS_PLUGIN_SLUG,
142 391 'title' => __( 'Settings', 'ablocks' ),
143 - 'capability' => 'manage_options',
392 + 'capability' => Permissions::SAVE_SETTINGS,
144 393 ];
145 394 if ( ! defined( 'ABLOCKS_PRO_VERSION' ) ) {
146 395 $menu[ ABLOCKS_PLUGIN_SLUG . '-get-pro' ] = [
147 396 'parent_slug' => ABLOCKS_PLUGIN_SLUG,
148 397 'title' => '<span class="dashicons dashicons-awards academy-blue-color"></span> ' . __( 'Get Pro', 'ablocks' ),
149 - 'capability' => 'manage_options',
398 + 'capability' => Permissions::ACCESS,
150 399 ];
151 400 }
152 401 return apply_filters( 'ablocks/admin_menu_list', $menu );
153 402 }
@@ -402,9 +651,9 @@
402 651
403 652 public static function get_content_by_object_id( string $id_or_fse_slug ) : ?string {
404 653 if ( is_numeric( $id_or_fse_slug ) ) {
405 654 if (
406 - ! current_user_can( 'manage_options' ) &&
655 + ! current_user_can( 'edit_post', $id_or_fse_slug ) &&
407 656 get_post_status( $id_or_fse_slug ) !== 'publish'
408 657 ) {
409 658 return null;
410 659 }