suffix pairs, checked in descending order of divisor. * @param float $number The number being abbreviated. */ $abbreviations = apply_filters( 'tptn_number_format_abbreviations', array( 1000000000 => 'B', 1000000 => 'M', 1000 => 'k', ), $number ); krsort( $abbreviations, SORT_NUMERIC ); /** * Filters the number of decimal places used when abbreviating a number. * * @since 4.4.0 * * @param int $decimals Maximum number of decimal places. * @param float $number The number being abbreviated. */ $decimals = absint( apply_filters( 'tptn_abbreviate_number_decimals', $decimals, $number ) ); $divisors = array_keys( $abbreviations ); foreach ( $divisors as $i => $divisor ) { if ( abs( $number ) >= $divisor ) { $value = round( $number / $divisor, $decimals ); // Rounding can push the value into the next tier, e.g. 999950 => 1000k. Bump it to 1M instead. if ( $i > 0 && abs( $value ) * $divisor >= $divisors[ $i - 1 ] ) { $divisor = $divisors[ $i - 1 ]; $value = round( $number / $divisor, $decimals ); } $suffix = $abbreviations[ $divisor ]; $precision = ( floor( $value ) === $value ) ? 0 : $decimals; return number_format_i18n( $value, $precision ) . $suffix; } } return number_format_i18n( $number ); } /** * Convert a string to CSV. * * @since 2.9.0 * * @param array $input Input string. * @param string $delimiter Delimiter. * @param string $enclosure Enclosure. * @param string $terminator Terminating string. * @return string CSV string. */ public static function str_putcsv( $input, $delimiter = ',', $enclosure = '"', $terminator = "\n" ) { // First convert associative array to numeric indexed array. $work_array = array(); foreach ( $input as $key => $value ) { $work_array[] = $value; } $string = ''; $input_size = count( $work_array ); for ( $i = 0; $i < $input_size; $i++ ) { // Nested array, process nest item. if ( is_array( $work_array[ $i ] ) ) { $string .= self::str_putcsv( $work_array[ $i ], $delimiter, $enclosure, $terminator ); } else { switch ( gettype( $work_array[ $i ] ) ) { case 'NULL': $formatted = ''; break; case 'boolean': $formatted = ( true === $work_array[ $i ] ) ? 'true' : 'false'; break; case 'integer': $formatted = (string) (int) $work_array[ $i ]; break; case 'double': $formatted = number_format( (float) $work_array[ $i ], 2, '.', '' ); break; case 'string': $formatted = str_replace( $enclosure, $enclosure . $enclosure, (string) $work_array[ $i ] ); break; default: $formatted = ''; break; } $string .= $enclosure . $formatted . $enclosure; $string .= ( $i < ( $input_size - 1 ) ) ? $delimiter : $terminator; } } return $string; } /** * Truncate a string to a certain length. * * @since 2.5.4 * * @param string $input String to truncate. * @param int $count Maximum number of characters to take. * @param string $more What to append if $input needs to be trimmed. * @param bool $break_words Optionally choose to break words. * @return string Truncated string. */ public static function trim_char( $input, $count = 60, $more = '…', $break_words = false ) { $input = wp_strip_all_tags( $input, true ); if ( 0 === $count ) { return ''; } if ( mb_strlen( $input ) > $count && $count > 0 ) { $count -= min( $count, mb_strlen( $more ) ); if ( ! $break_words ) { $input = preg_replace( '/\s+?(\S+)?$/u', '', mb_substr( $input, 0, $count + 1 ) ); } $input = mb_substr( $input, 0, $count ) . $more; } /** * Filters truncated string. * * @since 2.4.0 * * @param string $input String to truncate. * @param int $count Maximum number of characters to take. * @param string $more What to append if $input needs to be trimmed. * @param bool $break_words Optionally choose to break words. */ return apply_filters( 'tptn_trim_char', $input, $count, $more, $break_words ); } /** * Get the WP_Query arguments. * * @return array WP_Query arguments. */ public static function get_wp_query_arguments() { $arguments = array( // Author Parameters. 'author' => '', 'author_name' => '', 'author__in' => array(), 'author__not_in' => array(), // Category Parameters. 'cat' => '', 'category_name' => '', 'category__and' => array(), 'category__in' => array(), 'category__not_in' => array(), // Tag Parameters. 'tag' => '', 'tag_id' => '', 'tag__and' => array(), 'tag__in' => array(), 'tag__not_in' => array(), 'tag_slug__and' => array(), 'tag_slug__in' => array(), // Search Parameters. 'search_columns' => array(), 'exact' => false, 'sentence' => false, // Post & Page Parameters. 'p' => '', 'name' => '', 'page_id' => '', 'pagename' => '', 'post__in' => array(), 'post__not_in' => array(), 'post_parent' => '', 'post_parent__in' => array(), 'post_parent__not_in' => array(), 'post_name__in' => array(), // Password Parameters. 'has_password' => false, 'post_password' => null, // Post Type Parameters. 'post_type' => '', // Status Parameters. 'post_status' => '', // Comment Parameters. 'comment_count' => '', // Pagination Parameters. 'posts_per_page' => '', // Order & Orderby Parameters. 'orderby' => '', 'order' => '', // Date Parameters. 'year' => '', 'monthnum' => '', 'day' => '', 'hour' => '', 'minute' => '', 'second' => '', // Custom Field (post meta) Parameters. 'meta_key' => '', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'meta_value' => '', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value 'meta_value_num' => '', 'meta_compare' => '', ); return $arguments; } /** * Parse WP_Query variables to parse comma separated list of IDs and convert them to arrays as needed by WP_Query. * * @param array $query_vars Defined query variables. * @return array Complete query variables with undefined ones filled in empty. */ public static function parse_wp_query_arguments( $query_vars ) { $array_keys = array( 'category__in', 'category__not_in', 'category__and', 'post__in', 'post__not_in', 'post_name__in', 'tag__in', 'tag__not_in', 'tag__and', 'tag_slug__in', 'tag_slug__and', 'post_parent__in', 'post_parent__not_in', 'author__in', 'author__not_in', ); foreach ( $array_keys as $key ) { if ( isset( $query_vars[ $key ] ) ) { $query_vars[ $key ] = wp_parse_list( $query_vars[ $key ] ); } } return $query_vars; } /** * Check if the user agent matches known bots. * * Uses the jaybizzle/crawler-detect library for the primary check, and falls back to * a filterable list of patterns for bots that library doesn't recognise (or if the * library isn't available at all). * * @since 3.3.0 * @since 4.4.0 Switched to jaybizzle/crawler-detect for the primary detection. * @link https://github.com/JayBizzle/Crawler-Detect * * @return bool True if the user agent matches a known bot pattern, false otherwise. */ public static function is_bot() { if ( ! isset( $_SERVER['HTTP_USER_AGENT'] ) ) { return false; } if ( class_exists( CrawlerDetect::class ) ) { $crawler_detect = new CrawlerDetect(); if ( $crawler_detect->isCrawler() ) { return true; } } $user_agent = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); /** * Filter the list of known bot user agent patterns to check against. * * Defaults to a bundled list, used as a fallback alongside (or instead of, if the * jaybizzle/crawler-detect library failed to load) the library's own detection. * * @since 3.3.0 * * @param array $bot_user_agents List of bot user agent patterns. */ $bot_user_agents = apply_filters( 'tptn_bots', self::get_default_bot_user_agents() ); foreach ( $bot_user_agents as $bot_user_agent ) { if ( preg_match( '/' . preg_quote( strtolower( $bot_user_agent ), '/' ) . '/i', strtolower( $user_agent ) ) ) { return true; } } return false; } /** * Default list of bot user agent patterns, used as a fallback for is_bot(). * * @since 4.4.0 * * @return array List of bot user agent patterns. */ protected static function get_default_bot_user_agents() { return array( '11A465', 'AddThis.com', 'AdsBot-Google', 'Ahrefs', 'alexa site audit', 'AlipesNewsBot', 'Amazonbot', 'Amazon-Route53-Health-Check-Service', 'ApacheBench', 'AppDynamics', 'Applebot', 'ArchiveBot', 'Archive-It', 'AspiegelBot', 'Assetnote', 'axios', 'azure-logic-apps', 'Baiduspider', 'Barkrowler', 'bingbot', 'BLEXBot', 'BLP_bbot', 'BluechipBacklinks', 'Buck', 'Bytespider', 'CCBot', 'check_http', 'CloudFlare-Prefetch', 'cludo.com bot', 'colly', 'contentkingapp', 'Cookiebot', 'CopperEgg', 'crawler4j', 'Csnibot', 'Curebot', 'curl', 'CyotekWebCopy', 'Daum', 'Datadog Agent', 'DataForSeoBot', 'Detectify', 'DotBot', 'Dow Jones Searchbot', 'DuckDuckBot', 'facebookexternalhit', 'Faraday', 'FeedBurner', 'FeedFetcher-Google', 'feedonomics', 'Fess', 'Funnelback', 'Fuzz Faster U Fool', 'GAChecker', 'Ghost Inspector', 'Grapeshot', 'gobuster', 'gocolly', 'Googlebot', 'GoogleStackdriverMonitoring', 'Go-http-client', 'go-resty', 'GuzzleHttp', 'HeadlessChrome', 'heritrix', 'hokifyBot', 'Honolulu-bot', 'HTTrack', 'HubSpot Crawler', 'ICC-Crawler', 'Imperva', 'IonCrawl', 'jooble', 'KauaiBot', 'Kinza', 'LieBaoFast', 'linabot', 'Linespider', 'Linguee', 'LinkChecker', 'LinkedInBot', 'LinkUpBot', 'LinuxGetUrl', 'LMY47V', 'MacOutlook', 'Magnet.me', 'Magus Bot', 'Mail.RU_Bot', 'MauiBot', 'Mb2345Browser', 'MegaIndex', 'Microsoft Office', 'Microsoft Outlook', 'Microsoft Word', 'MicroMessenger', 'mindbreeze-crawler', 'mirrorweb.com', 'MJ12bot', 'monitoring-plugins', 'Monsidobot', 'MQQBrowser', 'msnbot', 'MSOffice', 'MTRobot', 'nagios-plugins', 'nettle', 'Neevabot', 'NewsCred', 'newspaper', 'Nuclei', 'NukeScan', 'OnCrawl', 'Orbbot', 'PageFreezer', 'panscient.com', 'PetalBot', 'Pingdom.com', 'Pinterestbot', 'PiplBot', 'python-requests', 'Qwantify', 'Re-re Studio', 'Riddler', 'RocketValidator', 'rogerbot', 'RustBot', 'Safeassign', 'Scrapy', 'Screaming Frog', 'SeobilityBot', 'Search365bot', 'SearchBlox', 'searchunify', 'Seekport', 'SemanticScholarBot', 'SemrushBot', 'SEOkicks', 'seoscanners', 'serpstatbot', 'SessionCam', 'SeznamBot', 'Site24x7', 'SiteAuditBot', 'siteimprove', 'SiteLockSpider', 'SiteSucker', 'SkypeRoom', 'Slackbot', 'Slurp', 'Sogou web spider', 'special_archiver', 'SpiderLing', 'StatusCake', 'Swiftbot', 'Synack', 'Turnitin', 'trendictionbot', 'trendkite-akashic-crawler', 'UCBrowser', 'Uptime', 'UptimeRobot', 'UT-Dorkbot', 'weborama-fetcher', 'WhiteHat Security', 'Wget', 'WTWBot', 'www.loc.gov', 'Xenu Link Sleuth', 'Vagabondo', 'VelenPublicWebCrawler', 'Yeti', 'Veracode Security Scan', 'YandexBot', 'YandexImages', 'YisouSpider', 'Zabbix', 'ZoominfoBot', 'ZoomSpider', ); } /** * Get all terms of a post. * * @since 4.0.0 * * @param int|\WP_Post $post Post ID or WP_Post object. * @return array Array of taxonomies. */ public static function get_all_terms( $post ) { $taxonomies = array(); if ( ! empty( $post ) ) { $post = get_post( $post ); } if ( ! empty( $post ) ) { $taxonomies = get_object_taxonomies( $post ); } $all_terms = array(); // Loop through the taxonomies and get the terms for the post for each taxonomy. foreach ( $taxonomies as $taxonomy ) { $terms = get_the_terms( $post, $taxonomy ); if ( $terms && ! is_wp_error( $terms ) ) { $all_terms = array_merge( $all_terms, $terms ); } } return $all_terms; } /** * Sanitize args. * * @since 4.1.1 * * @param array $args Array of arguments. * @return array Sanitized array of arguments. */ public static function sanitize_args( $args ): array { foreach ( $args as $key => $value ) { if ( is_string( $value ) ) { switch ( $key ) { case 'class': case 'className': case 'extra_class': $classes = explode( ' ', $value ); $sanitized_classes = array_map( 'sanitize_html_class', $classes ); $args[ $key ] = implode( ' ', $sanitized_classes ); break; default: $args[ $key ] = wp_kses_post( $value ); break; } } } return $args; } }