| | |
| | | 'icon' => jvbCSSIcon('settings'), |
| | | 'position' => 0 |
| | | ]; |
| | | $this->subpages = apply_filters('jvbAdminSubpages', []); |
| | | $this->subpages = get_option(BASE.'adminSubpage', []); |
| | | // delete_option(BASE.'admin_actions'); |
| | | // delete_option(BASE.'admin_subpages'); |
| | | // $this->getSubpages(); |
| | |
| | | add_filter(BASE.'admin_action_filter', [$this, 'handleCacheActions'], 10, 3); |
| | | |
| | | add_action('rest_api_init', [$this, 'registerRestRoutes']); |
| | | // Handle form submissions |
| | | add_action('admin_init', [$this, 'handleAdminPageSubmission']); |
| | | add_action('admin_notices', [$this, 'displayAdminNotices']); |
| | | } |
| | | |
| | | /** |
| | |
| | | public function handleIconAction(\WP_REST_Request $request): \WP_REST_Response |
| | | { |
| | | $action = sanitize_text_field($request->get_param('action')); |
| | | $icons = \JVBase\managers\IconsManager::getInstance(); |
| | | $source = sanitize_text_field($request->get_param('source') ?? 'icons'); // Add source param |
| | | $icons = \JVBase\managers\IconsManager::for($source); |
| | | |
| | | switch ($action) { |
| | | case 'refresh-icons': |
| | | $icons->forceRefresh(); |
| | | return new \WP_REST_Response([ |
| | | 'success' => true, |
| | | 'message' => 'Icon CSS regenerated successfully' |
| | | 'message' => "Icon CSS regenerated successfully for '{$source}'" |
| | | ]); |
| | | |
| | | case 'restore-icon-version': |
| | |
| | | if (current_user_can($action['capability'])) { |
| | | ?> |
| | | <a data-action="<?=$action['slug']?>" class="jvb-action"> |
| | | <?= jvbIcon($action['icon']); ?> |
| | | <?= jvbDashIcon($action['icon']); ?> |
| | | <span class="jvb-link-title"><?= esc_html($action['label'])?></span> |
| | | <span class="loader"><?=jvbIcon('arrows-clockwise')?><?=jvbIcon('check')?></span> |
| | | <span class="loader"><?=jvbDashIcon('arrows-clockwise')?><?=jvbDashIcon('check')?></span> |
| | | </a> |
| | | <?php |
| | | } |
| | |
| | | */ |
| | | protected function getIcon(string $icon = 'logo', bool $css = false): string |
| | | { |
| | | $svg = jvbIcon($icon, ['wrap' => false]); |
| | | $svg = jvbDashIcon($icon, ['wrap' => false]); |
| | | if ($css) { |
| | | // For CSS, replace currentColor with brand color |
| | | $svg = str_replace('currentColor', '#FF0080', $svg); |
| | |
| | | |
| | | <div class="jvb-cache-actions"> |
| | | <button type="button" class="button button-primary" data-action="flush-all"> |
| | | <?= jvbIcon('arrows-clockwise'); ?> |
| | | <?= jvbDashIcon('arrows-clockwise'); ?> |
| | | Flush All Caches |
| | | </button> |
| | | </div> |
| | |
| | | <td><?= $this->formatConnections($configs); ?></td> |
| | | <td> |
| | | <button type="button" class="button" data-action="flush-cache" data-group="<?= esc_attr($group); ?>"> |
| | | <?= jvbIcon('trash'); ?> Flush |
| | | <?= jvbDashIcon('trash'); ?> Flush |
| | | </button> |
| | | </td> |
| | | </tr> |
| | |
| | | <td><?= $this->formatConnections($configs); ?></td> |
| | | <td> |
| | | <button type="button" class="button" data-action="flush-cache" data-group="<?= esc_attr($group); ?>"> |
| | | <?= jvbIcon('trash'); ?> Flush |
| | | <?= jvbDashIcon('trash'); ?> Flush |
| | | </button> |
| | | </td> |
| | | </tr> |
| | |
| | | |
| | | public function renderIconsPage():void |
| | | { |
| | | $icons = \JVBase\managers\IconsManager::getInstance(); |
| | | // Get current source from query param or default to 'icons' |
| | | $current_source = $_GET['icon_source'] ?? 'icons'; |
| | | $current_source = sanitize_text_field($current_source); |
| | | |
| | | // Get all registered icon sources |
| | | $all_sources = ['icons', 'forms', 'dash']; // You could get this dynamically if needed |
| | | |
| | | $icons = \JVBase\managers\IconsManager::for($current_source); |
| | | $versions = $icons->getVersionHistory(); |
| | | $nonce = wp_create_nonce('wp_rest'); |
| | | |
| | |
| | | <div class="wrap jvb-admin-wrap"> |
| | | <h1>Icon Management</h1> |
| | | |
| | | <!-- Source Selector --> |
| | | <div class="jvb-icon-source-selector"> |
| | | <label for="icon-source-select">Icon Source:</label> |
| | | <select id="icon-source-select" onchange="window.location.href='<?= admin_url('admin.php?page=' . BASE . 'icons&icon_source='); ?>' + this.value"> |
| | | <?php foreach ($all_sources as $source): ?> |
| | | <option value="<?= esc_attr($source); ?>" <?= selected($current_source, $source, false); ?>> |
| | | <?= esc_html(ucfirst($source)); ?> |
| | | </option> |
| | | <?php endforeach; ?> |
| | | </select> |
| | | </div> |
| | | |
| | | <div class="jvb-icon-actions"> |
| | | <button type="button" class="button button-primary" data-action="refresh-icons"> |
| | | <?= jvbIcon('arrows-clockwise'); ?> |
| | | <button type="button" class="button button-primary" data-action="refresh-icons" data-source="<?= esc_attr($current_source); ?>"> |
| | | <?= jvbDashIcon('arrows-clockwise'); ?> |
| | | Force Refresh CSS |
| | | </button> |
| | | <button type="button" class="button" data-action="merge-icon-versions" id="merge-versions-btn" disabled> |
| | | <?= jvbIcon('git-merge'); ?> |
| | | <button type="button" class="button" data-action="merge-icon-versions" data-source="<?= esc_attr($current_source); ?>" id="merge-versions-btn" disabled> |
| | | <?= jvbDashIcon('git-merge'); ?> |
| | | Merge Selected Versions |
| | | </button> |
| | | </div> |
| | | |
| | | <h2>Version History</h2> |
| | | <h2>Version History for <?= esc_html(ucfirst($current_source)); ?></h2> |
| | | <table class="wp-list-table widefat fixed striped"> |
| | | <thead> |
| | | <tr> |
| | |
| | | <td> |
| | | <?= esc_html($version['icon_count']); ?> icons |
| | | <button type="button" |
| | | class="button-link" |
| | | data-action="view-icon-list" |
| | | class="button-link view-icon-list-btn" |
| | | data-timestamp="<?= esc_attr($version['timestamp']); ?>"> |
| | | (view) |
| | | </button> |
| | | </td> |
| | | <td><?= esc_html($version['size_formatted']); ?></td> |
| | | <td> |
| | | <button type="button" class="button" |
| | | <button type="button" class="button restore-version-btn" |
| | | data-action="restore-icon-version" |
| | | data-source="<?= esc_attr($current_source); ?>" |
| | | data-timestamp="<?= esc_attr($version['timestamp']); ?>"> |
| | | <?= jvbIcon('arrow-counter-clockwise'); ?> Restore |
| | | <?= jvbDashIcon('arrow-counter-clockwise'); ?> Restore |
| | | </button> |
| | | </td> |
| | | </tr> |
| | |
| | | (function() { |
| | | const apiUrl = '<?= esc_js(rest_url('jvb/v1/admin-icons')); ?>'; |
| | | const nonce = '<?= esc_js($nonce); ?>'; |
| | | const currentSource = '<?= esc_js($current_source); ?>'; |
| | | |
| | | // Helper function for API calls |
| | | function callIconAction(action, data = {}) { |
| | | const body = { action, ...data }; |
| | | const body = { action, source: currentSource, ...data }; |
| | | |
| | | return fetch(apiUrl, { |
| | | method: 'POST', |
| | |
| | | }); |
| | | |
| | | // Force refresh button |
| | | const refreshBtn = document.getElementById('refresh-icons-btn'); |
| | | if (refreshBtn) { |
| | | refreshBtn.addEventListener('click', function() { |
| | | if (confirm('Force regenerate icon CSS? This will reload the page.')) { |
| | | this.disabled = true; |
| | | callIconAction('refresh-icons'); |
| | | } |
| | | }); |
| | | } |
| | | document.querySelector('[data-action="refresh-icons"]')?.addEventListener('click', function() { |
| | | if (confirm('Force regenerate icon CSS? This will reload the page.')) { |
| | | this.disabled = true; |
| | | callIconAction('refresh-icons'); |
| | | } |
| | | }); |
| | | |
| | | // Merge versions button |
| | | const mergeBtn = document.getElementById('merge-versions-btn'); |
| | | if (mergeBtn) { |
| | | mergeBtn.addEventListener('click', function() { |
| | | const checkboxes = document.querySelectorAll('.version-checkbox:checked'); |
| | | const timestamps = Array.from(checkboxes).map(cb => parseInt(cb.value)); |
| | | document.getElementById('merge-versions-btn')?.addEventListener('click', function() { |
| | | const checkboxes = document.querySelectorAll('.version-checkbox:checked'); |
| | | const timestamps = Array.from(checkboxes).map(cb => parseInt(cb.value)); |
| | | |
| | | if (timestamps.length < 2) { |
| | | alert('Please select at least 2 versions to merge'); |
| | | return; |
| | | } |
| | | if (timestamps.length < 2) { |
| | | alert('Please select at least 2 versions to merge'); |
| | | return; |
| | | } |
| | | |
| | | if (confirm(`Merge ${timestamps.length} versions? This will create a new CSS file with all unique icons.`)) { |
| | | this.disabled = true; |
| | | callIconAction('merge-icon-versions', { timestamps: timestamps }); |
| | | } |
| | | }); |
| | | } |
| | | if (confirm(`Merge ${timestamps.length} versions? This will create a new CSS file with all unique icons.`)) { |
| | | this.disabled = true; |
| | | callIconAction('merge-icon-versions', { timestamps: timestamps }); |
| | | } |
| | | }); |
| | | |
| | | // Restore version buttons |
| | | document.querySelectorAll('.restore-version-btn').forEach(btn => { |
| | |
| | | </script> |
| | | <?php |
| | | } |
| | | |
| | | public static function addSubpage(string $key, array $value):void |
| | | { |
| | | $option = get_option(BASE.'adminSubpage', []); |
| | | if (empty($option) || !array_key_exists($key, $option)) { |
| | | $option[$key] = $value; |
| | | update_option(BASE.'adminSubpage', $option); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Handle admin page form submissions |
| | | * Fires after WordPress admin_init |
| | | */ |
| | | public function handleAdminPageSubmission(): void |
| | | { |
| | | // Only process on our admin pages |
| | | if (!isset($_GET['page']) || strpos($_GET['page'], BASE) !== 0) { |
| | | return; |
| | | } |
| | | |
| | | // Check for form submission |
| | | if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['submit'])) { |
| | | return; |
| | | } |
| | | |
| | | // Verify nonce |
| | | $nonce_field = BASE . 'admin_page_nonce'; |
| | | if (!isset($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], $nonce_field)) { |
| | | add_action('admin_notices', function() { |
| | | echo '<div class="notice notice-error"><p>Security check failed. Please try again.</p></div>'; |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | $page_slug = sanitize_text_field($_GET['page']); |
| | | |
| | | // Allow other classes to handle their page submissions |
| | | $result = apply_filters('jvb_admin_page_submission', null, $page_slug, $_POST); |
| | | |
| | | // Store result in transient for after redirect |
| | | if (is_array($result)) { |
| | | set_transient(BASE . 'admin_notice_' . get_current_user_id(), $result, 30); |
| | | } |
| | | |
| | | // Redirect to prevent form resubmission (POST-Redirect-GET pattern) |
| | | wp_safe_redirect(add_query_arg(['page' => $page_slug], admin_url('admin.php'))); |
| | | exit; |
| | | } |
| | | |
| | | /** |
| | | * Display admin notices from form submissions |
| | | */ |
| | | public function displayAdminNotices(): void |
| | | { |
| | | $notice = get_transient(BASE . 'admin_notice_' . get_current_user_id()); |
| | | |
| | | if ($notice && is_array($notice)) { |
| | | delete_transient(BASE . 'admin_notice_' . get_current_user_id()); |
| | | |
| | | $type = $notice['success'] ? 'success' : 'error'; |
| | | $message = $notice['message'] ?? 'Settings saved.'; |
| | | |
| | | echo '<div class="notice notice-' . esc_attr($type) . ' is-dismissible"><p>' . esc_html($message) . '</p></div>'; |
| | | } |
| | | } |
| | | } |