PluginProbe ʕ •ᴥ•ʔ
Superb Addons: Blocks, Patterns, Pre-built Pages, Sliders, Popups, Free Forms, Animations & More / 4.0.7
Superb Addons: Blocks, Patterns, Pre-built Pages, Sliders, Popups, Free Forms, Animations & More v4.0.7
4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 4.0.2 4.0.1 4.0.0 trunk 1.0.0 2.0.0 2.0.1 2.0.2 2.0.3 3.0 3.0.1 3.0.2 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.2 3.1.3 3.2.0 3.2.1 3.2.2 3.2.4 3.2.5 3.2.7 3.2.8 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.4.5 3.4.6 3.5.0 3.5.1 3.5.2 3.5.3 3.5.4 3.5.6 3.5.7 3.5.8 3.5.9 3.6.0 3.6.1 3.6.2 3.7.0 3.7.1
superb-blocks / src / data / controllers / class-css-controller.php
superb-blocks / src / data / controllers Last commit date
class-cache-controller.php 5 days ago class-css-controller.php 5 days ago class-domainshift-controller.php 5 days ago class-key-controller.php 5 days ago class-link-controller.php 5 days ago class-log-controller.php 5 days ago class-option-controller.php 5 days ago class-rest-controller.php 5 days ago
class-css-controller.php
439 lines
1 <?php
2
3 namespace SuperbAddons\Data\Controllers;
4
5 defined('ABSPATH') || exit();
6
7 use Exception;
8 use SuperbAddons\Config\Capabilities;
9 use SuperbAddons\Data\Utils\Engagement;
10 use WP_Error;
11 use stdClass;
12
13 class CSSController
14 {
15 const CSS_PATH = '/superb-addons/custom-css/';
16 const CSS_ROUTE = '/css-blocks';
17
18 const BLOCK_NAME_MAX_LENGTH = 145;
19
20 public function __construct()
21 {
22 $this->EnqueueCustomCSSAction();
23 RestController::AddRoute(self::CSS_ROUTE, array(
24 'methods' => 'POST',
25 'permission_callback' => array($this, 'CSSCallbackPermissionCheck'),
26 'callback' => array($this, 'CSSRouteCallback'),
27 ));
28 }
29
30 public function CSSCallbackPermissionCheck()
31 {
32 // Restrict endpoint to only users who have the proper capability.
33 if (!current_user_can(Capabilities::ADMIN)) {
34 return new WP_Error('rest_forbidden', esc_html__('Unauthorized. Please check user permissions.', "superb-blocks"), array('status' => 401));
35 }
36
37 return true;
38 }
39
40 public function CSSRouteCallback($request)
41 {
42 if (!isset($request['action'])) {
43 return new WP_Error('bad_request', 'Bad Request', array('status' => 400));
44 }
45 try {
46 switch ($request['action']) {
47 case 'save-block':
48 return $this->SaveBlock($request);
49 case 'delete-blocks':
50 return $this->DeleteBlocks($request);
51 case 'import-blocks':
52 return $this->ImportBlocks($request);
53 case 'activate-blocks':
54 return $this->ToggleBlocks($request, true);
55 case 'deactivate-blocks':
56 return $this->ToggleBlocks($request, false);
57 default:
58 return new WP_Error('bad_request_plugin', 'Bad Request', array('status' => 400));
59 }
60 } catch (Exception $ex) {
61 LogController::HandleException($ex);
62 return new WP_Error('internal_error_plugin', 'Internal Plugin Error', array('status' => 500));
63 }
64 }
65
66 private function SanitizeBlock($saved_block, $request_block)
67 {
68 $request_block = json_decode($request_block, true);
69 $is_active = isset($request_block['active']) ? !!$request_block['active'] : ($saved_block && isset($saved_block->active) ? !!$saved_block->active : true);
70 $block = new stdClass();
71 $block->name = isset($request_block['name']) && !empty($request_block['name']) ? sanitize_text_field(mb_substr($request_block['name'], 0, self::BLOCK_NAME_MAX_LENGTH)) : 'CSS Block ' . time();
72 $block->selectors = $this->SanitizeSelectors($request_block['selectors']);
73 $block->css = sanitize_textarea_field($request_block['css']);
74 $block->active = $is_active;
75 return $block;
76 }
77
78 private function SanitizeSelectors($selectors_request)
79 {
80 $selectors = [];
81 foreach ($selectors_request as $selector_request) {
82 $selector = new stdClass();
83 $selector->type = sanitize_text_field($selector_request['type']);
84 $selector->value = isset($selector_request['value']) && is_array($selector_request['value']) ? array_map(function ($str) {
85 return sanitize_text_field($str);
86 }, $selector_request['value']) : false;
87 $selectors[] = $selector;
88 }
89 return $selectors;
90 }
91
92 public static function GetBlocks()
93 {
94 return get_option('superb_addons_custom_css_blocks', []);
95 }
96
97 public static function UpdateBlocks($blocks)
98 {
99 $updated = update_option('superb_addons_custom_css_blocks', $blocks, false);
100 if ($updated) {
101 self::GenerateOptimizedCSS();
102 }
103 return $updated;
104 }
105
106 private function GetBlockIDArray($request)
107 {
108 if (!isset($request['block_ids']) || empty($request['block_ids'])) {
109 return false;
110 }
111
112 $block_ids = sanitize_text_field($request['block_ids']);
113 if (strpos($block_ids, ',') !== false) {
114 $block_ids = explode(",", $block_ids);
115 } else {
116 $block_ids = array($block_ids);
117 }
118
119 if (!$block_ids || empty($block_ids)) {
120 return false;
121 }
122
123 return $block_ids;
124 }
125
126 private function DeleteBlocks($request)
127 {
128 $block_ids = $this->GetBlockIDArray($request);
129 if (!$block_ids) {
130 return new WP_Error('bad_request_plugin', 'Bad Request', array('status' => 400));
131 }
132
133 $blocks = self::GetBlocks();
134 foreach ($block_ids as $block_id) {
135 if (!wp_is_uuid($block_id)) {
136 return new WP_Error('bad_request_plugin', 'Bad Request', array('status' => 400));
137 }
138 if (isset($blocks[$block_id])) {
139 unset($blocks[$block_id]);
140 }
141 }
142
143 self::UpdateBlocks($blocks);
144
145 return rest_ensure_response(['success' => true]);
146 }
147
148 private function ToggleBlocks($request, $active)
149 {
150 $block_ids = $this->GetBlockIDArray($request);
151 if (!$block_ids) {
152 return new WP_Error('bad_request_plugin', 'Bad Request', array('status' => 400));
153 }
154
155 $blocks = self::GetBlocks();
156 foreach ($block_ids as $block_id) {
157 if (!wp_is_uuid($block_id)) {
158 return new WP_Error('bad_request_plugin', 'Bad Request', array('status' => 400));
159 }
160 if (isset($blocks[$block_id])) {
161 $blocks[$block_id]->active = $active;
162 }
163 }
164
165 self::UpdateBlocks($blocks);
166
167 return rest_ensure_response(['success' => true]);
168 }
169
170 private function SaveBlock($request)
171 {
172 $blocks = self::GetBlocks();
173 $block_id = isset($request['id']) && wp_is_uuid($request['id']) ? sanitize_text_field($request['id']) : wp_generate_uuid4();
174 $saved_block = isset($blocks[$block_id]) ? $blocks[$block_id] : false;
175 $new_block = $this->SanitizeBlock($saved_block, $request['block']);
176 $blocks[$block_id] = $new_block;
177 self::UpdateBlocks($blocks);
178
179 Engagement::MarkUsed(Engagement::FEATURE_CSS);
180
181 return rest_ensure_response(['success' => true, 'id' => $block_id]);
182 }
183
184 private function ImportBlocks($request)
185 {
186 $files = $request->get_file_params();
187 if (empty($files) || empty($files['files'])) {
188 return new WP_Error('no_files', 'No files uploaded.', array('status' => 400));
189 }
190 $files = $files['files'];
191 if (!isset($files['tmp_name']) || empty($files['tmp_name'])) {
192 return new WP_Error('no_files', 'No files uploaded.', array('status' => 400));
193 }
194
195 global $wp_filesystem;
196 if (!is_object($wp_filesystem)) {
197 require_once(ABSPATH . 'wp-admin/includes/file.php');
198 WP_Filesystem();
199 }
200
201 $block_amount = 0;
202 $imported_blocks = array();
203 foreach ($files['tmp_name'] as $file) {
204 $file_content = $wp_filesystem->get_contents($file);
205 if (!$file_content) {
206 return rest_ensure_response(['success' => false, "text" => esc_html__("File(s) could not be accessed.", "superb-blocks")]);
207 }
208 $file_blocks = json_decode(base64_decode($file_content));
209 if (empty($file_blocks)) {
210 continue;
211 }
212
213 foreach ($file_blocks as $file_block) {
214 $block = $this->SanitizeBlock(false, wp_json_encode($file_block));
215 $block_id = wp_generate_uuid4();
216 $imported_blocks[$block_id] = $block;
217 $block_amount++;
218 }
219 }
220
221 if (empty($imported_blocks)) {
222 return rest_ensure_response(['success' => false, "text" => esc_html__("Blocks could not be imported. Please verify that the selected files are valid and try again.", "superb-blocks")]);
223 }
224
225 $blocks = self::GetBlocks();
226 $blocks = array_merge($blocks, $imported_blocks);
227 self::UpdateBlocks($blocks);
228
229 return rest_ensure_response(['success' => true, "text" => esc_html(sprintf(/* translators: %d: amount of blocks */__("%d CSS Block(s) imported successfully", "superb-blocks"), $block_amount))]);
230 }
231
232 private static function GenerateOptimizedCSS()
233 {
234 // Delete Previous Files
235 self::RemovePreviousCSSFiles();
236
237 $blocks = self::GetBlocks();
238 if (empty($blocks)) {
239 // No blocks to generate CSS from, remove the optimized CSS option
240 delete_option('superb_addons_optimized_css');
241 return;
242 }
243
244 $valid_types = apply_filters('superb_addons_custom_css_valid_types', array('full', 'front'));
245
246 $css_generations = [];
247 foreach ($blocks as $block) {
248 if (!isset($block->selectors) || !isset($block->css) || !isset($block->active) || !$block->active) {
249 continue;
250 }
251 foreach ($block->selectors as $selector) {
252 if (!isset($selector->type) || !in_array($selector->type, $valid_types)) {
253 continue;
254 }
255 if (!isset($selector->value) || empty($selector->value) || !is_array($selector->value)) {
256 $css_generations[$selector->type]["css"][] = $block->css;
257 if ($selector->type === 'full') {
258 // Full CSS - No need to continue with the rest of the selectors in this block
259 // Continue 2 is used to continue to the next outer loop block
260 continue 2;
261 }
262 continue;
263 }
264 foreach ($selector->value as $selector_value) {
265 $css_generations[$selector->type][$selector_value]["css"][] = $block->css;
266 }
267 }
268 }
269
270 if (version_compare(PHP_VERSION, '7.1', '>=')) {
271 // PHP 7.1 mininumum required for CSSTidy
272 // Include the CSSTidy class if PHP version is 7.1 or greater
273 include(SUPERBADDONS_PLUGIN_DIR . 'src/data/csstidy/class.csstidy.php');
274 $csstidy = new \SuperbAddons\CSSTidy\csstidy();
275
276 // Set some options :
277 $csstidy->set_cfg('optimise_shorthands', 2);
278 $csstidy->set_cfg('template', 'high');
279 } else {
280 $csstidy = false;
281 }
282
283 // Generate CSS Files
284 $optimized = array();
285 foreach ($css_generations as $key => $css_generation) {
286 if (isset($css_generation['css'])) {
287 // No inner key selections
288 $stylesheet = self::GenerateCSSFile($csstidy, $key, join("", $css_generation['css']));
289 if (!$stylesheet) {
290 continue;
291 }
292 $optimized[] = self::GetOptimizedArrayItem($stylesheet, $key);
293 unset($css_generation['css']);
294 }
295
296 if (empty($css_generation)) continue;
297 // Inner key selections
298 foreach ($css_generation as $inner_key => $inner_css_generation) {
299 $stylesheet = self::GenerateCSSFile($csstidy, $key . "-" . $inner_key, join("", $inner_css_generation['css']));
300 if (!$stylesheet) {
301 continue;
302 }
303 $optimized[] = self::GetOptimizedArrayItem($stylesheet, $key, $inner_key);
304 }
305 }
306
307 // Save the optimized CSS to the database
308 update_option('superb_addons_optimized_css', $optimized, true);
309 }
310
311 private static function GetOptimizedArrayItem($stylesheet, $type, $value = false)
312 {
313 return array(
314 'stylesheet' => sanitize_file_name($stylesheet),
315 'type' => sanitize_text_field($type),
316 'value' => sanitize_text_field($value),
317 'created' => time(),
318 );
319 }
320
321 private static function GetCSSDirectory()
322 {
323 $upload_dir = wp_upload_dir();
324 $upload_dir = $upload_dir['basedir'];
325 return $upload_dir . self::CSS_PATH;
326 }
327
328 private static function GetCSSDirectoryURL()
329 {
330 $upload_dir = wp_upload_dir();
331 $upload_dir = $upload_dir['baseurl'];
332 return set_url_scheme($upload_dir . self::CSS_PATH);
333 }
334
335 private static function RemovePreviousCSSFiles()
336 {
337 $upload_dir = self::GetCSSDirectory();
338 if (!is_dir($upload_dir) || strpos($upload_dir, self::CSS_PATH) === false) {
339 return;
340 }
341
342 $files = glob($upload_dir . '*');
343 foreach ($files as $file) {
344 if (file_exists($file)) {
345 wp_delete_file($file);
346 }
347 }
348 }
349
350 private static function GenerateCSSFile($csstidy, $filename, $content)
351 {
352 $filename = sanitize_file_name(sanitize_title($filename) . '.css');
353 // Parse the CSS
354 if ($csstidy) {
355 $csstidy->parse($content);
356 // Get back the optimized CSS Code
357 $css_optimized = $csstidy->print->plain();
358 } else {
359 // Simple fallback with preg_replace
360 $css_optimized = sanitize_text_field($content);
361 }
362
363 $css_optimized = wp_strip_all_tags($css_optimized);
364
365 global $wp_filesystem;
366 if (!is_object($wp_filesystem)) {
367 require_once(ABSPATH . 'wp-admin/includes/file.php');
368 WP_Filesystem();
369 }
370
371 $upload_dir = self::GetCSSDirectory();
372 if (!$wp_filesystem->is_dir($upload_dir)) {
373 wp_mkdir_p($upload_dir);
374 }
375
376 $success = $wp_filesystem->put_contents($upload_dir . $filename, $css_optimized, FS_CHMOD_FILE);
377 if (!$success) {
378 return false;
379 }
380
381 return $filename;
382 }
383
384 private function EnqueueCustomCSSAction()
385 {
386 add_action('wp_enqueue_scripts', array($this, 'EnqueueCustomCSS'));
387 }
388
389 public function EnqueueCustomCSS()
390 {
391 try {
392 $optimized_css = get_option('superb_addons_optimized_css', false);
393 if (!$optimized_css || empty($optimized_css)) return;
394
395 $baseurl = self::GetCSSDirectoryURL();
396 $enqueueChecks = apply_filters(
397 'superb_addons_custom_css_enqueue_checks',
398 array(
399 'full' => array("function" => false),
400 'front' => array("function" => 'is_front_page'),
401 )
402 );
403
404 foreach ($optimized_css as $stylesheet) {
405
406 if (!isset($stylesheet['type']) || !isset($enqueueChecks[$stylesheet['type']])) {
407 continue;
408 }
409
410 if (!isset($enqueueChecks[$stylesheet['type']]['function'])) {
411 return;
412 }
413
414 $should_enqueue =
415 (!$enqueueChecks[$stylesheet['type']]['function']
416 || isset($enqueueChecks[$stylesheet['type']]['params']) && call_user_func($enqueueChecks[$stylesheet['type']]['function'], $stylesheet['value']))
417 || (!isset($enqueueChecks[$stylesheet['type']]['params']) && call_user_func($enqueueChecks[$stylesheet['type']]['function']));
418
419 if (!$should_enqueue) continue;
420
421
422
423
424 $this->EnqueueStyle($baseurl, $stylesheet['stylesheet'], $stylesheet['created']);
425 }
426 } catch (Exception $ex) {
427 LogController::HandleException($ex);
428 }
429 }
430
431 private function EnqueueStyle($baseurl, $path, $created)
432 {
433 $path = sanitize_file_name($path);
434 $title = sanitize_title('superb-addons-custom-' . $path);
435 wp_register_style($title, $baseurl . $path, array(), $created);
436 wp_enqueue_style($title);
437 }
438 }
439