PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 3.3.9
AI Engine – The Chatbot, AI Framework & MCP for WordPress v3.3.9
3.5.7 3.5.6 3.5.5 3.5.4 3.5.3 3.5.2 3.5.1 3.5.0 3.4.9 3.4.8 3.4.7 0.2.1 1.6.91 0.2.2 1.6.92 0.2.3 1.6.93 0.2.4 1.6.94 0.2.5 1.6.95 0.2.6 1.6.96 0.2.7 1.6.97 0.2.8 1.6.98 0.2.9 1.6.99 0.3.0 1.7.0 0.3.1 1.7.1 0.3.2 1.7.2 0.3.3 1.7.3 0.3.4 1.7.4 0.3.5 1.7.5 0.3.6 1.7.6 0.4.0 1.7.7 0.4.1 1.7.8 0.4.2 1.7.9 0.4.3 1.8.0 0.4.4 1.8.1 0.4.5 1.8.2 0.4.6 1.8.3 0.4.7 1.8.4 0.4.8 1.8.5 0.4.9 1.8.6 0.5.0 1.8.7 0.5.1 1.8.8 0.5.2 1.8.9 0.5.3 1.9.0 0.5.4 1.9.1 0.5.5 1.9.2 0.5.6 1.9.3 0.5.7 1.9.4 0.5.8 1.9.5 0.5.9 1.9.6 0.6.0 1.9.7 0.6.1 1.9.8 0.6.2 1.9.81 0.6.3 1.9.82 0.6.4 1.9.83 0.6.5 1.9.84 0.6.6 1.9.85 0.6.7 1.9.86 0.6.8 1.9.87 0.6.9 1.9.88 0.7.0 1.9.89 0.7.1 1.9.90 0.7.2 1.9.91 0.7.3 1.9.92 0.7.4 1.9.93 0.7.5 1.9.94 0.7.6 1.9.95 0.7.7 1.9.96 0.7.8 1.9.97 0.7.9 1.9.98 0.8.0 1.9.99 0.8.1 2.0.0 0.8.2 2.0.1 0.8.3 2.0.2 0.8.4 2.0.3 0.8.5 2.0.4 0.8.6 2.0.5 0.8.7 2.0.6 0.8.8 2.0.7 0.8.9 2.0.8 0.9.0 2.0.9 0.9.2 2.1.0 0.9.3 2.1.1 0.9.4 2.1.2 0.9.5 2.1.3 0.9.6 2.1.4 0.9.7 2.1.5 0.9.8 2.1.6 0.9.81 2.1.7 0.9.82 2.1.8 0.9.83 2.1.9 0.9.84 2.2.0 0.9.85 2.2.1 0.9.86 2.2.2 0.9.87 2.2.3 0.9.88 2.2.4 0.9.89 2.2.5 0.9.9 2.2.51 0.9.91 2.2.52 0.9.92 2.2.53 0.9.93 2.2.54 0.9.94 2.2.56 0.9.95 2.2.57 0.9.96 2.2.6 0.9.97 2.2.60 0.9.98 2.2.61 0.9.99 2.2.62 1.0.0 2.2.63 1.0.01 2.2.70 1.0.1 2.2.80 1.0.2 2.2.81 1.0.3 2.2.90 1.0.4 2.2.91 1.0.5 2.2.92 1.0.6 2.2.93 1.0.7 2.2.94 1.0.8 2.2.95 1.0.9 2.3.0 1.1.0 2.3.1 1.1.1 2.3.2 1.1.2 2.3.3 1.1.3 2.3.4 1.1.4 2.3.5 1.1.5 2.3.6 1.1.6 2.3.7 1.1.7 2.3.8 1.1.8 2.3.9 1.1.9 2.4.0 1.2.0 2.4.1 1.2.1 2.4.2 1.2.2 2.4.3 1.2.21 2.4.4 1.2.3 2.4.5 1.2.30 2.4.6 1.3.0 2.4.7 1.3.1 2.4.8 1.3.2 2.4.9 1.3.3 2.5.0 1.3.31 2.5.1 1.3.32 2.5.2 1.3.33 2.5.3 1.3.34 2.5.4 1.3.35 2.5.5 1.3.36 2.5.6 1.3.37 2.5.7 1.3.38 2.5.8 1.3.39 2.5.9 1.3.40 2.6.0 1.3.41 2.6.1 1.3.42 2.6.2 1.3.43 2.6.3 1.3.44 2.6.5 1.3.45 2.6.6 1.3.46 2.6.7 1.3.47 2.6.8 1.3.48 2.6.9 1.3.49 2.7.0 1.3.50 2.7.1 1.3.51 2.7.2 1.3.52 2.7.3 1.3.53 2.7.4 1.3.54 2.7.5 1.3.56 2.7.6 1.3.57 2.7.7 1.3.58 2.7.8 1.3.59 2.7.9 1.3.60 2.8.0 1.3.61 2.8.1 1.3.62 2.8.2 1.3.63 2.8.3 1.3.64 2.8.4 1.3.65 2.8.5 1.3.66 2.8.6 1.3.67 2.8.7 1.3.68 2.8.8 1.3.69 2.8.9 1.3.70 2.9.0 1.3.71 2.9.1 1.3.72 2.9.2 1.3.73 2.9.3 1.3.74 2.9.4 1.3.75 2.9.5 1.3.76 2.9.6 1.3.77 2.9.7 1.3.78 2.9.8 1.3.79 2.9.9 1.3.80 3.0.0 1.3.81 3.0.1 1.3.82 3.0.2 1.3.83 3.0.3 1.3.84 3.0.4 1.3.85 3.0.5 1.3.86 3.0.6 1.3.87 3.0.7 1.3.88 3.0.8 1.3.89 3.0.9 1.3.90 3.1.0 1.3.91 3.1.1 1.3.92 3.1.2 1.3.93 3.1.3 1.3.94 3.1.4 1.3.95 3.1.5 1.3.96 3.1.6 1.3.97 3.1.7 1.3.98 3.1.8 1.3.99 3.1.9 1.4.0 3.2.0 1.4.1 3.2.1 1.4.2 3.2.2 1.4.3 3.2.3 1.4.4 3.2.4 1.4.5 3.2.5 1.4.6 3.2.6 1.4.7 3.2.7 1.4.8 3.2.8 1.4.9 3.2.9 1.5.0 3.3.0 1.5.1 3.3.1 1.5.2 3.3.2 1.5.3 3.3.3 1.5.4 3.3.4 1.5.5 3.3.5 1.5.6 3.3.6 1.5.7 3.3.7 1.5.8 3.3.8 1.5.9 3.3.9 1.6.0 3.4.0 1.6.1 3.4.1 1.6.2 3.4.2 1.6.3 3.4.3 1.6.5 3.4.4 1.6.51 3.4.5 1.6.52 3.4.6 1.6.53 1.6.54 1.6.55 1.6.56 1.6.57 1.6.58 1.6.59 1.6.60 1.6.61 1.6.62 1.6.63 1.6.64 1.6.65 1.6.66 1.6.67 1.6.68 trunk 1.6.69 0.0.1 1.6.70 0.0.2 1.6.71 0.0.3 1.6.72 0.0.4 1.6.73 0.0.5 1.6.74 0.0.6 1.6.75 0.0.7 1.6.76 0.0.8 1.6.77 0.0.9 1.6.78 0.1.0 1.6.79 0.1.1 1.6.81 0.1.2 1.6.82 0.1.3 1.6.83 0.1.4 1.6.84 0.1.5 1.6.85 0.1.6 1.6.86 0.1.7 1.6.87 0.1.8 1.6.88 0.1.9 1.6.89 0.2.0 1.6.90
ai-engine / common / admin.php
ai-engine / common Last commit date
admin.php 4 months ago helpers.php 6 months ago issues.php 7 months ago news.php 7 months ago ratings.php 7 months ago releases.txt 2 years ago rest.php 7 months ago
admin.php
510 lines
1 <?php
2
3 if ( !class_exists( 'MeowKit_MWAI_Admin' ) ) {
4
5 class MeowKit_MWAI_Admin {
6 public static $loaded = false;
7 public static $version = '4.0';
8 public static $admin_version = '4.0';
9 public static $network_license_modal_added = false;
10 public static $network_license_plugins = [];
11
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 public $prefix; // prefix used for actions, filters (mfrh)
30 public $mainfile; // plugin main file (media-file-renamer.php)
31 public $domain; // domain used for translation (media-file-renamer)
32 public $isPro = false;
33
34 // Store constructor params that affect per-instance setup
35 private $disableReview = false;
36
37 public static $logo = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNDYiIHZpZXdCb3g9IjAgMCA2NCA0NiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8ZyBjbGlwUGF0aD0idXJsKCNjbGlwMF8zMTBfMjI5KSI+CiAgICA8cGF0aCBkPSJNNjQgMzAuNjQwOEM2NCAyNy43OTg1IDYwLjA4MTYgMjUuODMwMyA1NS44Mjk4IDI1LjgzMDNDNTQuODU5MyAyNS44MzAzIDUzLjkzMTEgMjUuOTMzIDUzLjA3NiAyNi4xMjUzQzQ5Ljg4NjUgMTkuMDc5IDQxLjY1MzkgMTMuMDg1MyAzMi4wMDAyIDEzLjA4NTNDMzAuODMzNyAxMy4wODUzIDI5LjY4ODEgMTMuMTcyNyAyOC41Njk4IDEzLjMzOTJDMjcuMjA2OSAxMC4zMDc2IDIyLjY3NjIgMi40MzQyNiAxMS41OTU0IDAuMDgzMDA2NEMxMS4wNDkxIC0wLjAzMjc0NTYgMTAuNDk0NiAwLjI0MDU3OCAxMC4yNTkgMC43NDY5QzguODU5MTMgMy43NTYwOCA0Ljc0MjQ3IDE0LjQxMTYgMTAuMjQwMyAyNS45OTMxQzkuNTgxNjUgMjUuODg2NCA4Ljg4NzUxIDI1LjgzMDMgOC4xNzAyMiAyNS44MzAzQzMuOTE4MzkgMjUuODMwMyAwIDI3Ljc5ODUgMCAzMC42NDA4QzAgMzMuNDgzIDMuOTE4MzkgMzUuMjI3MiA4LjE3MDIyIDM1LjIyNzJDOC43MTEyNyAzNS4yMjcyIDkuMjM5MjUgMzUuMTk4OCA5Ljc0ODkzIDM1LjE0MzVDOS40MzYwMiAzNS4yNjY0IDkuMTIyNzUgMzUuNDA3NSA4LjgxMTcxIDM1LjU2NzdDNS42OTM4OCAzNy4xNzA3IDMuOTY4OCA0MC4wMzEyIDQuOTU5MDQgNDEuOTU2OEM1Ljk0ODkgNDMuODgyNCA5LjI3OTIgNDQuMTQ0MiAxMi4zOTcgNDIuNTQxMkMxMy4wNDY0IDQyLjIwNzQgMTMuNjM0OCA0MS44MTkgMTQuMTUxNiA0MS4zOTZDMTguMjYyNyA0NC40OTY3IDI0LjcyODMgNDUuOTgwOSAzMS45OTk4IDQ1Ljk4MDlDMzkuMjcxMyA0NS45ODA5IDQ1LjczNyA0NC40OTY3IDQ5Ljg0OCA0MS4zOTZDNTAuMzY0NCA0MS44MTkgNTAuOTUzMyA0Mi4yMDc0IDUxLjYwMjYgNDIuNTQxMkM1NC43MjA0IDQ0LjE0NDIgNTguMDUwMyA0My44ODI0IDU5LjA0MDYgNDEuOTU2OEM2MC4wMzA1IDQwLjAzMTIgNTguMzA1NyAzNy4xNzA3IDU1LjE4NzkgMzUuNTY3N0M1NC44NzYxIDM1LjQwNzUgNTQuNTYyMSAzNS4yNjY3IDU0LjI0ODUgMzUuMTQzNUM1NC43NTg5IDM1LjE5ODggNTUuMjg3NiAzNS4yMjc1IDU1LjgyOTQgMzUuMjI3NUM2MC4wODEyIDM1LjIyNzUgNjMuOTk5NiAzMy40ODM0IDYzLjk5OTYgMzAuNjQxMUw2NCAzMC42NDA4WiIgZmlsbD0id2hpdGUiLz4KICAgIDxwYXRoIGQ9Ik0yMi4yMjkzIDM2Ljc0NDNDMjYuNTkzNSAzNi43NDQzIDMwLjEzMTQgMzMuMjA2NCAzMC4xMzE0IDI4Ljg0MjJDMzAuMTMxNCAyNC40NzggMjYuNTkzNSAyMC45NDAxIDIyLjIyOTMgMjAuOTQwMUMxNy44NjUxIDIwLjk0MDEgMTQuMzI3MSAyNC40NzggMTQuMzI3MSAyOC44NDIyQzE0LjMyNzEgMzMuMjA2NCAxNy44NjUxIDM2Ljc0NDMgMjIuMjI5MyAzNi43NDQzWiIgZmlsbD0iIzAwRTI4RSIvPgogICAgPHBhdGggZD0iTTIyLjI2NTUgMzMuMTM2MUMyMy41MDIyIDMzLjEzNjEgMjQuNTA0NyAzMS4yODA1IDI0LjUwNDcgMjguOTkxNUMyNC41MDQ3IDI2LjcwMjQgMjMuNTAyMiAyNC44NDY4IDIyLjI2NTUgMjQuODQ2OEMyMS4wMjg4IDI0Ljg0NjggMjAuMDI2MiAyNi43MDI0IDIwLjAyNjIgMjguOTkxNUMyMC4wMjYyIDMxLjI4MDUgMjEuMDI4OCAzMy4xMzYxIDIyLjI2NTUgMzMuMTM2MVoiIGZpbGw9IiMzQzZFOEIiLz4KICAgIDxwYXRoIGQ9Ik0zMS45OTk4IDM3LjkxNTZDMzMuNDIzNyAzNy45MTU2IDM0LjU3ODEgMzcuMzQwOSAzNC41NzgxIDM2LjYzMTlDMzQuNTc4MSAzNS45MjI5IDMzLjQyMzcgMzUuMzQ4MSAzMS45OTk4IDM1LjM0ODFDMzAuNTc1OCAzNS4zNDgxIDI5LjQyMTUgMzUuOTIyOSAyOS40MjE1IDM2LjYzMTlDMjkuNDIxNSAzNy4zNDA5IDMwLjU3NTggMzcuOTE1NiAzMS45OTk4IDM3LjkxNTZaIiBmaWxsPSIjRkY5NDkzIi8+CiAgICA8cGF0aCBkPSJNNTQuMjUwMyAzNS4xMDU4QzU0Ljc2IDM1LjE2MTEgNTUuMjg3OSAzNS4xODk0IDU1LjgyOSAzNS4xODk0QzYwLjA4MDggMzUuMTg5NCA2My45OTkyIDMzLjQ0NTMgNjMuOTk5MiAzMC42MDNDNjMuOTk5MiAyNy43NjA4IDYwLjA4MDggMjUuNzkyNiA1NS44MjkgMjUuNzkyNkM1NS4xMTE3IDI1Ljc5MjYgNTQuNDE3NiAyNS44NDkgNTMuNzU4NSAyNS45NTU4QzU5LjI1NjcgMTQuMzc0MiA1NS4xMzk3IDMuNzE4NzIgNTMuNzQwMiAwLjcwOTU0NkM1My41MDQ2IDAuMjAzMjI1IDUyLjk1MDEgLTAuMDcwMDk5MSA1Mi40MDM4IDAuMDQ1NjUyOUM0MS4zMjMgMi4zOTY5MSAzNi43OTIzIDEwLjI3MDcgMzUuNDI5OCAxMy4zMDE1QzM0LjQ1NDEgMTMuMTU2NiAzMy40NTc5IDEzLjA3MTEgMzIuNDQ1MiAxMy4wNTE3QzMxLjI3NDMgMjAuMDMzIDI4Ljk2NTYgNDMuOTM2NSA1NC4zNDM2IDM1LjE0MzlDNTQuMzEyMyAzNS4xMzEyIDU0LjI4MTMgMzUuMTE4MSA1NC4yNDk5IDM1LjEwNThINTQuMjUwM1oiIGZpbGw9IiMyQjlERkYiLz4KICAgIDxwYXRoIGQ9Ik00MS43MzQyIDMzLjEzNjFDNDIuOTcwOSAzMy4xMzYxIDQzLjk3MzUgMzEuMjgwNSA0My45NzM1IDI4Ljk5MTVDNDMuOTczNSAyNi43MDI0IDQyLjk3MDkgMjQuODQ2OCA0MS43MzQyIDI0Ljg0NjhDNDAuNDk3NSAyNC44NDY4IDM5LjQ5NSAyNi43MDI0IDM5LjQ5NSAyOC45OTE1QzM5LjQ5NSAzMS4yODA1IDQwLjQ5NzUgMzMuMTM2MSA0MS43MzQyIDMzLjEzNjFaIiBmaWxsPSIjM0M2RThCIi8+CiAgPC9nPgogIDxkZWZzPgogICAgPGNsaXBQYXRoIGlkPSJjbGlwMF8zMTBfMjI5Ij4KICAgICAgPHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjQ1Ljk2MTciIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIDAuMDE5MTY1KSIvPgogICAgPC9jbGlwUGF0aD4KICA8L2RlZnM+Cjwvc3ZnPgo=';
38
39 public function __construct( $prefix, $mainfile, $domain, $isPro = false, $disableReview = false, $freeOnly = false ) {
40
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;
47
48 if ( is_admin() ) {
49
50 // Skip AJAX and REST requests to avoid unnecessary processing
51 if ( MeowKit_MWAI_Helpers::is_asynchronous_request() ) {
52 return;
53 }
54
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();
67 }
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 }
74 }
75
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 }
80
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;
93
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_MWAI_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_MWAI_Admin::$loaded ) {
149 // Check for potential issues with WordPress install, other plugins, etc.
150 new MeowKit_MWAI_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 );
159 }
160
161 MeowKit_MWAI_Admin::$loaded = true;
162 }
163
164 // PER-INSTANCE SETUP: Run for each plugin that uses this library
165 // Only admins get ratings prompts and news
166 if ( $this->is_user_admin() ) {
167 if ( !$this->disableReview ) {
168 new MeowKit_MWAI_Ratings( $this->prefix, $this->mainfile, $this->domain );
169 }
170 new MeowKit_MWAI_News( $this->domain );
171 }
172 }
173
174 /**
175 * Check if current user is a site administrator.
176 *
177 * This method is only called from run_admin_setup(), which guarantees
178 * that pluggable functions exist. No error logging needed - if the
179 * functions don't exist, we simply return false as a defensive fallback.
180 *
181 * @return bool True if user can manage options, false otherwise
182 */
183 public function is_user_admin() {
184 // Defensive check (should never fail if called from run_admin_setup)
185 if ( !function_exists( 'current_user_can' ) || !function_exists( 'wp_get_current_user' ) ) {
186 return false;
187 }
188 return current_user_can( 'manage_options' );
189 }
190
191 public function custom_plugin_row_meta( $links, $file ) {
192 $path = pathinfo( $file );
193 $pathName = basename( $path['dirname'] );
194 $thisPath = pathinfo( $this->mainfile );
195 $thisPathName = basename( $thisPath['dirname'] );
196 $isActive = is_plugin_active( $file );
197 if ( !$isActive ) {
198 return $links;
199 }
200 $isIssue = $this->isPro && !$this->is_registered();
201 if ( strpos( $pathName, $thisPathName ) !== false ) {
202 // In network admin, handle differently (no settings page available)
203 if ( is_network_admin() ) {
204 if ( $this->isPro && !$this->is_registered() ) {
205 // Show "Register License" link for unregistered Pro plugins
206 $new_links = [
207 'license' => sprintf(
208 '<a href="#" class="meowapps-network-license-link" data-prefix="%s" data-plugin="%s" style="color: #d63638;">%s</a>',
209 esc_attr( $this->prefix ),
210 esc_attr( $this->nice_name_from_file( $this->mainfile ) ),
211 __( 'Register License', $this->domain )
212 ),
213 ];
214 // Track this plugin for the modal
215 self::$network_license_plugins[ $this->prefix ] = $this->nice_name_from_file( $this->mainfile );
216 // Add modal output hook (only once)
217 if ( !self::$network_license_modal_added ) {
218 add_action( 'admin_footer', [ __CLASS__, 'output_network_license_modal' ] );
219 self::$network_license_modal_added = true;
220 }
221 }
222 elseif ( $this->isPro && $this->is_registered() ) {
223 // Pro plugin is registered
224 $new_links = [
225 'license' => '<span style="color: #a75bd6;">' . __( 'Pro Version', $this->domain ) . '</span>',
226 ];
227 }
228 else {
229 // Free plugin
230 $new_links = [
231 'license' => sprintf( '<span>' . __( '<a target="_blank" href="https://meowapps.com">Get the <u>Pro Version</u></a>', $this->domain ), $this->prefix ) . '</span>',
232 ];
233 }
234 }
235 else {
236 // Regular admin - show settings and license status
237 $new_links = [
238 'settings' =>
239 sprintf( __( '<a href="admin.php?page=%s_settings">Settings</a>', $this->domain ), $this->prefix ),
240 'license' =>
241 $this->is_registered() ?
242 ( '<span style="color: #a75bd6;">' . __( 'Pro Version', $this->domain ) . '</span>' ) :
243 ( $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>' ) ),
244 ];
245 }
246 $links = array_merge( $new_links, $links );
247 }
248 return $links;
249 }
250
251 /**
252 * Output the network license registration modal.
253 * Called via admin_footer hook in network admin.
254 */
255 public static function output_network_license_modal() {
256 $rest_url = esc_url( rest_url() );
257 $nonce = wp_create_nonce( 'wp_rest' );
258 ?>
259 <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;">
260 <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);">
261 <h2 style="margin:0 0 8px 0; font-size:18px;">Register License</h2>
262 <p id="meowapps-license-plugin-name" style="margin:0 0 16px 0; color:#666;"></p>
263 <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;" />
264 <p id="meowapps-license-message" style="margin:12px 0 0 0; padding:10px; border-radius:4px; display:none;"></p>
265 <div style="margin-top:16px; display:flex; gap:10px; justify-content:flex-end;">
266 <button type="button" id="meowapps-license-cancel" class="button">Cancel</button>
267 <button type="button" id="meowapps-license-submit" class="button button-primary">Validate & Register</button>
268 </div>
269 </div>
270 </div>
271 <script>
272 (function() {
273 var modal = document.getElementById('meowapps-network-license-modal');
274 var input = document.getElementById('meowapps-license-key-input');
275 var message = document.getElementById('meowapps-license-message');
276 var pluginName = document.getElementById('meowapps-license-plugin-name');
277 var submitBtn = document.getElementById('meowapps-license-submit');
278 var cancelBtn = document.getElementById('meowapps-license-cancel');
279 var currentPrefix = '';
280
281 function showMessage(text, isError) {
282 message.textContent = text;
283 message.style.display = 'block';
284 message.style.background = isError ? '#fcf0f1' : '#edfaef';
285 message.style.color = isError ? '#d63638' : '#1e7e34';
286 message.style.border = '1px solid ' + (isError ? '#d63638' : '#1e7e34');
287 }
288
289 function hideMessage() {
290 message.style.display = 'none';
291 }
292
293 function openModal(prefix, plugin) {
294 currentPrefix = prefix;
295 pluginName.textContent = plugin;
296 input.value = '';
297 hideMessage();
298 submitBtn.disabled = false;
299 submitBtn.textContent = 'Validate & Register';
300 modal.style.display = 'flex';
301 input.focus();
302 }
303
304 function closeModal() {
305 modal.style.display = 'none';
306 currentPrefix = '';
307 }
308
309 // Handle click on "Register License" links
310 document.addEventListener('click', function(e) {
311 if (e.target.classList.contains('meowapps-network-license-link')) {
312 e.preventDefault();
313 var prefix = e.target.getAttribute('data-prefix');
314 var plugin = e.target.getAttribute('data-plugin');
315 openModal(prefix, plugin);
316 }
317 });
318
319 // Close modal on cancel or clicking outside
320 cancelBtn.addEventListener('click', closeModal);
321 modal.addEventListener('click', function(e) {
322 if (e.target === modal) closeModal();
323 });
324
325 // Handle escape key
326 document.addEventListener('keydown', function(e) {
327 if (e.key === 'Escape' && modal.style.display === 'flex') {
328 closeModal();
329 }
330 });
331
332 // Handle enter key in input
333 input.addEventListener('keydown', function(e) {
334 if (e.key === 'Enter') {
335 submitBtn.click();
336 }
337 });
338
339 // Submit license
340 submitBtn.addEventListener('click', function() {
341 var licenseKey = input.value.trim();
342 if (!licenseKey) {
343 showMessage('Please enter a license key.', true);
344 return;
345 }
346
347 submitBtn.disabled = true;
348 submitBtn.textContent = 'Validating...';
349 hideMessage();
350
351 var restUrl = '<?php echo $rest_url; ?>meow-licenser/' + currentPrefix + '/v1/set_license/';
352
353 fetch(restUrl, {
354 method: 'POST',
355 headers: {
356 'Content-Type': 'application/json',
357 'X-WP-Nonce': '<?php echo $nonce; ?>'
358 },
359 body: JSON.stringify({ serialKey: licenseKey })
360 })
361 .then(function(response) { return response.json(); })
362 .then(function(data) {
363 if (data.success && data.data && !data.data.issue) {
364 showMessage('License registered successfully! Reloading...', false);
365 setTimeout(function() { location.reload(); }, 1500);
366 } else {
367 var errorMsg = 'License validation failed.';
368 if (data.data && data.data.issue) {
369 errorMsg = 'License issue: ' + data.data.issue;
370 }
371 showMessage(errorMsg, true);
372 submitBtn.disabled = false;
373 submitBtn.textContent = 'Validate & Register';
374 }
375 })
376 .catch(function(error) {
377 showMessage('Error: ' + error.message, true);
378 submitBtn.disabled = false;
379 submitBtn.textContent = 'Validate & Register';
380 });
381 });
382 })();
383 </script>
384 <?php
385 }
386
387 public function request_verify_ssl() {
388 return get_option( 'force_sslverify', false );
389 }
390
391 public function nice_name_from_file( $file ) {
392 $info = pathinfo( $file );
393 if ( !empty( $info ) ) {
394 if ( $info['filename'] == 'wplr-sync' ) {
395 return 'WP/LR Sync';
396 }
397 $info['filename'] = str_replace( '-', ' ', $info['filename'] );
398 $file = ucwords( $info['filename'] );
399 }
400 return $file;
401 }
402
403 public function admin_notices_licensed_free() {
404 if ( isset( $_POST[$this->prefix . '_reset_sub'] ) ) {
405 delete_option( $this->prefix . '_pro_serial' );
406 delete_option( $this->prefix . '_license' );
407 return;
408 }
409 $html = '<div class="notice notice-error">';
410 $html .= sprintf(
411 __( '<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 ),
412 $this->nice_name_from_file( $this->mainfile )
413 );
414 $html .= '<p>
415 <form method="post" action="">
416 <input type="hidden" name="' . $this->prefix . '_reset_sub" value="true">
417 <input type="submit" name="submit" id="submit" class="button" value="'
418 . __( 'Remove the license', $this->domain ) . '">
419 </form>
420 </p>';
421 $html .= '</div>';
422 wp_kses_post( $html );
423 }
424
425 public function admin_menu_start() {
426 // Hide the admin if user doesn't like Meow much
427 if ( get_option( 'meowapps_hide_meowapps', false ) ) {
428 register_setting( 'general', 'meowapps_hide_meowapps' );
429 add_settings_field( 'meowapps_hide_ads', 'Meow Apps Menu', [ $this, 'meowapps_hide_dashboard_callback' ], 'general' );
430 return;
431 }
432
433 // Create standard menu if it does not already exist
434 global $submenu;
435 if ( !isset( $submenu[ 'meowapps-main-menu' ] ) ) {
436 add_menu_page(
437 'Meow Apps',
438 '<img alt="Meow Apps" style="width: 21px; margin-left: -28px; position: absolute; margin-top: 2px;" src="' . MeowKit_MWAI_Admin::$logo . '" />Meow Apps',
439 'manage_options',
440 'meowapps-main-menu',
441 [ $this, 'admin_meow_apps' ],
442 '',
443 82
444 );
445 add_submenu_page(
446 'meowapps-main-menu',
447 __( 'Dashboard', $this->domain ),
448 __( 'Dashboard', $this->domain ),
449 'manage_options',
450 'meowapps-main-menu',
451 [ $this, 'admin_meow_apps' ]
452 );
453 }
454
455 // Add CSS to hide the default icon
456 add_action( 'admin_head', function () {
457 echo '<style>
458 #toplevel_page_meowapps-main-menu .wp-menu-image {
459 display: none;
460 }
461 </style>';
462 } );
463 }
464
465 public function meowapps_hide_dashboard_callback() {
466 $html = '<input type="checkbox" id="meowapps_hide_meowapps" name="meowapps_hide_meowapps" value="1" ' .
467 checked( 1, get_option( 'meowapps_hide_meowapps' ), false ) . '/>';
468 $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 );
469 echo MeowKit_MWAI_Helpers::wp_kses( $html );
470 }
471
472 public function is_registered() {
473 $is_registered = apply_filters( $this->prefix . '_meowapps_is_registered', false, $this->prefix );
474 return $is_registered;
475 }
476
477 public function get_phpinfo() {
478 if ( !$this->is_user_admin() || !function_exists( 'phpinfo' ) ) {
479 return;
480 }
481 ob_start();
482 // phpcs:disable WordPress.PHP.DevelopmentFunctions
483 phpinfo( INFO_GENERAL | INFO_CONFIGURATION | INFO_MODULES );
484 // phpcs:enable
485 $html = ob_get_contents();
486 ob_end_clean();
487 $html = preg_replace( '%^.*<body>(.*)</body>.*$%ms', '$1', $html );
488 return $html;
489 }
490
491 public function admin_meow_apps() {
492 $html = "<div id='meow-common-dashboard'></div>";
493 $html .= "<div style='height: 0; width: 0; overflow: hidden;' id='meow-common-phpinfo'>";
494 $html .= $this->get_phpinfo();
495 $html .= '</div>';
496 $html = preg_replace( "/<img[^>]+\>/i", '', $html );
497 echo wp_kses_post( $html );
498 }
499
500 public function admin_footer_text( $current ) {
501 return sprintf(
502 // translators: %1$s is the version of the interface; %2$s is a file path.
503 __( '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 ),
504 MeowKit_MWAI_Admin::$version,
505 __FILE__
506 );
507 }
508 }
509 }
510