EnqueueCustomCSSAction(); RestController::AddRoute(self::CSS_ROUTE, array( 'methods' => 'POST', 'permission_callback' => array($this, 'CSSCallbackPermissionCheck'), 'callback' => array($this, 'CSSRouteCallback'), )); } public function CSSCallbackPermissionCheck() { // Restrict endpoint to only users who have the proper capability. if (!current_user_can(Capabilities::ADMIN)) { return new WP_Error('rest_forbidden', esc_html__('Unauthorized. Please check user permissions.', "superb-blocks"), array('status' => 401)); } return true; } public function CSSRouteCallback($request) { if (!isset($request['action'])) { return new WP_Error('bad_request', 'Bad Request', array('status' => 400)); } try { switch ($request['action']) { case 'save-block': return $this->SaveBlock($request); case 'delete-blocks': return $this->DeleteBlocks($request); case 'import-blocks': return $this->ImportBlocks($request); case 'activate-blocks': return $this->ToggleBlocks($request, true); case 'deactivate-blocks': return $this->ToggleBlocks($request, false); default: return new WP_Error('bad_request_plugin', 'Bad Request', array('status' => 400)); } } catch (Exception $ex) { LogController::HandleException($ex); return new WP_Error('internal_error_plugin', 'Internal Plugin Error', array('status' => 500)); } } private function SanitizeBlock($saved_block, $request_block) { $request_block = json_decode($request_block, true); $is_active = isset($request_block['active']) ? !!$request_block['active'] : ($saved_block && isset($saved_block->active) ? !!$saved_block->active : true); $block = new stdClass(); $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(); $block->selectors = $this->SanitizeSelectors($request_block['selectors']); $block->css = sanitize_textarea_field($request_block['css']); $block->active = $is_active; return $block; } private function SanitizeSelectors($selectors_request) { $selectors = []; foreach ($selectors_request as $selector_request) { $selector = new stdClass(); $selector->type = sanitize_text_field($selector_request['type']); $selector->value = isset($selector_request['value']) && is_array($selector_request['value']) ? array_map(function ($str) { return sanitize_text_field($str); }, $selector_request['value']) : false; $selectors[] = $selector; } return $selectors; } public static function GetBlocks() { return get_option('superb_addons_custom_css_blocks', []); } public static function UpdateBlocks($blocks) { $updated = update_option('superb_addons_custom_css_blocks', $blocks, false); if ($updated) { self::GenerateOptimizedCSS(); } return $updated; } private function GetBlockIDArray($request) { if (!isset($request['block_ids']) || empty($request['block_ids'])) { return false; } $block_ids = sanitize_text_field($request['block_ids']); if (strpos($block_ids, ',') !== false) { $block_ids = explode(",", $block_ids); } else { $block_ids = array($block_ids); } if (!$block_ids || empty($block_ids)) { return false; } return $block_ids; } private function DeleteBlocks($request) { $block_ids = $this->GetBlockIDArray($request); if (!$block_ids) { return new WP_Error('bad_request_plugin', 'Bad Request', array('status' => 400)); } $blocks = self::GetBlocks(); foreach ($block_ids as $block_id) { if (!wp_is_uuid($block_id)) { return new WP_Error('bad_request_plugin', 'Bad Request', array('status' => 400)); } if (isset($blocks[$block_id])) { unset($blocks[$block_id]); } } self::UpdateBlocks($blocks); return rest_ensure_response(['success' => true]); } private function ToggleBlocks($request, $active) { $block_ids = $this->GetBlockIDArray($request); if (!$block_ids) { return new WP_Error('bad_request_plugin', 'Bad Request', array('status' => 400)); } $blocks = self::GetBlocks(); foreach ($block_ids as $block_id) { if (!wp_is_uuid($block_id)) { return new WP_Error('bad_request_plugin', 'Bad Request', array('status' => 400)); } if (isset($blocks[$block_id])) { $blocks[$block_id]->active = $active; } } self::UpdateBlocks($blocks); return rest_ensure_response(['success' => true]); } private function SaveBlock($request) { $blocks = self::GetBlocks(); $block_id = isset($request['id']) && wp_is_uuid($request['id']) ? sanitize_text_field($request['id']) : wp_generate_uuid4(); $saved_block = isset($blocks[$block_id]) ? $blocks[$block_id] : false; $new_block = $this->SanitizeBlock($saved_block, $request['block']); $blocks[$block_id] = $new_block; self::UpdateBlocks($blocks); return rest_ensure_response(['success' => true, 'id' => $block_id]); } private function ImportBlocks($request) { $files = $request->get_file_params(); if (empty($files) || empty($files['files'])) { return new WP_Error('no_files', 'No files uploaded.', array('status' => 400)); } $files = $files['files']; if (!isset($files['tmp_name']) || empty($files['tmp_name'])) { return new WP_Error('no_files', 'No files uploaded.', array('status' => 400)); } global $wp_filesystem; if (!is_object($wp_filesystem)) { require_once(ABSPATH . 'wp-admin/includes/file.php'); WP_Filesystem(); } $block_amount = 0; $imported_blocks = array(); foreach ($files['tmp_name'] as $file) { $file_content = $wp_filesystem->get_contents($file); if (!$file_content) { return rest_ensure_response(['success' => false, "text" => esc_html__("File(s) could not be accessed.", "superb-blocks")]); } $file_blocks = json_decode(base64_decode($file_content)); if (empty($file_blocks)) { continue; } foreach ($file_blocks as $file_block) { $block = $this->SanitizeBlock(false, wp_json_encode($file_block)); $block_id = wp_generate_uuid4(); $imported_blocks[$block_id] = $block; $block_amount++; } } if (empty($imported_blocks)) { 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")]); } $blocks = self::GetBlocks(); $blocks = array_merge($blocks, $imported_blocks); self::UpdateBlocks($blocks); 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))]); } private static function GenerateOptimizedCSS() { // Delete Previous Files self::RemovePreviousCSSFiles(); $blocks = self::GetBlocks(); if (empty($blocks)) { // No blocks to generate CSS from, remove the optimized CSS option delete_option('superb_addons_optimized_css'); return; } $valid_types = apply_filters('superb_addons_custom_css_valid_types', array('full', 'front')); $css_generations = []; foreach ($blocks as $block) { if (!isset($block->selectors) || !isset($block->css) || !isset($block->active) || !$block->active) { continue; } foreach ($block->selectors as $selector) { if (!isset($selector->type) || !in_array($selector->type, $valid_types)) { continue; } if (!isset($selector->value) || empty($selector->value) || !is_array($selector->value)) { $css_generations[$selector->type]["css"][] = $block->css; if ($selector->type === 'full') { // Full CSS - No need to continue with the rest of the selectors in this block // Continue 2 is used to continue to the next outer loop block continue 2; } continue; } foreach ($selector->value as $selector_value) { $css_generations[$selector->type][$selector_value]["css"][] = $block->css; } } } if (version_compare(PHP_VERSION, '7.1', '>=')) { // PHP 7.1 mininumum required for CSSTidy // Include the CSSTidy class if PHP version is 7.1 or greater include(SUPERBADDONS_PLUGIN_DIR . 'src/data/csstidy/class.csstidy.php'); $csstidy = new \SuperbAddons\CSSTidy\csstidy(); // Set some options : $csstidy->set_cfg('optimise_shorthands', 2); $csstidy->set_cfg('template', 'high'); } else { $csstidy = false; } // Generate CSS Files $optimized = array(); foreach ($css_generations as $key => $css_generation) { if (isset($css_generation['css'])) { // No inner key selections $stylesheet = self::GenerateCSSFile($csstidy, $key, join("", $css_generation['css'])); if (!$stylesheet) { continue; } $optimized[] = self::GetOptimizedArrayItem($stylesheet, $key); unset($css_generation['css']); } if (empty($css_generation)) continue; // Inner key selections foreach ($css_generation as $inner_key => $inner_css_generation) { $stylesheet = self::GenerateCSSFile($csstidy, $key . "-" . $inner_key, join("", $inner_css_generation['css'])); if (!$stylesheet) { continue; } $optimized[] = self::GetOptimizedArrayItem($stylesheet, $key, $inner_key); } } // Save the optimized CSS to the database update_option('superb_addons_optimized_css', $optimized, true); } private static function GetOptimizedArrayItem($stylesheet, $type, $value = false) { return array( 'stylesheet' => sanitize_file_name($stylesheet), 'type' => sanitize_text_field($type), 'value' => sanitize_text_field($value), 'created' => time(), ); } private static function GetCSSDirectory() { $upload_dir = wp_upload_dir(); $upload_dir = $upload_dir['basedir']; return $upload_dir . self::CSS_PATH; } private static function GetCSSDirectoryURL() { $upload_dir = wp_upload_dir(); $upload_dir = $upload_dir['baseurl']; return set_url_scheme($upload_dir . self::CSS_PATH); } private static function RemovePreviousCSSFiles() { $upload_dir = self::GetCSSDirectory(); if (!is_dir($upload_dir) || strpos($upload_dir, self::CSS_PATH) === false) { return; } $files = glob($upload_dir . '*'); foreach ($files as $file) { if (file_exists($file)) { wp_delete_file($file); } } } private static function GenerateCSSFile($csstidy, $filename, $content) { $filename = sanitize_file_name(sanitize_title($filename) . '.css'); // Parse the CSS if ($csstidy) { $csstidy->parse($content); // Get back the optimized CSS Code $css_optimized = $csstidy->print->plain(); } else { // Simple fallback with preg_replace $css_optimized = sanitize_text_field($content); } $css_optimized = wp_strip_all_tags($css_optimized); global $wp_filesystem; if (!is_object($wp_filesystem)) { require_once(ABSPATH . 'wp-admin/includes/file.php'); WP_Filesystem(); } $upload_dir = self::GetCSSDirectory(); if (!$wp_filesystem->is_dir($upload_dir)) { wp_mkdir_p($upload_dir); } $success = $wp_filesystem->put_contents($upload_dir . $filename, $css_optimized, FS_CHMOD_FILE); if (!$success) { return false; } return $filename; } private function EnqueueCustomCSSAction() { add_action('wp_enqueue_scripts', array($this, 'EnqueueCustomCSS')); } public function EnqueueCustomCSS() { try { $optimized_css = get_option('superb_addons_optimized_css', false); if (!$optimized_css || empty($optimized_css)) return; $baseurl = self::GetCSSDirectoryURL(); $enqueueChecks = apply_filters( 'superb_addons_custom_css_enqueue_checks', array( 'full' => array("function" => false), 'front' => array("function" => 'is_front_page'), ) ); foreach ($optimized_css as $stylesheet) { if (!isset($stylesheet['type']) || !isset($enqueueChecks[$stylesheet['type']])) { continue; } if (!isset($enqueueChecks[$stylesheet['type']]['function'])) { return; } $should_enqueue = (!$enqueueChecks[$stylesheet['type']]['function'] || isset($enqueueChecks[$stylesheet['type']]['params']) && call_user_func($enqueueChecks[$stylesheet['type']]['function'], $stylesheet['value'])) || (!isset($enqueueChecks[$stylesheet['type']]['params']) && call_user_func($enqueueChecks[$stylesheet['type']]['function'])); if (!$should_enqueue) continue; $this->EnqueueStyle($baseurl, $stylesheet['stylesheet'], $stylesheet['created']); } } catch (Exception $ex) { LogController::HandleException($ex); } } private function EnqueueStyle($baseurl, $path, $created) { $path = sanitize_file_name($path); $title = sanitize_title('superb-addons-custom-' . $path); wp_register_style($title, $baseurl . $path, array(), $created); wp_enqueue_style($title); } }