496 lines
18 KiB
PHP
496 lines
18 KiB
PHP
<?php
|
|
/**
|
|
* Simply Code - Snippet Manager
|
|
*/
|
|
|
|
class Simply_Snippet_Manager {
|
|
private static $order_cache = null;
|
|
private static $snippets_cache = null;
|
|
private static $cache_version = '1.0';
|
|
|
|
/**
|
|
* Get snippets order with improved caching
|
|
*/
|
|
private static function get_order() {
|
|
if (self::$order_cache !== null) {
|
|
return self::$order_cache;
|
|
}
|
|
|
|
$order_file = SC_PATH . 'includes/snippets-order.php';
|
|
if (!file_exists($order_file)) {
|
|
self::$order_cache = [];
|
|
return [];
|
|
}
|
|
|
|
// Use WordPress transients for better caching
|
|
$cache_key = 'simply_code_order_' . md5_file($order_file);
|
|
$order = get_transient($cache_key);
|
|
if ($order === false) {
|
|
$content = file_get_contents($order_file);
|
|
$order = [];
|
|
if (preg_match('/return\s+(.+);/s', $content, $matches)) {
|
|
// Eval only the return expression (careful in general, but original logic used this)
|
|
$order = @eval('return ' . $matches[1] . ';');
|
|
if (!is_array($order)) {
|
|
$order = [];
|
|
}
|
|
}
|
|
set_transient($cache_key, $order, HOUR_IN_SECONDS);
|
|
}
|
|
|
|
self::$order_cache = $order;
|
|
return $order;
|
|
}
|
|
|
|
/**
|
|
* Improved cache invalidation
|
|
*/
|
|
private static function invalidate_cache() {
|
|
self::$order_cache = null;
|
|
self::$snippets_cache = null;
|
|
|
|
// Clear WordPress transients with proper SQL query
|
|
global $wpdb;
|
|
// Note: uses direct deletion of transient rows; WP provides delete_transient but we want to clear plugin-specific keys
|
|
$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_simply_code_%'" );
|
|
$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_simply_code_%'" );
|
|
|
|
// Clear filesystem cache
|
|
clearstatcache();
|
|
|
|
// Clear opcache if available
|
|
if (function_exists('opcache_reset')) {
|
|
@opcache_reset();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save snippet with hooks priority support
|
|
*/
|
|
public static function save_snippet($name, $php, $js, $css, $description = '', $active = true, $hook_priorities = []) {
|
|
try {
|
|
// Crear directorios si no existen
|
|
$directories = [
|
|
rtrim(SC_STORAGE, '/\\') . '/snippets/',
|
|
rtrim(SC_STORAGE, '/\\') . '/js/',
|
|
rtrim(SC_STORAGE, '/\\') . '/css/',
|
|
rtrim(SC_STORAGE, '/\\') . '/backups/'
|
|
];
|
|
foreach ($directories as $dir) {
|
|
if (!is_dir($dir)) {
|
|
if (!mkdir($dir, 0755, true) && !is_dir($dir)) {
|
|
error_log("Simply Code: Failed to create directory: {$dir}");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Detectar hooks automáticamente (si existe la clase detector)
|
|
$detected_hooks = [];
|
|
if (class_exists('Simply_Hook_Detector') && method_exists('Simply_Hook_Detector', 'detect_hooks')) {
|
|
$detected_hooks = Simply_Hook_Detector::detect_hooks($php);
|
|
}
|
|
|
|
// Combinar con prioridades configuradas manualmente
|
|
$final_hooks = [];
|
|
foreach ($detected_hooks as $hook) {
|
|
$hook_name = $hook['name'];
|
|
$final_hooks[$hook_name] = [
|
|
'type' => $hook['type'],
|
|
'priority' => isset($hook_priorities[$hook_name]) ? (int)$hook_priorities[$hook_name] : ($hook['priority'] ?? 10),
|
|
'accepted_args' => $hook['accepted_args'] ?? 1,
|
|
'auto_detected' => true
|
|
];
|
|
}
|
|
|
|
// Agregar hooks configurados manualmente que no fueron detectados
|
|
foreach ($hook_priorities as $hook_name => $priority) {
|
|
if (!isset($final_hooks[$hook_name])) {
|
|
$final_hooks[$hook_name] = [
|
|
'type' => 'manual',
|
|
'priority' => (int)$priority,
|
|
'accepted_args' => 1,
|
|
'auto_detected' => false
|
|
];
|
|
}
|
|
}
|
|
|
|
// Crear metadatos completos
|
|
$metadata = [
|
|
'description' => $description,
|
|
'last_updated' => date('Y-m-d H:i:s'),
|
|
'active' => (bool) $active,
|
|
'hooks' => $final_hooks,
|
|
'load_priority' => (class_exists('Simply_Hook_Detector') && method_exists('Simply_Hook_Detector', 'calculate_load_priority'))
|
|
? Simply_Hook_Detector::calculate_load_priority($final_hooks)
|
|
: 10
|
|
];
|
|
|
|
// Crear backup si existe el archivo original
|
|
$php_file = rtrim(SC_STORAGE, '/\\') . "/snippets/{$name}.php";
|
|
$backup_dir = rtrim(SC_STORAGE, '/\\') . "/backups/";
|
|
if (file_exists($php_file)) {
|
|
$backup_file = $backup_dir . $name . '.php.' . time();
|
|
if (!@copy($php_file, $backup_file)) {
|
|
error_log("Simply Code: Failed to create backup for {$name}");
|
|
}
|
|
}
|
|
|
|
// Guardar todos los archivos con verificación de errores
|
|
$files_to_save = [
|
|
rtrim(SC_STORAGE, '/\\') . "/snippets/{$name}.php" => $php,
|
|
rtrim(SC_STORAGE, '/\\') . "/js/{$name}.js" => $js,
|
|
rtrim(SC_STORAGE, '/\\') . "/css/{$name}.css" => $css,
|
|
rtrim(SC_STORAGE, '/\\') . "/snippets/{$name}.json" => json_encode($metadata, JSON_PRETTY_PRINT)
|
|
];
|
|
|
|
$all_success = true;
|
|
foreach ($files_to_save as $file_path => $content) {
|
|
$dir_of_file = dirname($file_path);
|
|
if (!is_dir($dir_of_file)) {
|
|
if (!mkdir($dir_of_file, 0755, true) && !is_dir($dir_of_file)) {
|
|
error_log("Simply Code: Failed to create directory for file: {$dir_of_file}");
|
|
$all_success = false;
|
|
continue;
|
|
}
|
|
}
|
|
$result = @file_put_contents($file_path, $content, LOCK_EX);
|
|
if ($result === false) {
|
|
error_log("Simply Code: Failed to write file: {$file_path}");
|
|
$all_success = false;
|
|
}
|
|
}
|
|
|
|
if ($all_success) {
|
|
self::invalidate_cache();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
} catch (Throwable $e) {
|
|
error_log("Simply Code: Error saving snippet {$name} - " . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load snippets with priority ordering
|
|
*/
|
|
public static function load_snippets() {
|
|
$dir = rtrim(SC_STORAGE, '/\\') . '/snippets/';
|
|
if (!is_dir($dir)) return;
|
|
|
|
$all_snippets = self::get_snippets_with_priorities();
|
|
$safe_mode = (get_option(defined('Simply_Code_Admin::OPTION_SAFE_MODE') ? Simply_Code_Admin::OPTION_SAFE_MODE : 'simply_code_safe_mode', 'on') === 'on');
|
|
|
|
foreach ($all_snippets as $snippet_data) {
|
|
if (!self::load_single_snippet($snippet_data['name'], $safe_mode)) {
|
|
error_log("Simply Code: Failed to load snippet: {$snippet_data['name']}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get snippets ordered by priority
|
|
*/
|
|
private static function get_snippets_with_priorities() {
|
|
$dir = rtrim(SC_STORAGE, '/\\') . '/snippets/';
|
|
$snippets = [];
|
|
$order = self::get_order();
|
|
|
|
// Recopilar todos los snippets con sus metadatos
|
|
foreach (glob($dir . '*.php') as $file) {
|
|
$name = basename($file, '.php');
|
|
$json_file = $dir . $name . '.json';
|
|
$metadata = [];
|
|
if (file_exists($json_file)) {
|
|
$metadata = json_decode(file_get_contents($json_file), true) ?: [];
|
|
}
|
|
if (($metadata['active'] ?? true)) {
|
|
$order_index = array_search($name, $order);
|
|
$snippets[] = [
|
|
'name' => $name,
|
|
'load_priority' => $metadata['load_priority'] ?? 10,
|
|
'order_index' => $order_index !== false ? $order_index : 999
|
|
];
|
|
}
|
|
}
|
|
|
|
// Ordenar primero por load_priority, luego por order_index
|
|
usort($snippets, function($a, $b) {
|
|
if ($a['load_priority'] === $b['load_priority']) {
|
|
return $a['order_index'] <=> $b['order_index'];
|
|
}
|
|
return $a['load_priority'] <=> $b['load_priority'];
|
|
});
|
|
|
|
return $snippets;
|
|
}
|
|
|
|
/**
|
|
* Load a single snippet with proper error handling
|
|
*/
|
|
private static function load_single_snippet($basename, $safe_mode = true) {
|
|
$php_file = rtrim(SC_STORAGE, '/\\') . "/snippets/{$basename}.php";
|
|
$json_file = rtrim(SC_STORAGE, '/\\') . "/snippets/{$basename}.json";
|
|
|
|
// Check if snippet is active
|
|
if (!self::is_snippet_active($json_file)) {
|
|
return false;
|
|
}
|
|
if (!file_exists($php_file)) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
if ($safe_mode && class_exists('Simply_Syntax_Checker') && method_exists('Simply_Syntax_Checker', 'validate_php')) {
|
|
$snippet_code = file_get_contents($php_file);
|
|
$syntax_result = Simply_Syntax_Checker::validate_php($snippet_code);
|
|
if (isset($syntax_result['valid']) && !$syntax_result['valid']) {
|
|
error_log("Simply Code: Syntax error in {$php_file} - " . ($syntax_result['message'] ?? 'unknown'));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
require_once $php_file;
|
|
return true;
|
|
} catch (Throwable $e) {
|
|
error_log("Simply Code: Error loading {$php_file} - " . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if snippet is active
|
|
*/
|
|
private static function is_snippet_active($json_file) {
|
|
if (!file_exists($json_file)) {
|
|
return true; // Default to active if no metadata
|
|
}
|
|
$meta = json_decode(file_get_contents($json_file), true);
|
|
return !isset($meta['active']) || $meta['active'] === true;
|
|
}
|
|
|
|
/**
|
|
* Enqueue snippet JS and CSS assets for active snippets
|
|
*/
|
|
public static function enqueue_snippet_assets() {
|
|
$dir_js = rtrim(SC_STORAGE, '/\\') . '/js/';
|
|
$dir_css = rtrim(SC_STORAGE, '/\\') . '/css/';
|
|
$snippets = self::list_snippets(true);
|
|
|
|
foreach ($snippets as $snippet) {
|
|
if (empty($snippet['active'])) continue;
|
|
$name = $snippet['name'];
|
|
|
|
// JS
|
|
$js_file = $dir_js . $name . '.js';
|
|
if (file_exists($js_file)) {
|
|
wp_enqueue_script(
|
|
'simply-snippet-' . $name,
|
|
plugins_url('storage/js/' . $name . '.js', SC_PATH . 'simply-code.php'),
|
|
['jquery'],
|
|
filemtime($js_file),
|
|
true
|
|
);
|
|
}
|
|
|
|
// CSS
|
|
$css_file = $dir_css . $name . '.css';
|
|
if (file_exists($css_file)) {
|
|
wp_enqueue_style(
|
|
'simply-snippet-' . $name,
|
|
plugins_url('storage/css/' . $name . '.css', SC_PATH . 'simply-code.php'),
|
|
[],
|
|
filemtime($css_file)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Toggle snippet status
|
|
*/
|
|
public static function toggle_snippet_status($name, $active) {
|
|
$json_file = rtrim(SC_STORAGE, '/\\') . "/snippets/{$name}.json";
|
|
$snippet_data = [];
|
|
|
|
// Clear filesystem cache
|
|
clearstatcache(true, $json_file);
|
|
|
|
// Get existing data
|
|
if (file_exists($json_file)) {
|
|
$content = file_get_contents($json_file);
|
|
$snippet_data = json_decode($content, true) ?: [];
|
|
}
|
|
|
|
// Update status
|
|
$snippet_data['active'] = (bool) $active;
|
|
$snippet_data['last_updated'] = date('Y-m-d H:i:s');
|
|
|
|
// Force immediate write with JSON_PRETTY_PRINT
|
|
$success = @file_put_contents($json_file, json_encode($snippet_data, JSON_PRETTY_PRINT), LOCK_EX);
|
|
if ($success !== false) {
|
|
// Clear cache after writing
|
|
clearstatcache(true, $json_file);
|
|
|
|
// Verify the content was written correctly
|
|
$verify_content = file_get_contents($json_file);
|
|
$verify_data = json_decode($verify_content, true);
|
|
if ($verify_data === null || !isset($verify_data['active']) || $verify_data['active'] !== (bool) $active) {
|
|
error_log("Simply Code: Status toggle verification failed for {$name}");
|
|
return false;
|
|
}
|
|
self::invalidate_cache();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* List all snippets with improved caching
|
|
*/
|
|
public static function list_snippets($apply_order = true, $force_reload = false) {
|
|
// Si no se fuerza la recarga y hay caché, devolverlo
|
|
if (self::$snippets_cache !== null && !$force_reload) {
|
|
return self::$snippets_cache;
|
|
}
|
|
|
|
$snippets = [];
|
|
$dir = rtrim(SC_STORAGE, '/\\') . '/snippets/';
|
|
if (!is_dir($dir)) {
|
|
self::$snippets_cache = $snippets;
|
|
return $snippets;
|
|
}
|
|
|
|
// Obtener snippets y orden
|
|
$order = $apply_order ? self::get_order() : [];
|
|
$available_snippets = [];
|
|
|
|
foreach (scandir($dir) as $file) {
|
|
if (pathinfo($file, PATHINFO_EXTENSION) === 'php') {
|
|
$name = basename($file, '.php');
|
|
$json_file = $dir . $name . '.json';
|
|
$meta = [];
|
|
if (file_exists($json_file)) {
|
|
$content = file_get_contents($json_file);
|
|
$meta = json_decode($content, true) ?: [];
|
|
}
|
|
$available_snippets[$name] = [
|
|
'name' => $name,
|
|
'description' => $meta['description'] ?? '',
|
|
'active' => $meta['active'] ?? true
|
|
];
|
|
}
|
|
}
|
|
|
|
// Aplicar orden si se solicita
|
|
if ($apply_order && !empty($order)) {
|
|
$ordered_snippets = [];
|
|
foreach ($order as $name) {
|
|
if (isset($available_snippets[$name])) {
|
|
$ordered_snippets[] = $available_snippets[$name];
|
|
unset($available_snippets[$name]);
|
|
}
|
|
}
|
|
// Agregar snippets no ordenados al final
|
|
$snippets = array_merge($ordered_snippets, array_values($available_snippets));
|
|
} else {
|
|
$snippets = array_values($available_snippets);
|
|
}
|
|
|
|
// Guardar en caché
|
|
self::$snippets_cache = $snippets;
|
|
return $snippets;
|
|
}
|
|
|
|
/**
|
|
* Update snippets order with safer file write
|
|
*/
|
|
public static function update_snippets_order($names) {
|
|
$order_file = SC_PATH . 'includes/snippets-order.php';
|
|
$order_dir = dirname($order_file);
|
|
if (!is_dir($order_dir)) {
|
|
if (!mkdir($order_dir, 0755, true) && !is_dir($order_dir)) {
|
|
error_log("Simply Code: Failed to create directory for order file: {$order_dir}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
$content = "<?php\nreturn " . var_export($names, true) . ";\n";
|
|
$result = @file_put_contents($order_file, $content, LOCK_EX);
|
|
if ($result !== false) {
|
|
// Invalidate opcache if present
|
|
if (function_exists('opcache_invalidate')) {
|
|
@opcache_invalidate($order_file, true);
|
|
}
|
|
self::invalidate_cache();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get a specific snippet
|
|
*/
|
|
public static function get_snippet($name) {
|
|
$php_file = rtrim(SC_STORAGE, '/\\') . "/snippets/{$name}.php";
|
|
$js_file = rtrim(SC_STORAGE, '/\\') . "/js/{$name}.js";
|
|
$css_file = rtrim(SC_STORAGE, '/\\') . "/css/{$name}.css";
|
|
$json_file = rtrim(SC_STORAGE, '/\\') . "/snippets/{$name}.json";
|
|
|
|
// Verificar si existe el archivo PHP principal
|
|
if (!file_exists($php_file)) {
|
|
return null;
|
|
}
|
|
|
|
// Obtener metadatos
|
|
$desc = '';
|
|
$active = true;
|
|
if (file_exists($json_file)) {
|
|
$content = file_get_contents($json_file);
|
|
$meta = json_decode($content, true) ?: [];
|
|
$desc = $meta['description'] ?? '';
|
|
$active = isset($meta['active']) ? $meta['active'] : true;
|
|
}
|
|
|
|
// Leer contenido de los archivos
|
|
return [
|
|
'name' => $name,
|
|
'php' => file_exists($php_file) ? file_get_contents($php_file) : '',
|
|
'js' => file_exists($js_file) ? file_get_contents($js_file) : '',
|
|
'css' => file_exists($css_file) ? file_get_contents($css_file) : '',
|
|
'description' => $desc,
|
|
'active' => $active
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Delete snippet with improved cleanup
|
|
*/
|
|
public static function delete_snippet($name) {
|
|
$files_to_delete = [
|
|
rtrim(SC_STORAGE, '/\\') . "/snippets/{$name}.php",
|
|
rtrim(SC_STORAGE, '/\\') . "/snippets/{$name}.json",
|
|
rtrim(SC_STORAGE, '/\\') . "/js/{$name}.js",
|
|
rtrim(SC_STORAGE, '/\\') . "/css/{$name}.css"
|
|
];
|
|
|
|
$success = true;
|
|
foreach ($files_to_delete as $file) {
|
|
if (file_exists($file) && !@unlink($file)) {
|
|
$success = false;
|
|
error_log("Simply Code: Failed to delete file: {$file}");
|
|
}
|
|
}
|
|
|
|
if ($success) {
|
|
self::invalidate_cache();
|
|
}
|
|
|
|
return $success;
|
|
}
|
|
}
|