PluginProbe
Authorizer / 3.9.1
Authorizer v3.9.1
3.15.3 3.15.2 3.15.1 3.15.0 3.14.3 3.14.4 3.14.2 3.14.1 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.9.0 2.9.1 2.9.10 2.9.11 2.9.12 2.9.13 2.9.2 2.9.3 2.9.6 All 126 releases
authorizer / src / authorizer / class-helper.php

class-helper.php in Authorizer 3.9.1, at src/authorizer/class-helper.php

533 lines 18.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Authorizer
4 *
5 * @license GPL-2.0+
6 * @link https://github.com/uhm-coe/authorizer
7 * @package authorizer
8 */
9
10 namespace Authorizer;
11
12 /**
13 * Static class of helper methods.
14 */
15 class Helper {
16
17 /**
18 * Constants for determining our admin context (network or individual site).
19 */
20 const NETWORK_CONTEXT = 'multisite_admin';
21 const SINGLE_CONTEXT = 'single_admin';
22
23
24 /**
25 * HTML allowed when rendering translatable strings in the Authorizer UI.
26 * This is passed to wp_kses() when sanitizing HMTL strings.
27 *
28 * @var array
29 */
30 public static $allowed_html = array(
31 'a' => array(
32 'class' => array(),
33 'href' => array(),
34 'style' => array(),
35 'target' => array(),
36 'title' => array(),
37 ),
38 'b' => array(),
39 'br' => array(),
40 'div' => array(
41 'class' => array(),
42 ),
43 'em' => array(),
44 'hr' => array(),
45 'i' => array(),
46 'input' => array(
47 'aria-describedby' => array(),
48 'class' => array(),
49 'id' => array(),
50 'name' => array(),
51 'size' => array(),
52 'type' => array(),
53 'value' => array(),
54 ),
55 'label' => array(
56 'class' => array(),
57 'for' => array(),
58 ),
59 'p' => array(
60 'style' => array(),
61 ),
62 'span' => array(
63 'aria-hidden' => array(),
64 'class' => array(),
65 'id' => array(),
66 'style' => array(),
67 ),
68 'strong' => array(),
69 );
70
71 /**
72 * Encryption key (not secret!).
73 *
74 * @var string
75 */
76 protected static $key = "8QxnrvjdtweisvCBKEY!+0\0\0";
77
78 /**
79 * Encryption salt (not secret!).
80 *
81 * @var string
82 */
83 protected static $iv = 'R_O2D]jPn]1[fhJl!-P1.oe';
84
85
86 /**
87 * Grabs the admin context (single site or multisite) from the passed
88 * arguments.
89 *
90 * @param array $args Args (e.g., 'context' => Helper::NETWORK_CONTEXT).
91 * @return string Current mode.
92 */
93 public static function get_context( $args ) {
94 if (
95 is_array( $args ) &&
96 array_key_exists( 'context', $args ) &&
97 self::NETWORK_CONTEXT === $args['context']
98 ) {
99 return self::NETWORK_CONTEXT;
100 } else {
101 return self::SINGLE_CONTEXT;
102 }
103 }
104
105
106 /**
107 * Helper function to generate an HTML class name for an option (used in
108 * Authorizer Settings in the Approved User list).
109 *
110 * @param string $suffix Unique part of class name.
111 * @param boolean $is_multisite_user Whether to add an auth-multisite class.
112 * @return string Class name, e.g., "auth-email auth-multisite-email".
113 */
114 public static function get_css_class_name_for_option( $suffix = '', $is_multisite_user = false ) {
115 return $is_multisite_user ? "auth-$suffix auth-multisite-$suffix" : "auth-$suffix";
116 }
117
118
119 /**
120 * Basic encryption using a public (not secret!) key. Used for general
121 * database obfuscation of passwords.
122 *
123 * @param string $text String to encrypt.
124 * @param string $library Encryption library to use (openssl).
125 * @return string Encrypted string.
126 */
127 public static function encrypt( $text, $library = 'openssl' ) {
128 $result = '';
129
130 // Use openssl library (better) if it is enabled.
131 if ( function_exists( 'openssl_encrypt' ) && 'openssl' === $library ) {
132 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
133 $result = base64_encode(
134 openssl_encrypt(
135 $text,
136 'AES-256-CBC',
137 hash( 'sha256', self::$key ),
138 0,
139 substr( hash( 'sha256', self::$iv ), 0, 16 )
140 )
141 );
142 } elseif ( function_exists( 'mcrypt_encrypt' ) ) { // Use mcrypt library (deprecated in PHP 7.1) if php5-mcrypt extension is enabled.
143 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
144 $result = base64_encode( mcrypt_encrypt( MCRYPT_RIJNDAEL_256, self::$key, $text, MCRYPT_MODE_ECB, 'abcdefghijklmnopqrstuvwxyz012345' ) );
145 } else { // Fall back to basic obfuscation.
146 $length = strlen( $text );
147 for ( $i = 0; $i < $length; $i++ ) {
148 $char = substr( $text, $i, 1 );
149 $keychar = substr( self::$key, ( $i % strlen( self::$key ) ) - 1, 1 );
150 $char = chr( ord( $char ) + ord( $keychar ) );
151 $result .= $char;
152 }
153 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
154 $result = base64_encode( $result );
155 }
156
157 return $result;
158 }
159
160
161 /**
162 * Basic decryption using a public (not secret!) key. Used for general
163 * database obfuscation of passwords.
164 *
165 * @param string $secret String to encrypt.
166 * @param string $library Encryption lib to use (openssl).
167 * @return string Decrypted string
168 */
169 public static function decrypt( $secret, $library = 'openssl' ) {
170 $result = '';
171
172 // Use openssl library (better) if it is enabled.
173 if ( function_exists( 'openssl_decrypt' ) && 'openssl' === $library ) {
174 $result = openssl_decrypt(
175 base64_decode( $secret ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
176 'AES-256-CBC',
177 hash( 'sha256', self::$key ),
178 0,
179 substr( hash( 'sha256', self::$iv ), 0, 16 )
180 );
181 } elseif ( function_exists( 'mcrypt_decrypt' ) ) { // Use mcrypt library (deprecated in PHP 7.1) if php5-mcrypt extension is enabled.
182 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
183 $secret = base64_decode( $secret );
184 $result = rtrim( mcrypt_decrypt( MCRYPT_RIJNDAEL_256, self::$key, $secret, MCRYPT_MODE_ECB, 'abcdefghijklmnopqrstuvwxyz012345' ), "\0$result" );
185 } else { // Fall back to basic obfuscation.
186 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
187 $secret = base64_decode( $secret );
188 $length = strlen( $secret );
189 for ( $i = 0; $i < $length; $i++ ) {
190 $char = substr( $secret, $i, 1 );
191 $keychar = substr( self::$key, ( $i % strlen( self::$key ) ) - 1, 1 );
192 $char = chr( ord( $char ) - ord( $keychar ) );
193 $result .= $char;
194 }
195 }
196
197 return $result;
198 }
199
200
201 /**
202 * In a multisite environment, returns true if the current user is logged
203 * in and a user of the current blog. In single site mode, simply returns
204 * true if the current user is logged in.
205 *
206 * @return bool Whether current user is logged in and a user of the current blog.
207 */
208 public static function is_user_logged_in_and_blog_user() {
209 $is_user_logged_in_and_blog_user = false;
210 if ( is_multisite() ) {
211 $is_user_logged_in_and_blog_user = is_user_logged_in() && is_user_member_of_blog( get_current_user_id() );
212 } else {
213 $is_user_logged_in_and_blog_user = is_user_logged_in();
214 }
215 return $is_user_logged_in_and_blog_user;
216 }
217
218
219 /**
220 * Helper function to get all available usermeta keys as an array.
221 *
222 * @return array All usermeta keys for user.
223 */
224 public static function get_all_usermeta_keys() {
225 global $wpdb;
226 // phpcs:ignore WordPress.DB.DirectDatabaseQuery
227 $usermeta_keys = $wpdb->get_col( "SELECT DISTINCT $wpdb->usermeta.meta_key FROM $wpdb->usermeta" );
228 return $usermeta_keys;
229 }
230
231
232 /**
233 * Helper function that prints option tags for a select element for all
234 * roles the current user has permission to assign.
235 *
236 * @param string $selected_role Which role should be selected in the dropdown.
237 * @param string $disable_input 'disabled' if select element should be disabled.
238 * @param int $admin_mode Helper::NETWORK_CONTEXT if we are in that context.
239 * @return void
240 */
241 public static function wp_dropdown_permitted_roles( $selected_role = 'subscriber', $disable_input = 'not disabled', $admin_mode = self::SINGLE_CONTEXT ) {
242 $roles = get_editable_roles();
243 $current_user = wp_get_current_user();
244
245 // If we're in network admin, also show any roles that might exist only on
246 // specific sites in the network (themes can add their own roles).
247 if ( self::NETWORK_CONTEXT === $admin_mode ) {
248 // phpcs:ignore WordPress.WP.DeprecatedFunctions.wp_get_sitesFound
249 $sites = function_exists( 'get_sites' ) ? get_sites() : wp_get_sites( array( 'limit' => PHP_INT_MAX ) );
250 foreach ( $sites as $site ) {
251 $blog_id = function_exists( 'get_sites' ) ? $site->blog_id : $site['blog_id'];
252 switch_to_blog( $blog_id );
253 $roles = array_merge( $roles, get_editable_roles() );
254 restore_current_blog();
255 }
256 $unique_role_names = array();
257 foreach ( $roles as $role_name => $role_info ) {
258 if ( array_key_exists( $role_name, $unique_role_names ) ) {
259 unset( $roles[ $role_name ] );
260 } else {
261 $unique_role_names[ $role_name ] = true;
262 }
263 }
264 }
265
266 // If the currently selected role exists, but is not in the list of roles,
267 // the current user is not permitted to assign it. Assume they can't edit
268 // that user's role at all. Return only the one role for the dropdown list.
269 if ( strlen( $selected_role ) > 0 && ! array_key_exists( $selected_role, $roles ) && ! is_null( get_role( $selected_role ) ) ) {
270 return;
271 }
272
273 // Print an option element for each permitted role.
274 foreach ( $roles as $name => $role ) {
275 $is_selected = $selected_role === $name;
276
277 // Don't let a user change their own role (but network admins always can).
278 $is_disabled = $selected_role !== $name && 'disabled' === $disable_input && ! ( is_multisite() && current_user_can( 'manage_network' ) );
279 ?>
280 <option value="<?php echo esc_attr( $name ); ?>"<?php selected( $is_selected ); ?><?php disabled( $is_disabled ); ?>><?php echo esc_html( $role['name'] ); ?></option>
281 <?php
282 }
283
284 // Print default role (no role).
285 $is_selected = strlen( $selected_role ) === 0 || ! array_key_exists( $selected_role, $roles );
286 $is_disabled = strlen( $selected_role ) > 0 && 'disabled' === $disable_input && ! ( is_multisite() && current_user_can( 'manage_network' ) );
287 ?>
288 <option value=""<?php selected( $is_selected ); ?><?php disabled( $is_disabled ); ?>><?php esc_html_e( '&mdash; No role for this site &mdash;', 'authorizer' ); ?></option>
289 <?php
290 }
291
292
293 /**
294 * Helper function to search a multidimensional array for a value.
295 *
296 * @param string $needle Value to search for.
297 * @param array $haystack Multidimensional array to search.
298 * @param string $strict_mode 'strict' if strict comparisons should be used.
299 * @param string $case_sensitivity 'case sensitive' if comparisons should respect case.
300 * @return bool Whether needle was found.
301 */
302 public static function in_multi_array( $needle = '', $haystack = array(), $strict_mode = 'not strict', $case_sensitivity = 'case insensitive' ) {
303 if ( ! is_array( $haystack ) ) {
304 return false;
305 }
306 if ( 'case insensitive' === $case_sensitivity ) {
307 $needle = strtolower( $needle );
308 }
309 foreach ( $haystack as $item ) {
310 if ( 'case insensitive' === $case_sensitivity && ! is_array( $item ) ) {
311 $item = strtolower( $item );
312 }
313 if ( ( 'strict' === $strict_mode ? $item === $needle : $item == $needle ) || ( is_array( $item ) && self::in_multi_array( $needle, $item, $strict_mode, $case_sensitivity ) ) ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
314 return true;
315 }
316 }
317 return false;
318 }
319
320
321 /**
322 * Helper function to discover the email addresses in a value in a
323 * multidimensional array.
324 *
325 * @param array $haystack Multidimensional array, possibly containing an email.
326 * @param array $emails Array of email addresses found.
327 * @return array Array of Discovered emails, or empty array.
328 */
329 public static function find_emails_in_multi_array( $haystack, &$emails = array() ) {
330 if ( is_array( $haystack ) ) {
331 foreach ( $haystack as $key => $value ) {
332 self::find_emails_in_multi_array( $value, $emails );
333 }
334 } elseif ( filter_var( $haystack, FILTER_VALIDATE_EMAIL ) ) {
335 $emails[] = $haystack;
336 }
337
338 return $emails;
339 }
340
341
342 /**
343 * Helper function to determine if an URL is accessible.
344 *
345 * @param string $url URL that should be publicly reachable.
346 * @return boolean Whether the URL is publicly reachable.
347 */
348 public static function url_is_accessible( $url ) {
349 // Use wp_remote_retrieve_response_code() to retrieve the URL.
350 $response = wp_remote_get( $url );
351 $response_code = wp_remote_retrieve_response_code( $response );
352
353 // Return true if the document has loaded successfully without any redirection or error.
354 return $response_code >= 200 && $response_code < 400;
355 }
356
357
358 /**
359 * Helper function to reconstruct a URL split using parse_url().
360 *
361 * @param array $parts Array returned from parse_url().
362 * @return string URL.
363 */
364 public static function build_url( $parts = array() ) {
365 return (
366 ( isset( $parts['scheme'] ) ? "{$parts['scheme']}:" : '' ) .
367 ( ( isset( $parts['user'] ) || isset( $parts['host'] ) ) ? '//' : '' ) .
368 ( isset( $parts['user'] ) ? "{$parts['user']}" : '' ) .
369 ( isset( $parts['pass'] ) ? ":{$parts['pass']}" : '' ) .
370 ( isset( $parts['user'] ) ? '@' : '' ) .
371 ( isset( $parts['host'] ) ? "{$parts['host']}" : '' ) .
372 ( isset( $parts['port'] ) ? ":{$parts['port']}" : '' ) .
373 ( isset( $parts['path'] ) ? "{$parts['path']}" : '' ) .
374 ( isset( $parts['query'] ) ? "?{$parts['query']}" : '' ) .
375 ( isset( $parts['fragment'] ) ? "#{$parts['fragment']}" : '' )
376 );
377 }
378
379
380 /**
381 * Helper function to get a single user info array from one of the access
382 * control lists (pending, approved, or blocked).
383 *
384 * @param string $email Email address to retrieve info for.
385 * @param array $user_info_list List to get info from.
386 * @return mixed false if not found, otherwise: array(
387 * 'email' => '',
388 * 'role' => '',
389 * 'date_added' => '',
390 * ['usermeta' => [''|array()]]
391 * );
392 */
393 public static function get_user_info_from_list( $email, $user_info_list ) {
394 foreach ( $user_info_list as $user_info ) {
395 if ( 0 === strcasecmp( $user_info['email'], $email ) ) {
396 return $user_info;
397 }
398 }
399 return false;
400 }
401
402 /**
403 * Helper function to convert a string to lowercase. Prefers to use mb_strtolower,
404 * but will fall back to strtolower if the former is not available.
405 *
406 * @param string $str String to convert to lowercase.
407 * @return string Input in lowercase.
408 */
409 public static function lowercase( $str ) {
410 return function_exists( 'mb_strtolower' ) ? mb_strtolower( $str ) : strtolower( $str );
411 }
412
413
414 /**
415 * Helper function to convert seconds to human readable text.
416 *
417 * @see: http://csl.name/php-secs-to-human-text/
418 *
419 * @param int $secs Seconds to display as readable text.
420 * @return string Readable version of number of seconds.
421 */
422 public static function seconds_as_sentence( $secs ) {
423 $units = array(
424 'week' => 3600 * 24 * 7,
425 'day' => 3600 * 24,
426 'hour' => 3600,
427 'minute' => 60,
428 'second' => 1,
429 );
430
431 // Specifically handle zero.
432 if ( 0 === intval( $secs ) ) {
433 return '0 seconds';
434 }
435
436 $s = '';
437
438 foreach ( $units as $name => $divisor ) {
439 $quot = intval( $secs / $divisor );
440 if ( $quot ) {
441 $s .= "$quot $name";
442 $s .= ( abs( $quot ) > 1 ? 's' : '' ) . ', ';
443 $secs -= $quot * $divisor;
444 }
445 }
446
447 return substr( $s, 0, -2 );
448 }
449
450
451 /**
452 * Helper function to show a number as an ordinal (e.g., 5 as 5th).
453 *
454 * @see: https://stackoverflow.com/questions/3109978/display-numbers-with-ordinal-suffix-in-php
455 *
456 * @param int $number Number to show as an ordinal.
457 * @return string Number as an ordinal string.
458 */
459 public static function ordinal( $number = 0 ) {
460 $ends = array( 'th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th' );
461 if ( $number % 100 >= 11 && $number % 100 <= 13 ) {
462 return $number . 'th';
463 } else {
464 return $number . $ends[ $number % 10 ];
465 }
466 }
467
468
469 /**
470 * Generate CAS or OAuth2 authentication URL (wp-login.php URL with reauth=1 removed
471 * and external=cas or external=oauth2 added).
472 *
473 * @param string $provider External service provider type (e.g., 'cas', or 'oauth2').
474 */
475 public static function modify_current_url_for_external_login( $provider = 'cas' ) {
476 // Construct the URL of the current page (wp-login.php).
477 $url = '';
478 if ( isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) {
479 $url = set_url_scheme( esc_url_raw( wp_unslash( $_SERVER['HTTP_HOST'] ) ) . esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
480 }
481
482 // If we have a login form embedded elsewhere than wp-login.php, alter the
483 // URL to point to wp-login.php with a redirect to the current page. This
484 // will happen if the [authorizer_login_form] shortcode is used.
485 if ( false === strpos( $url, 'wp-login.php' ) ) {
486 $url = wp_login_url( $url );
487 }
488
489 // Edge case: If the WPS Hide Login plugin is installed, redirect to home
490 // page after logging in instead of the plugin's login endpoint, which will
491 // redirect to /wp-admin.
492 if ( class_exists( '\WPS\WPS_Hide_Login\Plugin' ) ) {
493 $url = wp_login_url( home_url() );
494 }
495
496 // Parse the URL into its components.
497 $parsed_url = wp_parse_url( $url );
498
499 // Fix up the querystring values (remove reauth, make sure external=cas).
500 $querystring = array();
501 if ( array_key_exists( 'query', $parsed_url ) ) {
502 parse_str( $parsed_url['query'], $querystring );
503 }
504 unset( $querystring['reauth'] );
505 $querystring['external'] = $provider;
506 $parsed_url['query'] = http_build_query( $querystring );
507
508 // Return the URL as a string.
509 return self::unparse_url( $parsed_url );
510 }
511
512
513 /**
514 * Reconstruct a URL after it has been deconstructed with parse_url().
515 *
516 * @param array $parsed_url Keys from parse_url().
517 * @return string URL constructed from the components in $parsed_url.
518 */
519 public static function unparse_url( $parsed_url = array() ) {
520 $scheme = isset( $parsed_url['scheme'] ) ? $parsed_url['scheme'] . '://' : '';
521 $host = isset( $parsed_url['host'] ) ? $parsed_url['host'] : '';
522 $port = isset( $parsed_url['port'] ) ? ':' . $parsed_url['port'] : '';
523 $user = isset( $parsed_url['user'] ) ? $parsed_url['user'] : '';
524 $pass = isset( $parsed_url['pass'] ) ? ':' . $parsed_url['pass'] : '';
525 $pass = $user || $pass ? "$pass@" : '';
526 $path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '';
527 $query = isset( $parsed_url['query'] ) ? '?' . $parsed_url['query'] : '';
528 $fragment = isset( $parsed_url['fragment'] ) ? '#' . $parsed_url['fragment'] : '';
529
530 return "$scheme$user$pass$host$port$path$query$fragment";
531 }
532 }
533