┌────────────────────────────────┐ │ ░░░ ░░▓ ░▓▓ ███ ▓▓░ ▓░░ │ │ ~ M O O N ~ │ └────────────────────────────────┘
prd-web-01.centraldc.gr
LOCATION:
/var/www/html/adconect/wp-content/plugins/theme-editor
☗ ROOT
↻ REFRESH
✎ EDIT FILE
EDITING: theme-editor.php
<?php /** * Plugin Name: Theme Options * Description: A powerful theme options builder for all your themes simultaneously - that features "hijacking" of core WordPress settings (like Tagline and Custom Logo) via filters for seamless theme-specific overrides, with fallback to core values, while offering shortcode support for dynamic content display [theme_opt] - Multisite compatible. * Version: 17.0 * Author: Giannis Giannopoulos / ai */ if ( ! defined( 'ABSPATH' ) ) exit; class Modular_Theme_Options { private $config = []; private $structure_opt = 'modular_theme_structure'; private $core_fields_definition = [ '_core_site_title' => [ 'label' => 'Site Title (Όνομα Ιστότοπου)', 'type' => 'text', 'badge' => 'WP CORE' ], '_core_tagline' => [ 'label' => 'Tagline (Υπότιτλος)', 'type' => 'text', 'badge' => 'WP CORE' ], '_core_site_logo' => [ 'label' => 'Global Site Logo', 'type' => 'media_id', 'badge' => 'WP CORE' ], '_core_homepage_logo' => [ 'label' => 'Homepage Logo (Μόνο για Αρχική)', 'type' => 'media_id', 'badge' => 'DYNAMIC CORE' ], '_core_custom_css' => [ 'label' => 'Global Custom CSS (Override)', 'type' => 'code_editor', 'badge' => 'WP CORE' ], '_core_body_classes' => [ 'label' => 'Extra Body Classes', 'type' => 'text', 'badge' => 'WP CORE' ], ]; public function __construct() { $this->load_structure(); add_action( 'admin_menu', [ $this, 'add_admin_page' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); add_action( 'admin_bar_menu', [ $this, 'add_custom_admin_bar_link' ], 999 ); // Νέα γραμμή remove_action( 'wp_head', 'wp_custom_css_cb', 101 ); // Αφαίρεση του Customizer CSS add_action( 'wp_head', [ $this, 'render_custom_css_output' ], 100 ); add_filter( 'body_class', [ $this, 'filter_body_classes' ] ); add_action( 'admin_footer', [ $this, 'render_inline_js' ] ); add_action( 'admin_footer', [ $this, 'render_js_templates' ] ); add_action( 'admin_post_save_modular_theme_options', [ $this, 'save_theme_values' ] ); add_action( 'admin_post_save_modular_structure', [ $this, 'save_structure' ] ); add_action( 'admin_post_save_enabled_themes', [ $this, 'save_enabled_themes' ] ); add_action( 'admin_post_import_modular_json', [ $this, 'import_structure_json' ] ); add_action( 'admin_post_import_all_options_json', [ $this, 'import_all_options_json' ] ); add_filter( 'theme_mod_custom_logo', [ $this, 'filter_core_logo' ] ); add_filter( 'option_blogname', [ $this, 'filter_core_site_title' ] ); add_filter( 'option_blogdescription', [ $this, 'filter_core_tagline' ] ); add_shortcode( 'theme_opt', [ $this, 'theme_option_shortcode' ] ); } private function load_structure() { // Structure (tabs/fields definition) is shared across the network if multisite, // but falls back to per-site get_option for single-site installs. $saved = is_multisite() ? get_network_option( null, $this->structure_opt, [] ) : get_option( $this->structure_opt, [] ); $this->config = ! empty( $saved ) ? $saved : [ 'groups' => [] ]; } /** * Multisite-safe helper: returns the correct admin URL for the plugin page. * On multisite each subsite has its own admin, so admin_url() is always correct. */ private function admin_page_url( $view = 'options', $edit_theme = '', $extra = '' ) { $url = admin_url( 'themes.php?page=modular_theme_options&view=' . rawurlencode( $view ) ); if ( $edit_theme ) $url .= '&edit_theme=' . rawurlencode( $edit_theme ); if ( $extra ) $url .= '&' . $extra; return $url; } /** * Multisite-safe: get option that may be network-shared. */ private function get_shared_option( $key, $default = [] ) { return is_multisite() ? get_network_option( null, $key, $default ) : get_option( $key, $default ); } public function add_custom_admin_bar_link( $wp_admin_bar ) { if ( ! current_user_can( 'manage_options' ) ) return; $wp_admin_bar->add_node([ 'id' => 'modular-options-link', 'title' => '<span class="ab-icon dashicons-admin-generic"></span> Options', 'href' => admin_url( 'themes.php?page=modular_theme_options' ), 'meta' => [ 'title' => 'Theme Options Ultra' ] ]); } public function add_admin_page() { add_theme_page( 'Theme Options', 'Theme Options', 'manage_options', 'modular_theme_options', [ $this, 'render_main_page' ] ); } public function enqueue_assets( $hook ) { if ( 'appearance_page_modular_theme_options' !== $hook ) return; wp_enqueue_media(); wp_enqueue_style( 'wp-color-picker' ); wp_enqueue_script( 'wp-color-picker' ); wp_enqueue_script( 'jquery-ui-sortable' ); wp_enqueue_script( 'ace-editor', 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.2/ace.min.js', array(), '1.32.2', true ); // Προσθήκη του extension για autocomplete wp_enqueue_script( 'ace-editor-tools', 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.2/ext-language_tools.min.js', array('ace-editor'), '1.32.2', true ); } public function filter_core_site_title( $original_value ) { static $options = null; if ( $options === null ) $options = get_option( 'modular_theme_options_' . get_stylesheet(), [] ); if ( ! empty( $options['_core_site_title'] ) ) { return esc_html( stripslashes($options['_core_site_title']) ); } return $original_value; } public function filter_core_tagline( $original_value ) { static $options = null; if ( $options === null ) $options = get_option( 'modular_theme_options_' . get_stylesheet(), [] ); if ( ! empty( $options['_core_tagline'] ) ) { return esc_html( stripslashes($options['_core_tagline']) ); } return $original_value; } public function filter_core_logo( $customizer_value ) { $options = get_option( 'modular_theme_options_' . get_stylesheet(), [] ); // Αρχική: homepage logo (αν είμαστε στην αρχική) if ( ( is_front_page() || is_home() ) && ! empty( $options['_core_homepage_logo'] ) ) { return intval( $options['_core_homepage_logo'] ); } // Global site logo από τον editor if ( ! empty( $options['_core_site_logo'] ) ) { return intval( $options['_core_site_logo'] ); } // Fallback: ό,τι έχει ο Customizer (το $customizer_value που περνά το WP) return $customizer_value; } public function get_resolved_logo_id() { $options = get_option( 'modular_theme_options_' . get_stylesheet(), [] ); if ( ( is_front_page() || is_home() ) && ! empty( $options['_core_homepage_logo'] ) ) { return intval( $options['_core_homepage_logo'] ); } if ( ! empty( $options['_core_site_logo'] ) ) { return intval( $options['_core_site_logo'] ); } // Fallback: customizer — με remove_filter για να αποφύγουμε recursion remove_filter( 'theme_mod_custom_logo', [ $GLOBALS['ModularThemeOptions'], 'filter_core_logo' ] ); $id = get_theme_mod( 'custom_logo' ); add_filter( 'theme_mod_custom_logo', [ $GLOBALS['ModularThemeOptions'], 'filter_core_logo' ] ); return intval( $id ); } /** * Προσθέτει τις custom classes του χρήστη στο body tag. */ public function filter_body_classes( $classes ) { $options = get_option( 'modular_theme_options_' . get_stylesheet(), [] ); if ( ! empty( $options['_core_body_classes'] ) ) { $custom_classes = explode( ' ', sanitize_text_field( $options['_core_body_classes'] ) ); $classes = array_merge( $classes, $custom_classes ); } return $classes; } public function render_custom_css_output() { $options = get_option( 'modular_theme_options_' . get_stylesheet(), [] ); // Αν ο δικός μας editor έχει περιεχόμενο if ( ! empty( $options['_core_custom_css'] ) ) { $css = stripslashes( $options['_core_custom_css'] ); echo "\n\n"; echo "<style id='modular-core-custom-css' type='text/css'>\n"; echo $css; echo "\n</style>\n"; } else { // Αν είναι άδειος, τρέχουμε τη default συνάρτηση του WordPress (Fallback) if ( function_exists( 'wp_custom_css_cb' ) ) { wp_custom_css_cb(); } } } public function render_main_page() { $current_tab = isset( $_GET['view'] ) ? sanitize_text_field( $_GET['view'] ) : 'options'; $active_theme_slug = get_stylesheet(); $edit_theme = isset( $_GET['edit_theme'] ) ? sanitize_text_field( $_GET['edit_theme'] ) : $active_theme_slug; $options = get_option( 'modular_theme_options_' . $edit_theme, [] ); ?> <div class="wrap"> <h1>Theme Options Ultra V16.3.0</h1> <?php if ( isset($_GET['msg']) ) : ?> <div class="notice notice-success is-dismissible"><p>Η ενέργεια ολοκληρεύθηκε με επιτυχία!</p></div> <?php endif; ?> <?php if ( isset($_GET['err']) ) : ?> <div class="notice notice-error is-dismissible"><p>Σφάλμα: <?php echo esc_html(sanitize_text_field($_GET['err'])); ?></p></div> <?php endif; ?> <div style="display:flex; gap:20px; align-items:flex-start;"> <div style="flex:1; min-width:0;"><!-- main column start --> <?php $selected_theme_obj = wp_get_theme($edit_theme); $screenshot_url = $selected_theme_obj->get_screenshot(); ?> <div class="theme-selector-wrap" style="background: #fff; padding: 15px; border: 1px solid #ccc; margin: 20px 0; display: flex; align-items: center; gap: 15px;"> <?php if ($screenshot_url) : ?> <div class="theme-screenshot-preview" style="flex-shrink: 0;"> <img src="<?php echo esc_url($screenshot_url); ?>" style="width: 120px; height: auto; border: 1px solid #ddd; display: block; border-radius: 3px;"> </div> <?php endif; ?> <strong>Επεξεργασία ρυθμίσεων για το Theme:</strong> <form method="get" action=""> <input type="hidden" name="page" value="modular_theme_options"> <input type="hidden" name="view" value="<?php echo esc_attr($current_tab); ?>"> <select name="edit_theme" onchange="this.form.submit()" style="min-width:200px;"> <?php $enabled_themes = $this->get_shared_option('modular_enabled_themes', []); foreach ( wp_get_themes() as $slug => $theme_obj ) : // Αν υπάρχουν επιλεγμένα themes και το τρέχον δεν είναι στη λίστα (και δεν είναι το ενεργό), το προσπερνάμε if (!empty($enabled_themes) && !in_array($slug, $enabled_themes) && $slug !== $active_theme_slug) continue; $label = $theme_obj->get( 'Name' ); if ($slug === $active_theme_slug) $label .= ' (Ενεργό)'; ?> <option value="<?php echo esc_attr( $slug ); ?>" <?php selected( $edit_theme, $slug ); ?>><?php echo esc_html( $label ); ?></option> <?php endforeach; ?> </select> </form> <?php if ($edit_theme === $active_theme_slug) : ?><span class="active-badge">ACTIVE</span><?php endif; ?> </div> <h2 class="nav-tab-wrapper"> <a href="?page=modular_theme_options&view=options&edit_theme=<?php echo esc_attr($edit_theme); ?>" class="nav-tab <?php echo $current_tab === 'options' ? 'nav-tab-active' : ''; ?>">Ρυθμίσεις</a> <a href="?page=modular_theme_options&view=builder&edit_theme=<?php echo esc_attr($edit_theme); ?>" class="nav-tab <?php echo $current_tab === 'builder' ? 'nav-tab-active' : ''; ?>">Builder</a> <a href="?page=modular_theme_options&view=tools&edit_theme=<?php echo esc_attr($edit_theme); ?>" class="nav-tab <?php echo $current_tab === 'tools' ? 'nav-tab-active' : ''; ?>">JSON Data</a> <a href="?page=modular_theme_options&view=info&edit_theme=<?php echo esc_attr($edit_theme); ?>" class="nav-tab <?php echo $current_tab === 'info' ? 'nav-tab-active' : ''; ?>">Info & Helpers</a> </h2> <?php if ( 'options' === $current_tab ) : ?> <form method="post" action="<?php echo admin_url( 'admin-post.php' ); ?>" id="options-form"> <input type="hidden" name="action" value="save_modular_theme_options"> <input type="hidden" name="edit_theme" value="<?php echo esc_attr( $edit_theme ); ?>"> <?php wp_nonce_field( 'modular_to_save', 'modular_nonce' ); ?> <div style="display: flex; border: 1px solid #ccc; background: #fff; min-height: 500px; align-items: flex-start;"> <div class="vertical-tabs-sidebar" style="background-color:#f0f0f1; position: sticky; top: 32px; min-height: 100%; overflow-y: auto;"> <ul class="vertical-tabs" style="width: 220px; margin: 0; background: #f0f0f1; border-right: 1px solid #ccc; list-style:none; padding:0;"> <?php if(!empty($this->config['groups'])) : foreach ( $this->config['groups'] as $i => $group ) : ?> <li style="margin:0;"><a href="#group-<?php echo $i; ?>" class="v-tab-link" style="display:block; padding: 15px; text-decoration: none; color: #222; border-bottom: 1px solid #ddd;"><?php echo esc_html( $group['name'] ); ?></a></li> <?php endforeach; endif; ?> <li style="text-align:center;"><?php submit_button('Αποθήκευση Όλων'); ?></li> </ul> </div> <div style="flex: 1; padding: 25px;"> <?php if(!empty($this->config['groups'])) : foreach ( $this->config['groups'] as $i => $group ) : ?> <div id="group-<?php echo $i; ?>" class="v-tab-content" style="display:none;"> <table class="form-table"> <?php if (!empty($group['fields'])) { foreach ($group['fields'] as $field) { $val = isset($options[$field['id']]) ? $options[$field['id']] : ''; $this->render_field_row($field, $val); } } ?> </table> </div> <?php endforeach; endif; ?> </div> </div> </form> <?php elseif ( 'builder' === $current_tab ) : ?> <div id="builder-error-msg" class="notice notice-error" style="display:none; margin-bottom:15px;"><p>Υπάρχουν διπλότυπα ή μη έγκυρα IDs πεδίων! Παρακαλώ διορθώστε τα για να ξεκλειδώσει η αποθήκευση.</p></div> <form method="post" action="<?php echo admin_url( 'admin-post.php' ); ?>" id="builder-form"> <input type="hidden" name="action" value="save_modular_structure"> <?php wp_nonce_field( 'modular_struct_save', 'modular_struct_nonce' ); ?> <div id="builder-container"> <?php if ( !empty($this->config['groups']) ) { foreach ( $this->config['groups'] as $gi => $group ) { $this->render_builder_group($gi, $group); } } ?> </div> <div class="builder-actions-panel" style="background:#fff; padding:15px; border:1px solid #ccc; margin-top:20px; display:flex; gap:10px; align-items:center; flex-wrap:wrap;"> <button type="button" class="button add-group">+ Νέο Tab</button> <div style="border-left:1px solid #ccc; height:25px; margin:0 10px;"></div> <span style="font-weight:bold; font-size:12px; color:#50575e;">Προσθήκη Συστημικού Στοιχείου:</span> <?php foreach($this->core_fields_definition as $core_id => $props): $already_added = false; if (!empty($this->config['groups'])) { foreach ($this->config['groups'] as $g) { if (!empty($g['fields'])) { foreach ($g['fields'] as $f) { if (isset($f['id']) && $f['id'] === $core_id) { $already_added = true; break 2; } } } } } $core_label = explode(' (', $props['label'])[0]; ?> <button type="button" class="button add-core-field" data-core-id="<?php echo esc_attr($core_id); ?>" <?php echo $already_added ? 'style="display:none;"' : ''; ?>><?php echo esc_html($core_label); ?></button> <?php endforeach; ?> </div> <?php submit_button('Αποθήκευση Δομής', 'primary', 'submit-builder-btn'); ?> </form> <?php elseif ( 'tools' === $current_tab ) : ?> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;"> <div class="card" style="max-width:100%; margin:0;"> <h2>JSON Δομής (Tabs & Fields)</h2> <form method="post" action="<?php echo admin_url('admin-post.php'); ?>"> <input type="hidden" name="action" value="import_modular_json"> <?php wp_nonce_field('modular_import_json', 'modular_import_json_nonce'); ?> <textarea name="import_json" style="width:100%; height: 450px; font-family: monospace; background:#1e1e1e; color:#7ec0ee; padding:15px;"><?php echo esc_textarea(json_encode($this->config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); ?></textarea> <?php submit_button('Εισαγωγή Δομής'); ?> </form> </div> <div class="card" style="max-width:100%; margin:0;"> <h2>Global JSON Δεδομένων (Όλα τα Themes)</h2> <form method="post" action="<?php echo admin_url('admin-post.php'); ?>"> <input type="hidden" name="action" value="import_all_options_json"> <?php wp_nonce_field('modular_import_all_options', 'modular_import_all_options_nonce'); ?> <textarea name="import_all_options" style="width:100%; height: 450px; font-family: monospace; background:#1e1e1e; color:#e6db74; padding:15px;"><?php echo esc_textarea(json_encode($this->get_all_themes_data(), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); ?></textarea> <?php submit_button('Εισαγωγή Όλων των Δεδομένων'); ?> </form> </div> </div> <?php elseif ( 'info' === $current_tab ) : ?> <div class="wrap-info-tab" style="display: flex; flex-direction: column; gap: 25px;"> <div class="card" style="max-width: 100%; background: #fff; padding: 20px; border: 1px solid #ccc; margin-top:20px;"> <h2>Ορατότητα Themes στο Dropdown</h2> <p class="description">Επιλέξτε ποια themes θα εμφανίζονται στη λίστα επιλογής στην κορυφή της σελίδας.</p> <form method="post" action="<?php echo admin_url('admin-post.php'); ?>"> <input type="hidden" name="action" value="save_enabled_themes"> <?php wp_nonce_field('save_enabled_themes_nonce', 'enabled_themes_nonce'); ?> <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; margin: 15px 0;"> <?php $all_themes = wp_get_themes(); $enabled_themes = $this->get_shared_option('modular_enabled_themes', []); foreach ($all_themes as $slug => $theme) : $checked = (empty($enabled_themes) || in_array($slug, $enabled_themes)) ? 'checked' : ''; ?> <label style="display: flex; align-items: center; gap: 8px; background: #f9f9f9; padding: 8px; border: 1px solid #ddd; border-radius: 4px;"> <input type="checkbox" name="enabled_themes[]" value="<?php echo esc_attr($slug); ?>" <?php echo $checked; ?>> <?php echo esc_html($theme->get('Name')); ?> </label> <?php endforeach; ?> </div> <?php submit_button('Αποθήκευση Ορατότητας Themes'); ?> </form> </div> <div class="card" style="max-width: 100%; background: #fff; padding: 20px; border: 1px solid #ccc; margin:0;"> <h2>Καθολικός Οδηγός Συντάξεων & PHP Snippets</h2> <table class="wp-list-table widefat fixed striped" style="margin-top:15px;"> <thead> <tr> <th style="width:15%; font-weight:bold;">Tab / Ομάδα</th> <th style="width:20%; font-weight:bold;">Όνομα Πεδίου</th> <th style="width:40%; font-weight:bold;">Κώδικας PHP</th> <th style="width:25%; font-weight:bold;">Shortcode</th> </tr> </thead> <tbody> <?php if (!empty($this->config['groups'])) { foreach ($this->config['groups'] as $group) { if (!empty($group['fields'])) { foreach ($group['fields'] as $field) { ?> <tr> <td><span class="dashicons dashicons-category" style="font-size:16px; vertical-align:middle; margin-right:4px;"></span> <b><?php echo esc_html($group['name']); ?></b></td> <td> <b><?php echo esc_html($field['label']); ?></b><br> <code><?php echo esc_html($field['id']); ?></code><br> <span class="type-badge"><?php echo esc_html($field['type']); ?></span> </td> <td> <pre class="info-pre"><?php if ($field['id'] === '_core_site_logo' || $field['id'] === '_core_homepage_logo') { echo esc_html("<?php\nglobal \$ModularThemeOptions;\n\$logo_id = \$ModularThemeOptions->get_resolved_logo_id();\nif(\$logo_id) {\n echo wp_get_attachment_image(\$logo_id, 'full');\n}\n?>"); } elseif ($field['id'] === '_core_site_title') { echo esc_html("<?php echo get_option('blogname'); ?>"); } elseif ($field['id'] === '_core_tagline') { echo esc_html("<?php echo get_option('blogdescription'); ?>"); } elseif ($field['type'] === 'media_array') { echo esc_html("<?php\n\$opts = get_option('modular_theme_options_' . get_stylesheet(), []);\n\$raw = !empty(\$opts['".$field['id']."']) ? \$opts['".$field['id']."'] : '';\n\$img_data = json_decode(stripslashes(\$raw), true) ?: [];\n\nif (!empty(\$img_data)) {\n echo '<img src=\"' . esc_url(\$img_data['url']) . '\" alt=\"' . esc_attr(\$img_data['alt']) . '\">';\n}\n?>"); } elseif ($field['type'] === 'boolean') { echo esc_html("<?php\n\$opts = get_option('modular_theme_options_' . get_stylesheet(), []);\n\$is_enabled = !empty(\$opts['".$field['id']."']) && \$opts['".$field['id']."'] === '1';\nif (\$is_enabled) { /* ... */ }\n?>"); } elseif ($field['type'] === 'html' || $field['type'] === 'code_editor') { echo esc_html("<?php\n\$opts = get_option('modular_theme_options_' . get_stylesheet(), []);\necho !empty(\$opts['".$field['id']."']) ? stripslashes(\$opts['".$field['id']."']) : '';\n?>"); } else { echo esc_html("<?php\n\$opts = get_option('modular_theme_options_' . get_stylesheet(), []);\necho !empty(\$opts['".$field['id']."']) ? esc_attr(stripslashes(\$opts['".$field['id']."'])) : '';\n?>"); } ?></pre> </td> <td> <pre class="sc-pre">[theme_opt id="<?php echo esc_attr($field['id']); ?>"]</pre> </td> </tr> <?php } } } } else { echo '<tr><td colspan="4">Δεν έχουν βρεθεί πεδία.</td></tr>'; } ?> </tbody> </table> </div> </div> <?php endif; ?> </div><!-- /main column --> <?php // ── Sidebar (permanent, outside tabs) ────────────────────────────── $themes_url = admin_url( 'themes.php' ); $switcher_url = null; // Detect common theme-switcher plugins if ( is_plugin_active( 'theme-switcha/theme-switcha.php' ) ) { $switcher_url = admin_url( 'themes.php?page=theme-switcha' ); } elseif ( is_plugin_active( 'advanced-theme-switcher/advanced-theme-switcher.php' ) ) { $switcher_url = admin_url( 'admin.php?page=advanced-theme-switcher' ); } elseif ( is_plugin_active( 'lh-theme-switcher/lh-theme-switcher.php' ) ) { $switcher_url = admin_url( 'tools.php?page=lh-theme-switcher' ); } elseif ( is_plugin_active( 'wp-theme-switcher/wp-theme-switcher.php' ) ) { $switcher_url = admin_url( 'themes.php?page=wp-theme-switcher' ); } elseif ( is_plugin_active( 'jonradio-multiple-themes/jonradio-multiple-themes.php' ) ) { $switcher_url = admin_url( 'options-general.php?page=jonradio-multiple-themes' ); } ?> <div class="mto-sidebar" style="width:200px; flex-shrink:0; position:sticky; top:32px; align-self:flex-start;"> <div style="background:#fff; border:1px solid #ccc; border-top:4px solid #2271b1; padding:14px; border-radius:2px; margin-bottom:12px;"> <h3 style="margin:0 0 10px 0; font-size:13px; color:#2271b1; text-transform:uppercase; letter-spacing:.5px;">🎨 Θέματα</h3> <a href="<?php echo esc_url( $themes_url ); ?>" class="button button-secondary" style="width:100%; text-align:center; margin-bottom:6px; display:block; box-sizing:border-box;"> <span class="dashicons dashicons-admin-appearance" style="vertical-align:middle; font-size:16px; margin-right:4px;"></span> Διαχείριση Themes </a> <?php if ( $switcher_url ) : ?> <a href="<?php echo esc_url( $switcher_url ); ?>" class="button button-secondary" style="width:100%; text-align:center; display:block; box-sizing:border-box; border-color:#9b59b6; color:#9b59b6;"> <span class="dashicons dashicons-randomize" style="vertical-align:middle; font-size:16px; margin-right:4px;"></span> Theme Switcher </a> <?php endif; ?> </div> <?php if ( is_multisite() && is_super_admin() ) : ?> <div style="background:#fff; border:1px solid #ccc; border-top:4px solid #f0a500; padding:14px; border-radius:2px;"> <h3 style="margin:0 0 10px 0; font-size:13px; color:#f0a500; text-transform:uppercase; letter-spacing:.5px;">🌐 Network</h3> <a href="<?php echo esc_url( network_admin_url( 'themes.php' ) ); ?>" class="button button-secondary" style="width:100%; text-align:center; display:block; box-sizing:border-box;"> <span class="dashicons dashicons-networking" style="vertical-align:middle; font-size:16px; margin-right:4px;"></span> Network Themes </a> </div> <?php endif; ?> </div><!-- /sidebar --> </div><!-- /flex wrapper --> </div><!-- /wrap --> <?php } // ── JS Templates (outside render_main_page scope) ───────────────────── public function render_js_templates() { $screen = get_current_screen(); if ( ! $screen || $screen->id !== 'appearance_page_modular_theme_options' ) return; ?> <script type="text/template" id="group-tmpl"><?php $this->render_builder_group('{{G_ID}}'); ?></script> <script type="text/template" id="field-tmpl"><?php $this->render_builder_field('{{G_ID}}', '{{F_ID}}'); ?></script> <?php foreach($this->core_fields_definition as $core_id => $props): ?> <script type="text/template" id="tmpl-<?php echo $core_id; ?>"> <?php $this->render_builder_field('{{G_ID}}', '{{F_ID}}', ['id' => $core_id, 'label' => $props['label'], 'type' => $props['type']], true); ?> </script> <?php endforeach; ?> <style> .v-tab-link.active { background: #fff !important; font-weight: bold; border-right: 1px solid #fff !important; color: #2271b1 !important; } .builder-group { background: #fff; border: 1px solid #ccc; padding: 20px; margin-bottom: 20px; border-left: 5px solid #2271b1; position: relative;} .fields-container { background: #fafafa; border: 1px dashed #bbb; min-height: 60px; padding: 10px; margin: 10px 0; } .builder-field { background: #f8f9fa; border: 1px solid #ddd; padding: 10px; margin: 8px 0; display: flex; gap: 10px; align-items: center; cursor: move; } .builder-field.is-core-field { border: 1px solid #722ed1 !important; border-left: 5px solid #722ed1 !important; background: #f9f5ff !important; } .core-badge { background: #722ed1; color: #fff; font-size: 9px; font-weight: bold; padding: 3px 6px; border-radius: 3px; text-transform: uppercase; display: inline-block; } .core-id-frozen { background: #f1f1f1 !important; color: #555 !important; font-family: monospace; font-weight: bold; border: 1px solid #d2c4e8 !important; cursor: not-allowed; } .builder-field.field-error { border-color: #d63638; background: #fbeae9; } .field-id-input.error-id { border-color: #d63638 !important; background: #fff0f0 !important; color: #d63638; } .ui-sortable-placeholder { background: #eef7ff !important; border: 1px dashed #2271b1 !important; visibility: visible !important; height: 45px; } .media-preview-wrap img { max-width: 130px; height: auto; border: 1px solid #ccc; display: block; margin-bottom: 8px; } .ace-editor-wrap { border: 1px solid #ddd; font-size: 13px; } .active-badge { background: #46b450; color: #fff; padding: 3px 8px; border-radius: 3px; font-size: 11px; font-weight: bold; } .type-badge { background: #e1f0fe; color: #1e72be; font-size: 9px; font-weight: bold; padding: 2px 6px; border-radius: 3px; display: inline-block; margin-top: 4px; text-transform: uppercase; border: 1px solid #c2e0fc; } .code-helper-toggle { background: #f7f7f7; border: 1px solid #ddd; border-radius: 4px; margin-top: 8px; padding: 4px; max-width: 100%; } .code-helper-toggle summary { font-weight: bold; cursor: pointer; padding: 4px; font-size: 11px; color: #2271b1; outline:none; } .code-helper-toggle pre { background: #272822; color: #f8f8f2; padding: 8px; border-radius: 3px; font-size: 11px; overflow-x: auto; margin: 4px 0 0 0; } .info-pre { margin:0; background:#272822; color:#f8f8f2; padding:8px; font-size:11px; line-height:1.4; border-radius:3px; overflow-x:auto; white-space: pre-wrap; } .sc-pre { margin:0; background:#eef3f6; color:#000; padding:8px; font-size:11px; font-weight:bold; border-radius:3px; border:1px solid #dcdcde; } .locked-id-field { background: #f0f0f1 !important; color: #646970 !important; cursor: not-allowed; border-color: #dcdcde !important; } .core-option-row { background: #fdfaff; border-left: 4px solid #722ed1 !important; padding-left:20px;} .form-table tbody tr > th { padding-left:20px;} .field-options-input { border: 1px solid #bbb; padding: 4px; font-size: 12px; width: 200px; background: #fff; } #wp-admin-bar-modular-options-link .ab-icon:before { content: "\f111"; top: 2px; } </style> <?php } private function get_all_themes_data() { global $wpdb; $prefix = 'modular_theme_options_'; $options_table = $wpdb->prefix . 'options'; // Multisite-safe: uses current blog prefix $results = $wpdb->get_results( $wpdb->prepare( "SELECT option_name, option_value FROM $options_table WHERE option_name LIKE %s", $wpdb->esc_like( $prefix ) . '%' ), ARRAY_A ); $data = []; foreach ( $results as $row ) { $theme_slug = str_replace( $prefix, '', $row['option_name'] ); $data[$theme_slug] = maybe_unserialize( $row['option_value'] ); } return $data; } private function is_field_has_data_globally( $field_id ) { static $all_data = null; if ( empty( $field_id ) ) return false; if ( $all_data === null ) $all_data = $this->get_all_themes_data(); foreach ( $all_data as $options ) { if ( is_array( $options ) && isset( $options[$field_id] ) && $options[$field_id] !== '' ) return true; } return false; } public function render_builder_group($gi, $group = []) { $name = isset($group['name']) ? $group['name'] : ''; ?> <div class="builder-group" data-gid="<?php echo esc_attr($gi); ?>"> <button type="button" class="remove-group-btn" style="position:absolute; top:10px; right:10px; background:none; border:none; cursor:pointer; color:#d63638; font-weight:bold; z-index:10;">✕ Διαγραφή</button> <h3>Tab Name: <input type="text" name="struct[groups][<?php echo $gi; ?>][name]" value="<?php echo esc_attr($name); ?>" class="group-name-input" required></h3> <div class="fields-container sortable-fields"> <?php if (!empty($group['fields'])) { foreach ($group['fields'] as $fi => $f) { $this->render_builder_field($gi, $fi, $f); } } ?> </div> <button type="button" class="button add-field" data-group="<?php echo $gi; ?>">+ Πεδίο</button> <button type="button" class="button add-hr-field" data-group="<?php echo $gi; ?>" style="color:#555;">── HR</button> </div> <?php } public function render_builder_field($gi, $fi, $field = [], $force_core = false) { $id = isset($field['id']) ? $field['id'] : ''; $label = isset($field['label']) ? $field['label'] : ''; $type = isset($field['type']) ? $field['type'] : 'text'; $options_val = isset($field['options']) ? $field['options'] : ''; $description = isset($field['description']) ? $field['description'] : ''; $is_core = $force_core || array_key_exists($id, $this->core_fields_definition); $badge_text = $is_core ? $this->core_fields_definition[$id]['badge'] : ''; $field_classes = 'builder-field'; if ($is_core) $field_classes .= ' is-core-field'; if ($type === 'hr') $field_classes .= ' is-hr-field'; if ($is_core) { $id_attr = 'class="field-id-input core-id-frozen" readonly value="' . esc_attr($id) . '"'; $type_attr = '<input type="hidden" name="struct[groups]['.$gi.'][fields]['.$fi.'][type]" value="'.esc_attr($type).'"><span class="core-badge">'.$badge_text.'</span>'; } else { $has_data = $this->is_field_has_data_globally($id); $id_attr = ($has_data && $type !== 'hr') ? 'class="field-id-input locked-id-field" readonly title="Περιέχει δεδομένα"' : 'class="field-id-input"'; $id_attr .= ' value="' . esc_attr($id) . '"'; $type_attr = '<select name="struct[groups]['.$gi.'][fields]['.$fi.'][type]" class="field-type-selector">'; foreach([ 'text' => 'Text', 'textarea' => 'Textarea', 'html' => 'HTML', 'media_id' => 'Media (ID)', 'media_url' => 'Media (URL)', 'media_array' => 'Media (Array)', 'color' => 'Color', 'code_editor' => 'CSS Editor', 'boolean' => 'Boolean (Yes/No)', 'select' => 'Select (Dropdown)', 'hr' => '── Horizontal Rule ──' ] as $k=>$v) { $type_attr .= '<option value="'.$k.'" '.selected($type, $k, false).'>'.$v.'</option>'; } $type_attr .= '</select>'; } if ($type === 'hr') { ?> <div class="<?php echo $field_classes; ?>" style="background:#f0f0f1; border:1px dashed #bbb; padding:8px 12px; display:flex; align-items:center; gap:8px; cursor:move;"> <span class="dashicons dashicons-menu" style="color:#aaa;"></span> <span style="color:#666; font-size:12px; flex:1;">── Horizontal Rule (Διαχωριστικό) ──</span> <input type="hidden" name="struct[groups][<?php echo $gi; ?>][fields][<?php echo $fi; ?>][id]" value="<?php echo esc_attr($id ?: 'hr_'.uniqid()); ?>"> <input type="hidden" name="struct[groups][<?php echo $gi; ?>][fields][<?php echo $fi; ?>][label]" value="HR"> <input type="hidden" name="struct[groups][<?php echo $gi; ?>][fields][<?php echo $fi; ?>][type]" value="hr"> <button type="button" class="remove-item" style="color:red; border:none; background:none; cursor:pointer;">✕</button> </div> <?php return; } ?> <div class="<?php echo $field_classes; ?>"> <span class="dashicons dashicons-menu" style="color:#aaa; cursor:move;"></span> <input type="text" name="struct[groups][<?php echo $gi; ?>][fields][<?php echo $fi; ?>][label]" value="<?php echo esc_attr($label); ?>" placeholder="Label" required> <div style="position:relative; display:inline-block;"> <input type="text" name="struct[groups][<?php echo $gi; ?>][fields][<?php echo $fi; ?>][id]" placeholder="ID" <?php echo $id_attr; ?> required> <?php if(!$is_core && !empty($has_data)): ?> <span class="dashicons dashicons-lock" style="position:absolute; right:5px; top:6px; font-size:14px; color:#a7aaad;"></span> <?php endif; ?> </div> <?php echo $type_attr; ?> <div class="options-input-wrapper" style="display: <?php echo ($type === 'select') ? 'inline-block' : 'none'; ?>;"> <input type="text" name="struct[groups][<?php echo $gi; ?>][fields][<?php echo $fi; ?>][options]" value="<?php echo esc_attr($options_val); ?>" class="field-options-input" placeholder="Label:value, Label2:value2"> </div> <input type="text" name="struct[groups][<?php echo $gi; ?>][fields][<?php echo $fi; ?>][description]" value="<?php echo esc_attr($description); ?>" placeholder="📝 Περιγραφή..." style="font-size:11px; border:1px solid #ddd; padding:3px 6px; min-width:140px; color:#646970;"> <button type="button" class="remove-item" style="color:red; border:none; background:none; cursor:pointer; margin-left:auto;">✕</button> </div> <?php } private function render_field_row($field, $value) { $name = "options[" . esc_attr($field['id']) . "]"; $is_core = array_key_exists($field['id'], $this->core_fields_definition); $description = isset($field['description']) ? $field['description'] : ''; if ($field['type'] === 'hr') { echo '<tr><td colspan="2" style="padding:6px 0;"><hr style="border:none; border-top:2px solid #ddd; margin:4px 0;"></td></tr>'; return; } $row_class = $is_core ? 'class="core-option-row"' : ''; $core_notice = $is_core ? ' <span class="core-badge" style="font-size:8px; padding:1px 4px; vertical-align: middle;">'.$this->core_fields_definition[$field['id']]['badge'].'</span>' : ''; echo '<tr '.$row_class.'>'; echo '<th>' . esc_html($field['label']) . $core_notice . '<br><code>' . esc_html($field['id']) . '</code><br><span class="type-badge">' . esc_html($field['type']) . '</span>'; if ($description) echo '<br><span style="font-size:11px; color:#646970; font-style:italic;">' . esc_html($description) . '</span>'; echo '</th><td>'; if (strpos($field['type'], 'media') !== false) { $img_id = 0; $preview = ''; $raw_value = wp_unslash($value); $decoded = json_decode($raw_value, true); if (is_array($decoded) && isset($decoded['id'])) { $img_id = $decoded['id']; } elseif ($field['type'] === 'media_id' || is_numeric($raw_value)) { $img_id = $raw_value; } elseif ($field['type'] === 'media_url' && !empty($raw_value)) { // Try to get ID from URL to show real thumbnail $img_id = attachment_url_to_postid($raw_value); if (!$img_id) $preview = $raw_value; // If no ID, use URL directly } if ($img_id) { $t = wp_get_attachment_image_src($img_id, 'thumbnail'); $preview = $t ? $t[0] : wp_get_attachment_url($img_id); } echo '<div class="media-preview-wrap">'; echo '<img src="'.esc_url($preview).'" class="img-preview" style="display:'.($preview ? 'block' : 'none').';">'; echo '<input type="hidden" name="'.$name.'" value="'.esc_attr($raw_value).'" class="media-hidden-input" data-type="'.$field['type'].'">'; echo '<button class="button upload-media-btn">Επιλογή</button> <button class="button remove-media-btn">X</button>'; echo '</div>'; } elseif ($field['type'] === 'html' || $field['type'] === 'code_editor') { $uid = 'ace_' . sanitize_key($field['id']); $mode = 'html'; // default if ($field['type'] === 'code_editor') { $mode = 'css'; } elseif ($field['type'] === 'javascript') { $mode = 'javascript'; } // We use stripslashes to show it correctly in editor echo '<input type="hidden" name="'.$name.'" id="'.$uid.'_val" value="'.esc_attr(stripslashes($value)).'">'; echo '<div id="'.$uid.'" class="ace-editor-wrap" data-mode="'.$mode.'" data-target="'.$uid.'_val" style="width:100%; height:200px; border:1px solid #ddd;"></div>'; } elseif ($field['type'] === 'color') { echo '<input type="text" name="'.$name.'" value="'.esc_attr($value).'" class="color-picker">'; } elseif ($field['type'] === 'textarea') { echo '<textarea name="'.$name.'" class="regular-text" rows="4" style="width:100%;">'.esc_textarea(stripslashes($value)).'</textarea>'; } elseif ($field['type'] === 'boolean') { echo '<select name="'.$name.'" style="width:150px;">'; echo '<option value="0" '.selected($value, '0', false).'>Όχι (0)</option>'; echo '<option value="1" '.selected($value, '1', false).'>Ναι (1)</option>'; echo '</select>'; } elseif ($field['type'] === 'select') { echo '<select name="'.$name.'" style="min-width:200px;">'; echo '<option value="">-- Επιλογή --</option>'; $raw_opts = isset($field['options']) ? $field['options'] : ''; foreach(explode(',', $raw_opts) as $opt) { $opt = trim($opt); if($opt === '') continue; if(strpos($opt, ':') !== false) { list($opt_label, $opt_val) = array_map('trim', explode(':', $opt, 2)); } else { $opt_label = $opt; $opt_val = $opt; } echo '<option value="'.esc_attr($opt_val).'" '.selected($value, $opt_val, false).'>'.esc_html($opt_label).'</option>'; } echo '</select>'; } else { echo '<input type="text" name="'.$name.'" value="'.esc_attr(stripslashes($value)).'" class="regular-text" style="width:100%;">'; } if (!$is_core) { echo '<details class="code-helper-toggle"><summary>⚙️ Snippet Κώδικα</summary><div><pre>'; echo esc_html('<?php $opts = get_option("modular_theme_options_" . get_stylesheet(), []); echo !empty($opts["'.$field['id'].'"]) ? stripslashes($opts["'.$field['id'].'"]) : ""; ?>'); echo '</pre></div></details>'; } echo '</td></tr>'; } public function save_theme_values() { check_admin_referer('modular_to_save', 'modular_nonce'); $options = isset($_POST['options']) ? $_POST['options'] : []; $clean_options = wp_unslash($options); $type_map = []; if (!empty($this->config['groups'])) { foreach ($this->config['groups'] as $group) { if (!empty($group['fields'])) { foreach ($group['fields'] as $f) { if (!empty($f['id'])) $type_map[$f['id']] = $f['type']; } } } } foreach ($clean_options as $key => $val) { $field_type = isset($type_map[$key]) ? $type_map[$key] : ''; if ($field_type === 'html' || $field_type === 'code_editor') { // Keep raw but slashed for DB safety $clean_options[$key] = wp_slash($val); } elseif (is_string($val) && strpos($val, '"id":') !== false) { $decoded = json_decode($val, true); if (json_last_error() === JSON_ERROR_NONE) { $clean_options[$key] = wp_json_encode($decoded, JSON_UNESCAPED_UNICODE); } } else { $clean_options[$key] = wp_slash($val); } } $edit_theme = sanitize_text_field( $_POST['edit_theme'] ); update_option( 'modular_theme_options_' . $edit_theme, $clean_options ); wp_redirect( $this->admin_page_url( 'options', $edit_theme, 'msg=1' ) ); exit; } public function save_structure() { check_admin_referer('modular_struct_save', 'modular_struct_nonce'); $data = wp_unslash($_POST['struct']); $sanitized = ['groups' => []]; if(!empty($data['groups'])) { foreach($data['groups'] as $group) { $new_group = ['name' => sanitize_text_field($group['name']), 'fields' => []]; if(!empty($group['fields'])) { foreach($group['fields'] as $field) { $fid = isset($field['id']) ? trim($field['id']) : ''; $flabel = isset($field['label']) ? trim($field['label']) : ''; $ftype = isset($field['type']) ? trim($field['type']) : ''; if ($ftype === 'hr') { $new_group['fields'][] = ['label'=>'HR','id'=>$fid ?: ('hr_'.uniqid()),'type'=>'hr','options'=>'','description'=>'']; continue; } if ($fid === '' && $flabel === '') continue; $is_core = array_key_exists($fid, $this->core_fields_definition); $new_group['fields'][] = [ 'label' => sanitize_text_field($flabel), 'id' => $is_core ? sanitize_text_field($fid) : sanitize_title($fid), 'type' => sanitize_text_field($ftype), 'options' => isset($field['options']) ? sanitize_text_field($field['options']) : '', 'description' => isset($field['description']) ? sanitize_textarea_field($field['description']) : '' ]; } } $sanitized['groups'][] = $new_group; } } if ( is_multisite() ) { update_network_option( null, $this->structure_opt, $sanitized ); } else { update_option( $this->structure_opt, $sanitized ); } wp_redirect( $this->admin_page_url( 'builder', '', 'msg=1' ) ); exit; } public function import_structure_json() { check_admin_referer( 'modular_import_json', 'modular_import_json_nonce' ); $data = json_decode( wp_unslash( $_POST['import_json'] ), true ); if ( json_last_error() !== JSON_ERROR_NONE ) { wp_redirect( $this->admin_page_url( 'tools', '', 'err=' . urlencode( 'Μη έγκυρο JSON.' ) ) ); exit; } if ( is_multisite() ) { update_network_option( null, $this->structure_opt, $data ); } else { update_option( $this->structure_opt, $data ); } wp_redirect( $this->admin_page_url( 'tools', '', 'msg=1' ) ); exit; } public function import_all_options_json() { check_admin_referer('modular_import_all_options', 'modular_import_all_options_nonce'); $data = json_decode(wp_unslash($_POST['import_all_options']), true); if ( json_last_error() !== JSON_ERROR_NONE ) { wp_redirect( $this->admin_page_url( 'tools', '', 'err=' . urlencode('Μη έγκυρο JSON.') ) ); exit; } if ( is_array($data) ) { foreach ( $data as $slug => $opts ) { update_option( 'modular_theme_options_' . sanitize_text_field($slug), $opts ); } } wp_redirect( $this->admin_page_url( 'tools', '', 'msg=1' ) ); exit; } public function save_enabled_themes() { check_admin_referer('save_enabled_themes_nonce', 'enabled_themes_nonce'); if (!current_user_can('manage_options')) wp_die('Unauthorized'); $enabled = isset($_POST['enabled_themes']) ? array_map('sanitize_text_field', $_POST['enabled_themes']) : []; if ( is_multisite() ) { update_network_option( null, 'modular_enabled_themes', $enabled ); } else { update_option( 'modular_enabled_themes', $enabled ); } wp_redirect( $this->admin_page_url( 'info', '', 'msg=1' ) ); exit; } public function theme_option_shortcode($atts) { $a = shortcode_atts(['id' => '', 'get' => 'url'], $atts); if($a['id'] === '_core_site_logo' || $a['id'] === '_core_homepage_logo') { $resolved_id = $this->get_resolved_logo_id(); if(!$resolved_id) return ''; return ($a['get'] === 'id') ? $resolved_id : wp_get_attachment_url($resolved_id); } $options = get_option('modular_theme_options_' . get_stylesheet(), []); if(empty($options[$a['id']]) && $a['id'] === '_core_site_title') return get_option('blogname'); if(empty($options[$a['id']]) && $a['id'] === '_core_tagline') return get_option('blogdescription'); $val = isset($options[$a['id']]) ? $options[$a['id']] : ''; $decoded = json_decode(stripslashes($val), true); if (is_array($decoded)) { return isset($decoded[$a['get']]) ? $decoded[$a['get']] : ''; } return stripslashes($val); } public function render_inline_js() { $screen = get_current_screen(); if ( ! $screen || $screen->id !== 'appearance_page_modular_theme_options' ) return; ?> <script type="text/javascript"> jQuery(document).ready(function($){ var tabs = $('.v-tab-link'), contents = $('.v-tab-content'); function activeTab(i) { tabs.removeClass('active'); contents.hide(); tabs.eq(i).addClass('active'); contents.eq(i).show(); setTimeout(function(){ $('.ace-editor-wrap').each(function() { var ed = $(this).data('ace'); if (ed) ed.resize(); }); }, 60); } if(tabs.length > 0) activeTab(0); tabs.on('click', function(e){ e.preventDefault(); activeTab(tabs.index(this)); }); if (typeof $.fn.wpColorPicker === 'function') { $('.color-picker').wpColorPicker(); } function initAceEditors() { if (typeof ace === 'undefined') return; $('.ace-editor-wrap').each(function() { if ($(this).data('ace')) return; var el = this, mode = $(el).data('mode') || 'css', target = '#' + $(el).data('target'); var editor = ace.edit(el); editor.setTheme('ace/theme/monokai'); // Διαφοροποίηση ανάλογα με το mode if (mode === 'css') { editor.session.setMode('ace/mode/css'); // Ειδικές ρυθμίσεις μόνο για CSS editor.setOptions({ enableBasicAutocompletion: true, enableLiveAutocompletion: true }); } else { editor.session.setMode('ace/mode/html'); } editor.setOptions({ fontSize: '13px', showPrintMargin: false, wrap: true, useSoftTabs: true, enableBasicAutocompletion: true, // Ενεργοποιεί το Ctrl+Space enableLiveAutocompletion: true, // Εμφανίζει προτάσεις καθώς γράφεις enableSnippets: true // Ενεργοποιεί έτοιμα κομμάτια κώδικα }); editor.setValue($(target).val(), -1); editor.session.on('change', function() { $(target).val(editor.getValue()); }); $(el).data('ace', editor); }); } var aceWait = setInterval(function() { if (typeof ace !== 'undefined') { clearInterval(aceWait); initAceEditors(); } }, 100); $(document).on('change', '.field-type-selector', function(){ var wrapper = $(this).closest('.builder-field').find('.options-input-wrapper'); if($(this).val() === 'select') { wrapper.css('display', 'inline-block'); } else { wrapper.hide(); } }); $('#options-form').on('submit', function() { $('.ace-editor-wrap').each(function() { var ed = $(this).data('ace'); if (ed) $('#' + $(this).data('target')).val(ed.getValue()); }); }); $(document).on('click', '.upload-media-btn', function(e){ e.preventDefault(); var btn = $(this), container = btn.closest('.media-preview-wrap'), input = container.find('.media-hidden-input'), img = container.find('.img-preview'), type = input.data('type'); var frame = wp.media({ title: 'Επιλογή Αρχείου', button: { text: 'Χρήση Αρχείου' }, multiple: false }); frame.on('select', function(){ var att = frame.state().get('selection').first().toJSON(); var prUrl = att.sizes && att.sizes.thumbnail ? att.sizes.thumbnail.url : att.url; img.attr('src', prUrl).show(); if (type === 'media_id') input.val(att.id); else if (type === 'media_url') input.val(att.url); else if (type === 'media_array') { input.val(JSON.stringify({id: att.id, url: att.url, title: att.title, alt: att.alt, width: att.width, height: att.height})); } }).open(); }); $(document).on('click', '.remove-media-btn', function(e){ e.preventDefault(); var c = $(this).closest('.media-preview-wrap'); c.find('.media-hidden-input').val(''); c.find('.img-preview').attr('src', '').hide(); }); function checkDuplicateIDs() { var ids = []; var hasDuplicates = false; $('.field-id-input').each(function() { var v = $(this).val().trim().toLowerCase(); $(this).removeClass('error-id').closest('.builder-field').removeClass('field-error'); if (v === '') return; if (ids.indexOf(v) !== -1) { hasDuplicates = true; $('.field-id-input').each(function() { if ($(this).val().trim().toLowerCase() === v) $(this).addClass('error-id').closest('.builder-field').addClass('field-error'); }); } else ids.push(v); }); if (hasDuplicates) { $('#builder-error-msg').show(); $('#submit-builder-btn').attr('disabled', 'disabled'); } else { $('#builder-error-msg').hide(); $('#submit-builder-btn').removeAttr('disabled'); } } $(document).on('keyup', '.field-id-input', function() { checkDuplicateIDs(); }); function initSortable() { $(".sortable-fields").sortable({ connectWith: ".sortable-fields", handle: ".dashicons-menu", stop: function() { reindexAllGroups(); checkDuplicateIDs(); } }).disableSelection(); } initSortable(); function reindexAllGroups() { $('.builder-group').each(function(gI){ $(this).find('.group-name-input').attr('name', 'struct[groups]['+gI+'][name]'); $(this).find('.builder-field').each(function(fI){ $(this).find('input[name], select[name]').each(function(){ var m = $(this).attr('name').match(/\[(\w+)\]$/); if(m) $(this).attr('name', 'struct[groups]['+gI+'][fields]['+fI+']['+m[1]+']'); }); }); }); } $('.add-group').click(function(){ var gid = 'g'+Date.now(); var html = $('#group-tmpl').html().replace(/{{G_ID}}/g, gid); $('#builder-container').append(html); reindexAllGroups(); initSortable(); }); // ΑΛΛΑΓΗ 1: Handler για Core Fields $(document).on('click', '.add-core-field', function(){ var coreId = $(this).data('core-id'); var targetGroup = $('.builder-group').first(); if(targetGroup.length === 0) { alert('Δημιουργήστε πρώτα ένα Tab!'); return; } var gid = targetGroup.data('gid'), fid = 'f' + Date.now(); var html = $('#tmpl-' + coreId).html().replace(/{{G_ID}}/g, gid).replace(/{{F_ID}}/g, fid); targetGroup.find('.fields-container').append(html); $(this).hide(); reindexAllGroups(); checkDuplicateIDs(); }); $(document).on('click', '.add-field', function(){ var g = $(this).closest('.builder-group'), gid = g.data('gid'), fid = 'f'+Date.now(); var html = $('#field-tmpl').html().replace(/{{G_ID}}/g, gid).replace(/{{F_ID}}/g, fid); g.find('.fields-container').append(html); reindexAllGroups(); }); // Handler για το κουμπί Horizontal Rule (HR) $(document).on('click', '.add-hr-field', function(){ var g = $(this).closest('.builder-group'), gid = g.data('gid'), fid = 'hr_' + Date.now(); var html = $('#field-tmpl').html().replace(/{{G_ID}}/g, gid).replace(/{{F_ID}}/g, fid); var $html = $(html); // Παραμετροποίηση του template ώστε να συμπεριφέρεται ως HR $html.addClass('is-hr-field').css({'background':'#f0f0f1', 'border':'1px dashed #bbb'}); $html.find('.field-type-selector').val('hr'); $html.find('input[placeholder="Label"]').val('HR').hide(); $html.find('.field-id-input').val(fid).hide(); $html.find('.field-options-input').hide(); $html.find('input[placeholder="📝 Περιγραφή..."]').hide(); g.find('.fields-container').append($html); reindexAllGroups(); }); $(document).on('click', '.remove-item', function(){ var field = $(this).closest('.builder-field'); var id = field.find('.field-id-input').val(); // Αν το πεδίο που διαγράφεται είναι Core Field, επανεμφανίζουμε το αντίστοιχο κουμπί προσθήκης if(field.hasClass('is-core-field')) { $('.add-core-field[data-core-id="' + id + '"]').show(); } field.remove(); reindexAllGroups(); checkDuplicateIDs(); }); }); </script> <?php } } $GLOBALS['ModularThemeOptions'] = new Modular_Theme_Options();
CANCEL
Name
Type
Size
Modified
Actions
↩ ..
DIR
—
—
📄 theme-editor.php
PHP
64.5 KB
2026-05-19 12:13
EDIT