PluginProbe
Code Engine – PHP Snippets, AI Functions & Automation for WordPress / 0.3.0
Code Engine – PHP Snippets, AI Functions & Automation for WordPress v0.3.0
0.5.7 0.5.6 0.5.5 0.5.4 0.5.3 0.5.2 0.5.1 0.5.0 0.4.9 0.4.8 0.4.7 0.4.6 trunk 0.0.1 0.0.2 0.2.8 0.2.9 0.3.0 0.3.1 0.3.2 0.3.3 0.3.4 0.3.5 0.3.6 0.3.7 All 33 releases
← All changes | common/admin.php +86 -582 0.4.60.3.0 View file →
@@ -1,200 +1,76 @@
1 1 <?php
2 2
3 -if ( !class_exists( 'MeowKit_MWCODE_Admin' ) ) {
3 +if ( !class_exists( 'MeowCommon_Admin' ) ) {
4 4
5 - class MeowKit_MWCODE_Admin {
5 + class MeowCommon_Admin {
6 +
6 7 public static $loaded = false;
7 - public static $version = '5.0';
8 - public static $admin_version = '5.0';
9 - public static $network_license_modal_added = false;
10 - public static $network_license_plugins = [];
8 + public static $version = "4.0";
9 + public static $admin_version = "4.0";
11 10
12 - /**
13 - * Storage for instances that need deferred initialization.
14 - *
15 - * WordPress Loading Sequence Problem:
16 - * 1. Load all plugin files
17 - * 2. Fire 'plugins_loaded' hook ← Most plugins instantiate Admin here
18 - * 3. Load wp-includes/pluggable.php ← current_user_can() defined here
19 - * 4. Fire 'init' hook ← Safe to use pluggable functions
20 - *
21 - * When plugins instantiate during 'plugins_loaded', the pluggable functions
22 - * (current_user_can, wp_get_current_user) don't exist yet. This array stores
23 - * instances until 'init' when we can safely call those functions.
24 - *
25 - * @var array
26 - */
27 - private static $deferred_instances = array();
28 -
29 11 public $prefix; // prefix used for actions, filters (mfrh)
30 12 public $mainfile; // plugin main file (media-file-renamer.php)
31 13 public $domain; // domain used for translation (media-file-renamer)
32 14 public $isPro = false;
33 15
34 - // Store constructor params that affect per-instance setup
35 - private $disableReview = false;
16 + public static $logo = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAxNDM0IDk0NyI+CiAgPHBhdGggZmlsbD0iIzAwMCIgZD0iTTgwNSA3NzdhNzkyIDc5MiAwIDAgMS0yNjItNDMgODExIDgxMSAwIDAgMS0yODYtMTY0QTk1OSA5NTkgMCAwIDEgNiAyMDAgMTU4IDE1OCAwIDAgMSAzMDQgOTdjNDEgOTYgOTQgMTc1IDE1OSAyMzNhNDk3IDQ5NyAwIDAgMCAzNzYgMTI5IDYwIDYwIDAgMCAxIDY3IDYwbDI3IDE4NmM0IDMzLTE4IDYzLTUxIDY4LTYgMC0zNCA0LTc3IDRaTTEyMiAxNjhsMiA1YTg0MSA4NDEgMCAwIDAgMjEyIDMwNyA2OTIgNjkyIDAgMCAwIDQ2OSAxNzdsLTExLTc2YTYxNiA2MTYgMCAwIDEtNDEyLTE2MiA3NjkgNzY5IDAgMCAxLTE4OC0yNzYgMzggMzggMCAwIDAtNTAtMjBjLTE4IDctMjcgMjctMjIgNDVaIi8+CiAgPHBhdGggZmlsbD0iI0ZEQTk2MCIgZD0ibTY0IDE4NCA0IDEyYTkwMCA5MDAgMCAwIDAgMjI4IDMyOSA3NTIgNzUyIDAgMCAwIDU3NyAxODhsLTI3LTE5NGE1NjMgNTYzIDAgMCAxLTQyMy0xNDQgNzA5IDcwOSAwIDAgMS0xNzQtMjU1IDk4IDk4IDAgMCAwLTE4NSA2NFoiLz4KICA8bWFzayBpZD0iYSIgd2lkdGg9IjgxNCIgaGVpZ2h0PSI2NTciIHg9IjYwIiB5PSI2MCIgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIgc3R5bGU9Im1hc2stdHlwZTpsdW1pbmFuY2UiPgogICAgPHBhdGggZmlsbD0iI2ZmZiIgZD0ibTY0IDE4NCA0IDEyYTkwMCA5MDAgMCAwIDAgMjI4IDMyOSA3NTIgNzUyIDAgMCAwIDU3NyAxODhsLTI3LTE5NGE1NjMgNTYzIDAgMCAxLTQyMy0xNDQgNzA5IDcwOSAwIDAgMS0xNzQtMjU1IDk4IDk4IDAgMCAwLTE4NSA2NFoiLz4KICA8L21hc2s+CiAgPGcgbWFzaz0idXJsKCNhKSI+CiAgICA8cGF0aCBmaWxsPSIjODA0NjI1IiBkPSJNMTIwIDUzMmMtNDEgMC04NC01LTEzMC0xNWwzMS0xNDVjMTAxIDIxIDE4MCAxMiAyMzMtMjcgNzAtNTEgODAtMTQxIDgwLTE0MmwxNDkgMTNhMzYzIDM2MyAwIDAgMS0xMzkgMjQ4IDM1MSAzNTEgMCAwIDEtMjI0IDY4Wm0zNjkgMTc1YzQ3LTMxIDg0LTcxIDExMC0xMTYgMzItNTYgNDYtMTIzIDQyLTE5Mi0zLTUxLTE1LTg3LTE2LTkxbC0xNDEgNDhhMjI1IDIyNSAwIDAgMS0xNSAxNjFjLTMzIDU4LTEwMSA5OS0yMDMgMTIwbDMwIDE0NmM3Ni0xNiAxNDEtNDEgMTkzLTc2Wk02MiAyNjljNjQtNCAxMjItMjIgMTc0LTUzQTQxMyA0MTMgMCAwIDAgNDIxLTQ3TDE4NC05MnYtMXMtMTYgNzEtNzMgMTAzQzkyIDIxIDcwIDI3IDQ0IDI5IDcgMzEtMzcgMjQtODYgOGwtNzQgMjI5YTYyMyA2MjMgMCAwIDAgMjIyIDMyWiIvPgogIDwvZz4KICA8cGF0aCBmaWxsPSIjMDAwIiBkPSJNMTM3MyA5NDdoLTExMGMtMzMgMC02MC0yNy02MC02MHYtOTdsLTM2IDg3YTYyIDYyIDAgMCAxLTU2IDM3aC03OWMtMjUgMC00Ni0xNC01Ni0zN2wtMzYtODd2OTdjMCAzMy0yNyA2MC02MCA2MEg3NjljLTMzIDAtNjAtMjctNjAtNjBWMzE2YzAtMzMgMjctNjAgNjAtNjBoMTQxYzI0IDAgNDYgMTUgNTUgMzdsMTA2IDI1OCAxMDctMjU4YzktMjIgMzEtMzcgNTUtMzdoMTQwYzM0IDAgNjAgMjcgNjAgNjB2NTcxYzAgMzMtMjYgNjAtNjAgNjBabS0zMTYtMTg4IDE0IDM0IDE1LTM0LTExIDFoLTdsLTExLTFabTE5OS0zMTRoN2MyMSAwIDQwIDExIDUwIDI4di05N2gtNDBsLTI5IDcwIDEyLTFabS00MjctNjl2OTdjMTEtMTcgMjktMjggNTEtMjhoNmwxMyAxLTI5LTcwaC00MVoiLz4KICA8cGF0aCBmaWxsPSIjZmZmIiBkPSJNNzY5IDg4N1YzMTZoMTQxbDE1OCAzODRoN2wxNTgtMzg0aDE0MHY1NzFoLTExMFY1MDVoLTdsLTE0NSAzNDloLTc5TDg4NiA1MDVoLTZ2MzgySDc2OVoiLz4KPC9zdmc+Cg==';
36 17
37 - public static $logo = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNDYiIHZpZXdCb3g9IjAgMCA2NCA0NiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8ZyBjbGlwUGF0aD0idXJsKCNjbGlwMF8zMTBfMjI5KSI+CiAgICA8cGF0aCBkPSJNNjQgMzAuNjQwOEM2NCAyNy43OTg1IDYwLjA4MTYgMjUuODMwMyA1NS44Mjk4IDI1LjgzMDNDNTQuODU5MyAyNS44MzAzIDUzLjkzMTEgMjUuOTMzIDUzLjA3NiAyNi4xMjUzQzQ5Ljg4NjUgMTkuMDc5IDQxLjY1MzkgMTMuMDg1MyAzMi4wMDAyIDEzLjA4NTNDMzAuODMzNyAxMy4wODUzIDI5LjY4ODEgMTMuMTcyNyAyOC41Njk4IDEzLjMzOTJDMjcuMjA2OSAxMC4zMDc2IDIyLjY3NjIgMi40MzQyNiAxMS41OTU0IDAuMDgzMDA2NEMxMS4wNDkxIC0wLjAzMjc0NTYgMTAuNDk0NiAwLjI0MDU3OCAxMC4yNTkgMC43NDY5QzguODU5MTMgMy43NTYwOCA0Ljc0MjQ3IDE0LjQxMTYgMTAuMjQwMyAyNS45OTMxQzkuNTgxNjUgMjUuODg2NCA4Ljg4NzUxIDI1LjgzMDMgOC4xNzAyMiAyNS44MzAzQzMuOTE4MzkgMjUuODMwMyAwIDI3Ljc5ODUgMCAzMC42NDA4QzAgMzMuNDgzIDMuOTE4MzkgMzUuMjI3MiA4LjE3MDIyIDM1LjIyNzJDOC43MTEyNyAzNS4yMjcyIDkuMjM5MjUgMzUuMTk4OCA5Ljc0ODkzIDM1LjE0MzVDOS40MzYwMiAzNS4yNjY0IDkuMTIyNzUgMzUuNDA3NSA4LjgxMTcxIDM1LjU2NzdDNS42OTM4OCAzNy4xNzA3IDMuOTY4OCA0MC4wMzEyIDQuOTU5MDQgNDEuOTU2OEM1Ljk0ODkgNDMuODgyNCA5LjI3OTIgNDQuMTQ0MiAxMi4zOTcgNDIuNTQxMkMxMy4wNDY0IDQyLjIwNzQgMTMuNjM0OCA0MS44MTkgMTQuMTUxNiA0MS4zOTZDMTguMjYyNyA0NC40OTY3IDI0LjcyODMgNDUuOTgwOSAzMS45OTk4IDQ1Ljk4MDlDMzkuMjcxMyA0NS45ODA5IDQ1LjczNyA0NC40OTY3IDQ5Ljg0OCA0MS4zOTZDNTAuMzY0NCA0MS44MTkgNTAuOTUzMyA0Mi4yMDc0IDUxLjYwMjYgNDIuNTQxMkM1NC43MjA0IDQ0LjE0NDIgNTguMDUwMyA0My44ODI0IDU5LjA0MDYgNDEuOTU2OEM2MC4wMzA1IDQwLjAzMTIgNTguMzA1NyAzNy4xNzA3IDU1LjE4NzkgMzUuNTY3N0M1NC44NzYxIDM1LjQwNzUgNTQuNTYyMSAzNS4yNjY3IDU0LjI0ODUgMzUuMTQzNUM1NC43NTg5IDM1LjE5ODggNTUuMjg3NiAzNS4yMjc1IDU1LjgyOTQgMzUuMjI3NUM2MC4wODEyIDM1LjIyNzUgNjMuOTk5NiAzMy40ODM0IDYzLjk5OTYgMzAuNjQxMUw2NCAzMC42NDA4WiIgZmlsbD0id2hpdGUiLz4KICAgIDxwYXRoIGQ9Ik0yMi4yMjkzIDM2Ljc0NDNDMjYuNTkzNSAzNi43NDQzIDMwLjEzMTQgMzMuMjA2NCAzMC4xMzE0IDI4Ljg0MjJDMzAuMTMxNCAyNC40NzggMjYuNTkzNSAyMC45NDAxIDIyLjIyOTMgMjAuOTQwMUMxNy44NjUxIDIwLjk0MDEgMTQuMzI3MSAyNC40NzggMTQuMzI3MSAyOC44NDIyQzE0LjMyNzEgMzMuMjA2NCAxNy44NjUxIDM2Ljc0NDMgMjIuMjI5MyAzNi43NDQzWiIgZmlsbD0iIzAwRTI4RSIvPgogICAgPHBhdGggZD0iTTIyLjI2NTUgMzMuMTM2MUMyMy41MDIyIDMzLjEzNjEgMjQuNTA0NyAzMS4yODA1IDI0LjUwNDcgMjguOTkxNUMyNC41MDQ3IDI2LjcwMjQgMjMuNTAyMiAyNC44NDY4IDIyLjI2NTUgMjQuODQ2OEMyMS4wMjg4IDI0Ljg0NjggMjAuMDI2MiAyNi43MDI0IDIwLjAyNjIgMjguOTkxNUMyMC4wMjYyIDMxLjI4MDUgMjEuMDI4OCAzMy4xMzYxIDIyLjI2NTUgMzMuMTM2MVoiIGZpbGw9IiMzQzZFOEIiLz4KICAgIDxwYXRoIGQ9Ik0zMS45OTk4IDM3LjkxNTZDMzMuNDIzNyAzNy45MTU2IDM0LjU3ODEgMzcuMzQwOSAzNC41NzgxIDM2LjYzMTlDMzQuNTc4MSAzNS45MjI5IDMzLjQyMzcgMzUuMzQ4MSAzMS45OTk4IDM1LjM0ODFDMzAuNTc1OCAzNS4zNDgxIDI5LjQyMTUgMzUuOTIyOSAyOS40MjE1IDM2LjYzMTlDMjkuNDIxNSAzNy4zNDA5IDMwLjU3NTggMzcuOTE1NiAzMS45OTk4IDM3LjkxNTZaIiBmaWxsPSIjRkY5NDkzIi8+CiAgICA8cGF0aCBkPSJNNTQuMjUwMyAzNS4xMDU4QzU0Ljc2IDM1LjE2MTEgNTUuMjg3OSAzNS4xODk0IDU1LjgyOSAzNS4xODk0QzYwLjA4MDggMzUuMTg5NCA2My45OTkyIDMzLjQ0NTMgNjMuOTk5MiAzMC42MDNDNjMuOTk5MiAyNy43NjA4IDYwLjA4MDggMjUuNzkyNiA1NS44MjkgMjUuNzkyNkM1NS4xMTE3IDI1Ljc5MjYgNTQuNDE3NiAyNS44NDkgNTMuNzU4NSAyNS45NTU4QzU5LjI1NjcgMTQuMzc0MiA1NS4xMzk3IDMuNzE4NzIgNTMuNzQwMiAwLjcwOTU0NkM1My41MDQ2IDAuMjAzMjI1IDUyLjk1MDEgLTAuMDcwMDk5MSA1Mi40MDM4IDAuMDQ1NjUyOUM0MS4zMjMgMi4zOTY5MSAzNi43OTIzIDEwLjI3MDcgMzUuNDI5OCAxMy4zMDE1QzM0LjQ1NDEgMTMuMTU2NiAzMy40NTc5IDEzLjA3MTEgMzIuNDQ1MiAxMy4wNTE3QzMxLjI3NDMgMjAuMDMzIDI4Ljk2NTYgNDMuOTM2NSA1NC4zNDM2IDM1LjE0MzlDNTQuMzEyMyAzNS4xMzEyIDU0LjI4MTMgMzUuMTE4MSA1NC4yNDk5IDM1LjEwNThINTQuMjUwM1oiIGZpbGw9IiMyQjlERkYiLz4KICAgIDxwYXRoIGQ9Ik00MS43MzQyIDMzLjEzNjFDNDIuOTcwOSAzMy4xMzYxIDQzLjk3MzUgMzEuMjgwNSA0My45NzM1IDI4Ljk5MTVDNDMuOTczNSAyNi43MDI0IDQyLjk3MDkgMjQuODQ2OCA0MS43MzQyIDI0Ljg0NjhDNDAuNDk3NSAyNC44NDY4IDM5LjQ5NSAyNi43MDI0IDM5LjQ5NSAyOC45OTE1QzM5LjQ5NSAzMS4yODA1IDQwLjQ5NzUgMzMuMTM2MSA0MS43MzQyIDMzLjEzNjFaIiBmaWxsPSIjM0M2RThCIi8+CiAgPC9nPgogIDxkZWZzPgogICAgPGNsaXBQYXRoIGlkPSJjbGlwMF8zMTBfMjI5Ij4KICAgICAgPHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjQ1Ljk2MTciIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIDAuMDE5MTY1KSIvPgogICAgPC9jbGlwUGF0aD4KICA8L2RlZnM+Cjwvc3ZnPgo=';
38 -
39 18 public function __construct( $prefix, $mainfile, $domain, $isPro = false, $disableReview = false, $freeOnly = false ) {
40 19
41 - // ALWAYS set instance properties first - these are needed regardless of when setup runs
42 - $this->prefix = $prefix;
43 - $this->mainfile = $mainfile;
44 - $this->domain = $domain;
45 - $this->isPro = $isPro;
46 - $this->disableReview = $disableReview;
20 + if ( !MeowCommon_Admin::$loaded ) {
21 + if ( is_admin() ) {
47 22
48 - if ( is_admin() ) {
23 + if ( MeowCommon_Helpers::is_asynchronous_request() ) {
24 + return;
25 + }
49 26
50 - // Skip AJAX and REST requests to avoid unnecessary processing
51 - if ( MeowKit_MWCODE_Helpers::is_asynchronous_request() ) {
52 - return;
53 - }
27 + // Check potential issues with this WordPress install, other plugins, etc.
28 + new MeowCommon_Issues( $prefix, $mainfile, $domain );
54 29
55 - // Check if WordPress pluggable functions are available yet.
56 - // These are defined in wp-includes/pluggable.php, which WordPress loads
57 - // AFTER the 'plugins_loaded' hook but BEFORE the 'init' hook.
58 - if ( !function_exists( 'current_user_can' ) || !function_exists( 'wp_get_current_user' ) ) {
59 - // Functions don't exist yet - defer admin setup until 'init' hook
60 - // This is NORMAL behavior when plugins instantiate on 'plugins_loaded'
61 - $this->defer_admin_setup();
62 - // Continue to rest of constructor (filters, license checks, etc.)
63 - } else {
64 - // Functions already exist - safe to run admin setup immediately
65 - // This happens when plugins instantiate on 'init' or later
66 - $this->run_admin_setup();
30 + // Create the Meow Apps Menu
31 + add_action( 'admin_menu', array( $this, 'admin_menu_start' ) );
32 + $page = isset( $_GET["page"] ) ? sanitize_text_field( $_GET["page"] ) : null;
33 + if ( $page === 'meowapps-main-menu' ) {
34 + add_filter( 'admin_footer_text', array( $this, 'admin_footer_text' ), 100000, 1 );
35 + }
67 36 }
68 -
69 - // License-related admin notices (doesn't require pluggable functions)
70 - $license = get_option( $this->prefix . '_license', '' );
71 - if ( !empty( $license ) && !$this->isPro ) {
72 - add_action( 'admin_notices', [ $this, 'admin_notices_licensed_free' ] );
73 - }
37 + MeowCommon_Admin::$loaded = true;
74 38 }
75 39
76 - // ALWAYS register these filters (they work at any time)
77 - add_filter( 'plugin_row_meta', [ $this, 'custom_plugin_row_meta' ], 10, 2 );
78 - add_filter( 'edd_sl_api_request_verify_ssl', [ $this, 'request_verify_ssl' ], 10, 0 );
79 - }
40 + // Variables for this plugin
41 + $this->prefix = $prefix;
42 + $this->mainfile = $mainfile;
43 + $this->domain = $domain;
44 + $this->isPro = $isPro;
80 45
81 - /**
82 - * Defer admin setup until WordPress 'init' hook.
83 - *
84 - * This method stores the current instance and registers a one-time
85 - * 'init' hook callback that will process all deferred instances.
86 - *
87 - * Why defer? Because we need current_user_can() to check permissions,
88 - * and that function doesn't exist until after 'plugins_loaded'.
89 - */
90 - private function defer_admin_setup() {
91 - // Add this instance to the queue for processing on 'init'
92 - self::$deferred_instances[] = $this;
46 + // If there is no mainfile, it's either a Pro only Plugin (with no Free version available) or a Theme.
93 47
94 - // Register the 'init' hook only once (for the first deferred instance)
95 - if ( count( self::$deferred_instances ) === 1 ) {
96 - add_action( 'init', array( __CLASS__, 'process_deferred_instances' ) );
97 - }
98 - }
99 -
100 - /**
101 - * Static callback for 'init' hook - processes all deferred instances.
102 - *
103 - * By the time 'init' fires, WordPress has loaded pluggable.php and
104 - * current_user_can() is guaranteed to exist. We process all instances
105 - * that were created during 'plugins_loaded' or earlier.
106 - *
107 - * This is called as a static method because it processes multiple instances.
108 - */
109 - public static function process_deferred_instances() {
110 - // Belt-and-suspenders check: pluggable functions should ALWAYS exist by 'init'
111 - // If they somehow don't, log a warning and bail (this should never happen)
112 - if ( !function_exists( 'current_user_can' ) || !function_exists( 'wp_get_current_user' ) ) {
113 - trigger_error(
114 - 'MeowKit_MWCODE_Admin: Pluggable functions still unavailable on init hook. ' .
115 - 'This should never happen and indicates a serious WordPress core issue.',
116 - E_USER_WARNING
117 - );
118 - return;
119 - }
120 -
121 - // Process each deferred instance's admin setup
122 - foreach ( self::$deferred_instances as $instance ) {
123 - $instance->run_admin_setup();
124 - }
125 -
126 - // Clear the array to free memory (we won't need these references anymore)
127 - self::$deferred_instances = array();
128 - }
129 -
130 - /**
131 - * Run admin setup - both shared (once) and per-instance (each plugin).
132 - *
133 - * SHARED SETUP (once for all plugins):
134 - * - Issues detection
135 - * - Meow Apps menu creation
136 - * - Admin footer customization
137 - *
138 - * PER-INSTANCE SETUP (once per plugin):
139 - * - Ratings system
140 - * - News system
141 - *
142 - * This method is called either immediately (if pluggable functions exist)
143 - * or deferred until 'init' (if they don't). Either way, it's safe to call
144 - * current_user_can() here.
145 - */
146 - private function run_admin_setup() {
147 - // SHARED SETUP: Only run once for all Meow Apps plugins
148 - if ( !MeowKit_MWCODE_Admin::$loaded ) {
149 - // Check for potential issues with WordPress install, other plugins, etc.
150 - new MeowKit_MWCODE_Issues( $this->prefix, $this->mainfile, $this->domain );
151 -
152 - // Create the unified Meow Apps menu (priority 5 to ensure early creation)
153 - add_action( 'admin_menu', [ $this, 'admin_menu_start' ], 5 );
154 -
155 - // Customize admin footer on Meow Apps pages
156 - $page = isset( $_GET['page'] ) ? sanitize_text_field( $_GET['page'] ) : null;
157 - if ( $page === 'meowapps-main-menu' ) {
158 - add_filter( 'admin_footer_text', [ $this, 'admin_footer_text' ], 100000, 1 );
48 + if ( is_admin() ) {
49 + $license = get_option( $this->prefix . '_license', "" );
50 + if ( !empty( $license ) && !$this->isPro ) {
51 + add_action( 'admin_notices', array( $this, 'admin_notices_licensed_free' ) );
159 52 }
160 -
161 - // Promote AI Engine on the WordPress 7 Connectors page when AI Engine
162 - // itself isn't installed. When AI Engine is active, its own banner
163 - // takes over — so this path only runs on "bare" Meow Apps installs.
164 - add_action( 'admin_footer', [ $this, 'maybe_render_wpai_promo' ] );
165 -
166 - MeowKit_MWCODE_Admin::$loaded = true;
167 - }
168 -
169 - // PER-INSTANCE SETUP: Run for each plugin that uses this library
170 - // Only admins get ratings prompts and news
171 - if ( $this->is_user_admin() ) {
172 - if ( !$this->disableReview ) {
173 - new MeowKit_MWCODE_Ratings( $this->prefix, $this->mainfile, $this->domain );
53 + if ( $this->is_user_admin() ) {
54 + if ( !$disableReview ) {
55 + new MeowCommon_Ratings( $prefix, $mainfile, $domain );
56 + }
57 + new MeowCommon_News( $domain );
174 58 }
175 - new MeowKit_MWCODE_News( $this->domain );
176 59 }
60 + add_filter( 'plugin_row_meta', array( $this, 'custom_plugin_row_meta' ), 10, 2 );
61 + add_filter( 'edd_sl_api_request_verify_ssl', array( $this, 'request_verify_ssl' ), 10, 0 );
177 62 }
178 63
179 - /**
180 - * Check if current user is a site administrator.
181 - *
182 - * This method is only called from run_admin_setup(), which guarantees
183 - * that pluggable functions exist. No error logging needed - if the
184 - * functions don't exist, we simply return false as a defensive fallback.
185 - *
186 - * @return bool True if user can manage options, false otherwise
187 - */
188 - public function is_user_admin() {
189 - // Defensive check (should never fail if called from run_admin_setup)
64 + function is_user_admin() {
190 65 if ( !function_exists( 'current_user_can' ) || !function_exists( 'wp_get_current_user' ) ) {
66 + error_log( 'MeowCommon_Admin is called too early. Please make sure it is called after the plugins_loaded filter.' );
191 67 return false;
192 68 }
193 69 return current_user_can( 'manage_options' );
194 70 }
195 71
196 - public function custom_plugin_row_meta( $links, $file ) {
72 + function custom_plugin_row_meta( $links, $file ) {
197 73 $path = pathinfo( $file );
198 74 $pathName = basename( $path['dirname'] );
199 75 $thisPath = pathinfo( $this->mainfile );
200 76 $thisPathName = basename( $thisPath['dirname'] );
@@ -203,202 +79,30 @@
203 79 return $links;
204 80 }
205 81 $isIssue = $this->isPro && !$this->is_registered();
206 82 if ( strpos( $pathName, $thisPathName ) !== false ) {
207 - // In network admin, handle differently (no settings page available)
208 - if ( is_network_admin() ) {
209 - if ( $this->isPro && !$this->is_registered() ) {
210 - // Show "Register License" link for unregistered Pro plugins
211 - $new_links = [
212 - 'license' => sprintf(
213 - '<a href="#" class="meowapps-network-license-link" data-prefix="%s" data-plugin="%s" style="color: #d63638;">%s</a>',
214 - esc_attr( $this->prefix ),
215 - esc_attr( $this->nice_name_from_file( $this->mainfile ) ),
216 - __( 'Register License', $this->domain )
217 - ),
218 - ];
219 - // Track this plugin for the modal
220 - self::$network_license_plugins[ $this->prefix ] = $this->nice_name_from_file( $this->mainfile );
221 - // Add modal output hook (only once)
222 - if ( !self::$network_license_modal_added ) {
223 - add_action( 'admin_footer', [ __CLASS__, 'output_network_license_modal' ] );
224 - self::$network_license_modal_added = true;
225 - }
226 - }
227 - elseif ( $this->isPro && $this->is_registered() ) {
228 - // Pro plugin is registered
229 - $new_links = [
230 - 'license' => '<span style="color: #a75bd6;">' . __( 'Pro Version', $this->domain ) . '</span>',
231 - ];
232 - }
233 - else {
234 - // Free plugin
235 - $new_links = [
236 - 'license' => sprintf( '<span>' . __( '<a target="_blank" href="https://meowapps.com">Get the <u>Pro Version</u></a>', $this->domain ), $this->prefix ) . '</span>',
237 - ];
238 - }
239 - }
240 - else {
241 - // Regular admin - show settings and license status
242 - $new_links = [
243 - 'settings' =>
83 + $new_links = array(
84 + 'settings' =>
244 85 sprintf( __( '<a href="admin.php?page=%s_settings">Settings</a>', $this->domain ), $this->prefix ),
245 - 'license' =>
246 - $this->is_registered() ?
247 - ( '<span style="color: #a75bd6;">' . __( 'Pro Version', $this->domain ) . '</span>' ) :
248 - ( $isIssue ? ( sprintf( '<span style="color: #ff3434;">' . __( 'License Issue', $this->domain ), $this->prefix ) . '</span>' ) : ( sprintf( '<span>' . __( '<a target="_blank" href="https://meowapps.com">Get the <u>Pro Version</u></a>', $this->domain ), $this->prefix ) . '</span>' ) ),
249 - ];
250 - }
86 + 'license' =>
87 + $this->is_registered() ?
88 + ('<span style="color: #a75bd6;">' . __( 'Pro Version', $this->domain ) . '</span>') :
89 + ( $isIssue ? (sprintf( '<span style="color: #ff3434;">' . __( 'License Issue', $this->domain ), $this->prefix ) . '</span>') : (sprintf( '<span>' . __( '<a target="_blank" href="https://meowapps.com">Get the <u>Pro Version</u></a>', $this->domain ), $this->prefix ) . '</span>') ),
90 + );
251 91 $links = array_merge( $new_links, $links );
252 92 }
253 93 return $links;
254 94 }
255 95
256 - /**
257 - * Output the network license registration modal.
258 - * Called via admin_footer hook in network admin.
259 - */
260 - public static function output_network_license_modal() {
261 - $rest_url = esc_url( rest_url() );
262 - $nonce = wp_create_nonce( 'wp_rest' );
263 - ?>
264 - <div id="meowapps-network-license-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:100000; align-items:center; justify-content:center;">
265 - <div style="background:#fff; padding:24px; border-radius:8px; max-width:450px; width:90%; box-shadow:0 4px 20px rgba(0,0,0,0.3);">
266 - <h2 style="margin:0 0 8px 0; font-size:18px;">Register License</h2>
267 - <p id="meowapps-license-plugin-name" style="margin:0 0 16px 0; color:#666;"></p>
268 - <input type="text" id="meowapps-license-key-input" placeholder="Enter your license key" style="width:100%; padding:10px; font-size:14px; border:1px solid #8c8f94; border-radius:4px; box-sizing:border-box;" />
269 - <p id="meowapps-license-message" style="margin:12px 0 0 0; padding:10px; border-radius:4px; display:none;"></p>
270 - <div style="margin-top:16px; display:flex; gap:10px; justify-content:flex-end;">
271 - <button type="button" id="meowapps-license-cancel" class="button">Cancel</button>
272 - <button type="button" id="meowapps-license-submit" class="button button-primary">Validate & Register</button>
273 - </div>
274 - </div>
275 - </div>
276 - <script>
277 - (function() {
278 - var modal = document.getElementById('meowapps-network-license-modal');
279 - var input = document.getElementById('meowapps-license-key-input');
280 - var message = document.getElementById('meowapps-license-message');
281 - var pluginName = document.getElementById('meowapps-license-plugin-name');
282 - var submitBtn = document.getElementById('meowapps-license-submit');
283 - var cancelBtn = document.getElementById('meowapps-license-cancel');
284 - var currentPrefix = '';
285 -
286 - function showMessage(text, isError) {
287 - message.textContent = text;
288 - message.style.display = 'block';
289 - message.style.background = isError ? '#fcf0f1' : '#edfaef';
290 - message.style.color = isError ? '#d63638' : '#1e7e34';
291 - message.style.border = '1px solid ' + (isError ? '#d63638' : '#1e7e34');
292 - }
293 -
294 - function hideMessage() {
295 - message.style.display = 'none';
296 - }
297 -
298 - function openModal(prefix, plugin) {
299 - currentPrefix = prefix;
300 - pluginName.textContent = plugin;
301 - input.value = '';
302 - hideMessage();
303 - submitBtn.disabled = false;
304 - submitBtn.textContent = 'Validate & Register';
305 - modal.style.display = 'flex';
306 - input.focus();
307 - }
308 -
309 - function closeModal() {
310 - modal.style.display = 'none';
311 - currentPrefix = '';
312 - }
313 -
314 - // Handle click on "Register License" links
315 - document.addEventListener('click', function(e) {
316 - if (e.target.classList.contains('meowapps-network-license-link')) {
317 - e.preventDefault();
318 - var prefix = e.target.getAttribute('data-prefix');
319 - var plugin = e.target.getAttribute('data-plugin');
320 - openModal(prefix, plugin);
321 - }
322 - });
323 -
324 - // Close modal on cancel or clicking outside
325 - cancelBtn.addEventListener('click', closeModal);
326 - modal.addEventListener('click', function(e) {
327 - if (e.target === modal) closeModal();
328 - });
329 -
330 - // Handle escape key
331 - document.addEventListener('keydown', function(e) {
332 - if (e.key === 'Escape' && modal.style.display === 'flex') {
333 - closeModal();
334 - }
335 - });
336 -
337 - // Handle enter key in input
338 - input.addEventListener('keydown', function(e) {
339 - if (e.key === 'Enter') {
340 - submitBtn.click();
341 - }
342 - });
343 -
344 - // Submit license
345 - submitBtn.addEventListener('click', function() {
346 - var licenseKey = input.value.trim();
347 - if (!licenseKey) {
348 - showMessage('Please enter a license key.', true);
349 - return;
350 - }
351 -
352 - submitBtn.disabled = true;
353 - submitBtn.textContent = 'Validating...';
354 - hideMessage();
355 -
356 - var restUrl = '<?php echo $rest_url; ?>meow-licenser/' + currentPrefix + '/v1/set_license/';
357 -
358 - fetch(restUrl, {
359 - method: 'POST',
360 - headers: {
361 - 'Content-Type': 'application/json',
362 - 'X-WP-Nonce': '<?php echo $nonce; ?>'
363 - },
364 - body: JSON.stringify({ serialKey: licenseKey })
365 - })
366 - .then(function(response) { return response.json(); })
367 - .then(function(data) {
368 - if (data.success && data.data && !data.data.issue) {
369 - showMessage('License registered successfully! Reloading...', false);
370 - setTimeout(function() { location.reload(); }, 1500);
371 - } else {
372 - var errorMsg = 'License validation failed.';
373 - if (data.data && data.data.issue) {
374 - errorMsg = 'License issue: ' + data.data.issue;
375 - }
376 - showMessage(errorMsg, true);
377 - submitBtn.disabled = false;
378 - submitBtn.textContent = 'Validate & Register';
379 - }
380 - })
381 - .catch(function(error) {
382 - showMessage('Error: ' + error.message, true);
383 - submitBtn.disabled = false;
384 - submitBtn.textContent = 'Validate & Register';
385 - });
386 - });
387 - })();
388 - </script>
389 - <?php
390 - }
391 -
392 - public function request_verify_ssl() {
96 + function request_verify_ssl() {
393 97 return get_option( 'force_sslverify', false );
394 98 }
395 99
396 - public function nice_name_from_file( $file ) {
100 + function nice_name_from_file( $file ) {
397 101 $info = pathinfo( $file );
398 102 if ( !empty( $info ) ) {
399 103 if ( $info['filename'] == 'wplr-sync' ) {
400 - return 'WP/LR Sync';
104 + return "WP/LR Sync";
401 105 }
402 106 $info['filename'] = str_replace( '-', ' ', $info['filename'] );
403 107 $file = ucwords( $info['filename'] );
404 108 }
@@ -404,9 +108,9 @@
404 108 }
405 109 return $file;
406 110 }
407 111
408 - public function admin_notices_licensed_free() {
112 + function admin_notices_licensed_free() {
409 113 if ( isset( $_POST[$this->prefix . '_reset_sub'] ) ) {
410 114 delete_option( $this->prefix . '_pro_serial' );
411 115 delete_option( $this->prefix . '_license' );
412 116 return;
@@ -413,26 +117,25 @@
413 117 }
414 118 $html = '<div class="notice notice-error">';
415 119 $html .= sprintf(
416 120 __( '<p>It looks like you are using the free version of the plugin (<b>%s</b>) but a license for the Pro version was also found. The Pro version might have been replaced by the Free version during an update (might be caused by a temporarily issue). If it is the case, <b>please download it again</b> from the <a target="_blank" href="https://meowapps.com">Meow Store</a>. If you wish to continue using the free version and clear this message, click on this button.', $this->domain ),
417 - $this->nice_name_from_file( $this->mainfile )
418 - );
419 - $html .= '<p>
420 - <form method="post" action="">
421 - <input type="hidden" name="' . $this->prefix . '_reset_sub" value="true">
422 - <input type="submit" name="submit" id="submit" class="button" value="'
423 - . __( 'Remove the license', $this->domain ) . '">
424 - </form>
425 - </p>';
121 + $this->nice_name_from_file( $this->mainfile ) );
122 + $html .= '<p>
123 + <form method="post" action="">
124 + <input type="hidden" name="' . $this->prefix . '_reset_sub" value="true">
125 + <input type="submit" name="submit" id="submit" class="button" value="'
126 + . __( 'Remove the license', $this->domain ) . '">
127 + </form>
128 + </p>';
426 129 $html .= '</div>';
427 130 wp_kses_post( $html );
428 131 }
429 132
430 - public function admin_menu_start() {
133 + function admin_menu_start() {
431 134 // Hide the admin if user doesn't like Meow much
432 135 if ( get_option( 'meowapps_hide_meowapps', false ) ) {
433 136 register_setting( 'general', 'meowapps_hide_meowapps' );
434 - add_settings_field( 'meowapps_hide_ads', 'Meow Apps Menu', [ $this, 'meowapps_hide_dashboard_callback' ], 'general' );
137 + add_settings_field( 'meowapps_hide_ads', 'Meow Apps Menu', array( $this, 'meowapps_hide_dashboard_callback' ), 'general' );
435 138 return;
436 139 }
437 140
438 141 // Create standard menu if it does not already exist
@@ -437,50 +140,38 @@
437 140
438 141 // Create standard menu if it does not already exist
439 142 global $submenu;
440 143 if ( !isset( $submenu[ 'meowapps-main-menu' ] ) ) {
441 - add_menu_page(
442 - 'Meow Apps',
443 - '<img alt="Meow Apps" style="width: 21px; margin-left: -28px; position: absolute; margin-top: 2px;" src="' . MeowKit_MWCODE_Admin::$logo . '" />Meow Apps',
444 - 'manage_options',
445 - 'meowapps-main-menu',
446 - [ $this, 'admin_meow_apps' ],
447 - '',
448 - 82
449 - );
450 - add_submenu_page(
451 - 'meowapps-main-menu',
452 - __( 'Dashboard', $this->domain ),
453 - __( 'Dashboard', $this->domain ),
454 - 'manage_options',
455 - 'meowapps-main-menu',
456 - [ $this, 'admin_meow_apps' ]
457 - );
144 + add_menu_page( 'Meow Apps', '<img alt="Meow Apps" style="width: 22px; margin-left: -30px; position: absolute; margin-top: -0px;" src="' . MeowCommon_Admin::$logo . '" />Meow Apps', 'manage_options', 'meowapps-main-menu',
145 + array( $this, 'admin_meow_apps' ), '', 82 );
146 + add_submenu_page( 'meowapps-main-menu', __( 'Dashboard', $this->domain ),
147 + __( 'Dashboard', $this->domain ), 'manage_options',
148 + 'meowapps-main-menu', array( $this, 'admin_meow_apps' ) );
458 149 }
459 150
460 151 // Add CSS to hide the default icon
461 - add_action( 'admin_head', function () {
152 + add_action( 'admin_head', function() {
462 153 echo '<style>
463 - #toplevel_page_meowapps-main-menu .wp-menu-image {
464 - display: none;
465 - }
466 - </style>';
467 - } );
154 + #toplevel_page_meowapps-main-menu .wp-menu-image {
155 + display: none;
156 + }
157 + </style>';
158 + });
468 159 }
469 160
470 - public function meowapps_hide_dashboard_callback() {
161 + function meowapps_hide_dashboard_callback() {
471 162 $html = '<input type="checkbox" id="meowapps_hide_meowapps" name="meowapps_hide_meowapps" value="1" ' .
472 - checked( 1, get_option( 'meowapps_hide_meowapps' ), false ) . '/>';
163 + checked( 1, get_option( 'meowapps_hide_meowapps' ), false ) . '/>';
473 164 $html .= __( '<label>Hide <b>Meow Apps</b> Menu</label><br /><small>Hide Meow Apps menu and all its components, for a cleaner admin. This option will be reset if a new Meow Apps plugin is installed.<br /><b>Once activated, an option will be added in your General settings to display it again.</b></small>', $this->domain );
474 - echo MeowKit_MWCODE_Helpers::wp_kses( $html );
165 + echo MeowCommon_Helpers::wp_kses( $html );
475 166 }
476 167
477 - public function is_registered() {
168 + function is_registered() {
478 169 $is_registered = apply_filters( $this->prefix . '_meowapps_is_registered', false, $this->prefix );
479 170 return $is_registered;
480 171 }
481 172
482 - public function get_phpinfo() {
173 + function get_phpinfo() {
483 174 if ( !$this->is_user_admin() || !function_exists( 'phpinfo' ) ) {
484 175 return;
485 176 }
486 177 ob_start();
@@ -488,215 +179,28 @@
488 179 phpinfo( INFO_GENERAL | INFO_CONFIGURATION | INFO_MODULES );
489 180 // phpcs:enable
490 181 $html = ob_get_contents();
491 182 ob_end_clean();
492 - $html = preg_replace( '%^.*<body>(.*)</body>.*$%ms', '$1', $html );
183 + $html = preg_replace( '%^.*<body>(.*)</body>.*$%ms','$1', $html );
493 184 return $html;
494 185 }
495 -
496 - public function admin_meow_apps() {
186 +
187 + function admin_meow_apps() {
497 188 $html = "<div id='meow-common-dashboard'></div>";
498 189 $html .= "<div style='height: 0; width: 0; overflow: hidden;' id='meow-common-phpinfo'>";
499 - $html .= $this->get_phpinfo();
500 - $html .= '</div>';
501 - $html = preg_replace( "/<img[^>]+\>/i", '', $html );
190 + $html .= $this->get_phpinfo();
191 + $html .= "</div>";
192 + $html = preg_replace("/<img[^>]+\>/i", "", $html);
502 193 echo wp_kses_post( $html );
503 194 }
504 195
505 - public function admin_footer_text( $current ) {
196 + function admin_footer_text( $current ) {
506 197 return sprintf(
507 198 // translators: %1$s is the version of the interface; %2$s is a file path.
508 199 __( 'Thanks for using <a href="https://meowapps.com">Meow Apps</a>! This is the Meow Admin %1$s <br /><i>Loaded from %2$s </i>', $this->domain ),
509 - MeowKit_MWCODE_Admin::$version,
200 + MeowCommon_Admin::$version,
510 201 __FILE__
511 202 );
512 203 }
513 -
514 - /**
515 - * Renders a promo banner on WordPress 7's Connectors page when AI Engine
516 - * isn't installed. Kept self-contained so the common library stays simple:
517 - * no new file, no REST endpoint, dismissal persists in localStorage.
518 - */
519 - public function maybe_render_wpai_promo() {
520 - // WordPress 7+ only.
521 - if ( ! class_exists( 'WP_Connector_Registry' ) ) {
522 - return;
523 - }
524 - // If AI Engine is installed, its own Connectors banner takes over.
525 - if ( class_exists( 'Meow_MWAI_Core' ) ) {
526 - return;
527 - }
528 - // Another Meow Apps plugin's common copy may have rendered already.
529 - if ( defined( 'MEOWAPPS_WPAI_PROMO_RENDERED' ) ) {
530 - return;
531 - }
532 - // Gate on the Connectors screen. The hook suffix differs between the
533 - // direct file (`options-connectors.php`) and the menu-page variant.
534 - $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
535 - $id = $screen ? $screen->id : ( isset( $GLOBALS['hook_suffix'] ) ? $GLOBALS['hook_suffix'] : '' );
536 - $targets = array( 'options-connectors', 'options-connectors.php', 'settings_page_options-connectors-wp-admin' );
537 - if ( ! in_array( $id, $targets, true ) ) {
538 - return;
539 - }
540 - define( 'MEOWAPPS_WPAI_PROMO_RENDERED', true );
541 -
542 - // Install button → WordPress's own plugin-install search page, pre-
543 - // filtered for AI Engine. One click from there to install. This is the
544 - // most reliable path: no custom nonces, native progress UI, native
545 - // filesystem credential prompt if needed.
546 - $can_install = current_user_can( 'install_plugins' );
547 - $install_url = $can_install
548 - ? self_admin_url( 'plugin-install.php?tab=search&type=term&s=AI+Engine' )
549 - : 'https://wordpress.org/plugins/ai-engine/';
550 - $wporg_url = 'https://wordpress.org/plugins/ai-engine/';
551 - $learn_url = 'https://meowapps.com/wordpress-7-ai-engine-gateway/';
552 -
553 - // Title is split so "AI Engine" can carry an anchor to wp.org. Keeping
554 - // the pieces as data (not one HTML string) avoids escaping surprises and
555 - // keeps the translation unit stable.
556 - $payload = array(
557 - 'titleBefore' => __( 'Highly recommended: Let ', 'meowapps' ),
558 - 'titleLink' => __( 'AI Engine', 'meowapps' ),
559 - 'titleAfter' => __( ' handle your connections.', 'meowapps' ),
560 - 'sub' => __( 'One plugin for every provider. Keep your AI setup clean and consistent: monitor your API costs in one place, log every single request, and avoid the mess of juggling separate plugins for each AI model.', 'meowapps' ),
561 - 'install' => __( 'Install AI Engine', 'meowapps' ),
562 - 'learn' => __( 'Learn more', 'meowapps' ),
563 - 'dismiss' => __( 'Dismiss', 'meowapps' ),
564 - 'installUrl' => $install_url,
565 - 'wporgUrl' => $wporg_url,
566 - 'learnUrl' => $learn_url,
567 - );
568 - ?>
569 - <style>
570 - .meowapps-wpai-promo {
571 - margin: 0 0 16px; padding: 14px 18px;
572 - display: flex; align-items: flex-start; gap: 14px;
573 - background: #f0f4ff; border: 1px solid #d6deff; border-radius: 4px;
574 - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
575 - font-size: 13px; line-height: 1.45; color: #1e1e1e;
576 - }
577 - .meowapps-wpai-promo-icon {
578 - width: 32px; height: 32px; border-radius: 50%;
579 - background: #2f5fff; color: #fff;
580 - display: flex; align-items: center; justify-content: center;
581 - flex-shrink: 0;
582 - margin-top: 1px;
583 - }
584 - .meowapps-wpai-promo-icon svg { width: 18px; height: 18px; display: block; }
585 - .meowapps-wpai-promo-body {
586 - flex: 1; min-width: 0;
587 - display: flex; flex-direction: column; gap: 10px;
588 - }
589 - .meowapps-wpai-promo-text strong { font-weight: 700; display: block; margin-bottom: 3px; }
590 - .meowapps-wpai-promo-text span { color: #50575e; }
591 - .meowapps-wpai-promo-titlelink {
592 - color: #2f5fff; text-decoration: none; border-bottom: 1px dashed #2f5fff;
593 - }
594 - .meowapps-wpai-promo-titlelink:hover { color: #2448cc; border-bottom-color: #2448cc; }
595 - .meowapps-wpai-promo-actions {
596 - display: flex; gap: 8px; flex-wrap: wrap; align-items: center;
597 - }
598 - .meowapps-wpai-promo-btn {
599 - appearance: none; border: 1px solid transparent; border-radius: 4px;
600 - padding: 7px 16px; font: inherit; font-size: 12.5px; font-weight: 600;
601 - cursor: pointer; text-decoration: none;
602 - transition: background 0.12s ease, border-color 0.12s ease;
603 - }
604 - .meowapps-wpai-promo-btn-primary { background: #2f5fff; color: #fff; }
605 - .meowapps-wpai-promo-btn-primary:hover { background: #2448cc; color: #fff; }
606 - .meowapps-wpai-promo-btn-secondary { background: #7c3aed; color: #fff; }
607 - .meowapps-wpai-promo-btn-secondary:hover { background: #6527c9; color: #fff; }
608 - .meowapps-wpai-promo-btn-dismiss {
609 - margin-left: auto;
610 - background: transparent; color: #6b7280;
611 - font-weight: 500; padding: 7px 10px;
612 - }
613 - .meowapps-wpai-promo-btn-dismiss:hover { color: #1e1e1e; background: rgba(0,0,0,0.04); }
614 - </style>
615 - <script>
616 - (function () {
617 - var D = <?php echo wp_json_encode( $payload ); ?>;
618 - try { if (localStorage.getItem('meowapps-wpai-promo-dismissed') === '1') return; } catch (e) {}
619 -
620 - function build() {
621 - var host = document.createElement('div');
622 - host.className = 'meowapps-wpai-promo';
623 - host.setAttribute('role', 'status');
624 - var iconSvg = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>';
625 - host.innerHTML = [
626 - '<span class="meowapps-wpai-promo-icon">', iconSvg, '</span>',
627 - '<div class="meowapps-wpai-promo-body">',
628 - '<div class="meowapps-wpai-promo-text">',
629 - '<strong></strong> <span class="meowapps-wpai-promo-sub"></span>',
630 - '</div>',
631 - '<div class="meowapps-wpai-promo-actions"></div>',
632 - '</div>'
633 - ].join('');
634 - // Title carries an anchor on "AI Engine" → wp.org plugin page.
635 - var strong = host.querySelector('strong');
636 - strong.appendChild(document.createTextNode(D.titleBefore));
637 - var tLink = document.createElement('a');
638 - tLink.href = D.wporgUrl;
639 - tLink.target = '_blank';
640 - tLink.rel = 'noopener noreferrer';
641 - tLink.className = 'meowapps-wpai-promo-titlelink';
642 - tLink.textContent = D.titleLink;
643 - strong.appendChild(tLink);
644 - strong.appendChild(document.createTextNode(D.titleAfter));
645 - host.querySelector('.meowapps-wpai-promo-sub').textContent = D.sub;
646 - var actions = host.querySelector('.meowapps-wpai-promo-actions');
647 -
648 - function link(label, cls, href, newTab) {
649 - var a = document.createElement('a');
650 - a.className = 'meowapps-wpai-promo-btn ' + cls;
651 - a.textContent = label;
652 - a.href = href;
653 - if (newTab) { a.target = '_blank'; a.rel = 'noopener noreferrer'; }
654 - actions.appendChild(a);
655 - return a;
656 - }
657 -
658 - // Install → WordPress's plugin-install search page, pre-filtered.
659 - // User lands on a familiar screen with AI Engine as the top hit and
660 - // can install with the native WordPress UX.
661 - link(D.install, 'meowapps-wpai-promo-btn-primary', D.installUrl, false);
662 - link(D.learn, 'meowapps-wpai-promo-btn-secondary', D.learnUrl, true);
663 -
664 - var d = document.createElement('button');
665 - d.type = 'button';
666 - d.className = 'meowapps-wpai-promo-btn meowapps-wpai-promo-btn-dismiss';
667 - d.textContent = D.dismiss;
668 - d.addEventListener('click', function () {
669 - try { localStorage.setItem('meowapps-wpai-promo-dismissed', '1'); } catch (e) {}
670 - if (host.parentNode) host.parentNode.removeChild(host);
671 - });
672 - actions.appendChild(d);
673 - return host;
674 - }
675 -
676 - function ensure() {
677 - if (document.querySelector('.meowapps-wpai-promo')) return;
678 - var page = document.querySelector('.connectors-page');
679 - if (page) { page.insertBefore(build(), page.firstChild); return; }
680 - var header = document.querySelector('.boot-layout__stage header');
681 - if (header && header.parentNode) {
682 - header.parentNode.insertBefore(build(), header.nextSibling);
683 - }
684 - }
685 -
686 - var tries = 0;
687 - var iv = setInterval(function () {
688 - ensure();
689 - if (document.querySelector('.meowapps-wpai-promo') || ++tries > 40) clearInterval(iv);
690 - }, 120);
691 -
692 - var app = document.getElementById('options-connectors-wp-admin-app')
693 - || document.getElementById('options-connectors-app');
694 - if (app && 'MutationObserver' in window) {
695 - new MutationObserver(ensure).observe(app, { childList: true, subtree: true });
696 - }
697 - })();
698 - </script>
699 - <?php
700 - }
701 204 }
702 205 }
206 +?>