PluginProbe
Meow Gallery / 4.2.2
Meow Gallery v4.2.2
5.5.6 5.5.5 5.5.4 5.5.3 5.5.2 5.5.1 5.5.0 5.4.9 5.4.8 5.4.7 4.1.5 4.1.6 4.1.7 4.1.8 4.1.9 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 4.2.7 4.2.8 4.2.9 All 158 releases
← All changes | common/admin.php +151 -483 5.4.7 → 4.2.2 View file →
@@ -1,509 +1,177 @@
1 1 <?php
2 2
3 -if ( !class_exists( 'MeowKit_MGL_Admin' ) ) {
3 +if ( !class_exists( 'MeowCommon_Admin' ) ) {
4 4
5 - class MeowKit_MGL_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 = [];
5 + class MeowCommon_Admin {
11 6
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();
7 + public static $loaded = false;
8 + public static $version = "3.6";
9 + public static $admin_version = "3.6";
28 10
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;
11 + public $prefix; // prefix used for actions, filters (mfrh)
12 + public $mainfile; // plugin main file (media-file-renamer.php)
13 + public $domain; // domain used for translation (media-file-renamer)
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,PHN2ZyB2ZXJzaW9uPSIxIiB2aWV3Qm94PSIwIDAgMTY1IDE2NSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8c3R5bGU+CiAgICAuc3Qye2ZpbGw6IzgwNDYyNX0uc3Qze2ZpbGw6I2ZkYTk2MH0KICA8L3N0eWxlPgogIDxwYXRoIGQ9Ik03MiA3YTc2IDc2IDAgMCAxIDg0IDkxQTc1IDc1IDAgMSAxIDcyIDd6IiBmaWxsPSIjNGE2YjhjIi8+CiAgPHBhdGggZD0iTTQ4IDQ4YzIgNSAyIDEwIDUgMTQgNSA4IDEzIDE3IDIyIDIwbDEtMTBjMS0yIDMtMyA1LTNoMTNjMiAwIDQgMSA1IDNsMyA5IDQtMTBjMi0zIDYtMiA5LTJoMTFjMyAyIDMgNSAzIDhsMiAzN2MwIDMtMSA3LTQgOGgtMTJjLTIgMC0zLTItNS00LTEgMS0yIDMtNCAzLTUgMS05IDEtMTMtMS0zIDItNSAyLTkgMnMtOSAxLTEwLTNjLTItNC0xLTggMC0xMi04LTMtMTUtNy0yMi0xMi03LTctMTUtMTQtMjAtMjMtMy00LTUtOC01LTEzIDEtNCAzLTEwIDYtMTMgNC0zIDEyLTIgMTUgMnoiIGZpbGw9IiMxMDEwMTAiLz4KICA8cGF0aCBjbGFzcz0ic3QyIiBkPSJNNDMgNTFsNCAxMS02IDVoLTZjLTMtNS0zLTExIDAtMTYgMi0yIDYtMyA4IDB6Ii8+CiAgPHBhdGggY2xhc3M9InN0MyIgZD0iTTQ3IDYybDMgNmMwIDMgMCA0LTIgNnMtNCAyLTcgMmwtNi05aDZsNi01eiIvPgogIDxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik01MCA2OGw4IDljLTMgMy01IDYtOSA4bC04LTljMyAwIDUgMCA3LTJzMy0zIDItNnoiLz4KICA8cGF0aCBkPSJNODIgNzRoMTJsNSAxOCAzIDExIDgtMjloMTNsMiA0MmgtOGwtMS0yLTEtMzEtMTAgMzItNyAxLTktMzMtMSAyOS0xIDRoLThsMy00MnoiIGZpbGw9IiNmZmYiLz4KICA8cGF0aCBjbGFzcz0ic3QzIiBkPSJNNTggNzdsNSA1Yy0xIDQtMiA4LTcgOGwtNy01YzQtMiA2LTUgOS04eiIvPgogIDxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik02MyA4Mmw5IDUtNiA5LTEwLTZjNSAwIDYtNCA3LTh6Ii8+CiAgPHBhdGggY2xhc3M9InN0MyIgZD0iTTcyIDg3bDMgMS0xIDExLTgtMyA2LTEweiIvPgo8L3N2Zz4K';
36 17
37 - public static $logo = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNDYiIHZpZXdCb3g9IjAgMCA2NCA0NiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8ZyBjbGlwUGF0aD0idXJsKCNjbGlwMF8zMTBfMjI5KSI+CiAgICA8cGF0aCBkPSJNNjQgMzAuNjQwOEM2NCAyNy43OTg1IDYwLjA4MTYgMjUuODMwMyA1NS44Mjk4IDI1LjgzMDNDNTQuODU5MyAyNS44MzAzIDUzLjkzMTEgMjUuOTMzIDUzLjA3NiAyNi4xMjUzQzQ5Ljg4NjUgMTkuMDc5IDQxLjY1MzkgMTMuMDg1MyAzMi4wMDAyIDEzLjA4NTNDMzAuODMzNyAxMy4wODUzIDI5LjY4ODEgMTMuMTcyNyAyOC41Njk4IDEzLjMzOTJDMjcuMjA2OSAxMC4zMDc2IDIyLjY3NjIgMi40MzQyNiAxMS41OTU0IDAuMDgzMDA2NEMxMS4wNDkxIC0wLjAzMjc0NTYgMTAuNDk0NiAwLjI0MDU3OCAxMC4yNTkgMC43NDY5QzguODU5MTMgMy43NTYwOCA0Ljc0MjQ3IDE0LjQxMTYgMTAuMjQwMyAyNS45OTMxQzkuNTgxNjUgMjUuODg2NCA4Ljg4NzUxIDI1LjgzMDMgOC4xNzAyMiAyNS44MzAzQzMuOTE4MzkgMjUuODMwMyAwIDI3Ljc5ODUgMCAzMC42NDA4QzAgMzMuNDgzIDMuOTE4MzkgMzUuMjI3MiA4LjE3MDIyIDM1LjIyNzJDOC43MTEyNyAzNS4yMjcyIDkuMjM5MjUgMzUuMTk4OCA5Ljc0ODkzIDM1LjE0MzVDOS40MzYwMiAzNS4yNjY0IDkuMTIyNzUgMzUuNDA3NSA4LjgxMTcxIDM1LjU2NzdDNS42OTM4OCAzNy4xNzA3IDMuOTY4OCA0MC4wMzEyIDQuOTU5MDQgNDEuOTU2OEM1Ljk0ODkgNDMuODgyNCA5LjI3OTIgNDQuMTQ0MiAxMi4zOTcgNDIuNTQxMkMxMy4wNDY0IDQyLjIwNzQgMTMuNjM0OCA0MS44MTkgMTQuMTUxNiA0MS4zOTZDMTguMjYyNyA0NC40OTY3IDI0LjcyODMgNDUuOTgwOSAzMS45OTk4IDQ1Ljk4MDlDMzkuMjcxMyA0NS45ODA5IDQ1LjczNyA0NC40OTY3IDQ5Ljg0OCA0MS4zOTZDNTAuMzY0NCA0MS44MTkgNTAuOTUzMyA0Mi4yMDc0IDUxLjYwMjYgNDIuNTQxMkM1NC43MjA0IDQ0LjE0NDIgNTguMDUwMyA0My44ODI0IDU5LjA0MDYgNDEuOTU2OEM2MC4wMzA1IDQwLjAzMTIgNTguMzA1NyAzNy4xNzA3IDU1LjE4NzkgMzUuNTY3N0M1NC44NzYxIDM1LjQwNzUgNTQuNTYyMSAzNS4yNjY3IDU0LjI0ODUgMzUuMTQzNUM1NC43NTg5IDM1LjE5ODggNTUuMjg3NiAzNS4yMjc1IDU1LjgyOTQgMzUuMjI3NUM2MC4wODEyIDM1LjIyNzUgNjMuOTk5NiAzMy40ODM0IDYzLjk5OTYgMzAuNjQxMUw2NCAzMC42NDA4WiIgZmlsbD0id2hpdGUiLz4KICAgIDxwYXRoIGQ9Ik0yMi4yMjkzIDM2Ljc0NDNDMjYuNTkzNSAzNi43NDQzIDMwLjEzMTQgMzMuMjA2NCAzMC4xMzE0IDI4Ljg0MjJDMzAuMTMxNCAyNC40NzggMjYuNTkzNSAyMC45NDAxIDIyLjIyOTMgMjAuOTQwMUMxNy44NjUxIDIwLjk0MDEgMTQuMzI3MSAyNC40NzggMTQuMzI3MSAyOC44NDIyQzE0LjMyNzEgMzMuMjA2NCAxNy44NjUxIDM2Ljc0NDMgMjIuMjI5MyAzNi43NDQzWiIgZmlsbD0iIzAwRTI4RSIvPgogICAgPHBhdGggZD0iTTIyLjI2NTUgMzMuMTM2MUMyMy41MDIyIDMzLjEzNjEgMjQuNTA0NyAzMS4yODA1IDI0LjUwNDcgMjguOTkxNUMyNC41MDQ3IDI2LjcwMjQgMjMuNTAyMiAyNC44NDY4IDIyLjI2NTUgMjQuODQ2OEMyMS4wMjg4IDI0Ljg0NjggMjAuMDI2MiAyNi43MDI0IDIwLjAyNjIgMjguOTkxNUMyMC4wMjYyIDMxLjI4MDUgMjEuMDI4OCAzMy4xMzYxIDIyLjI2NTUgMzMuMTM2MVoiIGZpbGw9IiMzQzZFOEIiLz4KICAgIDxwYXRoIGQ9Ik0zMS45OTk4IDM3LjkxNTZDMzMuNDIzNyAzNy45MTU2IDM0LjU3ODEgMzcuMzQwOSAzNC41NzgxIDM2LjYzMTlDMzQuNTc4MSAzNS45MjI5IDMzLjQyMzcgMzUuMzQ4MSAzMS45OTk4IDM1LjM0ODFDMzAuNTc1OCAzNS4zNDgxIDI5LjQyMTUgMzUuOTIyOSAyOS40MjE1IDM2LjYzMTlDMjkuNDIxNSAzNy4zNDA5IDMwLjU3NTggMzcuOTE1NiAzMS45OTk4IDM3LjkxNTZaIiBmaWxsPSIjRkY5NDkzIi8+CiAgICA8cGF0aCBkPSJNNTQuMjUwMyAzNS4xMDU4QzU0Ljc2IDM1LjE2MTEgNTUuMjg3OSAzNS4xODk0IDU1LjgyOSAzNS4xODk0QzYwLjA4MDggMzUuMTg5NCA2My45OTkyIDMzLjQ0NTMgNjMuOTk5MiAzMC42MDNDNjMuOTk5MiAyNy43NjA4IDYwLjA4MDggMjUuNzkyNiA1NS44MjkgMjUuNzkyNkM1NS4xMTE3IDI1Ljc5MjYgNTQuNDE3NiAyNS44NDkgNTMuNzU4NSAyNS45NTU4QzU5LjI1NjcgMTQuMzc0MiA1NS4xMzk3IDMuNzE4NzIgNTMuNzQwMiAwLjcwOTU0NkM1My41MDQ2IDAuMjAzMjI1IDUyLjk1MDEgLTAuMDcwMDk5MSA1Mi40MDM4IDAuMDQ1NjUyOUM0MS4zMjMgMi4zOTY5MSAzNi43OTIzIDEwLjI3MDcgMzUuNDI5OCAxMy4zMDE1QzM0LjQ1NDEgMTMuMTU2NiAzMy40NTc5IDEzLjA3MTEgMzIuNDQ1MiAxMy4wNTE3QzMxLjI3NDMgMjAuMDMzIDI4Ljk2NTYgNDMuOTM2NSA1NC4zNDM2IDM1LjE0MzlDNTQuMzEyMyAzNS4xMzEyIDU0LjI4MTMgMzUuMTE4MSA1NC4yNDk5IDM1LjEwNThINTQuMjUwM1oiIGZpbGw9IiMyQjlERkYiLz4KICAgIDxwYXRoIGQ9Ik00MS43MzQyIDMzLjEzNjFDNDIuOTcwOSAzMy4xMzYxIDQzLjk3MzUgMzEuMjgwNSA0My45NzM1IDI4Ljk5MTVDNDMuOTczNSAyNi43MDI0IDQyLjk3MDkgMjQuODQ2OCA0MS43MzQyIDI0Ljg0NjhDNDAuNDk3NSAyNC44NDY4IDM5LjQ5NSAyNi43MDI0IDM5LjQ5NSAyOC45OTE1QzM5LjQ5NSAzMS4yODA1IDQwLjQ5NzUgMzMuMTM2MSA0MS43MzQyIDMzLjEzNjFaIiBmaWxsPSIjM0M2RThCIi8+CiAgPC9nPgogIDxkZWZzPgogICAgPGNsaXBQYXRoIGlkPSJjbGlwMF8zMTBfMjI5Ij4KICAgICAgPHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjQ1Ljk2MTciIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIDAuMDE5MTY1KSIvPgogICAgPC9jbGlwUGF0aD4KICA8L2RlZnM+Cjwvc3ZnPgo=';
18 + public function __construct( $prefix, $mainfile, $domain, $isPro = false, $disableReview = false, $freeOnly = false ) {
38 19
39 - public function __construct( $prefix, $mainfile, $domain, $isPro = false, $disableReview = false, $freeOnly = false ) {
20 + if ( !MeowCommon_Admin::$loaded ) {
21 + if ( is_admin() ) {
40 22
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;
23 + // Check potential issues with this WordPress install, other plugins, etc.
24 + new MeowCommon_Issues( $prefix, $mainfile, $domain );
47 25
48 - if ( is_admin() ) {
26 + // Create the Meow Apps Menu
27 + add_action( 'admin_menu', array( $this, 'admin_menu_start' ) );
28 + if ( isset( $_GET['page'] ) && $_GET['page'] === 'meowapps-main-menu' ) {
29 + add_filter( 'admin_footer_text', array( $this, 'admin_footer_text' ), 100000, 1 );
30 + }
31 + }
32 + MeowCommon_Admin::$loaded = true;
33 + }
49 34
50 - // Skip AJAX and REST requests to avoid unnecessary processing
51 - if ( MeowKit_MGL_Helpers::is_asynchronous_request() ) {
52 - return;
53 - }
35 + // Variables for this plugin
36 + $this->prefix = $prefix;
37 + $this->mainfile = $mainfile;
38 + $this->domain = $domain;
39 + $this->isPro = $isPro;
54 40
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', '' );
41 + // If there is no mainfile, it's either a Pro only Plugin (with no Free version available) or a Theme.
42 + if ( is_admin() ) {
43 + $license = get_option( $this->prefix . '_license', "" );
71 44 if ( !empty( $license ) && !$this->isPro ) {
72 - add_action( 'admin_notices', [ $this, 'admin_notices_licensed_free' ] );
45 + add_action( 'admin_notices', array( $this, 'admin_notices_licensed_free' ) );
73 46 }
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_MGL_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_MGL_Admin::$loaded ) {
149 - // Check for potential issues with WordPress install, other plugins, etc.
150 - new MeowKit_MGL_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 );
47 + if ( !$disableReview ) {
48 + new MeowCommon_Ratings( $prefix, $mainfile, $domain );
159 49 }
50 + }
51 + add_filter( 'plugin_row_meta', array( $this, 'custom_plugin_row_meta' ), 10, 2 );
52 + add_filter( 'edd_sl_api_request_verify_ssl', array( $this, 'request_verify_ssl' ), 10, 0 );
53 + }
160 54
161 - MeowKit_MGL_Admin::$loaded = true;
162 - }
55 + function custom_plugin_row_meta( $links, $file ) {
56 + $path = pathinfo( $file );
57 + $pathName = basename( $path['dirname'] );
58 + $thisPath = pathinfo( $this->mainfile );
59 + $thisPathName = basename( $thisPath['dirname'] );
60 + $isActive = is_plugin_active( $file );
61 + if ( !$isActive ) {
62 + return $links;
63 + }
64 + $isIssue = $this->isPro && !$this->is_registered();
65 + if ( strpos( $pathName, $thisPathName ) !== false ) {
66 + $new_links = array(
67 + 'settings' =>
68 + sprintf( __( '<a href="admin.php?page=%s_settings">Settings</a>', $this->domain ), $this->prefix ),
69 + 'license' =>
70 + $this->is_registered() ?
71 + ('<span style="color: #a75bd6;">' . __( 'Pro Version', $this->domain ) . '</span>') :
72 + ( $isIssue ? (sprintf( '<span style="color: #ff3434;">' . __( 'License Issue', $this->domain ), $this->prefix ) . '</span>') : (sprintf( '<span>' . __( '<a target="_blank" href="https://store.meowapps.com">Get the <u>Pro Version</u></a>', $this->domain ), $this->prefix ) . '</span>') ),
73 + );
74 + $links = array_merge( $new_links, $links );
75 + }
76 + return $links;
77 + }
163 78
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_MGL_Ratings( $this->prefix, $this->mainfile, $this->domain );
169 - }
170 - new MeowKit_MGL_News( $this->domain );
171 - }
172 - }
79 + function request_verify_ssl() {
80 + return get_option( 'force_sslverify', false );
81 + }
173 82
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 - }
83 + function nice_name_from_file( $file ) {
84 + $info = pathinfo( $file );
85 + if ( !empty( $info ) ) {
86 + if ( $info['filename'] == 'wplr-sync' ) {
87 + return "WP/LR Sync";
88 + }
89 + $info['filename'] = str_replace( '-', ' ', $info['filename'] );
90 + $file = ucwords( $info['filename'] );
91 + }
92 + return $file;
93 + }
190 94
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 - }
95 + function admin_notices_licensed_free() {
96 + if ( isset( $_POST[$this->prefix . '_reset_sub'] ) ) {
97 + delete_option( $this->prefix . '_pro_serial' );
98 + delete_option( $this->prefix . '_license' );
99 + return;
100 + }
101 + $html = '<div class="notice notice-error">';
102 + $html .= sprintf(
103 + __( '<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://store.meowapps.com">Meow Store</a>. If you wish to continue using the free version and clear this message, click on this button.', $this->domain ),
104 + $this->nice_name_from_file( $this->mainfile ) );
105 + $html .= '<p>
106 + <form method="post" action="">
107 + <input type="hidden" name="' . $this->prefix . '_reset_sub" value="true">
108 + <input type="submit" name="submit" id="submit" class="button" value="'
109 + . __( 'Remove the license', $this->domain ) . '">
110 + </form>
111 + </p>';
112 + $html .= '</div>';
113 + wp_kses_post( $html );
114 + }
250 115
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 = '';
116 + function admin_menu_start() {
117 + // Hide the admin if user doesn't like Meow much
118 + if ( get_option( 'meowapps_hide_meowapps', false ) ) {
119 + register_setting( 'general', 'meowapps_hide_meowapps' );
120 + add_settings_field( 'meowapps_hide_ads', 'Meow Apps Menu', array( $this, 'meowapps_hide_dashboard_callback' ), 'general' );
121 + return;
122 + }
280 123
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 - }
124 + // Create standard menu if it does not already exist
125 + global $submenu;
126 + if ( !isset( $submenu[ 'meowapps-main-menu' ] ) ) {
127 + add_menu_page( 'Meow Apps', '<img alt="Meow Apps" style="width: 24px; margin-left: -30px; position: absolute; margin-top: -3px;" src="' . MeowCommon_Admin::$logo . '" />Meow Apps', 'manage_options', 'meowapps-main-menu',
128 + array( $this, 'admin_meow_apps' ), '', 82 );
129 + add_submenu_page( 'meowapps-main-menu', __( 'Dashboard', $this->domain ),
130 + __( 'Dashboard', $this->domain ), 'manage_options',
131 + 'meowapps-main-menu', array( $this, 'admin_meow_apps' ) );
132 + }
133 + }
288 134
289 - function hideMessage() {
290 - message.style.display = 'none';
291 - }
135 + function meowapps_hide_dashboard_callback() {
136 + $html = '<input type="checkbox" id="meowapps_hide_meowapps" name="meowapps_hide_meowapps" value="1" ' .
137 + checked( 1, get_option( 'meowapps_hide_meowapps' ), false ) . '/>';
138 + $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. <b>Once activated, an option will be added in your General settings to display it again.</b></small>', $this->domain );
139 + echo wp_kses_post( $html );
140 + }
292 141
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 - }
142 + function is_registered() {
143 + return apply_filters( $this->prefix . '_meowapps_is_registered', false, $this->prefix );
144 + }
303 145
304 - function closeModal() {
305 - modal.style.display = 'none';
306 - currentPrefix = '';
307 - }
146 + function get_phpinfo() {
147 + if ( !current_user_can( 'administrator' ) ) {
148 + return;
149 + }
150 + ob_start();
151 + // phpcs:disable WordPress.PHP.DevelopmentFunctions
152 + phpinfo( INFO_GENERAL | INFO_CONFIGURATION | INFO_MODULES );
153 + // phpcs:enable
154 + $html = ob_get_contents();
155 + ob_end_clean();
156 + $html = preg_replace( '%^.*<body>(.*)</body>.*$%ms','$1', $html );
157 + return $html;
158 + }
159 +
160 + function admin_meow_apps() {
161 + $html = "<div id='meow-common-dashboard'></div>";
162 + $html .= "<div style='height: 0; width: 0; overflow: hidden;' id='meow-common-phpinfo'>";
163 + $html .= $this->get_phpinfo();
164 + $html .= "</div>";
165 + echo wp_kses_post( $html );
166 + }
308 167
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_MGL_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_MGL_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_MGL_Admin::$version,
505 - __FILE__
506 - );
507 - }
508 - }
168 + function admin_footer_text( $current ) {
169 + return sprintf(
170 + // translators: %1$s is the version of the interface; %2$s is a file path.
171 + __( '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 ),
172 + MeowCommon_Admin::$version,
173 + __FILE__
174 + );
175 + }
176 + }
509 177 }