PluginProbe
Adminify – White Label, Admin Menu Editor, Login Customizer / 4.2.24
Adminify – White Label, Admin Menu Editor, Login Customizer v4.2.24
4.3.1 4.3.0 4.2.26 4.2.25 4.2.24 4.2.23 4.2.22 4.2.21 4.2.20 4.2.19 4.2.18 4.2.17 4.2.16 4.2.15 4.2.14 4.2.13 4.2.12 4.2.11 4.2.10 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 4.1.17 All 164 releases
adminify / Inc / Admin / Frames / Init.php

Init.php in Adminify – White Label, Admin Menu Editor, Login Customizer 4.2.24, at Inc/Admin/Frames/Init.php

276 lines 11.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace PXLBSAdminify\Inc\Admin\Frames;
4
5 use PXLBSAdminify\Inc\Utils;
6
7 // no direct access allowed
8 if (!defined('ABSPATH')) {
9 exit;
10 }
11 /**
12 * WP Adminify
13 * Init Class
14 *
15 * @author Jewel Theme <support@jeweltheme.com>
16 */
17
18 if (!class_exists('Init')) {
19 class Init
20 {
21 public static $instance;
22 public $admin;
23 public $frame;
24
25 public static function instance()
26 {
27 if (is_null(self::$instance)) {
28 self::$instance = new self();
29 }
30 return self::$instance;
31 }
32
33 public function __construct()
34 {
35
36 if ( ! Utils::is_admin_page_request() ) {
37 return;
38 }
39
40 if ( ! $this->is_allowed() ) {
41 if ( Utils::is_iframe() || ! Utils::has_fetch_metadata() ) {
42 $http_host = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : '';
43 $request_uri = isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : '';
44 $scheme = empty($_SERVER['HTTPS']) ? 'http' : 'https';
45 $actual_link = $scheme . '://' . $http_host . $request_uri;
46
47 if ( Utils::is_iframe() ) {
48 Frames::custom_plugin_change_reload($actual_link);
49 } else {
50 // No Fetch Metadata to go by - let the browser decide
51 // whether this really is the Adminify iframe.
52 Frames::maybe_break_out_of_frame($actual_link);
53 }
54 }
55 return;
56 }
57
58 // Without Fetch Metadata a POST is treated as coming from the frame:
59 // the shell template prints during `admin_init`, so rendering it for a
60 // form submission sends output before WordPress can redirect and the
61 // request dead-ends on a blank page. Submissions in a running Adminify
62 // UI always originate inside the iframe anyway.
63 $is_frame = Utils::is_iframe() || ( ! Utils::has_fetch_metadata() && ! Utils::is_get_request() );
64
65 if ( $is_frame ) {
66 $this->frame = new Frames();
67 } else {
68 $this->admin = new Admin();
69 }
70
71 }
72
73 /**
74 * Get the relative admin path without subdirectory prefix
75 * Handles root, subdirectory, subdomain, and multisite installations
76 *
77 * @return string Normalized path (e.g., /wp-admin/edit.php)
78 */
79 private function get_normalized_admin_path() {
80 $php_self = isset($_SERVER['PHP_SELF']) ? sanitize_text_field(wp_unslash($_SERVER['PHP_SELF'])) : '';
81
82 // Method 1: Use WordPress native function to get subdirectory path
83 // site_url() returns full URL including subdirectory
84 // e.g., https://example.com/blog or https://example.com
85 $site_url_path = wp_parse_url( site_url(), PHP_URL_PATH );
86
87 // Remove subdirectory prefix if exists
88 if ( ! empty( $site_url_path ) && $site_url_path !== '/' ) {
89 // Ensure path starts with subdirectory
90 if ( strpos( $php_self, $site_url_path ) === 0 ) {
91 $php_self = substr( $php_self, strlen( $site_url_path ) );
92 }
93 }
94
95 // Ensure path starts with /
96 if ( empty( $php_self ) || $php_self[0] !== '/' ) {
97 $php_self = '/' . $php_self;
98 }
99
100 return $php_self;
101 }
102
103 /**
104 * Check if current path matches the blocked URL pattern
105 * Supports exact match and ends-with matching for subdirectory compatibility
106 *
107 * @param string $blocked_url The URL pattern to check against
108 * @return bool True if current path matches the blocked URL
109 */
110 private function matches_blocked_url( $blocked_url ) {
111 $current_path = $this->get_normalized_admin_path();
112
113 // Exact match (normalized)
114 if ( $current_path === $blocked_url ) {
115 return true;
116 }
117
118 // Fallback: ends-with check for edge cases
119 // e.g., /wp-admin/customize.php should match even if normalization fails
120 $php_self = isset($_SERVER['PHP_SELF']) ? sanitize_text_field(wp_unslash($_SERVER['PHP_SELF'])) : '';
121 if ( $this->url_ends_with( $php_self, $blocked_url ) ) {
122 return true;
123 }
124
125 return false;
126 }
127
128 /**
129 * Check if a URL ends with a specific path
130 * Useful for subdirectory WordPress installs
131 *
132 * @param string $url Full URL or path to check
133 * @param string $ending The ending pattern to match
134 * @return bool
135 */
136 private function url_ends_with( $url, $ending ) {
137 $ending_length = strlen( $ending );
138 if ( $ending_length === 0 ) {
139 return true;
140 }
141 return substr( $url, -$ending_length ) === $ending;
142 }
143
144 /**
145 * Get WordPress installation context for debugging
146 *
147 * @return array Installation details
148 */
149 public function get_install_context() {
150 return [
151 'is_multisite' => is_multisite(),
152 'is_subdomain' => defined( 'SUBDOMAIN_INSTALL' ) && SUBDOMAIN_INSTALL,
153 'site_url' => site_url(),
154 'home_url' => home_url(),
155 'admin_url' => admin_url(),
156 'subdirectory' => wp_parse_url( site_url(), PHP_URL_PATH ) ?: '/',
157 'php_self' => isset($_SERVER['PHP_SELF']) ? sanitize_text_field(wp_unslash($_SERVER['PHP_SELF'])) : '',
158 'normalized_path' => $this->get_normalized_admin_path(),
159 ];
160 }
161
162 public function is_allowed() {
163
164 $not_allowed_urls = Admin::get_not_allowed_urls();
165
166 foreach ( $not_allowed_urls as $url_object ) {
167 if ( is_string( $url_object ) ) {
168
169 $is_allowed = true; // Scoped Default allowed
170 // Use normalized path matching for subdirectory compatibility
171 if ( $this->matches_blocked_url( $url_object ) ) {
172 $is_allowed = false; // not allowed
173 }
174
175 } else {
176
177 $is_allowed = false; // Scoped Default not allowed
178
179 // Use normalized path matching for subdirectory compatibility
180 if ( $url_object['url'] !== '*' && ! $this->matches_blocked_url( $url_object['url'] ) ) {
181 $is_allowed = true; // allowed
182 }
183
184 if ( ! $is_allowed && array_key_exists( 'query_params', $url_object ) ) {
185 if ( ! $this->check_query_params( $url_object['query_params'] ) ) {
186 $is_allowed = true; // allowed
187 }
188 }
189
190 if ( ! $is_allowed && array_key_exists( 'post_type', $url_object ) ) {
191 if ( ! $this->check_post_type( $url_object['post_type'] ) ) {
192 $is_allowed = true; // allowed
193 }
194 }
195
196 }
197
198 if ( ! $is_allowed ) {
199 return $is_allowed;
200 }
201
202 }
203
204 return true;
205
206 }
207
208 function check_query_params($query_params) {
209 // Pattern 1: Both keys and their values should check in $_GET
210 if (array_keys($query_params) === $query_params) {
211 foreach ($query_params as $key => $value) {
212 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
213 if (!isset($_GET[$key]) || sanitize_text_field(wp_unslash($_GET[$key])) != $value) {
214 return false; // Key doesn't exist or the value doesn't match
215 }
216 }
217 return true; // All keys and values match
218 }
219
220 // Pattern 2: Check for only keys in $_GET, no need to check their values
221 if (array_values($query_params) === $query_params) {
222 foreach ($query_params as $param) {
223 if ( substr($param, -1) === '!' ) {
224 $param = substr($param, 0, -1);
225 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
226 if ( isset($_GET[$param]) ) return false; // The key exists in $_GET
227 } else {
228 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
229 if ( ! isset($_GET[$param]) ) return false; // The key doesn't exist in $_GET
230 }
231
232 }
233 return true; // All keys exist
234 }
235
236 // Pattern 3: A mix of key existence and key-value matching
237 foreach ($query_params as $key => $value) {
238 if (is_numeric($key)) {
239 // For numeric keys, we're checking only existence (Pattern 1 behavior)
240 if ( substr($value, -1) === '!' ) {
241 $value = substr($value, 0, -1);
242 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
243 if ( isset($_GET[$value]) ) return false; // The key exists in $_GET
244 } else {
245 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
246 if ( ! isset($_GET[$value]) ) return false; // The key doesn't exist in $_GET
247 }
248 } else {
249 // For associative keys, we check for both key and value (Pattern 2 behavior)
250 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
251 if (!isset($_GET[$key]) || sanitize_text_field(wp_unslash($_GET[$key])) != $value) {
252 return false; // Key doesn't exist or value doesn't match
253 }
254 }
255 }
256
257 return true; // All conditions are met
258 }
259
260 function check_post_type($post_types) {
261 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
262 if ( isset( $_GET['post_type'] ) ) {
263 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
264 return in_array( sanitize_text_field( wp_unslash( $_GET['post_type'] ) ), $post_types );
265 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
266 } else if ( isset( $_GET['post'] ) ) {
267 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
268 return in_array( get_post_type( absint( wp_unslash( $_GET['post'] ) ) ), $post_types );
269 }
270 return in_array( 'post', $post_types );
271 }
272
273 }
274
275 }
276