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