| @@ -6,8 +6,10 @@ | ||
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | 8 | namespace WebberZone\Top_Ten\Util; |
| 9 | 9 | |
| 10 | +use Jaybizzle\CrawlerDetect\CrawlerDetect; | |
| 11 | + | |
| 10 | 12 | if ( ! defined( 'WPINC' ) ) { |
| 11 | 13 | die; |
| 12 | 14 | } |
| 13 | 15 | |
| @@ -62,23 +64,51 @@ | ||
| 62 | 64 | */ |
| 63 | 65 | return apply_filters( 'tptn_get_from_date', $from_date, $time, $daily_range, $hour_range ); |
| 64 | 66 | } |
| 65 | 67 | |
| 68 | + /** | |
| 69 | + * Get a human-readable label for a custom period range. | |
| 70 | + * | |
| 71 | + * @since 4.3.2 | |
| 72 | + * | |
| 73 | + * @param int|null $daily_range Number of days. Defaults to the 'daily_range' option. | |
| 74 | + * @return string 'Daily' when the range is 1 day, else 'Custom (N days)'. | |
| 75 | + */ | |
| 76 | + public static function get_daily_range_label( $daily_range = null ) { | |
| 77 | + $daily_range = isset( $daily_range ) ? absint( $daily_range ) : (int) \tptn_get_option( 'daily_range', 1 ); | |
| 66 | 78 | |
| 79 | + if ( 1 === $daily_range ) { | |
| 80 | + return __( 'Daily', 'top-10' ); | |
| 81 | + } | |
| 82 | + | |
| 83 | + return sprintf( | |
| 84 | + /* translators: %d: Number of days. */ | |
| 85 | + __( 'Custom (%d days)', 'top-10' ), | |
| 86 | + $daily_range | |
| 87 | + ); | |
| 88 | + } | |
| 89 | + | |
| 90 | + | |
| 67 | 91 | /** |
| 68 | 92 | * Convert float number to format based on the locale if number_format_count is true. |
| 69 | 93 | * |
| 70 | 94 | * @since 2.6.0 |
| 95 | + * @since 4.4.0 Added the `$style` parameter. | |
| 71 | 96 | * |
| 72 | - * @param float $number The number to convert based on locale. | |
| 73 | - * @param int $decimals Optional. Precision of the number of decimal places. Default 0. | |
| 97 | + * @param float $number The number to convert based on locale. | |
| 98 | + * @param int $decimals Optional. Precision of the number of decimal places. Default 0. | |
| 99 | + * @param string|null $style Optional. 'full' or 'abbreviated'. Default null, which uses the | |
| 100 | + * `number_format_style` setting. | |
| 74 | 101 | * @return string Converted number in string format. |
| 75 | 102 | */ |
| 76 | - public static function number_format_i18n( $number, $decimals = 0 ) { | |
| 103 | + public static function number_format_i18n( $number, $decimals = 0, $style = null ) { | |
| 77 | 104 | |
| 78 | 105 | $formatted = (float) $number; |
| 106 | + $style = $style ? $style : \tptn_get_option( 'number_format_style', 'full' ); | |
| 79 | 107 | |
| 80 | - if ( \tptn_get_option( 'number_format_count' ) ) { | |
| 108 | + if ( 'abbreviated' === $style ) { | |
| 109 | + $formatted = self::abbreviate_number( $formatted ); | |
| 110 | + } elseif ( \tptn_get_option( 'number_format_count' ) ) { | |
| 81 | 111 | $formatted = number_format_i18n( (float) $formatted ); |
| 82 | 112 | } |
| 83 | 113 | |
| 84 | 114 | /** |
| @@ -93,8 +123,75 @@ | ||
| 93 | 123 | return apply_filters( 'number_format_i18n', $formatted, $number, $decimals ); |
| 94 | 124 | } |
| 95 | 125 | |
| 96 | 126 | /** |
| 127 | + * Abbreviate a number, e.g. 1200 becomes 1.2k and 3400000 becomes 3.4M. | |
| 128 | + * | |
| 129 | + * The decimal separator is locale-aware via number_format_i18n(). | |
| 130 | + * | |
| 131 | + * @since 4.4.0 | |
| 132 | + * | |
| 133 | + * @param float|int $number The number to abbreviate. | |
| 134 | + * @param int $decimals Optional. Maximum number of decimal places. Default 1. | |
| 135 | + * @return string Abbreviated number, e.g. 1.2k, 3.4M. Numbers below the lowest | |
| 136 | + * threshold are returned locale-formatted without a suffix. | |
| 137 | + */ | |
| 138 | + public static function abbreviate_number( $number, $decimals = 1 ) { | |
| 139 | + $number = (float) $number; | |
| 140 | + $decimals = absint( $decimals ); | |
| 141 | + | |
| 142 | + /** | |
| 143 | + * Filters the abbreviation thresholds and suffixes. | |
| 144 | + * | |
| 145 | + * @since 4.4.0 | |
| 146 | + * | |
| 147 | + * @param array $abbreviations Array of divisor => suffix pairs, checked in descending order of divisor. | |
| 148 | + * @param float $number The number being abbreviated. | |
| 149 | + */ | |
| 150 | + $abbreviations = apply_filters( | |
| 151 | + 'tptn_number_format_abbreviations', | |
| 152 | + array( | |
| 153 | + 1000000000 => 'B', | |
| 154 | + 1000000 => 'M', | |
| 155 | + 1000 => 'k', | |
| 156 | + ), | |
| 157 | + $number | |
| 158 | + ); | |
| 159 | + krsort( $abbreviations, SORT_NUMERIC ); | |
| 160 | + | |
| 161 | + /** | |
| 162 | + * Filters the number of decimal places used when abbreviating a number. | |
| 163 | + * | |
| 164 | + * @since 4.4.0 | |
| 165 | + * | |
| 166 | + * @param int $decimals Maximum number of decimal places. | |
| 167 | + * @param float $number The number being abbreviated. | |
| 168 | + */ | |
| 169 | + $decimals = absint( apply_filters( 'tptn_abbreviate_number_decimals', $decimals, $number ) ); | |
| 170 | + | |
| 171 | + $divisors = array_keys( $abbreviations ); | |
| 172 | + | |
| 173 | + foreach ( $divisors as $i => $divisor ) { | |
| 174 | + if ( abs( $number ) >= $divisor ) { | |
| 175 | + $value = round( $number / $divisor, $decimals ); | |
| 176 | + | |
| 177 | + // Rounding can push the value into the next tier, e.g. 999950 => 1000k. Bump it to 1M instead. | |
| 178 | + if ( $i > 0 && abs( $value ) * $divisor >= $divisors[ $i - 1 ] ) { | |
| 179 | + $divisor = $divisors[ $i - 1 ]; | |
| 180 | + $value = round( $number / $divisor, $decimals ); | |
| 181 | + } | |
| 182 | + | |
| 183 | + $suffix = $abbreviations[ $divisor ]; | |
| 184 | + $precision = ( floor( $value ) === $value ) ? 0 : $decimals; | |
| 185 | + | |
| 186 | + return number_format_i18n( $value, $precision ) . $suffix; | |
| 187 | + } | |
| 188 | + } | |
| 189 | + | |
| 190 | + return number_format_i18n( $number ); | |
| 191 | + } | |
| 192 | + | |
| 193 | + /** | |
| 97 | 194 | * Convert a string to CSV. |
| 98 | 195 | * |
| 99 | 196 | * @since 2.9.0 |
| 100 | 197 | * |
| @@ -304,10 +401,15 @@ | ||
| 304 | 401 | |
| 305 | 402 | /** |
| 306 | 403 | * Check if the user agent matches known bots. |
| 307 | 404 | * |
| 405 | + * Uses the jaybizzle/crawler-detect library for the primary check, and falls back to | |
| 406 | + * a filterable list of patterns for bots that library doesn't recognise (or if the | |
| 407 | + * library isn't available at all). | |
| 408 | + * | |
| 308 | 409 | * @since 3.3.0 |
| 309 | - * @link https://github.com/janusman/robot-user-agents | |
| 410 | + * @since 4.4.0 Switched to jaybizzle/crawler-detect for the primary detection. | |
| 411 | + * @link https://github.com/JayBizzle/Crawler-Detect | |
| 310 | 412 | * |
| 311 | 413 | * @return bool True if the user agent matches a known bot pattern, false otherwise. |
| 312 | 414 | */ |
| 313 | 415 | public static function is_bot() { |
| @@ -314,11 +416,48 @@ | ||
| 314 | 416 | if ( ! isset( $_SERVER['HTTP_USER_AGENT'] ) ) { |
| 315 | 417 | return false; |
| 316 | 418 | } |
| 317 | 419 | |
| 420 | + if ( class_exists( CrawlerDetect::class ) ) { | |
| 421 | + $crawler_detect = new CrawlerDetect(); | |
| 422 | + | |
| 423 | + if ( $crawler_detect->isCrawler() ) { | |
| 424 | + return true; | |
| 425 | + } | |
| 426 | + } | |
| 427 | + | |
| 318 | 428 | $user_agent = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); |
| 319 | 429 | |
| 320 | - $bot_user_agents = array( | |
| 430 | + /** | |
| 431 | + * Filter the list of known bot user agent patterns to check against. | |
| 432 | + * | |
| 433 | + * Defaults to a bundled list, used as a fallback alongside (or instead of, if the | |
| 434 | + * jaybizzle/crawler-detect library failed to load) the library's own detection. | |
| 435 | + * | |
| 436 | + * @since 3.3.0 | |
| 437 | + * | |
| 438 | + * @param array $bot_user_agents List of bot user agent patterns. | |
| 439 | + */ | |
| 440 | + $bot_user_agents = apply_filters( 'tptn_bots', self::get_default_bot_user_agents() ); | |
| 441 | + | |
| 442 | + foreach ( $bot_user_agents as $bot_user_agent ) { | |
| 443 | + if ( preg_match( '/' . preg_quote( strtolower( $bot_user_agent ), '/' ) . '/i', strtolower( $user_agent ) ) ) { | |
| 444 | + return true; | |
| 445 | + } | |
| 446 | + } | |
| 447 | + | |
| 448 | + return false; | |
| 449 | + } | |
| 450 | + | |
| 451 | + /** | |
| 452 | + * Default list of bot user agent patterns, used as a fallback for is_bot(). | |
| 453 | + * | |
| 454 | + * @since 4.4.0 | |
| 455 | + * | |
| 456 | + * @return array List of bot user agent patterns. | |
| 457 | + */ | |
| 458 | + protected static function get_default_bot_user_agents() { | |
| 459 | + return array( | |
| 321 | 460 | '11A465', |
| 322 | 461 | 'AddThis.com', |
| 323 | 462 | 'AdsBot-Google', |
| 324 | 463 | 'Ahrefs', |
| @@ -496,25 +635,8 @@ | ||
| 496 | 635 | 'Zabbix', |
| 497 | 636 | 'ZoominfoBot', |
| 498 | 637 | 'ZoomSpider', |
| 499 | 638 | ); |
| 500 | - | |
| 501 | - /** | |
| 502 | - * Filter the list of known bots. | |
| 503 | - * | |
| 504 | - * @since 3.3.0 | |
| 505 | - * | |
| 506 | - * @param array $bot_user_agents List of known bots. | |
| 507 | - */ | |
| 508 | - $bot_user_agent = apply_filters( 'tptn_bots', $bot_user_agents ); | |
| 509 | - | |
| 510 | - foreach ( $bot_user_agents as $bot_user_agent ) { | |
| 511 | - if ( preg_match( '/' . preg_quote( strtolower( $bot_user_agent ), '/' ) . '/i', strtolower( $user_agent ) ) ) { | |
| 512 | - return true; | |
| 513 | - } | |
| 514 | - } | |
| 515 | - | |
| 516 | - return false; | |
| 517 | 639 | } |
| 518 | 640 | |
| 519 | 641 | /** |
| 520 | 642 | * Get all terms of a post. |