*/ namespace RankMath\Frontend; use stdClass; use RankMath\Helper; use RankMath\Traits\Hooker; use MyThemeShop\Helpers\Str; use MyThemeShop\Helpers\Url; use MyThemeShop\Helpers\HTML; defined( 'ABSPATH' ) || exit; /** * Add Attributes class. */ class Add_Attributes { use Hooker; /** * The Constructor. */ public function __construct() { $this->action( 'wp_head', 'add_attributes', 99 ); } /** * Add nofollow, target, title and alt attributes to link and images. */ public function add_attributes() { // Add rel="nofollow" & target="_blank" for external links. $this->add_noopener = $this->do_filter( 'noopener', true ); $this->nofollow_link = Helper::get_settings( 'general.nofollow_external_links' ); $this->nofollow_image = Helper::get_settings( 'general.nofollow_image_links' ); $this->new_window_link = Helper::get_settings( 'general.new_window_external_links' ); if ( $this->nofollow_link || $this->new_window_link || $this->nofollow_image || $this->add_noopener ) { $this->filter( 'the_content', 'add_link_attributes', 11 ); } // Add image title and alt. $this->is_alt = Helper::get_settings( 'general.add_img_alt' ) && Helper::get_settings( 'general.img_alt_format' ) ? trim( Helper::get_settings( 'general.img_alt_format' ) ) : false; $this->is_title = Helper::get_settings( 'general.add_img_title' ) && Helper::get_settings( 'general.img_title_format' ) ? trim( Helper::get_settings( 'general.img_title_format' ) ) : false; if ( $this->is_alt || $this->is_title ) { $this->filter( 'the_content', 'add_img_attributes', 11 ); $this->filter( 'post_thumbnail_html', 'add_img_attributes', 11 ); $this->filter( 'woocommerce_single_product_image_thumbnail_html', 'add_img_attributes', 11 ); } } /** * Add nofollow and target attributes to link. * * @param string $content Post content. * @return string */ public function add_link_attributes( $content ) { preg_match_all( '/<(a\s[^>]+)>/', $content, $matches ); if ( empty( $matches ) || empty( $matches[0] ) ) { return $content; } foreach ( $matches[0] as $link ) { $is_dirty = false; $attrs = HTML::extract_attributes( $link ); if ( ! $this->can_add_attributes( $attrs ) ) { continue; } if ( $this->should_add_nofollow( $attrs['href'] ) ) { if ( $this->nofollow_link || ( $this->nofollow_image && $this->is_valid_image( $attrs['href'] ) ) ) { $is_dirty = true; $this->set_rel_attribute( $attrs, 'nofollow', ( isset( $attrs['rel'] ) && ! Str::contains( 'dofollow', $attrs['rel'] ) && ! Str::contains( 'nofollow', $attrs['rel'] ) ) ); } } if ( $this->new_window_link && ! isset( $attrs['target'] ) ) { $is_dirty = true; $attrs['target'] = '_blank'; } if ( $this->add_noopener && $this->do_filter( 'noopener/domain', Url::get_domain( $attrs['href'] ) ) ) { $is_dirty = true; $this->set_rel_attribute( $attrs, 'noopener', ( isset( $attrs['rel'] ) && ! Str::contains( 'noopener', $attrs['rel'] ) ) ); } if ( $is_dirty ) { $new = ''; $content = str_replace( $link, $new, $content ); } } return $content; } /** * Set rel attribute. * * @param array $attrs Array which hold rel attribute. * @param string $property Property to add. * @param boolean $append Append or not. */ private function set_rel_attribute( &$attrs, $property, $append ) { if ( empty( $attrs['rel'] ) ) { $attrs['rel'] = $property; return; } if ( $append ) { $attrs['rel'] .= ' ' . $property; } } /** * Check if we can do anything * * @param array $attrs Array of link attributes. * * @return boolean */ private function can_add_attributes( $attrs ) { // If link has no href attribute or if the link is not valid then we don't need to do anything. if ( empty( $attrs['href'] ) || empty( parse_url( $attrs['href'], PHP_URL_HOST ) ) || ( isset( $attrs['role'] ) && 'button' === $attrs['role'] ) ) { return false; } // Skip if there is no href or it's a hash link like "#id". // Skip if relative link. // Skip for same domain ignoring sub-domain if any. if ( ! Url::is_external( $attrs['href'] ) ) { return false; } return true; } /** * Check if we need to add nofollow for this link, based on "nofollow_domains" & "nofollow_exclude_domains" * * @param string $url Link URL. * @return bool */ private function should_add_nofollow( $url ) { if ( ! $this->nofollow_link && ! $this->nofollow_image ) { return false; } $include_domains = $this->get_nofollow_domains( 'include' ); $exclude_domains = $this->get_nofollow_domains( 'exclude' ); $parent_domain = Url::get_domain( $url ); // Check if domain is in list. if ( ! empty( $include_domains ) ) { return Str::contains( $parent_domain, $include_domains ); } // Check if domains is NOT in list. if ( ! empty( $exclude_domains ) && Str::contains( $parent_domain, $exclude_domains ) ) { return false; } return true; } /** * Get domain for nofollow * * @param string $type Type either include or exclude. * @return array */ private function get_nofollow_domains( $type ) { static $rank_math_nofollow_domains; if ( isset( $rank_math_nofollow_domains[ $type ] ) ) { return $rank_math_nofollow_domains[ $type ]; } $setting = 'include' === $type ? 'nofollow_domains' : 'nofollow_exclude_domains'; $domains = Helper::get_settings( "general.{$setting}" ); $domains = Str::to_arr_no_empty( $domains ); $rank_math_nofollow_domains[ $type ] = empty( $domains ) ? false : join( ';', $domains ); return $rank_math_nofollow_domains[ $type ]; } /** * Is a valid image url. * * @param string $url Image url. * * @return boolean */ private function is_valid_image( $url ) { foreach ( [ '.jpg', '.jpeg', '.png', '.gif' ] as $ext ) { if ( Str::contains( $ext, $url ) ) { return true; } } return false; } /** * Add 'title' and 'alt' attribute to image. * * @param string $content Post content. * @return string */ public function add_img_attributes( $content ) { if ( empty( $content ) ) { return $content; } $stripped_content = preg_replace( '@<(script|style)[^>]*?>.*?@si', '', $content ); preg_match_all( '/]+)\/?>/iU', $stripped_content, $matches, PREG_SET_ORDER ); if ( empty( $matches ) ) { return $content; } $post = $this->get_post(); foreach ( $matches as $image ) { $is_dirty = false; $attrs = HTML::extract_attributes( $image[0] ); if ( ! isset( $attrs['src'] ) ) { continue; } $post->filename = $attrs['src']; $this->set_image_attribute( $attrs, 'alt', $this->is_alt, $is_dirty, $post ); $this->set_image_attribute( $attrs, 'title', $this->is_title, $is_dirty, $post ); if ( $is_dirty ) { $new = ''; $content = str_replace( $image[0], $new, $content ); } } return $content; } /** * Get post object. * * @return object */ private function get_post() { $post = \get_post(); if ( empty( $post ) ) { $post = new stdClass; } return $post; } /** * Set image attribute after checking condition. * * @param array $attrs Array which hold rel attribute. * @param string $attribute Attribute to set. * @param boolean $condition Condition to check. * @param boolean $is_dirty Is dirty variable. * @param object $post Post Object. */ private function set_image_attribute( &$attrs, $attribute, $condition, &$is_dirty, $post ) { if ( $condition && empty( $attrs[ $attribute ] ) ) { $is_dirty = true; $attrs[ $attribute ] = trim( Helper::replace_vars( $condition, $post ) ); } } }