self::TIER_PRO, 'promote' => self::TIER_PRO, 'eshop_insights' => self::TIER_PRO, 'customer_profiling'=> self::TIER_PRO, 'seo' => self::TIER_FREE, 'pricing_offers' => self::TIER_FREE, 'storefront' => self::TIER_FREE, 'home' => self::TIER_FREE, 'catalog_sync' => self::TIER_FREE, 'custom_post_types' => self::TIER_FREE, 'setup' => self::TIER_FREE, 'tools' => self::TIER_FREE, ); } private static function destination_tiers() { return array( 'fulfillment' => self::TIER_FREE, 'withdrawal' => self::TIER_FREE, 'marketplace_order_importer'=> self::TIER_PRO, 'phone_orders' => self::TIER_PRO, 'branch_scanner' => self::TIER_PRO, 'stock_import' => self::TIER_PRO, 'product_exchanger' => self::TIER_PRO, 'orders_export' => self::TIER_PRO, 'marketplace_feeds' => self::TIER_FREE, 'catalog_importer' => self::TIER_FREE, 'price_export' => self::TIER_FREE, 'product_needs' => self::TIER_FREE, 'wishlist' => self::TIER_FREE, 'waitlist' => self::TIER_FREE, 'product_requests' => self::TIER_FREE, 'contact_form' => self::TIER_FREE, 'ratings' => self::TIER_FREE, 'customer_intelligence' => self::TIER_PRO, 'customer_profile' => self::TIER_PRO, 'user_roles' => self::TIER_FREE, 'catalogue_mode' => self::TIER_FREE, 'staff_notifications' => self::TIER_FREE, 'surveys' => self::TIER_PRO, 'cancellations' => self::TIER_PRO, 'ai_chat_analytics' => self::TIER_PRO, 'audience_builder' => self::TIER_PRO, 'web-push' => self::TIER_PRO, 'user_vitals' => self::TIER_PRO, 'contact_discovery' => self::TIER_PRO, 'web_push_display' => self::TIER_PRO, 'events' => self::TIER_PRO, 'competitions' => self::TIER_PRO, 'faq' => self::TIER_FREE, 'store_pages' => self::TIER_FREE, 'google_reviews' => self::TIER_FREE, 'food_beverages' => self::TIER_FREE, 'ad_connectors' => self::TIER_PRO, 'pnl_settings' => self::TIER_PRO, 'brand_margins' => self::TIER_PRO, 'consent' => self::TIER_FREE, 'conversions' => self::TIER_FREE, 'conversion_goals' => self::TIER_FREE, 'erp_bridge' => self::TIER_PRO, 'erp_database' => self::TIER_PRO, 'remote_export' => self::TIER_PRO, 'as_cleanup' => self::TIER_PRO, 'product_orders_analytics' => self::TIER_PRO, 'problematic_variations' => self::TIER_FREE, 'media_cleanup' => self::TIER_FREE, 'data_prep_scheduler' => self::TIER_FREE, 'my_settings' => self::TIER_FREE, 'plugin_usage' => self::TIER_FREE, 'button_styles' => self::TIER_FREE, 'tools' => self::TIER_FREE, 'stock_recovery' => self::TIER_PRO, 'missing_hot_sellers' => self::TIER_PRO, 'connections' => self::TIER_FREE, 'signals' => self::TIER_FREE, 'utm_explorer' => self::TIER_PRO, 'option_groups' => self::TIER_FREE, ); } private static function page_slug_tiers() { return array( 'ai-eshop-chat' => self::TIER_FREE, 'ai-eshop-optimizer' => self::TIER_FREE, ); } public static function tier_for($id, $group = '') { $id = (string) $id; $group = (string) $group; $explicit = self::destination_tiers(); $generated = self::generated_map(); if (isset($generated[$id])) { return self::filter_tier(array('tier' => $generated[$id], 'source' => 'generated'), $id, $group); } if (isset(self::section_tiers()[$id])) { $out = array('tier' => self::section_tiers()[$id], 'source' => 'section'); return self::filter_tier($out, $id, $group); } $module = class_exists('AIEO_DMM') ? AIEO_DMM::instance()->get_module($id) : null; if ($module && method_exists($module, 'is_pro_locked')) { $declared = false; try { $r = new ReflectionMethod($module, 'is_pro_locked'); $declared = ($r->getDeclaringClass()->getName() !== 'AIEO_DMM_Module_Base'); } catch (Exception $e) { $declared = false; } if ($declared) { $out = array( 'tier' => $module->is_pro_locked() ? self::TIER_PRO : self::TIER_FREE, 'source' => 'module', ); return self::filter_tier($out, $id, $group); } } if (isset($explicit[$id])) { $out = array('tier' => $explicit[$id], 'source' => 'explicit'); } else { $groups = self::group_tiers(); if ($group !== '' && isset($groups[$group])) { $out = array('tier' => $groups[$group], 'source' => 'group'); } else { $out = array('tier' => self::TIER_FREE, 'source' => 'default'); } } return self::filter_tier($out, $id, $group); } private static function filter_tier(array $out, $id, $group) { $filtered = apply_filters('aieo_capability_tier', $out, $id, $group); if (is_array($filtered) && isset($filtered['tier']) && in_array($filtered['tier'], array(self::TIER_FREE, self::TIER_PRO), true)) { return array( 'tier' => $filtered['tier'], 'source' => isset($filtered['source']) ? (string) $filtered['source'] : 'filter', ); } return $out; } private static $generated = null; public static function generated_map() { if (is_array(self::$generated)) { return self::$generated; } self::$generated = array(); $path = AIEO_DIR . 'includes/dmm/data/capability-tiers.php'; if (!file_exists($path)) { return self::$generated; } $raw = include $path; if (!is_array($raw)) { return self::$generated; } foreach ($raw as $id => $tier) { $tier = is_string($tier) ? strtolower(trim($tier)) : ''; if (in_array($tier, array(self::TIER_FREE, self::TIER_PRO), true)) { self::$generated[(string) $id] = $tier; } } return self::$generated; } private static function section_tiers() { return array( 'fulfillment_couriers' => self::TIER_FREE, 'fulfillment_settings' => self::TIER_FREE, 'fulfillment_maintenance' => self::TIER_FREE, 'fulfillment_documents' => self::TIER_PRO, ); } public static function all() { if (is_array(self::$memo)) { return self::$memo; } $loc = self::locale(); $fp = self::fingerprint(); $cached = get_transient(self::TRANSIENT); $fresh = is_array($cached) && isset($cached['v'], $cached['by_locale']) && $cached['v'] === $fp && is_array($cached['by_locale']); if ($fresh && isset($cached['by_locale'][$loc]) && is_array($cached['by_locale'][$loc])) { self::$memo = $cached['by_locale'][$loc]; return self::$memo; } $rows = self::build(); $store = $fresh ? $cached : array('v' => $fp, 'by_locale' => array()); $store['by_locale'][$loc] = $rows; if (count($store['by_locale']) > self::MAX_CACHED_LOCALES) { $store['by_locale'] = array_slice( $store['by_locale'], -self::MAX_CACHED_LOCALES, null, true ); } set_transient(self::TRANSIENT, $store, self::TTL); self::$memo = $rows; return $rows; } public static function flush() { self::$memo = null; delete_transient(self::TRANSIENT); } private static function locale() { return (string) (function_exists('get_user_locale') ? get_user_locale() : get_locale()); } private static function fingerprint() { $shape = array(); if (class_exists('AIEO_DMM')) { foreach ((array) AIEO_DMM::instance()->get_subtabs() as $t) { $shape[] = (isset($t['id']) ? (string) $t['id'] : '') . '|' . (isset($t['group']) ? (string) $t['group'] : '') . '|' . (isset($t['section']) ? (string) $t['section'] : ''); } } sort($shape); return self::SCHEMA . '|' . (defined('AIEO_VERSION') ? AIEO_VERSION : '0') . '|' . md5(implode("\n", $shape)); } private static function build() { if (!class_exists('AIEO_DMM')) { return array(); } $dmm = AIEO_DMM::instance(); $rows = array(); $absorbed = method_exists('AIEO_DMM', 'absorbed_subtabs') ? (array) AIEO_DMM::absorbed_subtabs() : array(); $aliases = method_exists($dmm, 'get_module_aliases') ? (array) $dmm->get_module_aliases() : array(); $owner_of = static function ($id) use ($absorbed, $aliases) { $id = (string) $id; return in_array($id, $absorbed, true) && isset($aliases[$id]) ? (string) $aliases[$id] : $id; }; foreach ((array) $dmm->get_subtabs() as $tab) { $id = isset($tab['id']) ? (string) $tab['id'] : ''; if ($id === '') { continue; } $group = isset($tab['group']) ? (string) $tab['group'] : ''; $t = self::tier_for($id, $group); $module = $dmm->get_module($id); $rows[$id] = array( 'id' => $id, 'label' => isset($tab['label']) ? wp_specialchars_decode((string) $tab['label'], ENT_QUOTES) : $id, 'kind' => 'page', 'page' => '', 'group' => $group, 'section' => isset($tab['section']) ? (string) $tab['section'] : '', 'icon' => is_object($module) && method_exists($module, 'get_icon') ? (string) $module->get_icon() : '', 'tier' => $t['tier'], 'tier_source' => $t['source'], 'url' => method_exists($dmm, 'get_subtab_url') ? $dmm->get_subtab_url($id) : '', 'description' => class_exists('AIEO_Menu_Descriptions') ? (string) AIEO_Menu_Descriptions::for_subtab($id) : '', ); } $slugs = self::page_slug_tiers(); foreach ((array) AIEO_DMM::standalone_screens() as $slug => $screen) { $slug = (string) $slug; if ($slug === '' || isset($rows[$slug]) || empty($screen['nav_group'])) { continue; } $rows[$slug] = array( 'id' => $slug, 'label' => wp_specialchars_decode((string) (isset($screen['nav_label']) ? $screen['nav_label'] : $screen['label']), ENT_QUOTES), 'kind' => 'page', 'page' => '', 'group' => (string) $screen['nav_group'], 'section' => isset($screen['nav_section']) ? (string) $screen['nav_section'] : '', 'icon' => isset($screen['icon']) ? (string) $screen['icon'] : '', 'tier' => isset($slugs[$slug]) ? $slugs[$slug] : self::TIER_FREE, 'tier_source' => isset($slugs[$slug]) ? 'page_slug' : 'default', 'url' => admin_url('admin.php?page=' . $slug), 'description' => class_exists('AIEO_Menu_Descriptions') ? (string) AIEO_Menu_Descriptions::for_subtab($slug) : '', ); } if (method_exists($dmm, 'build_admin_navigator_entries')) { foreach ((array) $dmm->build_admin_navigator_entries() as $nav) { $url = isset($nav['url']) ? (string) $nav['url'] : ''; if ($url === '') { continue; } if (strpos($url, '#') === false && strpos($url, '_section=') === false) { continue; } $parent = ''; if (preg_match('/dmm_tab=([a-z0-9_-]+)/i', $url, $m)) { $parent = $owner_of($m[1]); } elseif (preg_match('/[?&]page=([a-z0-9_-]+)/i', $url, $pm) && isset($rows[$pm[1]]) && 'page' === $rows[$pm[1]]['kind']) { $parent = $pm[1]; } if (preg_match('/[?&][a-z]+_section=([a-z0-9_-]+)/i', $url, $sm)) { $section_key = $parent . '_' . $sm[1]; $sections = self::section_tiers(); if (isset($sections[$section_key])) { $rows['tool:' . md5($url)] = array( 'id' => 'tool:' . md5($url), 'label' => isset($nav['label']) ? wp_specialchars_decode((string) $nav['label'], ENT_QUOTES) : '', 'kind' => 'tool', 'page' => $parent, 'group' => isset($rows[$parent]['group']) ? (string) $rows[$parent]['group'] : '', 'tier' => $sections[$section_key], 'tier_source' => 'section', 'icon' => isset($nav['icon']) ? (string) $nav['icon'] : '', 'url' => $url, 'description' => class_exists('AIEO_Menu_Descriptions') ? (string) AIEO_Menu_Descriptions::for_subtab($section_key) : '', ); continue; } } $group = isset($rows[$parent]['group']) ? (string) $rows[$parent]['group'] : ''; if (isset($rows[$parent])) { $t = array('tier' => $rows[$parent]['tier'], 'source' => 'page'); } else { $slug = ''; if (preg_match('/[?&]page=([a-z0-9_-]+)/i', $url, $pm)) { $slug = $pm[1]; } $slugs = self::page_slug_tiers(); $t = ($slug !== '' && isset($slugs[$slug])) ? array('tier' => $slugs[$slug], 'source' => 'page_slug') : self::tier_for($parent, $group); } $key = 'tool:' . md5($url); $rows[$key] = array( 'id' => $key, 'label' => isset($nav['label']) ? wp_specialchars_decode((string) $nav['label'], ENT_QUOTES) : '', 'kind' => 'tool', 'page' => $parent, 'group' => $group, 'tier' => $t['tier'], 'tier_source' => $t['source'], 'icon' => isset($nav['icon']) ? (string) $nav['icon'] : '', 'url' => $url, 'description' => '', ); } } $prose = array(); $prose_file = __DIR__ . '/data/page-tabs.php'; if (is_readable($prose_file)) { $loaded = include $prose_file; if (is_array($loaded)) { $prose = $loaded; } } foreach ((array) $dmm->get_modules() as $parent => $module) { if (!is_object($module) || !method_exists($module, 'get_tabs')) { continue; } $arg = method_exists($module, 'get_tab_arg') ? (string) $module->get_tab_arg() : ''; $tabs = class_exists('AIEO_DMM_UI') ? AIEO_DMM_UI::normalise_tabs((array) $module->get_tabs(), $arg) : array(); if (empty($tabs)) { continue; } $owner = $owner_of($parent); $group = isset($rows[$owner]['group']) ? (string) $rows[$owner]['group'] : ''; $t = isset($rows[$owner]) ? array('tier' => $rows[$owner]['tier'], 'source' => 'page') : self::tier_for((string) $owner, $group); $tab_base = method_exists($module, 'get_tab_base_url') ? (string) $module->get_tab_base_url() : ''; foreach ($tabs as $tid => $tab) { $url = $tab_base !== '' ? add_query_arg($tab['query'], $tab_base) : add_query_arg( array_merge(array('page' => AIEO_DMM::MENU_SLUG, 'dmm_tab' => $parent), $tab['query']), admin_url('admin.php') ); $p = isset($prose[$parent . '.' . $tid]) ? (array) $prose[$parent . '.' . $tid] : array(); $key = 'tab:' . $parent . ':' . $tid; $rows[$key] = array( 'id' => $key, 'label' => wp_specialchars_decode((string) $tab['label'], ENT_QUOTES), 'kind' => 'tab', 'page' => $owner, 'group' => $group, 'tier' => $t['tier'], 'tier_source' => $t['source'], 'icon' => isset($tab['icon']) ? (string) $tab['icon'] : '', 'url' => $url, 'description' => isset($p['description']) ? (string) $p['description'] : '', 'keywords' => isset($p['keywords']) ? (string) $p['keywords'] : '', ); } } foreach (self::described_actions() as $row) { $aid = isset($row['id']) ? (string) $row['id'] : ''; if ($aid === '' || empty($row['label'])) { continue; } $parent = isset($row['page']) ? (string) $row['page'] : ''; $group = isset($rows[$parent]['group']) ? (string) $rows[$parent]['group'] : ''; $t = isset($rows[$parent]) ? array('tier' => $rows[$parent]['tier'], 'source' => 'page') : self::tier_for($parent, $group); $key = 'action:' . $parent . ':' . $aid; $rows[$key] = array( 'id' => $key, 'label' => (string) $row['label'], 'kind' => 'action', 'page' => $parent, 'group' => $group, 'tier' => $t['tier'], 'tier_source' => $t['source'], 'url' => isset($rows[$parent]['url']) ? (string) $rows[$parent]['url'] : '', 'description' => isset($row['description']) ? (string) $row['description'] : '', 'keywords' => isset($row['ajax']) ? (string) $row['ajax'] : '', 'action_id' => $aid, 'informs_customer' => !empty($row['informs_customer']), 'confirm' => isset($row['confirm']) ? (string) $row['confirm'] : '', ); } $rows = apply_filters('aieo_capabilities', $rows); if (!is_array($rows)) { return array(); } foreach ($rows as $id => $row) { if (!is_array($row)) { unset($rows[$id]); continue; } if (!isset($row['icon'])) { $rows[$id]['icon'] = ''; } if (!isset($row['section']) && 'page' === (string) ($row['kind'] ?? '')) { $rows[$id]['section'] = ''; } if (isset($row['tier'], $row['tier_source'])) { continue; } if (isset($row['tier']) && in_array($row['tier'], array(self::TIER_FREE, self::TIER_PRO), true)) { $rows[$id]['tier_source'] = 'declared'; continue; } $parent = isset($row['page']) ? (string) $row['page'] : ''; if ('' !== $parent && isset($rows[$parent]['tier'])) { $rows[$id]['tier'] = $rows[$parent]['tier']; $rows[$id]['tier_source'] = 'page'; continue; } $rows[$id]['tier'] = self::TIER_FREE; $rows[$id]['tier_source'] = 'default'; } return $rows; } public static function described_actions() { static $memo = null; if (is_array($memo)) { return $memo; } $memo = self::read_actions_file('includes/dmm/data/actions-described.json'); if (empty($memo)) { $memo = self::read_actions_file('docs/actions-described.json'); } if (empty($memo)) { foreach (self::actions_index() as $row) { if (!empty($row['label'])) { $memo[] = $row; } } } $strings = self::action_strings(); if (!empty($strings)) { foreach ($memo as &$row) { $id = isset($row['id']) ? (string) $row['id'] : ''; if ($id === '' || !isset($strings[$id])) { continue; } foreach (array('label', 'description', 'context') as $f) { if (!empty($strings[$id][$f])) { $row[$f] = (string) $strings[$id][$f]; } } } unset($row); } return $memo; } private static function action_strings() { static $memo = null; if (is_array($memo)) { return $memo; } $memo = array(); $path = __DIR__ . '/data/action-strings.php'; if (is_readable($path)) { $raw = include $path; if (is_array($raw)) { $memo = $raw; } } return $memo; } public static function actions_index() { static $memo = null; if (!is_array($memo)) { $memo = self::read_actions_file('includes/dmm/data/actions-index.json'); if (empty($memo)) { $memo = self::read_actions_file('docs/actions-index.json'); } } return $memo; } private static function read_actions_file($rel) { $root = defined('AIEO_PLUGIN_DIR') ? AIEO_PLUGIN_DIR : (defined('AIEO_DIR') ? AIEO_DIR : ''); if ($root === '') { return array(); } $path = rtrim((string) $root, '/\\') . '/' . ltrim($rel, '/'); if (!is_readable($path)) { return array(); } $raw = json_decode((string) file_get_contents($path), true); return (is_array($raw) && isset($raw['actions']) && is_array($raw['actions'])) ? $raw['actions'] : array(); } public static function action($action_id) { foreach (self::described_actions() as $row) { if (isset($row['id']) && (string) $row['id'] === (string) $action_id) { return $row; } } return null; } public static function stats() { $rows = self::all(); $out = array( 'pages' => 0, 'tabs' => 0, 'tools' => 0, 'actions' => 0, 'total' => 0, 'free' => 0, 'pro' => 0, 'unclassified' => 0, ); foreach ($rows as $r) { $out['total']++; if ('page' === $r['kind']) { $out['pages']++; } elseif ('tab' === $r['kind']) { $out['tabs']++; } elseif ('action' === $r['kind']) { $out['actions']++; } else { $out['tools']++; } if (self::TIER_PRO === $r['tier']) { $out['pro']++; } else { $out['free']++; } if ('default' === $r['tier_source']) { $out['unclassified']++; } } return $out; } public static function unclassified() { $out = array(); foreach (self::all() as $id => $r) { if ('default' === $r['tier_source']) { $out[$id] = $r; } } return $out; } public static function by_tier($tier) { $tier = ($tier === self::TIER_PRO) ? self::TIER_PRO : self::TIER_FREE; $out = array(); foreach (self::all() as $id => $r) { if ($r['tier'] === $tier) { $out[$id] = $r; } } return $out; } public static function site_tier() { if (class_exists('AIEO_Panel_Manifest') && method_exists('AIEO_Panel_Manifest', 'build')) { $m = AIEO_Panel_Manifest::build(); if (isset($m['tier']['name'])) { return (string) $m['tier']['name']; } } return self::TIER_FREE; } } if (defined('WP_CLI') && WP_CLI) { class AIEO_CLI_Capabilities { public function __invoke($args, $assoc) { AIEO_Capability_Registry::flush(); if (!empty($assoc['map'])) { $map = array(); foreach (AIEO_Capability_Registry::all() as $id => $r) { if ('page' === $r['kind']) { $map[$id] = $r['tier']; } } ksort($map); WP_CLI::line(wp_json_encode($map, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); return; } $rows = !empty($assoc['unclassified']) ? AIEO_Capability_Registry::unclassified() : AIEO_Capability_Registry::all(); if (!empty($assoc['tier'])) { $want = (string) $assoc['tier']; $rows = array_filter($rows, static function ($r) use ($want) { return $r['tier'] === $want; }); } if (!empty($assoc['kind'])) { $want = (string) $assoc['kind']; $rows = array_filter($rows, static function ($r) use ($want) { return $r['kind'] === $want; }); } $format = isset($assoc['format']) ? (string) $assoc['format'] : 'table'; $out = array(); foreach ($rows as $r) { $out[] = array( 'id' => $r['id'], 'label' => $r['label'], 'kind' => $r['kind'], 'page' => $r['page'], 'group' => $r['group'], 'tier' => $r['tier'], 'tier_source' => $r['tier_source'], 'url' => $r['url'], ); } if ('count' === $format) { WP_CLI::line((string) count($out)); return; } $s = AIEO_Capability_Registry::stats(); if ('table' === $format) { WP_CLI::line(sprintf( '%d pages · %d tools · %d total | %d free · %d pro | %d unclassified | this site is on %s', $s['pages'], $s['tools'], $s['total'], $s['free'], $s['pro'], $s['unclassified'], strtoupper(AIEO_Capability_Registry::site_tier()) )); WP_CLI::line(''); } if (empty($out)) { WP_CLI::success(!empty($assoc['unclassified']) ? 'Everything has a tier.' : 'Nothing matched.'); return; } WP_CLI\Utils\format_items($format, $out, array('id', 'label', 'kind', 'page', 'group', 'tier', 'tier_source', 'url')); } } WP_CLI::add_command('aieo capabilities', 'AIEO_CLI_Capabilities'); } add_action('activated_plugin', array('AIEO_Capability_Registry', 'flush')); add_action('deactivated_plugin', array('AIEO_Capability_Registry', 'flush')); add_action('upgrader_process_complete', array('AIEO_Capability_Registry', 'flush'));