main_page = [
'page_title' => 'JakeVan Settings',
'menu_title' => 'JakeVan',
'capability' => 'manage_options',
'menu_slug' => BASE . 'settings',
'icon' => jvbCSSIcon('settings'),
'position' => 0
];
$this->subpages = get_option(BASE.'adminSubpage', []);
// delete_option(BASE.'admin_actions');
// delete_option(BASE.'admin_subpages');
// $this->getSubpages();
$this->getActions();
// Hook into WordPress admin
add_action('admin_menu', [$this, 'registerAdminPages']);
add_action('admin_enqueue_scripts', [$this, 'enqueueAdminAssets']);
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']);
}
/**
* Register REST API routes for admin actions
*/
public function registerRestRoutes(): void
{
register_rest_route('jvb/v1', '/admin-cache', [
'methods' => 'POST',
'callback' => [$this, 'handleCacheAction'],
'permission_callback' => [$this, 'checkAdminPermission']
]);
register_rest_route('jvb/v1', '/admin-icons', [
'methods' => 'POST',
'callback' => [$this, 'handleIconAction'],
'permission_callback' => [$this, 'checkAdminPermission']
]);
}
/**
* Check if user has admin permissions
*/
public function checkAdminPermission(\WP_REST_Request $request): bool
{
if (!current_user_can('manage_options')) {
return false;
}
// Verify nonce
$nonce = $request->get_header('X-WP-Nonce');
if (!wp_verify_nonce($nonce, 'wp_rest')) {
return false;
}
return true;
}
/**
* Handle cache-related actions
*/
public function handleCacheAction(\WP_REST_Request $request): \WP_REST_Response
{
$action = sanitize_text_field($request->get_param('action'));
switch ($action) {
case 'flush-all':
$total = Cache::flushAll();
return new \WP_REST_Response([
'success' => true,
'message' => $total.' caches flushed successfully'
]);
case 'flush-cache':
$group = sanitize_text_field($request->get_param('group'));
if (empty($group)) {
return new \WP_REST_Response([
'success' => false,
'message' => 'No cache group specified'
], 400);
}
Cache::for($group)?->flush();
return new \WP_REST_Response([
'success' => true,
'message' => "Cache group '{$group}' flushed successfully"
]);
default:
return new \WP_REST_Response([
'success' => false,
'message' => 'Invalid action'
], 400);
}
}
/**
* Handle icon-related actions
*/
public function handleIconAction(\WP_REST_Request $request): \WP_REST_Response
{
$action = sanitize_text_field($request->get_param('action'));
$source = sanitize_text_field($request->get_param('source') ?? 'icons');
$icons = IconsManager::for($source);
switch ($action) {
case 'refresh-icons':
// Force regenerate CSS immediately
$icons->forceRefresh();
IconsManager::regenerateAllCSS([$source => true]);
return new \WP_REST_Response([
'success' => true,
'message' => "Icon CSS regenerated successfully for '{$source}'"
]);
case 'refresh-all-icons':
// Regenerate all icon sources
foreach (['icons', 'forms', 'dash'] as $src) {
IconsManager::for($src)->forceRefresh();
}
IconsManager::regenerateAllCSS();
return new \WP_REST_Response([
'success' => true,
'message' => 'All icon CSS files regenerated successfully'
]);
case 'restore-icon-version':
$timestamp = (int)$request->get_param('timestamp');
if (empty($timestamp)) {
return new \WP_REST_Response([
'success' => false,
'message' => 'No timestamp provided'
], 400);
}
if ($icons->restoreVersion($timestamp)) {
return new \WP_REST_Response([
'success' => true,
'message' => 'Icon version restored successfully'
]);
}
return new \WP_REST_Response([
'success' => false,
'message' => 'Failed to restore icon version'
], 500);
case 'merge-icon-versions':
$timestamps = $request->get_param('timestamps');
if (empty($timestamps) || !is_array($timestamps)) {
return new \WP_REST_Response([
'success' => false,
'message' => 'No versions selected for merging'
], 400);
}
$timestamps = array_map('intval', $timestamps);
if (count($timestamps) < 2) {
return new \WP_REST_Response([
'success' => false,
'message' => 'Please select at least 2 versions to merge'
], 400);
}
if ($icons->mergeVersions($timestamps)) {
// Regenerate CSS after merge
IconsManager::regenerateAllCSS([$source => true]);
return new \WP_REST_Response([
'success' => true,
'message' => 'Icon versions merged successfully'
]);
}
return new \WP_REST_Response([
'success' => false,
'message' => 'Failed to merge icon versions'
], 500);
default:
return new \WP_REST_Response([
'success' => false,
'message' => 'Invalid action'
], 400);
}
}
/**
* Register a subpage to appear under the main settings page
*
* @param string $page_title The text to be displayed in the title tags of the page
* @param string $menu_title The text to be used for the menu
* @param string $capability The capability required to access the page
* @param string $menu_slug The slug name to refer to this menu by
* @param callable $callback The function to be called to output the content for this page
* @param string|null $icon
* @param int|null $position The position in the menu order (optional)
* @return bool Success or failure
*/
public function registerSubpage(
string $page_title,
string $menu_title,
string $capability,
string $menu_slug,
callable $callback,
string|null $icon = null,
int|null $position = null
):bool {
return true;
// if (empty($page_title) ||
// empty($menu_title) ||
// empty($capability) ||
// empty($menu_slug) ||
// !is_callable($callback)) {
// error_log('This wasn\'t registered: '.print_r([
// 'page_title' => $page_title,
// 'menu_title' => $menu_title,
// 'capability' => $capability,
// 'menu_slug' => $menu_slug,
// 'callback' => $callback
// ], true));
// return false;
// }
//
// $this->getSubpages();
//
// $subpage = [
// 'page_title' => $page_title,
// 'menu_title' => $menu_title,
// 'capability' => $capability,
// 'menu_slug' => BASE . $menu_slug,
// 'callback' => $callback,
// 'position' => $position
// ];
//
// if (!is_null($position)) {
// $subpage['position'] = $position;
// }
// if (!is_null($icon)) {
// $subpage['icon'] = $this->getIcon($icon);
// }
//
// $this->subpages[] = $subpage;
//
// $this->setSubpages();
//
// return true;
}
protected function getSubpages()
{
$this->subpages = get_option(BASE.'admin_subpages', []);
}
protected function setSubpages()
{
update_option(BASE.'admin_subpages', $this->subpages);
}
public function registerAction(
string $label,
string $slug,
string $capability,
string|null $icon = null
):bool {
$this->getActions();
$action = [
'label' => $label,
'slug' => sanitize_title($slug),
'capability'=> $capability,
];
if (!is_null($icon)) {
$action['icon'] = $icon;
}
$this->actions[sanitize_title($slug)] = $action;
$this->setActions();
return true;
}
protected function getActions()
{
$this->actions = get_option(BASE.'admin_actions', []);
}
protected function setActions()
{
update_option(BASE.'admin_actions', $this->actions);
}
/**
* Register admin pages with WordPress
*/
public function registerAdminPages():void
{
// Add main settings page
$this->page_hook = add_menu_page(
$this->main_page['page_title'],
$this->main_page['menu_title'],
$this->main_page['capability'],
$this->main_page['menu_slug'],
[$this, 'renderMainPage'],
$this->main_page['icon'],
$this->main_page['position']
);
// Add main page as a subpage to make it appear in submenu
add_submenu_page(
$this->main_page['menu_slug'],
$this->main_page['page_title'],
'Overview',
$this->main_page['capability'],
$this->main_page['menu_slug']
);
add_submenu_page(
$this->main_page['menu_slug'],
'Cache',
'Cache',
'manage_options',
BASE.'cache',
[$this, 'renderCachePage']
);
add_submenu_page(
$this->main_page['menu_slug'],
'Icon Management',
'Icons',
'manage_options',
BASE.'icons',
[$this, 'renderIconsPage']
);
// $this->getSubpages();
// Add registered subpages
foreach ($this->subpages as $page) {
if (!array_key_exists('page_title', $page) ||
!array_key_exists('menu_title', $page) ||
!array_key_exists('capability', $page) ||
!array_key_exists('menu_slug', $page) ||
!array_key_exists('callback', $page)) {
error_log('Invalid admin page config for '.print_r($page, true));
continue;
}
add_submenu_page(
$this->main_page['menu_slug'],
$page['page_title'],
$page['menu_title'],
$page['capability'],
$page['menu_slug'],
$page['callback']
);
}
}
/**
* Render the main settings page
*/
public function renderMainPage():void
{
?>
= esc_html($this->main_page['page_title']); ?>
renderDashboard(); ?>
Active Users
renderUserStats(); ?>
Recent Content
renderRecentContent(); ?>
Quick Actions
renderActions(); ?>
Quick Links
renderQuickLinks(); ?>
queue()->getStatus();
error_log('Queue Status: '.print_r($queue_status, true));
// Other system checks
$memory_usage = memory_get_usage(true) / 1024 / 1024;
// Display status items
?>
Queue Status:
= (isset($queue_status['pending']) ? esc_html($queue_status['pending']) : '0'); ?> pending,
= (isset($queue_status['processing']) ?
esc_html($queue_status['processing']) :
'0'); ?> processing
Memory Usage:
= round($memory_usage, 2); ?> MB
Plugin Version:
1.0.0
Content Types:
= count(JVB_CONTENT); ?> registered
Taxonomies:
= count(JVB_TAXONOMY); ?> registered
$config) {
$count = count_users()['avail_roles'][$role]??0;
?>
= esc_html($count); ?>
=$config['name']?>
$config) {
$content_types[jvbCheckBase($content)] = $config['plural'];
}
?>
| Content Type |
Last 7 Days |
Total |
$label) : ?>
get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->posts}
WHERE post_type = %s
AND post_date > %s
AND post_status = 'publish'",
$type,
$week_ago
));
$total_count = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = %s AND post_status = 'publish'",
$type
));
?>
| = esc_html($label); ?> |
= esc_html($recent_count); ?> |
= esc_html($total_count); ?> |
'Moderate Artist Requests',
'url' => admin_url('admin.php?page=' . BASE . 'artist_approval'),
'icon' => 'dashicons-id'
],
[
'title' => 'Manage Shops',
'url' => admin_url('edit-tags.php?taxonomy=' . BASE . 'shop'),
'icon' => 'dashicons-store'
],
[
'title' => 'Queue Status',
'url' => admin_url('admin.php?page=' . BASE . 'queue_status'),
'icon' => 'dashicons-list-view'
],
[
'title' => 'Manage Styles',
'url' => admin_url('edit-tags.php?taxonomy=' . BASE . 'style'),
'icon' => 'dashicons-admin-customizer'
]
];
foreach ($links as $link) {
?>
= esc_html($link['title']); ?>
actions as $action) {
if (current_user_can($action['capability'])) {
?>
= jvbDashIcon($action['icon']); ?>
= esc_html($action['label'])?>
=jvbDashIcon('arrows-clockwise')?>=jvbDashIcon('check')?>
rest_url('jvb/v1/admin-action'),
'nonce' => wp_create_nonce('wp_rest'),
'action' => wp_create_nonce('itsme'),
]
);
}
/**
* Create a custom SVG icon for the admin menu
* @param string $icon Icon name
* @param bool $css Whether to convert to CSS
* @return string Base64 encoded SVG icon
*/
protected function getIcon(string $icon = 'logo', bool $css = false): string
{
$svg = jvbDashIcon($icon, ['wrap' => false]);
if ($css) {
// For CSS, replace currentColor with brand color
$svg = str_replace('currentColor', '#FF0080', $svg);
// Clean and prepare the SVG for CSS
$svg = preg_replace('/[\r\n\t]+/', ' ', $svg); // Remove newlines and tabs
$svg = preg_replace('/\s{2,}/', ' ', $svg); // Minimize spaces
$svg = trim($svg); // Trim whitespace
// Base64 encoding is the most reliable approach for CSS background images
return 'url(data:image/svg+xml;base64,' . base64_encode($svg) . ')';
}
// Convert the SVG to a data URI
return 'data:image/svg+xml;base64,' . base64_encode($svg);
}
public function renderCachePage():void
{
$groups = Cache::getAllGroups();
// Separate generic vs. specific caches
$generic_groups = [];
$content_specific = [];
$nonce = wp_create_nonce('wp_rest');
// Separate by type
$generic = [];
$specific = [];
foreach ($groups as $group => $data) {
if ($this->isBoundToContentOrTaxonomy($group)) {
$specific[$group] = $data;
} else {
$generic[$group] = $data;
}
}
?>
Cache Management
Generic Caches & Connections
| Cache Group |
Connected To |
Actions |
| No generic caches registered |
$configs): ?>
| = esc_html($group); ?> |
= $this->formatConnections($configs); ?> |
|
Content-Specific Caches
| Cache Group |
Connected To |
Actions |
$configs): ?>
| = esc_html($group); ?> |
= $this->formatConnections($configs); ?> |
|
$data): ?>
| = esc_html($group); ?> |
= $this->formatConnections($data); ?> |
|
$config) {
if (jvbNoBase($key) === $group) {
return true;
}
}
}
if (defined('JVB_TAXONOMY')) {
foreach (JVB_TAXONOMY as $key => $config) {
if (jvbNoBase($key) === $group) {
return true;
}
}
}
return false;
}
protected function formatConnections(array $data): string
{
$parts = [];
if (!empty($data['connects_to'])) {
$targets = array_map(function($conn) {
$flush_text = $conn['flush'] ? ' (flush all)' : '';
return $conn['group'] . $flush_text;
}, $data['connects_to']);
$parts[] = 'Invalidates: ' . implode(', ', $targets);
}
if (!empty($data['connected_from'])) {
$sources = array_map(fn($conn) => $conn['group'], $data['connected_from']);
$parts[] = 'Invalidated by: ' . implode(', ', $sources);
}
return $parts ? implode('
', $parts) : 'No connections';
}
public function handleCacheActions($response, $request, $action):WP_REST_Response
{
if (!str_starts_with($action, 'flush-')) {
return $response;
}
if ($action === 'flush-all') {
wp_cache_flush();
return new WP_REST_Response([
'success' => true,
'message' => 'All caches flushed successfully'
]);
}
if (str_starts_with($action, 'flush-cache')) {
$group = $request->get_param('group');
if (empty($group)) {
return new WP_REST_Response([
'success' => false,
'message' => 'No cache group specified'
], 400);
}
$group = sanitize_text_field($request->get_param('group'));
Cache::invalidateGroup($group);
return new WP_REST_Response([
'success' => true,
'message' => "Cache group '{$group}' flushed successfully"
]);
}
return $response;
}
public function renderIconsPage():void
{
// 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 = IconsManager::for($current_source);
$versions = $icons->getVersionHistory();
$nonce = wp_create_nonce('wp_rest');
?>
Icon Management
Version History for = esc_html(ucfirst($current_source)); ?>
Security check failed. Please try again.
';
});
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 '' . esc_html($message) . '
';
}
}
}