tags. * Displays output only for developer or specified user IDs. * * @param mixed $var Data to print. * @param string|int|bool|null $left_margin Left margin (px, string value, or true for 200px). * @param int|array|null $user_id Single user ID or array of IDs allowed to see the output. * @param bool $write_bool Convert boolean to "TRUE"/"FALSE". * * @return void */ public static function print_r( $var, $left_margin = null, $user_id = null, $write_bool = true ) { $current_user_id = get_current_user_id(); // Permission check if ( $user_id !== null ) { if ( is_array( $user_id ) ) { if ( ! in_array( $current_user_id, array_map( 'intval', $user_id ), true ) ) { return; } } elseif ( intval( $user_id ) !== $current_user_id ) { return; } } elseif ( $current_user_id !== 1 ) { return; } // Margin calculation if ( is_numeric( $left_margin ) ) { $margin = intval( $left_margin ) . 'px'; } elseif ( is_string( $left_margin ) ) { $margin = sanitize_text_field( $left_margin ); } elseif ( $left_margin === true ) { $margin = '180px'; } else { $margin = '0'; } // Boolean conversion if ( $write_bool && is_bool( $var ) ) { $var = $var ? 'TRUE' : 'FALSE'; } // Output echo '
';
wp_kses_post( print_r( $var ) ); // phpcs:ignore
echo '';
} // End print_r()
/**
* Get the admin role that should have access to help docs settings
*
* @return string Role name
*/
public static function admin_role() : string {
// Allow filtering the admin role, in case a site uses a custom role for admins
return apply_filters( 'helpdocs_admin_role', 'administrator' );
} // End admin_role()
/**
* Check if the current user can manage help docs
*
* @return bool
*/
public static function user_can_edit( $user_id = null ) : bool {
if ( ! $user_id ) {
$user_id = get_current_user_id();
}
if ( ! $user_id ) {
return false;
}
$test_mode = Bootstrap::is_test_mode();
$cache_key = '';
if ( ! $test_mode ) {
$cache_key = 'helpdocs_perm_edit_' . absint( $user_id );
$cached = get_transient( $cache_key );
if ( false !== $cached ) {
return (bool) $cached;
}
}
$user = get_userdata( $user_id );
if ( ! $user ) {
return false;
}
$user_roles = (array) $user->roles;
$can_edit = false;
// Admins should always have access
$admin_role = self::admin_role();
if ( in_array( $admin_role, $user_roles, true ) ) {
$can_edit = true;
} else {
$edit_roles = (array) get_option( 'helpdocs_edit_roles', [] );
if ( ! empty( $edit_roles ) ) {
$first_key = key( $edit_roles );
$allowed_roles = is_numeric( $first_key ) ? $edit_roles : array_keys( $edit_roles );
if ( array_intersect( $user_roles, $allowed_roles ) ) {
$can_edit = true;
}
}
}
if ( ! $test_mode && ! empty( $cache_key ) ) {
set_transient( $cache_key, ( $can_edit ? 1 : 0 ), 12 * HOUR_IN_SECONDS );
}
return $can_edit;
} // End user_can_edit()
/**
* Check if the current user can view help docs
*
* @param int|null $doc_id Optional doc ID to check per-doc access
* @param int|null $user_id Optional user ID (defaults to current user)
* @return bool
*/
public static function user_can_view( $doc_id = null, $user_id = null ) : bool {
if ( ! $user_id ) {
$user_id = get_current_user_id();
}
$user = get_userdata( $user_id );
if ( ! $user ) {
return false;
}
$user_roles = (array) $user->roles;
// Admin shortcut
if ( in_array( self::admin_role(), $user_roles, true ) ) {
return true;
}
// 1. If checking a specific doc, keep the direct meta check for performance
if ( $doc_id ) {
$doc_roles = maybe_unserialize( get_post_meta( $doc_id, 'helpdocs_view_roles', true ) );
if ( is_array( $doc_roles ) && ! empty( $doc_roles ) ) {
return (bool) array_intersect( $user_roles, $doc_roles );
}
// Fallback to global plugin settings if no specific doc roles
$access_type = get_option( 'helpdocs_user_view_type', 'capability' );
if ( $access_type === 'capability' ) {
$cap = get_option( 'helpdocs_user_view_cap', 'manage_options' );
return user_can( $user_id, $cap );
} else {
$roles = (array) get_option( 'helpdocs_view_roles', [] );
return (bool) array_intersect( $user_roles, $roles );
}
}
// 2. If no doc_id, check if the user has permission to see ANY docs in the 'main' location
// We use get_docs() here to leverage the existing permission/cache logic you just built
$docs = self::get_docs( [ 'site_location' => 'main' ] );
return ! empty( $docs );
} // End user_can_view()
/**
* Get an array of available user roles for use in settings options
*
* @return array Array of role options with 'label' and 'value' keys
*/
public static function get_role_options() : array {
$roles = get_editable_roles();
$role_options = [];
foreach ( $roles as $key => $role ) {
if ( $key != 'administrator' ) {
$role_options[] = [
'label' => $role[ 'name' ],
'value' => $key
];
}
}
return $role_options;
} // End get_roles()
/**
* Get all admin user emails, which can be used for notifications or other purposes
*
* @return array Array of unique admin email addresses
*/
public static function get_all_admin_emails() : array {
$users = get_users( [
'fields' => [ 'user_email' ],
'role__in' => [ self::admin_role() ],
] );
$emails = [];
foreach ( $users as $user ) {
if ( is_email( $user->user_email ) ) {
$emails[] = $user->user_email;
}
}
return array_unique( $emails );
} // End get_all_admin_emails()
/**
* Check if we're on any of the plugin's screens
*
* @param string|null $textdomain
* @return bool
*/
public static function is_our_screen( $textdomain = null ) : bool {
$current_screen = get_current_screen();
if ( ! $current_screen ) {
return false;
}
$text_domain = $textdomain ?? Bootstrap::textdomain();
$is_helpdocs_screen = $current_screen->id === 'toplevel_page_' . $text_domain || $current_screen->id === 'toplevel_page_' . $text_domain . '-network';
$is_post_type_list_screen = $current_screen->id === 'edit-' . HelpDocs::$post_type || $current_screen->id === 'edit-' . Imports::$post_type;
$is_taxonomy_list_screen = $current_screen->id === 'edit-' . Folders::$taxonomy;
$is_our_dashboard = $current_screen->id === 'dashboard_page_admin-help-dashboard';
return $is_helpdocs_screen || $is_post_type_list_screen || $is_taxonomy_list_screen || $is_our_dashboard;
} // End is_our_screen()
/**
* Check if plugin is in test mode
*
* @param bool $params Whether to include query parameters in the returned URL
* @param bool|string $domain Whether to include the domain in the returned URL. If 'only', returns only the domain without protocol. *
* @return string
*/
public static function get_current_url( $params = true, $domain = true ) : string {
$host = '';
if ( isset( $_SERVER[ 'HTTP_HOST' ] ) ) {
$host = sanitize_text_field( wp_unslash( $_SERVER[ 'HTTP_HOST' ] ) );
}
if ( $domain === true ) {
$protocol = ( isset( $_SERVER[ 'HTTPS' ] ) && $_SERVER[ 'HTTPS' ] !== 'off' ) ? 'https' : 'http';
$domain = $host ? $protocol . '://' . $host : '';
} elseif ( $domain === 'only' ) {
return $host;
} else {
$domain = '';
}
$uri = '';
if ( isset( $_SERVER[ 'REQUEST_URI' ] ) ) {
$uri = esc_url_raw( wp_unslash( $_SERVER[ 'REQUEST_URI' ] ) );
}
$full_url = $domain . $uri;
if ( ! $params ) {
return strtok( $full_url, '?' );
}
return $full_url;
} // End get_current_url()
/**
* Get the current admin page URL, with options to include query parameters
*
* @param bool $params Whether to include query parameters in the returned URL
* @return string
*/
public static function get_current_admin_url( $params = true ) : string {
$uri = filter_input( INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_URL );
if ( ! $params ) {
$uri = strtok( $uri, '?' );
}
return Bootstrap::admin_url( basename( $uri ) );
} // End get_current_admin_url()
/**
* Check if two urls match while ignoring order of params
* Also allow ignoring addtional params that $url1 has that $url2 does not
*
* @param string $url1
* @param string $url2
* @param bool
* @return bool
*/
public static function do_urls_match( $url1, $url2, $ignore_addt_params = true ) : bool {
$parts1 = wp_parse_url( $url1 );
$parts2 = wp_parse_url( $url2 );
$scheme1 = strtolower( $parts1[ 'scheme' ] ?? '' );
$scheme2 = strtolower( $parts2[ 'scheme' ] ?? '' );
if ( $scheme1 !== $scheme2 ) {
return false;
}
$host1 = strtolower( $parts1[ 'host' ] ?? '' );
$host2 = strtolower( $parts2[ 'host' ] ?? '' );
if ( $host1 !== $host2 ) {
return false;
}
$path1 = trim( urldecode( $parts1[ 'path' ] ?? '' ), '/' );
$path2 = trim( urldecode( $parts2[ 'path' ] ?? '' ), '/' );
if ( $path1 !== $path2 ) {
return false;
}
parse_str( $parts1[ 'query' ] ?? '', $query1 );
parse_str( $parts2[ 'query' ] ?? '', $query2 );
if ( ! $ignore_addt_params && count( $query1 ) !== count( $query2 ) ) {
return false;
}
if ( count( $query1 ) > 0 ) {
ksort( $query1 );
ksort( $query2 );
if ( ! $ignore_addt_params && array_diff( $query1, $query2 ) ) {
return false;
} elseif ( $ignore_addt_params && ! empty( array_diff( $query2, $query1 ) ) ) {
return false;
}
}
return true;
} // End do_urls_match()
/**
* Convert a UTC date string to the site's local timezone and format it
*
* @param string|null $date Date string in UTC (defaults to current time)
* @param string $format Date format (default: 'F j, Y g:i A T')
* @param string|null $timezone Timezone identifier (defaults to site's timezone)
* @return string Formatted date string in local timezone
*/
public static function convert_timezone( $date = null, $format = 'F j, Y g:i A T', $timezone = null ) : string {
$date_string = $date ?? gmdate( 'Y-m-d H:i:s' );
$timezone_string = $timezone ?: wp_timezone_string();
$datetime = new \DateTime( $date_string, new \DateTimeZone( 'UTC' ) );
$datetime->setTimezone( new \DateTimeZone( $timezone_string ) );
return $datetime->format( $format );
} // End convert_timezone()
/**
* Get the appropriate contrast color (black or white) for a given hex color
*
* @param string $hex_color Hex color code (e.g. '#ff0000' or 'ff0000')
* @return string Contrast color ('#000000' or '#FFFFFF')
*/
public static function get_contrast_color( $hex_color ) : string {
$hex = ltrim( $hex_color, '#' );
if ( strlen( $hex ) === 3 ) {
$hex = $hex[ 0 ] . $hex[ 0 ] . $hex[ 1 ] . $hex[ 1 ] . $hex[ 2 ] . $hex[ 2 ];
}
if ( strlen( $hex ) !== 6 ) {
return '#000000';
}
$r = hexdec( substr( $hex, 0, 2 ) ) / 255;
$g = hexdec( substr( $hex, 2, 2 ) ) / 255;
$b = hexdec( substr( $hex, 4, 2 ) ) / 255;
$r = ( $r <= 0.03928 ) ? $r / 12.92 : pow( ( $r + 0.055 ) / 1.055, 2.4 );
$g = ( $g <= 0.03928 ) ? $g / 12.92 : pow( ( $g + 0.055 ) / 1.055, 2.4 );
$b = ( $b <= 0.03928 ) ? $b / 12.92 : pow( ( $b + 0.055 ) / 1.055, 2.4 );
$luminance = ( 0.2126 * $r ) + ( 0.7152 * $g ) + ( 0.0722 * $b );
$contrast_with_black = ( $luminance + 0.05 ) / 0.05;
$contrast_with_white = 1.05 / ( $luminance + 0.05 );
return ( $contrast_with_black >= $contrast_with_white ) ? '#000000' : '#FFFFFF';
} // End get_contrast_color()
/**
* Get the site's domain name, with options to capitalize and/or remove the extension
*
* @param bool $capitalize Whether to capitalize the domain name
* @param bool $remove_ext Whether to remove the domain extension (e.g. .com)
* @return string The processed domain name
*/
public static function get_domain( $capitalize = false, $remove_ext = false ) : string {
$host = wp_parse_url( home_url(), PHP_URL_HOST );
if ( ! $host ) {
return '';
}
$host = strtolower( $host );
if ( ! $capitalize && ! $remove_ext ) {
return $host;
}
$pos = strrpos( $host, '.' );
if ( $pos === false ) {
return $capitalize ? strtoupper( $host ) : $host;
}
$prefix = substr( $host, 0, $pos );
$suffix = substr( $host, $pos + 1 );
if ( $capitalize ) {
$prefix = strtoupper( $prefix );
}
return $remove_ext ? $prefix : $prefix . '.' . $suffix;
} // End get_domain()
/**
* Check if we're currently on a Gutenberg page
*
* @return bool
*/
public static function is_gutenberg() : bool {
if ( function_exists( 'is_gutenberg_page' ) && is_gutenberg_page() ) {
return true;
}
if ( ! function_exists( 'get_current_screen' ) ) {
return false;
}
$screen = get_current_screen();
if ( ! $screen ) {
return false;
}
return method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor();
} // End is_gutenberg()
/**
* Get a human-readable time difference string (e.g. "2 hours ago") from a given date
*
* @param string $datetime Date string in a format recognized by strtotime()
* @return string Human-readable time difference
*/
public static function time_elapsed_string( $datetime ) : string {
$timestamp = strtotime( $datetime );
if ( ! $timestamp ) {
return 'just now';
}
$time_diff = human_time_diff( $timestamp );
return $time_diff . ' ago';
} // End time_elapsed_string()
/**
* Base64 URL-safe encoding
*
* @param string $data Data to encode
* @return string URL-safe Base64 encoded string
*/
public static function base64url_encode( $data ) : string {
return rtrim( strtr( base64_encode( $data ), '+/', '-_' ), '=' );
} // End base64url_encode()
/**
* Base64 URL-safe decoding
*
* @param string $data URL-safe Base64 encoded string
* @return string Decoded data
*/
public static function base64url_decode( $data ) : string {
return base64_decode( str_pad( strtr( $data, '-_', '+/' ), strlen( $data ) % 4, '=', STR_PAD_RIGHT ) );
} // End base64url_decode()
/**
* Remove query strings from url without refresh
*
* @param null|string|array $qs Query strings to remove; can be a string or array of keys. *
* @return void
*/
public static function remove_qs_without_refresh( $qs = null ) {
if ( ! is_null( $qs ) ) {
if ( ! is_array( $qs ) ) {
$qs = [ $qs ];
}
$new_url = remove_query_arg( $qs, self::get_current_url() );
} else {
$new_url = self::get_current_url( false );
}
$args = [
'title' => is_admin() ? get_admin_page_title() : get_the_title(),
'url' => $new_url,
];
$handle = 'helpdocs-remove-qs';
wp_enqueue_script(
$handle,
Bootstrap::url( 'inc/js/qs-remove.js' ),
[ 'jquery' ],
Bootstrap::script_version(),
true
);
wp_localize_script( $handle, 'helpdocs_remove_qs', $args );
} // End remove_qs_without_refresh()
/**
* Add query strings to url without refresh
*
* @param string|array $qs
* @param string|array $value
* @return void
*/
public static function add_qs_without_refresh( $qs, $value ) {
$new_url = add_query_arg( $qs, $value, self::get_current_url() );
$args = [
'title' => is_admin() ? get_admin_page_title() : get_the_title(),
'url' => $new_url,
];
$handle = 'helpdocs-add-qs';
wp_enqueue_script(
$handle,
Bootstrap::url( 'inc/js/qs-add.js' ),
[ 'jquery' ],
Bootstrap::script_version(),
true
);
wp_localize_script( $handle, 'helpdocs_add_qs', $args );
} // End add_qs_without_refresh()
/**
* Get the plugin page title, which can be customized in settings
*
* @return string The page title
*/
public static function page_title() {
return sanitize_text_field( get_option( 'helpdocs_page_title', Bootstrap::name() ) );
} // End page_title()
/**
* Get the list of available Dashicons, which can be filtered by other code
*
* @return array List of Dashicon class names
*/
public static function get_dashicons() {
$dashicons = [
'menu', 'admin-site', 'dashboard', 'admin-media', 'admin-page', 'admin-comments', 'admin-appearance', 'admin-plugins', 'admin-users', 'admin-tools', 'admin-settings',
'admin-network', 'admin-generic', 'admin-home', 'admin-collapse', 'filter', 'admin-customizer', 'admin-multisite', 'admin-links', 'format-links', 'admin-post',
'format-standard', 'format-image', 'format-gallery', 'format-audio', 'format-video', 'format-chat', 'format-status', 'format-aside', 'format-quote', 'welcome-write-blog',
'welcome-edit-page', 'welcome-add-page', 'welcome-view-site', 'welcome-widgets-menus', 'welcome-comments', 'welcome-learn-more', 'image-crop', 'image-rotate', 'image-rotate-left',
'image-rotate-right', 'image-flip-vertical', 'image-flip-horizontal', 'image-filter', 'undo', 'redo', 'editor-bold', 'editor-italic', 'editor-ul', 'editor-ol', 'editor-quote',
'editor-alignleft', 'editor-aligncenter', 'editor-alignright', 'editor-insertmore', 'editor-spellcheck', 'editor-distractionfree', 'editor-expand', 'editor-contract',
'editor-kitchensink', 'editor-underline', 'editor-justify', 'editor-textcolor', 'editor-paste-word', 'editor-paste-text', 'editor-removeformatting', 'editor-video',
'editor-customchar', 'editor-outdent', 'editor-indent', 'editor-help', 'editor-strikethrough', 'editor-unlink', 'editor-rtl', 'editor-break', 'editor-code', 'editor-paragraph',
'editor-table', 'align-left', 'align-right', 'align-center', 'align-none', 'lock', 'unlock', 'calendar', 'calendar-alt', 'visibility', 'hidden', 'post-status', 'edit',
'post-trash', 'trash', 'sticky', 'external', 'arrow-up', 'arrow-down', 'arrow-left', 'arrow-right', 'arrow-up-alt', 'arrow-down-alt', 'arrow-left-alt', 'arrow-right-alt',
'arrow-up-alt2', 'arrow-down-alt2', 'arrow-left-alt2', 'arrow-right-alt2', 'leftright', 'sort', 'randomize', 'list-view', 'excerpt-view', 'grid-view', 'hammer', 'art', 'migrate',
'performance', 'universal-access', 'universal-access-alt', 'tickets', 'nametag', 'clipboard', 'heart', 'megaphone', 'schedule', 'wordpress', 'wordpress-alt', 'pressthis', 'update',
'screenoptions', 'cart', 'feedback', 'cloud', 'translation', 'tag', 'category', 'archive', 'tagcloud', 'text', 'media-archive', 'media-audio', 'media-code', 'media-default',
'media-document', 'media-interactive', 'media-spreadsheet', 'media-text', 'media-video', 'playlist-audio', 'playlist-video', 'controls-play', 'controls-pause', 'controls-forward',
'controls-skipforward', 'controls-back', 'controls-skipback', 'controls-repeat', 'controls-volumeon', 'controls-volumeoff', 'yes', 'no', 'no-alt', 'plus', 'plus-alt',
'plus-alt2', 'minus', 'dismiss', 'marker', 'star-filled', 'star-half', 'star-empty', 'flag', 'info', 'warning', 'share', 'share1', 'share-alt', 'share-alt2', 'twitter', 'rss',
'email', 'email-alt', 'facebook', 'facebook-alt', 'networking', 'googleplus', 'location', 'location-alt', 'camera', 'images-alt', 'images-alt2', 'video-alt', 'video-alt2', 'video-alt3',
'vault', 'shield', 'shield-alt', 'sos', 'search', 'slides', 'analytics', 'chart-pie', 'chart-bar', 'chart-line', 'chart-area', 'groups', 'businessman', 'id', 'id-alt', 'products',
'awards', 'forms', 'testimonial', 'portfolio', 'book', 'book-alt', 'download', 'upload', 'backup', 'clock', 'lightbulb', 'microphone', 'desktop', 'tablet', 'smartphone', 'phone',
'smiley', 'index-card', 'carrot', 'building', 'store', 'album', 'palmtree', 'tickets-alt', 'money', 'thumbs-up', 'thumbs-down', 'layout', 'align-pull-left', 'align-pull-right',
'block-default', 'cloud-saved', 'cloud-upload', 'columns', 'cover-image', 'embed-audio', 'embed-generic', 'embed-photo', 'embed-post', 'embed-video', 'exit', 'html', 'info-outline',
'insert-after', 'insert-before', 'insert', 'remove', 'shortcode', 'table-col-after', 'table-col-before', 'table-col-delete', 'table-row-after', 'table-row-before', 'table-row-delete',
'saved', 'amazon', 'google', 'linkedin', 'pinterest', 'podio', 'reddit', 'spotify', 'twitch', 'whatsapp', 'xing', 'youtube', 'database-add', 'database-export', 'database-import',
'database-remove', 'database-view', 'database', 'bell', 'airplane', 'car', 'calculator', 'ames', 'printer', 'beer', 'coffee', 'drumstick', 'food', 'bank', 'hourglass', 'money-alt',
'open-folder', 'pdf', 'pets', 'privacy', 'superhero', 'superhero-alt', 'edit-page', 'fullscreen-alt', 'fullscreen-exit-alt'
];
$dashicons = apply_filters( 'helpdocs_dashicons', $dashicons );
sort( $dashicons );
return $dashicons;
} // End get_dashicons()
/**
* Get the currently selected Dashicon for the plugin's menu, with a default fallback
*
* @return string The Dashicon class name (e.g. 'dashicons-admin-site')
*/
public static function get_icon() {
$icon = sanitize_text_field( get_option( 'helpdocs_dashicon', 'dashicons-editor-help' ) );
if ( ! str_starts_with( $icon, 'dashicons-' ) ) {
$icon = 'dashicons-' . $icon;
}
return $icon;
} // End get_icon()
/**
* Output the logo image HTML if the option to include the logo is enabled
*
* @return void
*/
public static function maybe_include_logo() {
if ( ! get_option( 'helpdocs_include_logo_on_docs', true ) ) {
return;
}
$logo_url = sanitize_text_field( get_option( 'helpdocs_doc_logo', self::get_page_or_default_logo_url() ) );
if ( $logo_url ) {
return '