PluginProbe
Admin and Site Enhancements (ASE) / 9.0.2
Admin and Site Enhancements (ASE) v9.0.2
9.1.1 9.1.0 9.0.2 9.0.1 9.0.0 8.9.2 8.9.1 8.9.0 8.8.8 8.8.7 8.8.6 8.8.5 8.8.4 8.8.3 8.8.2 8.8.1 8.8.0 8.7.3 8.7.2 8.7.1 8.2.1 8.2.2 8.2.3 8.3.0 8.3.1 All 273 releases
admin-site-enhancements / classes / class-open-external-links-in-new-tab.php
class-open-external-links-in-new-tab.php
154 lines 6.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace ASENHA\Classes;
4
5 /**
6 * Class for Open All External Links in New Tab module
7 *
8 * @since 6.9.5
9 */
10 class Open_External_Links_In_New_Tab {
11 /**
12 * Parse links in content to add target="_blank" rel="noopener noreferrer nofollow" attributes
13 *
14 * @since 4.9.0
15 * @param string $content HTML content after the_content.
16 * @return string
17 */
18 public function add_target_and_rel_atts_to_content_links( $content ) {
19 if ( empty( $content ) ) {
20 return $content;
21 }
22 $exclude_new_tab_rules = array();
23 $exclude_nofollow_rules = array();
24 // regex pattern for "a href"
25 $regexp = "<a\\s[^>]*href=(\"??)([^\" >]*?)\\1[^>]*>";
26 if ( !preg_match_all(
27 "/{$regexp}/siU",
28 $content,
29 $matches,
30 PREG_SET_ORDER
31 ) ) {
32 return $content;
33 }
34 foreach ( $matches as $match ) {
35 $original_tag = $match[0];
36 // e.g. <a title="Link Title" href="http://www.example.com/sit-quaerat">
37 $tag = $match[0];
38 // Same value as $original_tag but for further processing
39 $url = $match[2];
40 // e.g. http://www.example.com/sit-quaerat
41 if ( false !== strpos( $url, get_site_url() ) ) {
42 // Internal link. Do nothing.
43 continue;
44 }
45 if ( false === strpos( $url, 'http' ) ) {
46 // Relative link to internal URL. Do nothing.
47 continue;
48 }
49 // External link. Let's do something.
50 $omit_new_tab = !empty( $exclude_new_tab_rules ) && self::url_host_matches_any_rule( $url, $exclude_new_tab_rules );
51 $omit_nofollow = !empty( $exclude_nofollow_rules ) && self::url_host_matches_any_rule( $url, $exclude_nofollow_rules );
52 if ( false === $omit_new_tab ) {
53 // Regex pattern for target="_blank|parent|self|top"
54 $pattern = '/target\\s*=\\s*"\\s*_(blank|parent|self|top)\\s*"/';
55 if ( 0 === preg_match( $pattern, $tag ) ) {
56 // Replace closing > with ' target="_blank">'.
57 $tag = substr_replace( $tag, ' target="_blank">', -1 );
58 }
59 }
60 // If there's no 'rel' attribute in $tag, add rel.
61 $pattern = '/rel\\s*=\\s*\\"[a-zA-Z0-9_\\s]*\\"/';
62 if ( 0 === preg_match( $pattern, $tag ) ) {
63 if ( false === $omit_nofollow ) {
64 $tag = substr_replace( $tag, ' rel="noopener noreferrer nofollow">', -1 );
65 } else {
66 $tag = substr_replace( $tag, ' rel="noopener noreferrer">', -1 );
67 }
68 } elseif ( false === $omit_nofollow ) {
69 // replace rel="noopener" with rel="noopener noreferrer nofollow".
70 if ( false !== strpos( $tag, 'noopener' ) && false === strpos( $tag, 'noreferrer' ) && false === strpos( $tag, 'nofollow' ) ) {
71 $tag = str_replace( 'noopener', 'noopener noreferrer nofollow', $tag );
72 }
73 } else {
74 // Extend existing rel with noreferrer when nofollow must not be added.
75 if ( false !== strpos( $tag, 'noopener' ) && false === strpos( $tag, 'noreferrer' ) && false === strpos( $tag, 'nofollow' ) ) {
76 $tag = str_replace( 'noopener', 'noopener noreferrer', $tag );
77 }
78 }
79 // Replace original a href tag with one containing target and rel attributes above.
80 $content = str_replace( $original_tag, $tag, $content );
81 }
82 return $content;
83 }
84
85 /**
86 * Normalize textarea lines to lowercase trimmed host suffix rules (Pro caller only).
87 * Option strings are sanitized with sanitize_textarea_field(); keep newline separation here.
88 *
89 * @param string $textarea Saved option string.
90 * @return string[] Non-empty lowercase rules.
91 */
92 private static function parse_domain_rules_from_textarea( $textarea ) {
93 if ( !is_string( $textarea ) || '' === $textarea ) {
94 return array();
95 }
96 $lines = preg_split( '/\\r\\n|\\r|\\n/', $textarea );
97 $rules = array();
98 foreach ( $lines as $line ) {
99 $line = strtolower( trim( $line ) );
100 if ( '' !== $line ) {
101 $rules[] = $line;
102 }
103 }
104 return $rules;
105 }
106
107 /**
108 * Whether a URL's host matches any suffix rule (exact host or subdomain of rule).
109 * Administrators should avoid excessively short suffix rules (e.g. TLD-only); matching is naive suffix-based.
110 *
111 * @param string $url Full absolute URL including scheme.
112 * @param array<int, string> $rules Lowercased rules from textarea.
113 * @return bool
114 */
115 private static function url_host_matches_any_rule( $url, array $rules ) {
116 if ( '' === $url || empty( $rules ) ) {
117 return false;
118 }
119 $hostname = wp_parse_url( $url, PHP_URL_HOST );
120 if ( empty( $hostname ) || !is_string( $hostname ) ) {
121 return false;
122 }
123 $hostname_lc = strtolower( $hostname );
124 foreach ( $rules as $rule ) {
125 if ( self::hostname_matches_rule_lowercase( $hostname_lc, $rule ) ) {
126 return true;
127 }
128 }
129 return false;
130 }
131
132 /**
133 * Core host vs rule comparison (both arguments lowercased).
134 *
135 * @param string $hostname_lc Parsed link host from wp_parse_url, lowercased.
136 * @param string $rule_lc Trimmed textarea line, lowercased.
137 * @return bool
138 */
139 private static function hostname_matches_rule_lowercase( $hostname_lc, $rule_lc ) {
140 if ( '' === $hostname_lc || '' === $rule_lc ) {
141 return false;
142 }
143 if ( $hostname_lc === $rule_lc ) {
144 return true;
145 }
146 $suffix = '.' . $rule_lc;
147 if ( strlen( $hostname_lc ) <= strlen( $rule_lc ) ) {
148 return false;
149 }
150 return substr( $hostname_lc, -strlen( $suffix ) ) === $suffix;
151 }
152
153 }
154