GIF94;
| Path : /var/www/wordpress/wp-content/plugins/oxymade/includes/ |
| Current File : /var/www/wordpress/wp-content/plugins/oxymade/includes/components.php |
<?php
/**
* OxyMade Components Management
* Handles importing and syncing components with Oxygen
*/
namespace OxyMade;
class Components {
/**
* Check if Oxygen is active
*/
public static function is_oxygen_active() {
return function_exists('\\Breakdance\\Data\\get_global_option') &&
function_exists('\\Breakdance\\BreakdanceOxygen\\Selectors\\getOxySelectors');
}
/**
* Get components from JSON file
*/
public static function get_components_from_json() {
$json_file = plugin_dir_path(__FILE__) . '../data/components.json';
if (!file_exists($json_file)) {
return false;
}
$json_content = file_get_contents($json_file);
$components = json_decode($json_content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return false;
}
return $components;
}
/**
* Get components from remote URL with security validation
*/
public static function get_components_from_remote($design_set = null) {
// Get design set from options if not provided
if ($design_set === null) {
$design_set = get_option('oxymade_default_designset_template', 'Layers');
}
// Validate design set parameter
$allowed_design_sets = array('layers', 'minimal', 'modern', 'classic');
$design_set = strtolower(sanitize_file_name($design_set));
if (!in_array($design_set, $allowed_design_sets, true)) {
error_log('OxyMade: Invalid design set requested: ' . $design_set);
return false;
}
// Build secure URL
$base_url = defined('OXYMADE_API_URL') ? OXYMADE_API_URL : 'https://oxymade.com';
$url = trailingslashit($base_url) . 'assets/components/' . $design_set . '.json';
// Validate URL
if (!filter_var($url, FILTER_VALIDATE_URL)) {
error_log('OxyMade: Invalid URL constructed: ' . $url);
return false;
}
// Make request with strict SSL verification
$response = wp_remote_get($url, array(
'timeout' => 30,
'sslverify' => true, // Enforce SSL verification
'redirection' => 0, // Don't follow redirects
'headers' => array(
'User-Agent' => 'OxyMade/' . OXYMADE_VERSION,
'Accept' => 'application/json'
)
));
// Check for request errors
if (is_wp_error($response)) {
error_log('OxyMade: Remote request failed: ' . $response->get_error_message());
return false;
}
// Check response code
$response_code = wp_remote_retrieve_response_code($response);
if (200 !== $response_code) {
error_log('OxyMade: Invalid response code: ' . $response_code);
return false;
}
// Get response body
$body = wp_remote_retrieve_body($response);
if (empty($body)) {
error_log('OxyMade: Empty response body');
return false;
}
// Validate content type
$content_type = wp_remote_retrieve_header($response, 'content-type');
if (false === strpos($content_type, 'application/json')) {
error_log('OxyMade: Invalid content type: ' . $content_type);
return false;
}
// Decode JSON
$components = json_decode($body, true);
if (JSON_ERROR_NONE !== json_last_error()) {
error_log('OxyMade: JSON decode error: ' . json_last_error_msg());
return false;
}
// Validate component structure
if (!is_array($components)) {
error_log('OxyMade: Invalid components data structure');
return false;
}
// Sanitize each component
$sanitized_components = array();
foreach ($components as $component) {
$sanitized = self::sanitize_component($component);
if ($sanitized) {
$sanitized_components[] = $sanitized;
}
}
if (empty($sanitized_components)) {
error_log('OxyMade: No valid components after sanitization');
return false;
}
return $sanitized_components;
}
/**
* Sanitize individual component data
*/
private static function sanitize_component($component) {
if (!is_array($component)) {
return false;
}
$sanitized = array();
// Sanitize ID (required)
if (isset($component['id'])) {
$sanitized['id'] = sanitize_key($component['id']);
} else {
return false; // ID is required
}
// Sanitize name
if (isset($component['name'])) {
$sanitized['name'] = sanitize_text_field($component['name']);
}
// Sanitize description
if (isset($component['description'])) {
$sanitized['description'] = sanitize_textarea_field($component['description']);
}
// Sanitize category
if (isset($component['category'])) {
$sanitized['category'] = sanitize_text_field($component['category']);
}
// Sanitize collection
if (isset($component['collection'])) {
$sanitized['collection'] = sanitize_text_field($component['collection']);
}
// Sanitize preview URL
if (isset($component['preview_url'])) {
$url = esc_url_raw($component['preview_url']);
if (filter_var($url, FILTER_VALIDATE_URL)) {
$sanitized['preview_url'] = $url;
}
}
// Sanitize properties (recursive)
if (isset($component['properties']) && is_array($component['properties'])) {
$sanitized['properties'] = self::sanitize_component_properties($component['properties']);
}
// Sanitize children (recursive)
if (isset($component['children']) && is_array($component['children'])) {
$sanitized_children = array();
foreach ($component['children'] as $child) {
$sanitized_child = self::sanitize_component($child);
if ($sanitized_child) {
$sanitized_children[] = $sanitized_child;
}
}
$sanitized['children'] = $sanitized_children;
}
return $sanitized;
}
/**
* Sanitize component properties
*/
private static function sanitize_component_properties($properties) {
if (!is_array($properties)) {
return array();
}
$sanitized = array();
foreach ($properties as $key => $value) {
$key = sanitize_key($key);
if (is_array($value)) {
$sanitized[$key] = self::sanitize_component_properties($value);
} elseif (is_string($value)) {
// Check if it's a URL
if (filter_var($value, FILTER_VALIDATE_URL)) {
$sanitized[$key] = esc_url_raw($value);
} else {
$sanitized[$key] = sanitize_text_field($value);
}
} elseif (is_numeric($value)) {
$sanitized[$key] = $value;
} elseif (is_bool($value)) {
$sanitized[$key] = (bool) $value;
}
}
return $sanitized;
}
/**
* Get components from remote with caching
*
* @param string|null $design_set Design set name
* @return array|false Components array or false on failure
*/
public static function get_components_from_remote_cached($design_set = null) {
if ($design_set === null) {
$design_set = get_option('oxymade_default_designset_template', 'Layers');
}
$design_set = strtolower(sanitize_file_name($design_set));
$cache_key = 'oxymade_components_' . $design_set;
// Try cache first
$cached = get_transient($cache_key);
if (false !== $cached && is_array($cached)) {
return $cached;
}
// Fetch from remote
$components = self::get_components_from_remote($design_set);
if (false === $components) {
return false;
}
// Cache for 24 hours
set_transient($cache_key, $components, DAY_IN_SECONDS);
return $components;
}
/**
* Clear component cache
*
* @param string|null $design_set Design set name or null to clear all
* @return void
*/
public static function clear_component_cache($design_set = null) {
if (null !== $design_set) {
$design_set = strtolower(sanitize_file_name($design_set));
$cache_key = 'oxymade_components_' . $design_set;
delete_transient($cache_key);
} else {
// Clear all component caches
global $wpdb;
$wpdb->query(
"DELETE FROM {$wpdb->options}
WHERE option_name LIKE '_transient_oxymade_components_%'
OR option_name LIKE '_transient_timeout_oxymade_components_%'"
);
}
}
/**
* Get collections from components
*/
public static function get_collections_from_components($components) {
$collections = [];
foreach ($components as $component) {
if (isset($component['collection']) && !in_array($component['collection'], $collections)) {
$collections[] = $component['collection'];
}
}
return $collections;
}
/**
* AJAX handler for syncing components
*/
public static function ajax_sync_components() {
// Check capabilities
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
// Check nonce for security
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'oxymade-admin-nonce')) {
wp_send_json_error('Invalid security token');
exit;
}
$mode = isset($_POST['mode']) ? sanitize_text_field($_POST['mode']) : 'add_new';
try {
$result = self::sync_components_with_oxygen($mode);
if ($result) {
wp_send_json_success([
'message' => 'Components synced successfully with Oxygen',
'mode' => $mode
]);
} else {
wp_send_json_error('Failed to sync components with Oxygen');
}
} catch (Exception $e) {
wp_send_json_error('Error: ' . $e->getMessage());
}
}
/**
* Sync components with Oxygen
*/
public static function sync_components_with_oxygen($mode = 'add_new') {
// Check if Oxygen is active
if (!self::is_oxygen_active()) {
throw new Exception('Oxygen is not active');
}
// Get components from JSON (local first, then remote if needed)
$components = self::get_components_from_json();
if (!$components) {
// Try remote if local fails
$design_set = get_option('oxymade_default_designset_template', 'Layers');
$components = self::get_components_from_remote($design_set);
if (!$components) {
throw new Exception('Failed to load components from both local and remote sources');
}
}
// Apply heading preset properties (font_weight, line_height, letter_spacing) from cached presets
$heading_presets = get_option('oxymade_heading_presets', []);
if (!empty($heading_presets)) {
foreach ($components as &$component) {
if (($component['collection'] ?? '') !== 'OxyMade Headings') {
continue;
}
$name = $component['name'] ?? '';
if (!isset($heading_presets[$name])) {
continue;
}
$preset = $heading_presets[$name];
$typo = &$component['properties']['breakpoint_base']['typography'];
if (!empty($preset['line_height'])) {
$typo['line_height'] = $preset['line_height'];
}
if (!empty($preset['font_weight'])) {
$typo['font_weight'] = $preset['font_weight'];
}
if (!empty($preset['letter_spacing'])) {
$typo['letter_spacing'] = $preset['letter_spacing'];
}
}
unset($component, $typo);
}
// Get collections
$collections = self::get_collections_from_components($components);
// Save components to Oxygen using oxygen_ prefix based on mode
switch ($mode) {
case 'update':
$result = self::update_oxygen_components($components, $collections);
break;
case 'add_new':
default:
$result = self::add_new_oxygen_components($components, $collections);
break;
}
if ($result) {
// Mark as registered
update_option('oxymade_components_registered', true);
}
return $result;
}
/**
* Add new components to Oxygen
*/
public static function add_new_oxygen_components($new_components, $collections) {
// Get existing components from Oxygen
$current_components = \Breakdance\Data\get_global_option('oxy_selectors_json_string');
$current_collections = \Breakdance\Data\get_global_option('oxy_selectors_collections_json_string');
if (!is_array($current_components)) {
$current_components = [];
}
if (!is_array($current_collections)) {
$current_collections = [];
}
// Create map of existing components by ID
$existing_component_map = [];
foreach ($current_components as $component) {
if (isset($component['id'])) {
$existing_component_map[$component['id']] = $component;
}
}
// Add only new components (skip existing ones)
$components_to_add = [];
foreach ($new_components as $component) {
if (!isset($existing_component_map[$component['id']])) {
$components_to_add[] = $component;
}
}
// If no new components to add, return early
if (empty($components_to_add)) {
return true;
}
// Add new components at the top
$updated_components = array_merge($components_to_add, $current_components);
// Merge collections - ensure OxyMade Components collection is first
$updated_collections = array_unique(array_merge($collections, $current_collections));
// Move OxyMade Components collection to the top if it exists
$oxymade_components_key = array_search('OxyMade Components', $updated_collections);
if ($oxymade_components_key !== false) {
unset($updated_collections[$oxymade_components_key]);
array_unshift($updated_collections, 'OxyMade Components');
}
// Use Oxygen's global options with oxygen_ prefix
\Breakdance\Data\set_global_option('oxy_selectors_json_string', $updated_components);
\Breakdance\Data\set_global_option('oxy_selectors_collections_json_string', $updated_collections);
// Create revision
\Breakdance\Data\GlobalRevisions\add_new_revision($updated_components, 'oxygen_selectors');
// Regenerate cache
\Breakdance\Render\generateCacheForGlobalSettings();
return true;
}
/**
* Update existing components in Oxygen by removing and re-importing
*/
public static function update_oxygen_components($new_components, $collections) {
// Get existing components from Oxygen
$current_components = \Breakdance\Data\get_global_option('oxy_selectors_json_string');
$current_collections = \Breakdance\Data\get_global_option('oxy_selectors_collections_json_string');
if (!is_array($current_components)) {
$current_components = [];
}
if (!is_array($current_collections)) {
$current_collections = [];
}
// Create a map of framework component IDs for quick lookup
$framework_component_ids = [];
foreach ($new_components as $component) {
$framework_component_ids[] = $component['id'];
}
// Keep only non-framework components (user's custom components and selectors)
$user_components = [];
foreach ($current_components as $component) {
if (!in_array($component['id'], $framework_component_ids)) {
$user_components[] = $component;
} else {
}
}
// Start with fresh framework components at the top
$updated_components = $new_components;
// Add existing user components at the bottom
foreach ($user_components as $component) {
$updated_components[] = $component;
}
// Merge collections - ensure OxyMade Components collection is first
$updated_collections = array_unique(array_merge($collections, $current_collections));
// Move OxyMade Components collection to the top if it exists
$oxymade_components_key = array_search('OxyMade Components', $updated_collections);
if ($oxymade_components_key !== false) {
unset($updated_collections[$oxymade_components_key]);
array_unshift($updated_collections, 'OxyMade Components');
}
// Use Oxygen's global options with oxygen_ prefix
\Breakdance\Data\set_global_option('oxy_selectors_json_string', $updated_components);
\Breakdance\Data\set_global_option('oxy_selectors_collections_json_string', $updated_collections);
// Create revision
\Breakdance\Data\GlobalRevisions\add_new_revision($updated_components, 'oxygen_selectors');
// Clear all caches
\wp_cache_flush();
// Regenerate cache
\Breakdance\Render\generateCacheForGlobalSettings();
// Force cache regeneration with delay
usleep(500000); // 500ms delay
\Breakdance\Render\generateCacheForGlobalSettings();
return true;
}
/**
* Debug components
*/
public static function debug_components() {
$components = self::get_components_from_json();
$collections = self::get_collections_from_components($components);
$debug_info = [
'total_components' => count($components),
'collections' => $collections,
'sample_components' => array_slice($components, 0, 3),
'has_oxygen_function' => self::is_oxygen_active(),
'breakdance_mode' => 'oxygen'
];
// Check if components are already in Oxygen
if (self::is_oxygen_active()) {
$current_components = \Breakdance\Data\get_global_option('oxy_selectors_json_string');
$current_collections = \Breakdance\Data\get_global_option('oxy_selectors_collections_json_string');
$debug_info['oxygen_components_count'] = is_array($current_components) ? count($current_components) : 'not array';
$debug_info['oxygen_collections_count'] = is_array($current_collections) ? count($current_collections) : 'not array';
$debug_info['oxygen_components_exists'] = !empty($current_components);
}
return $debug_info;
}
}