PluginProbe
Attributes for Blocks / trunk
Attributes for Blocks vtrunk
1.0.15 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9
attributes-for-blocks / attributes-for-blocks.php

attributes-for-blocks.php in Attributes for Blocks trunk, at attributes-for-blocks.php

320 lines 7.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: Attributes for Blocks
4 * Plugin URI: https://wordpress.org/plugins/attributes-for-blocks
5 * Description: Allows to add HTML attributes to Gutenberg blocks.
6 * Version: 1.0.15
7 * Author: skadev
8 * Author URI: https://profiles.wordpress.org/skadev/
9 */
10
11 namespace wsd\afb;
12
13 use WP_HTML_Tag_Processor;
14
15 defined('ABSPATH') || exit;
16
17 function_exists('get_plugin_data') || require_once ABSPATH . 'wp-admin/includes/plugin.php';
18 $afb_plugin = get_plugin_data(__FILE__, false, false);
19 define('WSD_AFB_VER', $afb_plugin['Version']);
20 define('WSD_AFB_FILE', __FILE__);
21 define('WSD_AFB_DIR', dirname(__FILE__));
22
23 /**
24 * Blocks known to not work properly with Attributes for Blocks.
25 *
26 * @return array
27 */
28 function get_unsupported_blocks() {
29 return apply_filters('afb_unsupported_blocks', [
30 'core/freeform',
31 'core/html',
32 'core/shortcode',
33 'core/legacy-widget',
34 ]);
35 }
36
37
38 /**
39 * Enqueue editor assets.
40 */
41 function editor_assets() {
42
43 /** @var array */
44 $asset = include WSD_AFB_DIR . '/build/index.asset.php';
45
46 wp_enqueue_style(
47 'attributes-for-blocks',
48 plugins_url('build/style-index.css', WSD_AFB_FILE),
49 [],
50 $asset['version'],
51 'all'
52 );
53
54 wp_enqueue_script(
55 'attributes-for-blocks',
56 plugins_url('build/index.js', WSD_AFB_FILE),
57 $asset['dependencies'],
58 $asset['version'],
59 false
60 );
61
62 wp_localize_script(
63 'attributes-for-blocks',
64 'afbData',
65 ['unsupportedBlocks' => get_unsupported_blocks()]
66 );
67
68 if(function_exists('wp_set_script_translations')) {
69 wp_set_script_translations('attributes-for-blocks', 'attributes-for-blocks');
70 }
71 }
72 add_action('enqueue_block_editor_assets', __NAMESPACE__ . '\\editor_assets', 5);
73
74
75 /**
76 * Should additional attributes from this plugin be applied to a block.
77 *
78 * @param mixed $attributes Block attributes.
79 * @return bool
80 */
81 function has_attributes($attributes) {
82 return is_array($attributes)
83 && isset($attributes['attributesForBlocks'])
84 && is_array($attributes['attributesForBlocks'])
85 && count($attributes['attributesForBlocks']) > 0;
86 }
87
88
89 /**
90 * Handle merging custom attribute with existing attribute.
91 *
92 * @param string $attribute Attribute name.
93 * @param string $current Current attribute value.
94 * @param string $add Value to merge with.
95 * @return string
96 */
97 function merge_attributes($attribute, $current, $add) {
98
99 /** Clean attribute. */
100 switch($attribute) {
101 case 'style':
102 if($current === $add) {
103 break;
104 }
105 /** Ensure style applied via JS matches input style. */
106 $add = str_replace(': ', ':', $add);
107 $add = str_replace('; ', ';', $add);
108 $add = rtrim($add, ';');
109 $current = rtrim($current, ';');
110 /** Fix `WP_HTML_Tag_Processor` stripping leading `-`, causing a mismatch. */
111 $current = str_replace('-afb-placeholder', 'afb-placeholder', $current);
112 $current = str_replace('afb-placeholder', '-afb-placeholder', $current);
113 break;
114 }
115
116 $current = trim(
117 /** Remove the existing attribute value when it already exists (added via JS while the block also has PHP `render_callback`). */
118 str_replace($add, '', $current)
119 );
120 $add = trim($add);
121
122 /** Nothing to merge. */
123 if(empty($current)) {
124 return $add;
125 }
126
127 /** Determine the separator based on attribute type. */
128 $separator = ' ';
129 switch($attribute) {
130 case 'style':
131 if(substr($current, -1) !== ';' && substr($add, 0, 1) !== ';') {
132 $separator = ';';
133 }
134 break;
135 }
136 /** @var string */
137 $separator = apply_filters('afb_attribute_separator', $separator, $attribute);
138
139 return implode($separator, [$current, $add]);
140 }
141
142
143 /**
144 * @param array $args AFB settings.
145 * @param WP_HTML_Tag_Processor $tags
146 * @return array Attribute name and value pairs.
147 */
148 function get_attributes($args, $tags) {
149
150 $attributes = [];
151
152 foreach($args as $key => $value) {
153
154 /** Override attribute. */
155 if(strpos($key, '@') === 0) {
156 $attributes[substr($key, 1)] = $value;
157 continue;
158 }
159
160 /** Merge attribute. */
161 $attributes[$key] = merge_attributes(
162 $key,
163 $tags->get_attribute($key) ?? '',
164 $value
165 );
166 }
167
168 return apply_filters('afb_get_attributes', $attributes, $args, $tags);
169 }
170
171
172 /**
173 * Add attributes to block root element.
174 *
175 * @param array $args AFB settings.
176 * @param string $html Block HTML.
177 * @return string Block HTML with additional attributes.
178 */
179 function add_attributes($args, $html) {
180
181 $tags = new WP_HTML_Tag_Processor($html);
182 if($tags->next_tag()) {
183
184 /** Add attributes. */
185 foreach(get_attributes($args, $tags) as $key => $value) {
186 $tags->set_attribute($key, $value);
187 }
188
189 return $tags->get_updated_html();
190 }
191
192 return $html;
193 }
194
195
196 /**
197 * When registering a block, add AFB argument and wrap `render_callback`.
198 *
199 * @param array $args
200 * @param string $name
201 * @return array
202 */
203 function block_args($args, $name) {
204
205 static $not_supported;
206 if(!is_array($not_supported)) {
207 $not_supported = get_unsupported_blocks();
208 }
209
210 if(in_array($name, $not_supported, true)) {
211 return $args;
212 }
213
214 if(!isset($args['attributes']) || !is_array($args['attributes'])) {
215 $args['attributes'] = [];
216 }
217
218 /** Register AFB attributes, this is necessary for `/wp-json/wp/v2/block-renderer` REST endpoint to not throw `rest_additional_properties_forbidden`. */
219 $args['attributes']['attributesForBlocks'] = [
220 'type' => 'object',
221 'default' => [],
222 ];
223
224 return $args;
225 }
226 add_filter('register_block_type_args', __NAMESPACE__ . '\\block_args', 10, 2);
227
228
229 /**
230 * Add attributes to blocks' root HTML element when applicable.
231 *
232 * @param string $block_content Rendered block.
233 * @param array $block Parsed array representation of block.
234 * @return string
235 */
236 function render_block($block_content, $block) {
237
238 static $not_supported;
239 if(!is_array($not_supported)) {
240 $not_supported = get_unsupported_blocks();
241 }
242
243 if(in_array($block['blockName'], $not_supported, true)) {
244 return $block_content;
245 }
246
247 if(!has_attributes($block['attrs'] ?? [])) {
248 return $block_content;
249 }
250
251 return add_attributes($block['attrs']['attributesForBlocks'], $block_content);
252 }
253 add_filter('render_block', __NAMESPACE__ . '\\render_block', 10, 2);
254
255
256 /**
257 * Strips `attributesForBlocks` block attribute when the current user doesn't have `unfiltered_html` capabilities.
258 *
259 * @param string $content Content to filter through KSES.
260 */
261 function sanitize_attributes($content) {
262
263 if(strpos($content, '<!-- wp:') === false) { // !has_blocks()
264 return $content;
265 }
266
267 if(strpos($content, 'attributesForBlocks') === false) {
268 return $content;
269 }
270
271 if(!defined('SECURE_AUTH_COOKIE')) {
272 if(!function_exists('wp_cookie_constants')) {
273 require ABSPATH . WPINC . '/default-constants.php';
274 }
275 wp_cookie_constants();
276 }
277
278 if(!function_exists('wp_get_current_user')) {
279 require ABSPATH . WPINC . '/pluggable.php';
280 }
281
282 if(current_user_can('unfiltered_html')) {
283 return $content;
284 }
285
286 $matches = null;
287 if(preg_match_all('/\\\?"attributesForBlocks\\\?"\s*:\s*{[^}]*}/', $content, $matches)) {
288
289 foreach($matches[0] as $atts) {
290
291 $empty_atts = '"attributesForBlocks":{}';
292 $was_slashed = wp_unslash($atts) !== $atts;
293 if($was_slashed) {
294 $empty_atts = wp_slash($empty_atts);
295 }
296 $content = str_replace($atts, $empty_atts, $content);
297 }
298 }
299
300 return $content;
301 }
302 add_filter('pre_kses', __NAMESPACE__ . '\\sanitize_attributes');
303
304
305 /**
306 * Add GitHub and Donate links on the plugins page.
307 *
308 * @param array $plugin_meta
309 * @param string $plugin_file
310 * @return array
311 */
312 function plugin_links($plugin_meta, $plugin_file) {
313 if($plugin_file === plugin_basename(WSD_AFB_FILE)) {
314 $plugin_meta[] = '<a href="https://github.com/ska-dev-1/attributes-for-blocks" target="_blank" rel="noopener noreferrer">GitHub</a>';
315 $plugin_meta[] = '<a href="https://buymeacoffee.com/skadev" target="_blank" rel="noopener noreferrer">Donate</a>';
316 }
317 return $plugin_meta;
318 }
319 add_filter('plugin_row_meta', __NAMESPACE__ . '\\plugin_links', 10, 2);
320