| @@ -64,8 +64,10 @@ | ||
| 64 | 64 | // Add Custom Default Gravatar Image |
| 65 | 65 | if (!empty($this->custom_gravatar) && array_key_exists('enable', $this->custom_gravatar)) { |
| 66 | 66 | // Add Custom Default Gravatar Image |
| 67 | 67 | add_filter('avatar_defaults', [$this, 'add_custom_gravatar_image'], 99); |
| 68 | + // ...and serve it from this site rather than through gravatar.com. | |
| 69 | + add_filter('get_avatar_data', [$this, 'serve_custom_gravatar_locally'], 99); | |
| 68 | 70 | } |
| 69 | 71 | |
| 70 | 72 | // If Admin bar Editor Plugin not Installed |
| 71 | 73 | if (! class_exists('\JewelTheme\AdminBarEditor\AdminBarEditor')) { |
| @@ -539,26 +541,147 @@ | ||
| 539 | 541 | */ |
| 540 | 542 | $wp_admin_bar->remove_node('wp-logo'); |
| 541 | 543 | } |
| 542 | 544 | |
| 545 | + /** | |
| 546 | + * The avatar images an administrator configured in the plugin settings. | |
| 547 | + * | |
| 548 | + * Keyed by URL, because that is the identifier WordPress stores in the | |
| 549 | + * `avatar_default` option and hands back to `get_avatar()` later. | |
| 550 | + * | |
| 551 | + * @return array<string,string> Avatar URL => label. | |
| 552 | + */ | |
| 553 | + private function custom_gravatar_images() | |
| 554 | + { | |
| 555 | + $images = []; | |
| 556 | + | |
| 557 | + if ( empty( $this->custom_gravatar['image'] ) || ! is_array( $this->custom_gravatar['image'] ) ) { | |
| 558 | + return $images; | |
| 559 | + } | |
| 560 | + | |
| 561 | + foreach ( $this->custom_gravatar['image'] as $value ) { | |
| 562 | + if ( empty( $value['avatar_image']['url'] ) ) { | |
| 563 | + continue; | |
| 564 | + } | |
| 565 | + | |
| 566 | + $avatar_url = esc_url_raw( $value['avatar_image']['url'] ); | |
| 567 | + $images[ $avatar_url ] = ! empty( $value['avatar_name'] ) | |
| 568 | + ? sanitize_text_field( $value['avatar_name'] ) | |
| 569 | + : __( 'Custom Gravatar', 'adminify' ); | |
| 570 | + } | |
| 571 | + | |
| 572 | + return $images; | |
| 573 | + } | |
| 574 | + | |
| 543 | 575 | // Custom Avatars |
| 544 | 576 | public function add_custom_gravatar_image($avatar_defaults) |
| 545 | 577 | { |
| 546 | 578 | // Register the avatar images configured by the administrator in the plugin settings. |
| 547 | - if ( ! empty( $this->custom_gravatar['image'] ) && is_array( $this->custom_gravatar['image'] ) ) { | |
| 548 | - foreach ( $this->custom_gravatar['image'] as $value ) { | |
| 549 | - if ( empty( $value['avatar_image']['url'] ) ) { | |
| 550 | - continue; | |
| 551 | - } | |
| 552 | - $avatar_url = esc_url_raw( $value['avatar_image']['url'] ); | |
| 553 | - $avatar_name = ! empty( $value['avatar_name'] ) ? sanitize_text_field( $value['avatar_name'] ) : __( 'Custom Gravatar', 'adminify' ); | |
| 554 | - $avatar_defaults[ $avatar_url ] = $avatar_name; | |
| 555 | - } | |
| 579 | + return array_merge( (array) $avatar_defaults, $this->custom_gravatar_images() ); | |
| 580 | + } | |
| 581 | + | |
| 582 | + /** | |
| 583 | + * Serve a custom default avatar from this site instead of through gravatar.com. | |
| 584 | + * | |
| 585 | + * Core hands the chosen default to Gravatar as the `d=` query arg, which means | |
| 586 | + * gravatar.com has to fetch the image from THIS server before it can serve it | |
| 587 | + * back. That only works when the URL is publicly resolvable: on a local install, | |
| 588 | + * a staging site behind HTTP auth, or an intranet, Gravatar cannot reach it and | |
| 589 | + * every avatar that falls back to the default comes out broken. | |
| 590 | + * | |
| 591 | + * Gravatar precedence is preserved: a user who genuinely has a Gravatar still | |
| 592 | + * gets theirs, because the custom image is only substituted once | |
| 593 | + * email_has_gravatar() has established there is nothing to fall back FROM. That | |
| 594 | + * keeps "Default Avatar" meaning what WordPress says it means. | |
| 595 | + * | |
| 596 | + * @param array $args Avatar data, after processing. | |
| 597 | + * @return array | |
| 598 | + */ | |
| 599 | + public function serve_custom_gravatar_locally( $args ) | |
| 600 | + { | |
| 601 | + if ( empty( $args['default'] ) || ! is_string( $args['default'] ) ) { | |
| 602 | + return $args; | |
| 556 | 603 | } |
| 557 | 604 | |
| 558 | - return $avatar_defaults; | |
| 605 | + $images = $this->custom_gravatar_images(); | |
| 606 | + | |
| 607 | + if ( ! isset( $images[ $args['default'] ] ) ) { | |
| 608 | + return $args; | |
| 609 | + } | |
| 610 | + | |
| 611 | + // `force_default` means the caller asked for the default image specifically - | |
| 612 | + // the radio list on Settings > Discussion renders every option that way - so | |
| 613 | + // there is nothing to look up. | |
| 614 | + if ( empty( $args['force_default'] ) && $this->email_has_gravatar( $args['url'] ) ) { | |
| 615 | + return $args; | |
| 616 | + } | |
| 617 | + | |
| 618 | + // `found_avatar` is deliberately untouched: it only decides whether | |
| 619 | + // get_avatar() adds the `avatar-default` class, which is correct here. | |
| 620 | + $args['url'] = $args['default']; | |
| 621 | + | |
| 622 | + return $args; | |
| 559 | 623 | } |
| 560 | 624 | |
| 625 | + /** | |
| 626 | + * Whether the address behind a gravatar.com URL actually has an avatar there. | |
| 627 | + * | |
| 628 | + * `d=404` is Gravatar's documented way of asking the question: instead of serving | |
| 629 | + * a fallback image it answers 404 when the address is unknown. The answer is | |
| 630 | + * cached per hash, so a busy comment thread costs one request per new address | |
| 631 | + * rather than one per avatar rendered. | |
| 632 | + * | |
| 633 | + * The hash is read back out of the URL core just built rather than recomputed, | |
| 634 | + * which avoids re-implementing its resolution of user IDs, comments, posts, | |
| 635 | + * WP_User objects and raw hashes - and means a URL another plugin has already | |
| 636 | + * pointed somewhere else is left alone. | |
| 637 | + * | |
| 638 | + * @param string $avatar_url The avatar URL core built. | |
| 639 | + * @return bool True when the address has a Gravatar, or when it is not safe to say. | |
| 640 | + */ | |
| 641 | + private function email_has_gravatar( $avatar_url ) | |
| 642 | + { | |
| 643 | + if ( empty( $avatar_url ) || ! is_string( $avatar_url ) ) { | |
| 644 | + return true; | |
| 645 | + } | |
| 646 | + | |
| 647 | + $path = wp_parse_url( $avatar_url, PHP_URL_PATH ); | |
| 648 | + $hash = $path ? basename( $path ) : ''; | |
| 649 | + | |
| 650 | + // SHA-256 in current core (hash('sha256', ...) in get_avatar_data), MD5 in | |
| 651 | + // older releases. Anything else is not a gravatar.com avatar URL - another | |
| 652 | + // plugin has already pointed it elsewhere - and is none of our business. | |
| 653 | + if ( ! preg_match( '/^[a-f0-9]{32}$|^[a-f0-9]{64}$/i', $hash ) ) { | |
| 654 | + return true; | |
| 655 | + } | |
| 656 | + | |
| 657 | + $cache_key = 'adminify_has_gravatar_' . $hash; | |
| 658 | + $cached = get_transient( $cache_key ); | |
| 659 | + | |
| 660 | + if ( false !== $cached ) { | |
| 661 | + return '1' === $cached; | |
| 662 | + } | |
| 663 | + | |
| 664 | + $response = wp_remote_head( | |
| 665 | + 'https://secure.gravatar.com/avatar/' . $hash . '?d=404&s=1', | |
| 666 | + array( 'timeout' => 3 ) | |
| 667 | + ); | |
| 668 | + | |
| 669 | + if ( is_wp_error( $response ) ) { | |
| 670 | + // Offline, blocked, or too slow. Show the custom image rather than a URL | |
| 671 | + // gravatar.com may not be able to resolve either, and ask again soon - | |
| 672 | + // a temporary outage should not pin a real Gravatar out of view for a week. | |
| 673 | + set_transient( $cache_key, '0', 15 * MINUTE_IN_SECONDS ); | |
| 674 | + return false; | |
| 675 | + } | |
| 676 | + | |
| 677 | + $has_gravatar = 200 === (int) wp_remote_retrieve_response_code( $response ); | |
| 678 | + | |
| 679 | + set_transient( $cache_key, $has_gravatar ? '1' : '0', WEEK_IN_SECONDS ); | |
| 680 | + | |
| 681 | + return $has_gravatar; | |
| 682 | + } | |
| 683 | + | |
| 561 | 684 | // Check Last Login Column |
| 562 | 685 | public function last_login_column_info( $user_login ) |
| 563 | 686 | { |
| 564 | 687 | $user = get_user_by( 'login', $user_login ); |
| @@ -772,15 +895,8 @@ | ||
| 772 | 895 | } |
| 773 | 896 | } |
| 774 | 897 | } |
| 775 | 898 | |
| 776 | - | |
| 777 | - /** Control Interval Heartbeat API **/ | |
| 778 | - public function control_heartbeat_api($settings) | |
| 779 | - { | |
| 780 | - $settings['interval'] = 60; | |
| 781 | - return $settings; | |
| 782 | - } | |
| 783 | 899 | |
| 784 | 900 | /** Remove Query Strings from Scripts/Styles **/ |
| 785 | 901 | public function remove_script_versions($src) |
| 786 | 902 | { |