External Links - Version 5.5.4

Version Description

  • Remove HTML comments added in 5.4.1 to assist in troubleshooting some select site issues
Download this release

Release Info

Developer Mike_Koepke
Plugin Icon wp plugin External Links
Version 5.5.4
Comparing to
See all releases

Version 5.5.4

anchor-utils/anchor-utils.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Anchor Utils
4
+ * Author: Denis de Bernardy & Mike Koepke <http://www.semiologic.com>
5
+ * Version: 1.6.2
6
+ */
7
+
8
+ // no longer used
9
+
external-links-anchor-utils.php ADDED
@@ -0,0 +1,400 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * External Links Anchor Utils
4
+ * Author: Denis de Bernardy & Mike Koepke <http://www.semiologic.com>
5
+ * Version: 1.6.1
6
+ *
7
+ * Forked from Anchor-Utils
8
+ */
9
+
10
+ if ( @ini_get('pcre.backtrack_limit') <= 1000000 )
11
+ @ini_set('pcre.backtrack_limit', 1000000);
12
+ if ( @ini_get('pcre.recursion_limit') <= 250000 )
13
+ @ini_set('pcre.recursion_limit', 250000);
14
+
15
+ /**
16
+ * external_links_anchor_utils
17
+ *
18
+ * @packageExternal Links Anchor Utils
19
+ **/
20
+
21
+ class external_links_anchor_utils {
22
+
23
+ private $external_links = null;
24
+
25
+ /**
26
+ * constructor
27
+ */
28
+ public function __construct( external_links $external_links, $apply_globally = true, $inc_text_widgets = true ) {
29
+
30
+ $this->external_links = $external_links;
31
+
32
+ if ( $apply_globally ) {
33
+ add_action('template_redirect', array($this, 'ob_start'), 100);
34
+ }
35
+ else {
36
+ add_filter('the_content', array($this, 'filter'), 1000000);
37
+ add_filter('the_excerpt', array($this, 'filter'), 1000000);
38
+ add_filter('comment_text', array($this, 'filter'), 1000000);
39
+ if ( $inc_text_widgets )
40
+ add_filter('widget_text', array($this, 'filter'), 1000000);
41
+ }
42
+
43
+ } #external_links_anchor_utils
44
+
45
+
46
+ /**
47
+ * ob_start()
48
+ *
49
+ * @return void
50
+ **/
51
+
52
+ function ob_start() {
53
+ ob_start(array($this, 'ob_filter'));
54
+ add_action('wp_footer', array($this, 'ob_flush'), 1000000);
55
+ } # ob_start()
56
+
57
+ /**
58
+ * ob_filter()
59
+ *
60
+ * @param string $text
61
+ * @return string $text
62
+ **/
63
+
64
+ function ob_filter($text) {
65
+ global $escape_anchor_filter;
66
+ $escape_anchor_filter = array();
67
+
68
+ $text = $this->escape($text);
69
+
70
+ $text = preg_replace_callback("/
71
+ <\s*a\s+
72
+ ([^<>]+)
73
+ >
74
+ (.*?)
75
+ <\s*\/\s*a\s*>
76
+ /isx", array($this, 'ob_filter_callback'), $text);
77
+
78
+ $text = $this->unescape($text);
79
+
80
+ return $text;
81
+ } # ob_filter()
82
+
83
+
84
+ /**
85
+ * ob_flush()
86
+ *
87
+ * @return void
88
+ **/
89
+
90
+ static function ob_flush() {
91
+ ob_end_flush();
92
+ } # ob_flush()
93
+
94
+
95
+ /**
96
+ * ob_filter_callback()
97
+ *
98
+ * @param array $match
99
+ * @return string $str
100
+ **/
101
+
102
+ function ob_filter_callback($match) {
103
+ # skip empty anchors
104
+ if ( !trim($match[2]) )
105
+ return $match[0];
106
+
107
+ # parse anchor
108
+ $anchor = $this->parse_anchor($match);
109
+
110
+ if ( !$anchor )
111
+ return $match[0];
112
+
113
+ # filter anchor
114
+ // $anchor = apply_filters( 'ob_filter_anchor', $anchor );
115
+ $anchor = $this->external_links->filter( $anchor );
116
+
117
+ # return anchor
118
+ return $this->build_anchor($anchor);
119
+ } # ob_filter_callback()
120
+
121
+
122
+ /**
123
+ * filter()
124
+ *
125
+ * @param string $text
126
+ * @return string $text
127
+ **/
128
+
129
+ function filter($text) {
130
+ global $escape_anchor_filter;
131
+ $escape_anchor_filter = array();
132
+
133
+ $text = $this->escape($text);
134
+
135
+ $text = preg_replace_callback("/
136
+ <\s*a\s+
137
+ ([^<>]+)
138
+ >
139
+ (.*?)
140
+ <\s*\/\s*a\s*>
141
+ /isx", array($this, 'filter_callback'), $text);
142
+
143
+ $text = $this->unescape($text);
144
+
145
+ return $text;
146
+ } # filter()
147
+
148
+
149
+ /**
150
+ * filter_callback()
151
+ *
152
+ * @param array $match
153
+ * @return string $str
154
+ **/
155
+
156
+ function filter_callback($match) {
157
+ # skip empty anchors
158
+ if ( !trim($match[2]) )
159
+ return $match[0];
160
+
161
+ # parse anchor
162
+ $anchor = $this->parse_anchor($match);
163
+
164
+ if ( !$anchor )
165
+ return $match[0];
166
+
167
+ # filter anchor
168
+ // $anchor = apply_filters( 'filter_anchor', $anchor );
169
+ $anchor = $this->external_links->filter( $anchor );
170
+
171
+ # return anchor
172
+ return $this->build_anchor($anchor);
173
+ } # filter_callback()
174
+
175
+
176
+ /**
177
+ * parse_anchor()
178
+ *
179
+ * @param array $match
180
+ * @return array $anchor
181
+ **/
182
+
183
+ function parse_anchor($match) {
184
+ $anchor = array();
185
+ $anchor['attr'] = $this->parse_attrs($match[1]);
186
+
187
+ if ( !is_array($anchor['attr']) || empty($anchor['attr']['href']) # parser error or no link
188
+ || trim($anchor['attr']['href']) != esc_url($anchor['attr']['href'], null, 'db') ) # likely a script
189
+ return false;
190
+
191
+ foreach ( array('class', 'rel') as $attr ) {
192
+ if ( !isset($anchor['attr'][$attr]) ) {
193
+ $anchor['attr'][$attr] = array();
194
+ } else {
195
+ $anchor['attr'][$attr] = explode(' ', $anchor['attr'][$attr]);
196
+ $anchor['attr'][$attr] = array_map('trim', $anchor['attr'][$attr]);
197
+ }
198
+ }
199
+
200
+ $anchor['body'] = $match[2];
201
+
202
+ $anchor['attr']['href'] = @html_entity_decode($anchor['attr']['href'], ENT_COMPAT, get_option('blog_charset'));
203
+
204
+ return $anchor;
205
+ } # parse_anchor()
206
+
207
+
208
+ /**
209
+ * build_anchor()
210
+ *
211
+ * @param array $anchor
212
+ * @return string $anchor
213
+ **/
214
+
215
+ function build_anchor($anchor) {
216
+ $anchor['attr']['href'] = esc_url($anchor['attr']['href']);
217
+
218
+ $str = '<a';
219
+ foreach ( $anchor['attr'] as $k => $v ) {
220
+ if ( is_array($v) ) {
221
+ $v = array_unique($v);
222
+ if ( $v )
223
+ $str .= ' ' . $k . '="' . implode(' ', $v) . '"';
224
+ } else {
225
+ if ($k)
226
+ $str .= ' ' . $k . '="' . $v . '"';
227
+ else
228
+ $str .= ' ' . $v;
229
+ }
230
+ }
231
+ $str .= '>' . $anchor['body'] . '</a>';
232
+
233
+ return $str;
234
+ } # build_anchor()
235
+
236
+
237
+ /**
238
+ * escape()
239
+ *
240
+ * @param string $text
241
+ * @return string $text
242
+ **/
243
+
244
+ function escape($text) {
245
+ global $escape_anchor_filter;
246
+
247
+ if ( !isset($escape_anchor_filter) )
248
+ $escape_anchor_filter = array();
249
+
250
+ foreach ( array(
251
+ 'head' => "/
252
+ .*?
253
+ <\s*\/\s*head\s*>
254
+ /isx",
255
+ 'blocks' => "/
256
+ <\s*(script|style|object|textarea)(?:\s.*?)?>
257
+ .*?
258
+ <\s*\/\s*\\1\s*>
259
+ /isx",
260
+ ) as $regex ) {
261
+ $text = preg_replace_callback($regex, array($this, 'escape_callback'), $text);
262
+ }
263
+
264
+ return $text;
265
+ } # escape()
266
+
267
+
268
+ /**
269
+ * escape_callback()
270
+ *
271
+ * @param array $match
272
+ * @return string $text
273
+ **/
274
+
275
+ function escape_callback($match) {
276
+ global $escape_anchor_filter;
277
+
278
+ $tag_id = "----escape_external_links_anchor_utils:" . md5($match[0]) . "----";
279
+ $escape_anchor_filter[$tag_id] = $match[0];
280
+
281
+ return $tag_id;
282
+ } # escape_callback()
283
+
284
+
285
+ /**
286
+ * unescape()
287
+ *
288
+ * @param string $text
289
+ * @return string $text
290
+ **/
291
+
292
+ function unescape($text) {
293
+ global $escape_anchor_filter;
294
+
295
+ if ( !$escape_anchor_filter )
296
+ return $text;
297
+
298
+ $unescape = array_reverse($escape_anchor_filter);
299
+
300
+ return str_replace(array_keys($unescape), array_values($unescape), $text);
301
+ } # unescape()
302
+
303
+ /**
304
+ * Parse an attributes string into an array. If the string starts with a tag,
305
+ * then the attributes on the first tag are parsed. This parses via a manual
306
+ * loop and is designed to be safer than using DOMDocument.
307
+ *
308
+ * @param string|* $attrs
309
+ * @return array
310
+ *
311
+ * @example parse_attrs( 'src="example.jpg" alt="example"' )
312
+ * @example parse_attrs( '<img src="example.jpg" alt="example">' )
313
+ * @example parse_attrs( '<a href="example"></a>' )
314
+ * @example parse_attrs( '<a href="example">' )
315
+ */
316
+ function parse_attrs($attrs) {
317
+
318
+ if ( !is_scalar($attrs) )
319
+ return (array) $attrs;
320
+
321
+ $attrs = str_split( trim($attrs) );
322
+
323
+ if ( '<' === $attrs[0] ) # looks like a tag so strip the tagname
324
+ while ( $attrs && ! ctype_space($attrs[0]) && $attrs[0] !== '>' )
325
+ array_shift($attrs);
326
+
327
+ $arr = array(); # output
328
+ $name = ''; # for the current attr being parsed
329
+ $value = ''; # for the current attr being parsed
330
+ $mode = 0; # whether current char is part of the name (-), the value (+), or neither (0)
331
+ $stop = false; # delimiter for the current $value being parsed
332
+ $space = ' '; # a single space
333
+ $paren = 0; # in parenthesis for js attrs
334
+
335
+ foreach ( $attrs as $j => $curr ) {
336
+
337
+ if ( $mode < 0 ) {# name
338
+ if ( '=' === $curr ) {
339
+ $mode = 1;
340
+ $stop = false;
341
+ } elseif ( '>' === $curr ) {
342
+ '' === $name or $arr[ $name ] = $value;
343
+ break;
344
+ } elseif ( !ctype_space($curr) ) {
345
+ if ( ctype_space( $attrs[ $j - 1 ] ) ) { # previous char
346
+ '' === $name or $arr[ $name ] = ''; # previous name
347
+ $name = $curr; # initiate new
348
+ } else {
349
+ $name .= $curr;
350
+ }
351
+ }
352
+ } elseif ( $mode > 0 ) {# value
353
+ if ( $paren ) {
354
+ $value .= $curr;
355
+ if ( $curr === "(")
356
+ $paren += 1;
357
+ elseif ( $curr === ")")
358
+ $paren -= 1;
359
+ }
360
+ else {
361
+ if ( $stop === false ) {
362
+ if ( !ctype_space($curr) ) {
363
+ if ( '"' === $curr || "'" === $curr ) {
364
+ $value = '';
365
+ $stop = $curr;
366
+ } else {
367
+ $value = $curr;
368
+ $stop = $space;
369
+ }
370
+ }
371
+ } elseif ( $stop === $space ? ctype_space($curr) : $curr === $stop ) {
372
+ $arr[ $name ] = $value;
373
+ $mode = 0;
374
+ $name = $value = '';
375
+ } else {
376
+ $value .= $curr;
377
+ if ( $curr === "(")
378
+ $paren += 1;
379
+ elseif ( $curr === ")")
380
+ $paren -= 1;
381
+ }
382
+ }
383
+ } else {# neither
384
+
385
+ if ( '>' === $curr )
386
+ break;
387
+ if ( !ctype_space( $curr ) ) {
388
+ # initiate
389
+ $name = $curr;
390
+ $mode = -1;
391
+ }
392
+ }
393
+ }
394
+
395
+ # incl the final pair if it was quoteless
396
+ '' === $name or $arr[ $name ] = $value;
397
+
398
+ return $arr;
399
+ }
400
+ } # external_links_anchor_utils
external.png ADDED
Binary file
readme.txt ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === External Links ===
2
+ Contributors: Denis-de-Bernardy, Mike_Koepke
3
+ Donate link: http://www.semiologic.com/partners/
4
+ Tags: external-links, nofollow, link-target, link-icon, semiologic
5
+ Requires at least: 2.8
6
+ Tested up to: 3.9
7
+ License: GPLv2 or later
8
+ License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
+
10
+ The external links plugin for WordPress lets you process outgoing links differently from internal links.
11
+
12
+
13
+ == Description ==
14
+
15
+ The external links plugin for WordPress lets you process outgoing links differently from internal links.
16
+
17
+ Under Settings / External Links, you can configure the plugin to:
18
+
19
+ - Process all outgoing links, rather than only those within your entries' content and text widgets.
20
+ - Add an external link icon to outgoing links. You can use a class="no_icon" attribute on links to override this.
21
+ - Add rel=nofollow to the links. You can use a rel="follow" attribute on links to override this.
22
+ - Open outgoing links in new windows. Note that this can damage your visitor's trust towards your site in that they can think your site used a pop-under.
23
+
24
+ = Help Me! =
25
+
26
+ The [Semiologic forum](http://forum.semiologic.com) is the best place to report issues. Please note, however, that while community members and I do our best to answer all queries, we're assisting you on a voluntary basis.
27
+
28
+ If you require more dedicated assistance, consider using [Semiologic Pro](http://www.semiologic.com).
29
+
30
+
31
+ == Installation ==
32
+
33
+ 1. Upload the plugin folder to the `/wp-content/plugins/` directory
34
+ 2. Activate the plugin through the 'Plugins' menu in WordPress
35
+
36
+
37
+ == Change Log ==
38
+
39
+ = 5.5.4 =
40
+
41
+ - Remove HTML comments added in 5.4.1 to assist in troubleshooting some select site issues
42
+
43
+ = 5.5.3 =
44
+
45
+ - Use template_redirect hook and put ourselves after default hooks that most 404/redirect plugins want to use and Yoast's at priority 99999
46
+
47
+ = 5.5.2 =
48
+
49
+ - Fix compatibility with Yoast WP SEO plugin when Force Title Rewrite option is on and using the Apply Globally setting of this plugin.
50
+
51
+ = 5.5.1 =
52
+
53
+ - Additional tweak to global callback processing
54
+
55
+ = 5.5 =
56
+
57
+ - Use wp_print_footer_scripts hook instead of wp_footer as some themes fail to call wp_footer();
58
+ - Use own custom version of the anchor_utils class
59
+ - Content, excerpt and comment filters no longer called when Apply Globally is selected. Improves performance.
60
+
61
+ = 5.4.1 =
62
+
63
+ - Troubleshooting release. Adds a few html comments in the page source to ensure hooks are being called.
64
+
65
+ = 5.4 =
66
+
67
+ - Handle nested parenthesis in javascript event attributes on links and images
68
+
69
+ = 5.3.2 =
70
+
71
+ - Temporarily placeholders links - http:// and https:// (no other url components) are no longer processed.
72
+
73
+ = 5.3.1 =
74
+
75
+ - Fix localization
76
+
77
+ = 5.3 =
78
+
79
+ - Fix: Conflict with Auto Thickbox plugin that would result in text widgets still being filtered even though option was turned off
80
+ - Fix: Ensure this plugin filter is executed way back in the change to prevent other plugins/themes from reversing our changes
81
+ - Code refactoring
82
+ - WP 3.9 compat
83
+
84
+ = 5.2.1 =
85
+
86
+ - Checks for new sem_dofollow class to determine if Do Follow plugin is active
87
+ - WP 3.8 compat
88
+
89
+ = 5.2 =
90
+
91
+ - Further updates to the link attribute parsing code
92
+ - Fixed bug where external link was not processed if it was preceded by an empty text anchor link.
93
+
94
+ = 5.1 =
95
+
96
+ - Take two! With issues now with breaking google adsense code reverted back to 4.2 parsing code but added more advanced dom attribute parsing code to handle various link configurations.
97
+
98
+ = 5.0 =
99
+
100
+ - Completely replaced the mechanism for parsing links to resolve the various errors that have been occurring with different external services' link attributes
101
+ - Tested with WP 3.7
102
+
103
+ = 4.2 =
104
+
105
+ - WP 3.6 compat
106
+ - PHP 5.4 compat
107
+ - Fixed issue with parsing of links with non-standard (class, href, rel, target) attributes included in the <a> tag. This caused Twitter Widgets to break.
108
+ - Fixed issue where the external link icon was not added if the url specified by href had a preceding space href=" http://www.example.com"
109
+ - Fixed issue with links containing onClick (or other javascript event) attributes with embedded javascript code. WordPress' Threaded Comments does this
110
+ - Fixed issue with 2 spaces being injected between <a and class/href/rel/etc. i.e <a href="http://example.com">
111
+
112
+ = 4.1 =
113
+
114
+ - WP 3.5 compat
115
+
116
+ = 4.0.6 =
117
+
118
+ - WP 3.0.1 compat
119
+
120
+ = 4.0.5 =
121
+
122
+ - WP 3.0 compat
123
+
124
+ = 4.0.4 =
125
+
126
+ - Force a higher pcre.backtrack_limit and pcre.recursion_limit to avoid blank screens on large posts
127
+
128
+ = 4.0.3 =
129
+
130
+ - Improve case-insensitive handling of domains
131
+ - Improve image handling
132
+ - Switch back to using a target attribute: work around double windows getting opened in Vista/IE7
133
+ - Disable entirely in feeds
134
+
135
+ = 4.0.2 =
136
+
137
+ - Don't enforce new window pref in feeds
138
+
139
+ = 4.0.1 =
140
+
141
+ - Ignore case when comparing domains
142
+
143
+ = 4.0 =
144
+
145
+ - Allow to force a follow when the nofollow option is toggled
146
+ - Enhance escape/unescape methods
147
+ - Localization
148
+ - Code enhancements and optimizations
sem-external-links-admin.php ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * external_links_admin
4
+ *
5
+ * @package External Links
6
+ **/
7
+
8
+ class external_links_admin {
9
+ /**
10
+ * external_links_admin()
11
+ */
12
+ public function __construct() {
13
+ add_action('settings_page_external-links', array($this, 'save_options'), 0);
14
+ }
15
+
16
+ /**
17
+ * save_options()
18
+ *
19
+ * @return void
20
+ **/
21
+
22
+ function save_options() {
23
+ if ( !$_POST || !current_user_can('manage_options') )
24
+ return;
25
+
26
+ check_admin_referer('external_links');
27
+
28
+ foreach ( array('global', 'icon', 'target', 'nofollow', 'text_widgets') as $var )
29
+ $$var = isset($_POST[$var]);
30
+
31
+ update_option('external_links', compact('global', 'icon', 'target', 'nofollow', 'text_widgets'));
32
+
33
+ echo "<div class=\"updated fade\">\n"
34
+ . "<p>"
35
+ . "<strong>"
36
+ . __('Settings saved.', 'external-links')
37
+ . "</strong>"
38
+ . "</p>\n"
39
+ . "</div>\n";
40
+ } # save_options()
41
+
42
+
43
+ /**
44
+ * edit_options()
45
+ *
46
+ * @return void
47
+ **/
48
+
49
+ static function edit_options() {
50
+ echo '<div class="wrap">' . "\n"
51
+ . '<form method="post" action="">';
52
+
53
+ wp_nonce_field('external_links');
54
+
55
+ $options = external_links::get_options();
56
+
57
+ if ( $options['nofollow'] && ( function_exists('strip_nofollow') || class_exists('sem_dofollow') ) ) {
58
+ echo "<div class=\"error\">\n"
59
+ . "<p>"
60
+ . __('Note: Your rel=nofollow preferences is being ignored because the dofollow plugin is enabled on your site.', 'external-links')
61
+ . "</p>\n"
62
+ . "</div>\n";
63
+ }
64
+
65
+ echo '<h2>' . __('External Links Settings', 'external-links') . '</h2>' . "\n";
66
+
67
+ echo '<table class="form-table">' . "\n";
68
+
69
+ echo '<tr>' . "\n"
70
+ . '<th scope="row">'
71
+ . __('Apply Globally', 'external-links')
72
+ . '</th>' . "\n"
73
+ . '<td>'
74
+ . '<label>'
75
+ . '<input type="checkbox" name="global"'
76
+ . checked($options['global'], true, false)
77
+ . ' />'
78
+ . '&nbsp;'
79
+ . __('Apply these settings to all outbound links on the site except those in scripts, styles and the html head section.', 'external-links')
80
+ . '</label>'
81
+ . '</td>' . "\n"
82
+ . '</tr>' . "\n";
83
+
84
+ echo '<tr>' . "\n"
85
+ . '<th scope="row">'
86
+ . __('Apply to Text Widgets', 'external-links')
87
+ . '</th>' . "\n"
88
+ . '<td>'
89
+ . '<label>'
90
+ . '<input type="checkbox" name="text_widgets"'
91
+ . checked($options['text_widgets'], true, false)
92
+ . ' />'
93
+ . '&nbsp;'
94
+ . __('Apply these settings to all text widgets in addition to those in posts and comments.', 'external-links')
95
+ . '</label>'
96
+ . '</td>' . "\n"
97
+ . '</tr>' . "\n";
98
+
99
+ echo '<tr>' . "\n"
100
+ . '<th scope="row">'
101
+ . __('Add Icons', 'external-links')
102
+ . '</th>' . "\n"
103
+ . '<td>'
104
+ . '<label>'
105
+ . '<input type="checkbox" name="icon"'
106
+ . checked($options['icon'], true, false)
107
+ . ' />'
108
+ . '&nbsp;'
109
+ . __('Mark outbound links with an icon.', 'external-links')
110
+ . '</label>'
111
+ . '<br />' . "\n"
112
+ . __('Note: You can override this behavior by adding a class="no_icon" to individual links.', 'external-links')
113
+ . '</td>' . "\n"
114
+ . '</tr>' . "\n";
115
+
116
+ echo '<tr>' . "\n"
117
+ . '<th scope="row">'
118
+ . __('Add No Follow', 'external-links')
119
+ . '</th>' . "\n"
120
+ . '<td>'
121
+ . '<label>'
122
+ . '<input type="checkbox" name="nofollow"'
123
+ . checked($options['nofollow'], true, false)
124
+ . ' />'
125
+ . '&nbsp;'
126
+ . __('Add a rel=nofollow attribute to outbound links.', 'external-links')
127
+ . '</label>'
128
+ . '<br />' . "\n"
129
+ . __('Note: You can override this behavior by adding the attribute rel="follow" to individual links.', 'external-links')
130
+ . '</td>' . "\n"
131
+ . '</tr>' . "\n";
132
+
133
+ echo '<tr>' . "\n"
134
+ . '<th scope="row">'
135
+ . __('Open in New Windows', 'external-links')
136
+ . '</th>' . "\n"
137
+ . '<td>'
138
+ . '<label>'
139
+ . '<input type="checkbox" name="target"'
140
+ . checked($options['target'], true, false)
141
+ . ' />'
142
+ . '&nbsp;'
143
+ . __('Open outbound links in new windows.', 'external-links')
144
+ . '</label>'
145
+ . '<br />' . "\n"
146
+ . __('Note: Some usability experts discourage this, claiming that <a href="http://www.useit.com/alertbox/9605.html">this can damage your visitors\' trust</a> towards your site. Others highlight that computer-illiterate users do not always know how to use the back button, and encourage the practice for that reason.', 'external-links')
147
+ . '</td>' . "\n"
148
+ . '</tr>' . "\n";
149
+
150
+ echo '</table>' . "\n";
151
+
152
+ echo '<p class="submit">'
153
+ . '<input type="submit"'
154
+ . ' value="' . esc_attr(__('Save Changes', 'external-links')) . '"'
155
+ . ' />'
156
+ . '</p>' . "\n";
157
+
158
+ echo '</form>' . "\n"
159
+ . '</div>' . "\n";
160
+ } # edit_options()
161
+ } # external_links_admin
162
+
163
+ $external_links_admin = new external_links_admin();
sem-external-links-info.php ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <?php
2
+ __('External Links', 'external-links');
3
+ __('Marks outbound links as such, with various effects that are configurable under <a href="options-general.php?page=external-links">Settings / External Links</a>.', 'external-links');
4
+ ?>
sem-external-links.css ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ a.external_icon {
2
+ background: url(external.png) center right no-repeat;
3
+ padding-right: 13px;
4
+ }
sem-external-links.php ADDED
@@ -0,0 +1,356 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Plugin Name: External Links
4
+ Plugin URI: http://www.semiologic.com/software/external-links/
5
+ Description: Marks outbound links as such, with various effects that are configurable under <a href="options-general.php?page=external-links">Settings / External Links</a>.
6
+ Version: 5.5.4
7
+ Author: Denis de Bernardy & Mike Koepke
8
+ Author URI: http://www.getsemiologic.com
9
+ Text Domain: external-links
10
+ Domain Path: /lang
11
+ License: Dual licensed under the MIT and GPLv2 licenses
12
+ */
13
+
14
+ /*
15
+ Terms of use
16
+ ------------
17
+
18
+ This software is copyright Denis de Bernardy & Mike Koepke, and is distributed under the terms of the MIT and GPLv2 licenses.
19
+
20
+ **/
21
+
22
+ /**
23
+ * external_links
24
+ *
25
+ * @package External Links
26
+ **/
27
+
28
+ class external_links {
29
+
30
+ protected $opts;
31
+
32
+ protected $anchor_utils;
33
+
34
+ /**
35
+ * Plugin instance.
36
+ *
37
+ * @see get_instance()
38
+ * @type object
39
+ */
40
+ protected static $instance = NULL;
41
+
42
+ /**
43
+ * URL to this plugin's directory.
44
+ *
45
+ * @type string
46
+ */
47
+ public $plugin_url = '';
48
+
49
+ /**
50
+ * Path to this plugin's directory.
51
+ *
52
+ * @type string
53
+ */
54
+ public $plugin_path = '';
55
+
56
+ /**
57
+ * Access this plugin’s working instance
58
+ *
59
+ * @wp-hook plugins_loaded
60
+ * @return object of this class
61
+ */
62
+ public static function get_instance()
63
+ {
64
+ NULL === self::$instance and self::$instance = new self;
65
+
66
+ return self::$instance;
67
+ }
68
+
69
+ /**
70
+ * Loads translation file.
71
+ *
72
+ * Accessible to other classes to load different language files (admin and
73
+ * front-end for example).
74
+ *
75
+ * @wp-hook init
76
+ * @param string $domain
77
+ * @return void
78
+ */
79
+ public function load_language( $domain )
80
+ {
81
+ load_plugin_textdomain(
82
+ $domain,
83
+ FALSE,
84
+ dirname(plugin_basename(__FILE__)) . '/lang'
85
+ );
86
+ }
87
+
88
+ /**
89
+ * Constructor.
90
+ *
91
+ *
92
+ */
93
+ public function __construct() {
94
+ $this->plugin_url = plugins_url( '/', __FILE__ );
95
+ $this->plugin_path = plugin_dir_path( __FILE__ );
96
+ $this->load_language( 'external-links' );
97
+
98
+ add_action( 'plugins_loaded', array ( $this, 'init' ) );
99
+ }
100
+
101
+
102
+ /**
103
+ * init()
104
+ *
105
+ * @return void
106
+ **/
107
+
108
+ function init() {
109
+ // more stuff: register actions and filters
110
+ if ( !is_admin() ) {
111
+ if ( !class_exists('external_links_anchor_utils') )
112
+ include $this->plugin_path . '/external-links-anchor-utils.php';
113
+
114
+ $o = external_links::get_options();
115
+
116
+ // add_filter(($o['global'] ? 'ob_' : '' ) . 'filter_anchor', array($this, 'filter'));
117
+
118
+ $inc_text_widgets = false;
119
+ if ( isset( $o['text_widgets'] ) && $o['text_widgets'] )
120
+ $inc_text_widgets = true;
121
+
122
+ $this->anchor_utils = new external_links_anchor_utils( $this, $o['global'], $inc_text_widgets );
123
+
124
+ if ( $o['icon'] )
125
+ add_action('wp_enqueue_scripts', array($this, 'styles'), 5);
126
+
127
+ unset($o);
128
+ }
129
+ else {
130
+ add_action('admin_menu', array($this, 'admin_menu'));
131
+ add_action('load-settings_page_external-links', array($this, 'external_links_admin'));
132
+ }
133
+ }
134
+
135
+ /**
136
+ * external_links_admin()
137
+ *
138
+ * @return void
139
+ **/
140
+ function external_links_admin() {
141
+ include_once $this->plugin_path . '/sem-external-links-admin.php';
142
+ }
143
+
144
+ /**
145
+ * styles()
146
+ *
147
+ * @return void
148
+ **/
149
+
150
+ function styles() {
151
+ $folder = plugin_dir_url(__FILE__);
152
+ wp_enqueue_style('external-links', $folder . 'sem-external-links.css', null, '20090903');
153
+ } # styles()
154
+
155
+
156
+ /**
157
+ * filter()
158
+ *
159
+ * @param $anchor
160
+ * @return string
161
+ */
162
+
163
+ function filter($anchor) {
164
+ # disable in feeds
165
+ if ( is_feed() )
166
+ return $anchor;
167
+
168
+ # ignore local urls
169
+ if ( external_links::is_local_url($anchor['attr']['href']) )
170
+ return $anchor;
171
+
172
+ # no icons for images
173
+ $is_image = (bool) preg_match("/^\s*<\s*img\s.+?>\s*$/is", $anchor['body']);
174
+
175
+ $o = external_links::get_options();
176
+
177
+ if ( !in_array('external', $anchor['attr']['class']) )
178
+ $anchor['attr']['class'][] = 'external';
179
+
180
+ if ( !$is_image && $o['icon'] && !in_array('external_icon', $anchor['attr']['class'])
181
+ && !in_array('no_icon', $anchor['attr']['class'])
182
+ && !in_array('noicon', $anchor['attr']['class']) )
183
+ $anchor['attr']['class'][] = 'external_icon';
184
+
185
+ if ( $o['nofollow'] && !function_exists('strip_nofollow')
186
+ && !in_array('nofollow', $anchor['attr']['rel'])
187
+ && !in_array('follow', $anchor['attr']['rel']) )
188
+ $anchor['attr']['rel'][] = 'nofollow';
189
+
190
+ if ( $o['target'] && empty($anchor['attr']['target']) )
191
+ $anchor['attr']['target'] = '_blank';
192
+
193
+ return $anchor;
194
+ } # filter()
195
+
196
+ /**
197
+ * is_local_url()
198
+ *
199
+ * @param string $url
200
+ * @return bool $is_local_url
201
+ **/
202
+
203
+ function is_local_url($url) {
204
+ if ( in_array(substr($url, 0, 1), array('?', '#')) || strpos($url, '://') === false )
205
+ return true;
206
+ elseif ( $url == 'http://' || $url == 'https://' )
207
+ return true;
208
+ elseif ( preg_match("~/go(/|\.)~i", $url) )
209
+ return false;
210
+
211
+ static $site_domain;
212
+
213
+ if ( !isset($site_domain) ) {
214
+ $site_domain = get_option('home');
215
+ $site_domain = parse_url($site_domain);
216
+ $site_domain = $site_domain['host'];
217
+ if ($site_domain == false)
218
+ return false;
219
+ elseif (is_array($site_domain)) {
220
+ if (isset($site_domain['host']))
221
+ $site_domain = $site_domain['host'];
222
+ else
223
+ return false;
224
+ }
225
+ $site_domain = preg_replace("/^www\./i", '', $site_domain);
226
+
227
+ # The following is not bullet proof, but it's good enough for a WP site
228
+ if ( $site_domain != 'localhost' && !preg_match("/\d+(\.\d+){3}/", $site_domain) ) {
229
+ if ( preg_match("/\.([^.]+)$/", $site_domain, $tld) ) {
230
+ $tld = end($tld);
231
+ } else {
232
+ $site_domain = false;
233
+ return false;
234
+ }
235
+
236
+ $site_domain = substr($site_domain, 0, strlen($site_domain) - 1 - strlen($tld));
237
+
238
+ if ( preg_match("/\.([^.]+)$/", $site_domain, $subtld) ) {
239
+ $subtld = end($subtld);
240
+ if ( strlen($subtld) <= 4 ) {
241
+ $site_domain = substr($site_domain, 0, strlen($site_domain) - 1 - strlen($subtld));
242
+ $site_domain = explode('.', $site_domain);
243
+ $site_domain = array_pop($site_domain);
244
+ $site_domain .= ".$subtld";
245
+ } else {
246
+ $site_domain = $subtld;
247
+ }
248
+ }
249
+
250
+ $site_domain .= ".$tld";
251
+ }
252
+
253
+ $site_domain = strtolower($site_domain);
254
+ }
255
+
256
+ if ( !$site_domain )
257
+ return false;
258
+
259
+ $link_domain = @parse_url($url);
260
+ if ($link_domain === false)
261
+ return true;
262
+ elseif (is_array($link_domain)) {
263
+ if (isset($link_domain['host']))
264
+ $link_domain = $link_domain['host'];
265
+ else
266
+ return false;
267
+ }
268
+ $link_domain = preg_replace("/^www\./i", '', $link_domain);
269
+ $link_domain = strtolower($link_domain);
270
+
271
+ if ( $site_domain == $link_domain ) {
272
+ return true;
273
+ } elseif ( function_exists('is_multisite') && is_multisite() ) {
274
+ return false;
275
+ } else {
276
+ $site_elts = explode('.', $site_domain);
277
+ $link_elts = explode('.', $link_domain);
278
+
279
+ while ( ( $site_elt = array_pop($site_elts) ) && ( $link_elt = array_pop($link_elts) ) ) {
280
+ if ( $site_elt !== $link_elt )
281
+ return false;
282
+ }
283
+
284
+ return empty($link_elts) || empty($site_elts);
285
+ }
286
+ } # is_local_url()
287
+
288
+
289
+ /**
290
+ * get_options
291
+ *
292
+ * @return array $options
293
+ **/
294
+
295
+ static function get_options() {
296
+ static $o;
297
+
298
+ if ( !is_admin() && isset($o) )
299
+ return $o;
300
+
301
+ $o = get_option('external_links');
302
+
303
+ if ( $o === false || !isset($o['text_widgets']) )
304
+ $o = external_links::init_options();
305
+
306
+ return $o;
307
+ } # get_options()
308
+
309
+
310
+ /**
311
+ * init_options()
312
+ *
313
+ * @return array $options
314
+ **/
315
+
316
+ function init_options() {
317
+ $o = get_option('external_links');
318
+
319
+ $defaults = array(
320
+ 'global' => false,
321
+ 'icon' => true,
322
+ 'target' => false,
323
+ 'nofollow' => true,
324
+ 'text_widgets' => true,
325
+ );
326
+
327
+ if ( !$o )
328
+ $o = $defaults;
329
+ else
330
+ $o = wp_parse_args($o, $defaults);
331
+
332
+ update_option('external_links', $o);
333
+
334
+ return $o;
335
+ } # init_options()
336
+
337
+
338
+
339
+ /**
340
+ * admin_menu()
341
+ *
342
+ * @return void
343
+ **/
344
+
345
+ function admin_menu() {
346
+ add_options_page(
347
+ __('External Links', 'external-links'),
348
+ __('External Links', 'external-links'),
349
+ 'manage_options',
350
+ 'external-links',
351
+ array('external_links_admin', 'edit_options')
352
+ );
353
+ } # admin_menu()
354
+ } # external_links
355
+
356
+ $external_links = external_links::get_instance();
sem-external-links.pot ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # External Links pot file.
2
+ # Copyright (C) 2010 Mesoconcepts <http://www.mesoconcepts.com>
3
+ # This file is distributed under the same license as the External Links package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ #, fuzzy
7
+ msgid ""
8
+ msgstr ""
9
+ "Project-Id-Version: 4.0.5\n"
10
+ "Report-Msgid-Bugs-To: https://tickets.semiologic.com\n"
11
+ "POT-Creation-Date: 2010-05-13 16:35+0200\n"
12
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+ "Language-Team: LANGUAGE <LL@li.org>\n"
15
+ "MIME-Version: 1.0\n"
16
+ "Content-Type: text/plain; charset=CHARSET\n"
17
+ "Content-Transfer-Encoding: 8bit\n"
18
+
19
+ #: sem-external-links-admin.php:29
20
+ msgid "Settings saved."
21
+ msgstr ""
22
+
23
+ #: sem-external-links-admin.php:53
24
+ msgid ""
25
+ "Note: Your rel=nofollow preferences is being ignored because the dofollow "
26
+ "plugin is enabled on your site."
27
+ msgstr ""
28
+
29
+ #: sem-external-links-admin.php:60
30
+ msgid "External Links Settings"
31
+ msgstr ""
32
+
33
+ #: sem-external-links-admin.php:66
34
+ msgid "Apply Globally"
35
+ msgstr ""
36
+
37
+ #: sem-external-links-admin.php:74
38
+ msgid ""
39
+ "Apply these settings to all outbound links, including those in sidebars, "
40
+ "rather than to those in posts and comments."
41
+ msgstr ""
42
+
43
+ #: sem-external-links-admin.php:81
44
+ msgid "Add Icons"
45
+ msgstr ""
46
+
47
+ #: sem-external-links-admin.php:89
48
+ msgid "Mark outbound links with an icon."
49
+ msgstr ""
50
+
51
+ #: sem-external-links-admin.php:92
52
+ msgid ""
53
+ "Note: You can override this behavior by adding a class=\"no_icon\" to "
54
+ "individual links."
55
+ msgstr ""
56
+
57
+ #: sem-external-links-admin.php:98
58
+ msgid "Add No Follow"
59
+ msgstr ""
60
+
61
+ #: sem-external-links-admin.php:106
62
+ msgid "Add a rel=nofollow attribute to outbound links."
63
+ msgstr ""
64
+
65
+ #: sem-external-links-admin.php:109
66
+ msgid ""
67
+ "Note: You can override this behavior by adding a rel=\"follow\" to "
68
+ "individual links."
69
+ msgstr ""
70
+
71
+ #: sem-external-links-admin.php:115
72
+ msgid "Open in New Windows"
73
+ msgstr ""
74
+
75
+ #: sem-external-links-admin.php:123
76
+ msgid "Open outbound links in new windows."
77
+ msgstr ""
78
+
79
+ #: sem-external-links-admin.php:126
80
+ msgid ""
81
+ "Note: Some usability experts discourage this, claiming that <a href=\"http://"
82
+ "www.useit.com/alertbox/9605.html\">this can damage your visitors' trust</a> "
83
+ "towards your site. Others highlight that computer-illiterate users do not "
84
+ "always know how to use the back button, and encourage the practice for that "
85
+ "reason."
86
+ msgstr ""
87
+
88
+ #: sem-external-links-admin.php:134
89
+ msgid "Save Changes"
90
+ msgstr ""
91
+
92
+ #: sem-external-links-info.php:2 sem-external-links.php:216
93
+ #: sem-external-links.php:217
94
+ msgid "External Links"
95
+ msgstr ""
96
+
97
+ #: sem-external-links-info.php:3
98
+ msgid ""
99
+ "Marks outbound links as such, with various effects that are configurable "
100
+ "under <a href=\"options-general.php?page=external-links\">Settings / "
101
+ "External Links</a>."
102
+ msgstr ""