File "INIT.php"

Full Path: /home/rubycabfbt/www/wp-content/plugins/wtec-webp/includes/ATEC/INIT.php
File size: 22.05 KB
MIME-type: text/x-php
Charset: utf-8

<?php
/**
 * ATEC_INIT
 *
 * Static UI helper class for atec Plugins.
 * Handles menu registration, admin notices, and legacy wrappers.
 * Used across all plugins in admin context only.
 */
namespace ATEC;
defined('ABSPATH') || exit;

// ===== Static Toolbox Class =====
final class INIT {

static $require_install = [ 'wpc', 'wpca', 'wpcm', 'wpcr', 'wpds', 'wpf', 'wpfm', 'wpht', 'wpmcl', 'wppp', 'wprbt', 'wps', 'wpwp' ];
static $skip_load_check = [ 'wp4t', 'wpau', 'wpav', 'wpbl', 'wpcom', 'wpcro', 'wpds', 'wphc', 'wpht', 'wpdpp', 'wpl', 'wpll', 'wplu', 'wpmcl', 'wpmin', 'wpocb', 'wppo', 'wppp', 'wps', 'wpur', 'wprbt', 'wpsi', 'wpsr', 'wpsmc', 'wpsv', 'wpta', 'wpu', 'wpwf' ];
static $admin_styles_loaded = false;
static $allowed_admin_tags = 
	[	
		'svg' => [ 'class' => true, 'viewBox' => true, 'fill' => true, 'xmlns' => true, 'width' => true, 'height' => true, 'preserveAspectRatio' => true ],
		'g' => [ 'stroke' => true, 'fill' => true, 'transform' => true ],
		'path' => [ 'd' => true, 'fill' => true, 'fill-rule' => true, 'clip-rule' => true, 'stroke' => true, 'stroke-width' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true ],
		'circle' => [ 'cx' => true, 'cy' => true, 'r' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true ],
		'div' => [ 'class' => true, 'style' => true ],
		'span' => [ 'class' => true, 'style' => true ],
	];

// ADMIN

public static function admin_bar($wp_admin_bar, $slug= '', $action = null, $nav=null, $id= '', $title= '', $icon=false): void
{
	$id .= $id !== '' ? '_' : '';
	$dash = '';
	if ($icon)
	{
		if (str_starts_with($icon, '#')) $dash = '<span class="ab-icon dashicons-' . str_replace('#', '', $icon) . '" style="margin:0;"></span>';
		else $icon = \ATEC\SVG::plain($slug);
	}
	else $icon = '';
	
	if (str_contains($title, '#'))	// has alt. title 
	{
		$ex = explode('#', $title);
		$title = $ex[0];
		$alt = $ex[1];
	}
	else $alt = '';
		
	$args = 
		[
			'id'	=> 'atec_'.$slug.'_'.$id.'admin_bar',
			'meta'  => $alt !== '' ? [ 'title' => $alt ] : [],
			'title'	=> wp_kses('<div class="atec-admin-bar-row">'. $icon . $dash . $title. '</div>', self::$allowed_admin_tags)
		];
	if ($slug!== '') 
	{
		$args['href'] = esc_url((empty($action) && empty($nav)) ? self::admin_url($slug) : self::build_url($slug, $action, $nav));
	}
	$wp_admin_bar->add_node($args);
}

public static function admin_bar_button($wp_admin_bar, string $slug, string $label, string $cmd, string $tooltip = '', string $id = '', string $parent = ''): void
{
	$base_id = 'atec-' . $slug;
	if ($id !== '') { $base_id .= '-' . sanitize_key($id); 	}

	$func = 'atec_' . $slug . '_ajax_cb';
	
	$args = [
		'id'		=> $base_id,
		'title'		=> esc_html($label),
		'href'		=> '#',
		'meta'	=> [
			'title'			=> esc_attr($tooltip),
			'onclick'	=> "{$func}(" . json_encode($cmd) . "); return false;",
		],
	];
	
	if ($parent !== '') $args['parent'] = $parent;
	$wp_admin_bar->add_node($args);

}

public static function build_url($una_or_slug, $action = null, $nav = null, $args = []): string
{
	if (is_string($una_or_slug))	// is_string is a slug
	{
		$menu_slug = 'atec_'.$una_or_slug;
		$una = (object)
		array(
			'slug' => $una_or_slug,
			'url' => self::admin_url($una_or_slug),
			'nonce' => wp_create_nonce($menu_slug.'_nonce'),
		);
	}
	else $una = $una_or_slug;

	$nav = $nav ?? ($una->nav ?? '');
	
	if ($action === null) $action = '';
	elseif ($action!==null && str_contains($action, '&'))	// If $action is a combined string of actions
	{ 
		parse_str($action, $temp); 
		$action = array_key_first($temp); $args = array_merge($temp, $args); 
	}

	$arr = array_merge([ 'action' => $action, 'nav' => $nav, '_wpnonce' => $una->nonce ], $args);
	$arr = array_filter($arr, fn($v) => $v !== null && $v !== '');

	$query_str = http_build_query($arr);
	$sep = str_contains($una->url, '?') ? '&' : '?';
	return $una->url . $sep . $query_str;
}

public static function home_url(): string
{
	static $cached = null;
	if ($cached === null) $cached = home_url();
	return $cached;
}

public static function home_host(): string
{
	static $cached = null;
	if ($cached === null) $cached = wp_parse_url(self::home_url(),PHP_URL_HOST);
	return $cached;
}

public static function site_url(): string
{
	static $cached = null;
	if ($cached === null) $cached = get_site_url();
	return $cached;
}

public static function site_host(): string
{
	static $cached = null;
	if ($cached === null) $cached = wp_parse_url(self::site_url(),PHP_URL_HOST);
	return $cached;
}

public static function site_url_path(): string
{
	static $cached = null;
	if ($cached === null) $cached = rtrim((string) (wp_parse_url(self::site_url(), PHP_URL_PATH) ?? ''), '/');
	return $cached;
}

public static function normalized_uri(): string
{
	$uri = self::_SERVER('REQUEST_URI');
	$uri = strtok($uri, '?');	// strip query
	$uri = rtrim($uri, '/');	// normalize trailing slash

	$site_url_path = self::site_url_path();	// subdir support
	if ($site_url_path && $site_url_path !== '/' && str_starts_with($uri, $site_url_path)) $uri = substr($uri, strlen($site_url_path)); 
	return $uri;
}

public static function admin_url( $slug = '' ): string
{
	static $cached_base = null;
	if ( $cached_base === null ) $cached_base = admin_url(); // WP-safe and filter-aware

	return $slug !== '' 
		? $cached_base . 'admin.php?page=atec_' . urlencode( $slug )
		: $cached_base;
}

public static function admin_bar_option($slug): int
{
	$option_key = 'atec_admin_bar';
	$options  = get_option($option_key);
	return $options[$slug] ?? 0;
}

public static function set_admin_bar_option($slug, $delete = false): void
{
	$option_key = 'atec_admin_bar';
	$options = (array) get_option($option_key,[]);
	
	if ($delete) unset($options[$slug]);
	else $options[$slug] = \ATEC\TOOLS::clean_request_bool('set');
	update_option($option_key, $options);
	if (!$delete)
	{ 
		wp_safe_redirect(self::admin_url($slug));
		exit;
	}
}

public static function add_plugin_settings($plugin_file)
{
	add_filter('plugin_action_links_'.plugin_basename($plugin_file), [self::class, 'plugin_settings'], 10, 2);
}

public static function plugin_settings(array $links, $plugin_file): array
{
	$dir = self::plugin_dir(dirname($plugin_file));
	$slug = \ATEC\GROUP::slug_by_dir($dir);

	$url = self::admin_url($slug);
	$icon = \ATEC\SVG::styled('wrench', 14);
	array_unshift($links, '<a href="' . esc_url($url) . '" style="vertical-align:sub">' . $icon . '</a>');
	return $links;
}

public static function is_settings_updated(): bool
{
	return isset($_GET['settings-updated']) && $_GET['settings-updated']==true;	// phpcs:ignore
}

// TOOLS

public static function extension_enabled($type)
{
	switch ($type)
	{
		case 'apcu':
			return extension_loaded('apcu') && apcu_enabled();
			break;
			
		case 'openssl':
			return extension_loaded('openssl');
			break;
	}
	return false;
}

public static function _GET($key, $default = '')
{
	if (isset($_GET[$key])) return sanitize_text_field(wp_unslash($_GET[$key]));	// phpcs:ignore
	return $default;
}

public static function _SERVER($key, $default = '')
{
	if (isset($_SERVER[$key])) return sanitize_text_field(wp_unslash($_SERVER[$key]));
	return $default;
}

public static function _POST($key, $is_code=false)
{
	if (isset($_POST[$key])) $str = wp_unslash($_POST[$key]);	// phpcs:ignore
	else $str = '';
	return $is_code ? $str : sanitize_textarea_field($str);
}

public static function bool($value): bool { return filter_var($value, FILTER_VALIDATE_BOOLEAN); }

public static function nonce(): string  // Nonce key from current slug, misleading old function name
{ return self::slug() . '_nonce'; }

public static function nonce_key(string $slug, bool $ajax = false): string	// Full nonce key: 'atec_{slug}[_ajax]_nonce'
{ return 'atec_' . $slug . ($ajax ? '_ajax' : '') . '_nonce'; }

public static function slug(): string
{
	static $cached = null;
	if ($cached === null)
	{
		$query = self::query();
		$pos = strpos($query, '?page=');
		if ($pos !== false) 
		{
			$start = $pos + 6;
			$end = strcspn($query, '&', $start);
			$cached = substr($query, $start, $end);
		}
		else
		{
			return '';
		}
	}
	return $cached;
}

public static function query(): string
{
	static $cached = null;
	if ($cached === null) $cached = self::_SERVER('REQUEST_URI');
	return $cached;
}

public static function trailingdotit($str): string
{
	$plain = trim(strip_tags($str));	// phpcs:ignore
	$has_punct = preg_match('/[.!]$/', $plain);
	return rtrim($str) . ($has_punct ? '' : '.');
}

// IS_?...

public static function is_real_admin(): bool
{
	static $cached = null;
	if ($cached === null) $cached = self::is_interactive() && is_admin();
	return $cached;
}

public static function is_plugins_page(): bool
{
	static $cached = null;
	if ($cached === null)
	{
		$q = strtok(self::query(), '?'); // remove query string
		$needle = '/wp-admin/plugins.php';
		$cached = substr($q, -strlen($needle)) === $needle;
	}
	return $cached;
}

public static function is_ajax(): bool
{ return defined('DOING_AJAX') && DOING_AJAX; }

public static function is_cron(): bool
{ return defined('DOING_CRON') && DOING_CRON; }

public static function is_cli(): bool
{ return defined('WP_CLI') && WP_CLI; }

public static function is_rest_or_cli(): bool
{ return (defined('REST_REQUEST') && REST_REQUEST) || (self::is_cli()); }

// Detect if the current request is an interactive browser page load.
// Excludes AJAX, REST, CRON, and CLI.
public static function is_interactive(): bool
{
	static $cached = null;
	if ($cached === null) $cached = !(self::is_ajax() || self::is_cron() || self::is_rest_or_cli());
	return $cached;
}

public static function is_editor_mode(): bool
{
	if (is_feed() || is_preview()) return true;
	$editorParams = ['brizy-edit', 'elementor-preview', 'fl_builder', 'is-editor-iframe', 'editor', 'frontend', 'iframe'];
	foreach ($editorParams as $key) { if (isset($_GET[$key])) return true; } // phpcs:ignore
	return false;
}

public static function is_atec_dev_mode(): bool
{
	static $cached = null;
	if ($cached === null) $cached = get_option('atec_dev_mode');
	return $cached;
}

public static function client_ip(): string
{ return self::_SERVER('REMOTE_ADDR'); }

public static function host_ip(): string
{ return self::_SERVER('SERVER_ADDR'); }

public static function is_localhost(): bool
{
	$ip = self::client_ip();
	return in_array($ip, ['127.0.0.1', '::1'], true) || self::_SERVER('SERVER_NAME') === 'localhost';
}

// INIT

public static function register_activation_deactivation_hook($plugin_file, $activate = -1, $deactivate = 0, $slug = '')
{
	register_activation_hook($plugin_file, function () use ($plugin_file, $slug, $activate) 
	{ 
		self::integrity_check(self::plugin_by_dir($plugin_file));
		if ($activate === 1) require dirname($plugin_file) . '/includes/atec-' . $slug . '-activation.php'; 
	});

	if ($deactivate === 1 && $slug !== '') 
	{
		register_deactivation_hook($plugin_file, function () use ($plugin_file, $slug) 
		{ require dirname($plugin_file) . '/includes/atec-' . $slug . '-deactivation.php'; });
	}
}

public static function maybe_register_settings($dir, $slug, $noNav = false, $custom = '')
{
	$option_page = self::_POST('option_page');
	$is_option_page = strpos($option_page, 'atec_' . strtoupper($slug)) !== false;

	$require = $is_option_page;
	
	if (!$require)
	{
		$query = self::query();
		$is_plugin_page = strpos($query, 'admin.php?page=atec_' . $slug) !== false;
		if ($is_plugin_page)
		{
			$require = 
			($noNav || preg_match('/nav=[^&]*Settings/', $query)) ||
			($custom === '' ? true : strpos($query, $custom) !== false) ||
			(strpos($query, 'settings-updated=true') !== false);
		}
	}
	
	if ($require) require("$dir/includes/atec-$slug-register-settings.php");
}

public static function integrity_check($plugin): void // only on activation or when agreed
{
	if (self::bool(get_option('atec_allow_integrity_check',false)))
	{
		$domain = rawurlencode(get_bloginfo('url'));
		wp_remote_get("https://atecplugins.com/WP-Plugins/activated.php?plugin={$plugin}&domain={$domain}");
	}
}

// SETTINGS

public static function license_ok()
{
	static $cached = null;
	if ($cached === null) 
	{
		$cached = get_transient('atec_license_code');
		if (!$cached) $cached = \ATEC\TOOLS::pro_license();
	}
	return $cached;
}

public static function get_settings($slug, $option=null)
{
	$settings = get_option('atec_'.strtoupper($slug).'_settings',[]);
	
	if ($option === null || !is_array($settings) || empty($settings)) return $settings;
	
	if (!array_key_exists($option, $settings)) return null;
	
	$bool = filter_var($settings[$option], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
	if ($bool !== null) return $bool;

	return $settings[$option];
}

public static function update_settings($slug, $settings): void
{ update_option('atec_'.strtoupper($slug).'_settings', $settings); }

public static function delete_settings($slug): void
{ delete_option('atec_'.strtoupper($slug).'_settings'); }

public static function set_version($slug, $version)
{ wp_cache_set("atec_{$slug}_version", $version, 'atec_np'); }

// PLUGINS

public static function plugin_fixed_name($p) 
{
	$p = ucwords(str_replace('-', ' ', $p));
	return trim(str_ireplace(
		['atec', 'apcu', 'foxyfy', 'webp', 'svg', 'ssl', 'smtp', 'oc benchmark'],
		['atec', 'APCu', 'FoxyFy', 'WebP', 'SVG', 'SSL', 'SMTP', 'OC Benchmark'],
		$p));
}

public static function plugin_prefix($p): string { return in_array($p, ['mega-cache', 'cache-tune']) ? '' : 'atec-'; }

public static function normalize_path(string $path): string
{
	$path = str_replace('\\', '/', $path);												// Replace backslashes with forward slashes
	$path = preg_replace('|(?<!:)/+|', '/', $path);									// Remove redundant slashes (except double-slash at start for network shares)
	if (isset($path[1]) && $path[1] === ':') $path = ucfirst($path);		// Uppercase Windows drive letter if present
	return $path;
}

public static function plugin_by_dir($dir): string
{
	$dir = self::normalize_path($dir);
	$pluginDir = self::normalize_path(self::plugin_dir());

	if (!$dir || !$pluginDir || strpos($dir, $pluginDir) !== 0) return '';

	$relative = substr($dir, strlen($pluginDir)); // Strip base dir
	$parts = explode('/', ltrim($relative, '/'));

	return $parts[0] ?? '';
}

public static function content_dir() : string
{ 
	static $cached = null;
	if ($cached === null) $cached = dirname(self::plugin_dir());
	return $cached;
}

public static function plugin_dir($plugin = null) : string
{ return $GLOBALS['atec_plugins_globals']['WP_PLUGIN_DIR'] . ($plugin ? '/' . $plugin : ''); }

public static function plugin_url($plugin = null) : string
{ return $GLOBALS['atec_plugins_globals']['WP_PLUGIN_URL'] . ($plugin ? '/' . $plugin : ''); }

public static function plugin_url_by_dir($dir) : string		// required by self::menu, reg_script, reg_style
{ return self::plugin_url(self::plugin_by_dir($dir)); }

// MENU

public static function group_page($plugin): void
{ \ATEC\DASHBOARD::init($plugin); }

public static function group_callback($plugin): callable
{ return function () use ($plugin) { self::group_page($plugin); 	}; }

public static function dashboard_callback($plugin, $slug): callable
{ return function () use ($plugin, $slug) { require self::plugin_dir($plugin) . "/includes/atec-{$slug}-dashboard.php"; }; }

// restrict certain plugins to post/page editors
// Maps capability keys (WP) and semantic role shortcuts (admin/editor)
public static function current_user_can($role): bool
{
	static $cached = null;

	if ($cached === null)
	{
		$cached = 
			[	
				'admin'	=> current_user_can('manage_options'),
				'editor'	=> current_user_can('edit_posts') || current_user_can('edit_pages'),
				'user'	=> current_user_can('edit_user'),
				'theme'	=> current_user_can('edit_theme_options'),
			];
	}

	return $cached[$role] ?? false;
}

public static function admin_head_styles()
{
	self::$admin_styles_loaded = true;

	$id = 'atec-admin-head';
	wp_register_style($id, false, [], '1.0.0');
	wp_enqueue_style($id);
	wp_add_inline_style($id,
		'#toplevel_page_atec_group .wp-submenu .atec-svg-icon { display: inline-flex; max-width: 20px !important; text-align: center; vertical-align: middle; margin: 0 6px 0 -3px; }
		#wpadminbar .atec-admin-bar-row { display: flex; gap: 5px; align-items: center; }
		#wpadminbar .atec-admin-bar-row svg { display: block; flex: none; object-fit: contain; width: 18px; height: 18px; max-height: 18px; }
	');
}

public static function menu($dir, $slug, $title, $css=[], $js=[]): bool
{
	if (!self::$admin_styles_loaded) self::admin_head_styles();
	
	static $style_loaded = null;
	static $atec_group_active = null;
	static $single_slug = ['wpmc'];

	$single = in_array($slug, $single_slug);
	$menu_slug = 'atec_'.$slug;
	$plugin = self::plugin_by_dir($dir);

	if (!self::current_user_can('admin')) return false;
		
	$current = self::slug();
	if ($current==='atec_group') { if ($style_loaded===null) { \ATEC\TOOLS::load_atec_style($dir, ['style']); $style_loaded=true; } }
	elseif ($current===$menu_slug)
	{
		if (!$style_loaded) $css[]= 'style';
		if (!in_array($slug, self::$skip_load_check)) { $css[]= 'check'; $js[]= 'check'; }
		add_action('admin_enqueue_scripts', function () use ($dir, $css, $js) 
		{
			if (!empty($css)) \ATEC\TOOLS::load_atec_style($dir, $css);
			if (!empty($js)) \ATEC\TOOLS::load_atec_script($dir, $js); 
		});
		if (in_array($slug, self::$require_install)) require "$dir/includes/atec-{$slug}-install.php";
	}
	
	$callback = self::dashboard_callback($plugin, $slug);
	$group_callback = self::group_callback($plugin);

	if ($single) add_menu_page($title, $title, 'manage_options', $menu_slug, $callback, \ATEC\SVG::base64($slug)); // standalone menu
	else
	{
		$group_slug = 'atec_group';
		if ($atec_group_active===null) // add top-level group if not already present
		{
			$icon_data = \ATEC\SVG::base64('wpa');
			add_menu_page('atec-systems', 'atec-systems', 'manage_options', $group_slug, $group_callback, $icon_data);
			add_submenu_page($group_slug, 'Group', '<span class="wp-menu-image dashicons-before dashicons-sos" style="margin: 0 8px 0 -5px; "></span>Dashboard', 'manage_options', $group_slug, $group_callback);
			$atec_group_active = true;
		}

		// Add submenu if plugin does not have an admin bar link like atec-updates
		add_submenu_page($group_slug, $title, '<span style="margin-right: 2px;">' . \ATEC\SVG::plain($slug,'atec-svg-icon') . '</span>' . $title, 'manage_options', $menu_slug, $callback);	//in_array($slug, $hidden_slug) ? 'atec-hidden-menu' : 
	}

	return true;
}

// DEBUG

private static $admin_debug_cache = null;

public static function get_admin_debug(): array
{
	if (self::$admin_debug_cache === null) { self::$admin_debug_cache = get_option('atec_admin_debug', []); }
	return self::$admin_debug_cache;
}

public static function set_admin_debug($slug, $notice = []): void 
{
	$arr = self::get_admin_debug();
	
	if (empty($notice)) unset($arr[$slug]);
	else $arr[$slug] = $notice;
	
	$option_key = 'atec_admin_debug';
	if (empty($arr)) delete_option($option_key);
	else update_option($option_key, $arr, false);
}

public static function delete_admin_debug($slug): void 
{
	self::set_admin_debug($slug, []);
}

public static function is_admin_debug($slug): bool
{
	$arr = self::get_admin_debug();
	return isset($arr[$slug]);
}

public static function admin_debug($slug, $dir = ''): void 
{
	$arr = self::get_admin_debug();
	if (!isset($arr[$slug])) return;

	$notice = $arr[$slug];
	if (empty($notice) || !isset($notice['message'])) return;

	if ($dir) self::add_admin_notice_action($dir, $notice['type'], $notice['message']);
	else self::admin_notice($slug, $notice['type'], $notice['message']);
}

public static function admin_debug_all(): void 
{
	$arr = self::get_admin_debug();
	if (empty($arr)) return;
	foreach($arr as $key => $notice)
	{
		$type = $notice['type'];
		$msg = $notice['message'];
		add_action('admin_notices', function () use ($key, $type, $msg) { self::admin_notice($key, $type, $msg); });
	}
}

// NOTICE

public static function build_notice(array &$notice, string $type= '', string $str= ''): void
{
	if (!isset($notice)) $notice = [];
	if ($type=== '') $type = 'warning';
	$str = self::trailingdotit($str);
	$message = $notice['message'] ?? '';
	$message .= ($message === '' ? '' : ' ') . $str;
	if (!empty($notice['type']) && $notice['type'] !== 'info') { $type = $notice['type']; } // if $type is more important than "info", like "warning"
	$notice['type'] = $type;
	$notice['message'] = $message;
}

public static function admin_notice($slug, $type= '', $msg= ''): void 
{
	if ($type === '') $type = 'warning';	// ['info', 'warning', 'error', 'success']

	$plugin = \ATEC\GROUP::plugin_by_slug($slug);
	if ($slug === 'wpu') $plugin_name = 'Updates';
	else
	{
		$prefix = self::plugin_prefix($plugin);
		$plugin_name = self::plugin_fixed_name(($prefix !=='' ? $prefix.' ' : '') . $plugin);
	}

	$id = uniqid();
	echo 
	'<div id="atec_notice_' . esc_attr($id) . '" class="notice notice-', esc_attr($type), ' is-dismissible" role="alert" aria-live="assertive">
		<p>', esc_html($plugin_name.': '.self::trailingdotit($msg)), '</p>
		<button type="button" class="notice-dismiss" aria-label="Dismiss this notice" data-slug="' . esc_attr($slug) . '" data-id="' . esc_attr($id) . '"></button>
	</div>';

	static $admin_footer_loaded = null;
	if (!$admin_footer_loaded)
	{
		$admin_footer_loaded = true;
		add_action('admin_footer', function() 
		{
			$id = 'atec-admin-footer';
			wp_register_script($id, false, ['jquery'], '1.0.0', true);
			wp_enqueue_script($id);
			wp_add_inline_script($id, '
				jQuery(document).on("click", ".notice-dismiss", function() 
				{
					const id = jQuery(this).parent().attr("id");
					jQuery.ajax({ 
						url: ajaxurl, type: "POST", data: {action: "atec_admin_notice_dismiss", slug: jQuery(this).data("slug"), id: id },
						success: function(response) { if (response.success) jQuery("#"+id).slideUp(); }
					});
				});
			');
		}, 10 ,0);
	}
}

public static function dismiss_notice()
{
	$slug = self::_POST('slug');
	$id = self::_POST('id');	
	if (!$slug || !$id) { wp_send_json_error('Missing parameters'); }
	if (strpos($id, 'atec_notice_') !== 0) { wp_send_json_error('Invalid notice ID'); }
	\ATEC\INIT::delete_admin_debug($slug);
	wp_send_json_success('Notice dismissed');
}

public static function add_admin_notice_action($dir, $type= '', $msg= ''): void 
{
	$slug = \ATEC\GROUP::slug_by_dir($dir);
	add_action('admin_notices', function () use ($slug, $type, $msg) { self::admin_notice($slug, $type, $msg); });	// display on next admin load
}

}
?>