External Links - Version 5.2.1

Version Description

  • Checks for new sem_dofollow class to determine if Do Follow plugin is active
  • WP 3.8 compat
Download this release

Release Info

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

Code changes from version 5.3 to 5.2.1

Files changed (3) hide show
  1. anchor-utils/anchor-utils.php +384 -2
  2. readme.txt +1 -8
  3. sem-external-links.php +32 -109
anchor-utils/anchor-utils.php CHANGED
@@ -2,8 +2,390 @@
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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  /*
3
  * Anchor Utils
4
  * Author: Denis de Bernardy & Mike Koepke <http://www.semiologic.com>
5
+ * Version: 1.4
6
  */
7
 
8
+ if ( @ini_get('pcre.backtrack_limit') <= 750000 )
9
+ @ini_set('pcre.backtrack_limit', 750000);
10
+ if ( @ini_get('pcre.recursion_limit') <= 250000 )
11
+ @ini_set('pcre.recursion_limit', 250000);
12
 
13
+ /**
14
+ * anchor_utils
15
+ *
16
+ * @package Anchor Utils
17
+ **/
18
+
19
+ class anchor_utils {
20
+
21
+ /**
22
+ * constructor
23
+ */
24
+ public function __construct( $inc_text_widgets = true ) {
25
+ add_filter('the_content', array($this, 'filter'), 100);
26
+ add_filter('the_excerpt', array($this, 'filter'), 100);
27
+ add_filter('comment_text', array($this, 'filter'), 100);
28
+ if ( $inc_text_widgets )
29
+ add_filter('widget_text', array($this, 'filter'), 100);
30
+
31
+ add_action('wp_head', array($this, 'ob_start'), 10000);
32
+ } #anchor_utils
33
+
34
+
35
+ /**
36
+ * ob_start()
37
+ *
38
+ * @return void
39
+ **/
40
+
41
+ function ob_start() {
42
+ static $done = false;
43
+
44
+ if ( $done )
45
+ return;
46
+
47
+ if ( has_filter('ob_filter_anchor') ) {
48
+ ob_start(array($this, 'ob_filter'));
49
+ add_action('wp_footer', array($this, 'ob_flush'), 10000);
50
+ $done = true;
51
+ }
52
+ } # ob_start()
53
+
54
+ /**
55
+ * ob_filter()
56
+ *
57
+ * @param string $text
58
+ * @return string $text
59
+ **/
60
+
61
+ function ob_filter($text) {
62
+ global $escape_anchor_filter;
63
+ $escape_anchor_filter = array();
64
+
65
+ $text = $this->escape($text);
66
+
67
+ $text = preg_replace_callback("/
68
+ <\s*a\s+
69
+ ([^<>]+)
70
+ >
71
+ (.*?)
72
+ <\s*\/\s*a\s*>
73
+ /isx", array($this, 'ob_filter_callback'), $text);
74
+
75
+ $text = $this->unescape($text);
76
+
77
+ return $text;
78
+ } # ob_filter()
79
+
80
+
81
+ /**
82
+ * ob_flush()
83
+ *
84
+ * @return void
85
+ **/
86
+
87
+ static function ob_flush() {
88
+ static $done = true;
89
+
90
+ if ( $done )
91
+ return;
92
+
93
+ ob_end_flush();
94
+ $done = true;
95
+ } # ob_flush()
96
+
97
+
98
+ /**
99
+ * ob_filter_callback()
100
+ *
101
+ * @param array $match
102
+ * @return string $str
103
+ **/
104
+
105
+ function ob_filter_callback($match) {
106
+ # skip empty anchors
107
+ if ( !trim($match[2]) )
108
+ return $match[0];
109
+
110
+ # parse anchor
111
+ $anchor = $this->parse_anchor($match);
112
+
113
+ if ( !$anchor )
114
+ return $match[0];
115
+
116
+ # filter anchor
117
+ $anchor = apply_filters('ob_filter_anchor', $anchor);
118
+
119
+ # return anchor
120
+ return $this->build_anchor($anchor);
121
+ } # ob_filter_callback()
122
+
123
+
124
+ /**
125
+ * filter()
126
+ *
127
+ * @param string $text
128
+ * @return string $text
129
+ **/
130
+
131
+ function filter($text) {
132
+ if ( !has_filter('filter_anchor') )
133
+ return $text;
134
+
135
+ global $escape_anchor_filter;
136
+ $escape_anchor_filter = array();
137
+
138
+ $text = $this->escape($text);
139
+
140
+ $text = preg_replace_callback("/
141
+ <\s*a\s+
142
+ ([^<>]+)
143
+ >
144
+ (.*?)
145
+ <\s*\/\s*a\s*>
146
+ /isx", array($this, 'filter_callback'), $text);
147
+
148
+ $text = $this->unescape($text);
149
+
150
+ return $text;
151
+ } # filter()
152
+
153
+
154
+ /**
155
+ * filter_callback()
156
+ *
157
+ * @param array $match
158
+ * @return string $str
159
+ **/
160
+
161
+ function filter_callback($match) {
162
+ # skip empty anchors
163
+ if ( !trim($match[2]) )
164
+ return $match[0];
165
+
166
+ # parse anchor
167
+ $anchor = $this->parse_anchor($match);
168
+
169
+ if ( !$anchor )
170
+ return $match[0];
171
+
172
+ # filter anchor
173
+ $anchor = apply_filters('filter_anchor', $anchor);
174
+
175
+ # return anchor
176
+ return $this->build_anchor($anchor);
177
+ } # filter_callback()
178
+
179
+
180
+ /**
181
+ * parse_anchor()
182
+ *
183
+ * @param array $match
184
+ * @return array $anchor
185
+ **/
186
+
187
+ function parse_anchor($match) {
188
+ $anchor = array();
189
+ // $anchor['attr'] = shortcode_parse_atts($match[1]);
190
+ $anchor['attr'] = $this->parse_attrs($match[1]);
191
+
192
+ if ( !is_array($anchor['attr']) || empty($anchor['attr']['href']) # parser error or no link
193
+ || trim($anchor['attr']['href']) != esc_url($anchor['attr']['href'], null, 'db') ) # likely a script
194
+ return false;
195
+
196
+ foreach ( array('class', 'rel') as $attr ) {
197
+ if ( !isset($anchor['attr'][$attr]) ) {
198
+ $anchor['attr'][$attr] = array();
199
+ } else {
200
+ $anchor['attr'][$attr] = explode(' ', $anchor['attr'][$attr]);
201
+ $anchor['attr'][$attr] = array_map('trim', $anchor['attr'][$attr]);
202
+ }
203
+ }
204
+
205
+ $anchor['body'] = $match[2];
206
+
207
+ $anchor['attr']['href'] = @html_entity_decode($anchor['attr']['href'], ENT_COMPAT, get_option('blog_charset'));
208
+
209
+ return $anchor;
210
+ } # parse_anchor()
211
+
212
+
213
+ /**
214
+ * build_anchor()
215
+ *
216
+ * @param array $anchor
217
+ * @return string $anchor
218
+ **/
219
+
220
+ function build_anchor($anchor) {
221
+ $anchor['attr']['href'] = esc_url($anchor['attr']['href']);
222
+
223
+ $str = '<a';
224
+ foreach ( $anchor['attr'] as $k => $v ) {
225
+ if ( is_array($v) ) {
226
+ $v = array_unique($v);
227
+ if ( $v )
228
+ $str .= ' ' . $k . '="' . implode(' ', $v) . '"';
229
+ } else {
230
+ if ($k)
231
+ $str .= ' ' . $k . '="' . $v . '"';
232
+ else
233
+ $str .= ' ' . $v;
234
+ }
235
+ }
236
+ $str .= '>' . $anchor['body'] . '</a>';
237
+
238
+ return $str;
239
+ } # build_anchor()
240
+
241
+
242
+ /**
243
+ * escape()
244
+ *
245
+ * @param string $text
246
+ * @return string $text
247
+ **/
248
+
249
+ function escape($text) {
250
+ global $escape_anchor_filter;
251
+
252
+ if ( !isset($escape_anchor_filter) )
253
+ $escape_anchor_filter = array();
254
+
255
+ foreach ( array(
256
+ 'head' => "/
257
+ .*?
258
+ <\s*\/\s*head\s*>
259
+ /isx",
260
+ 'blocks' => "/
261
+ <\s*(script|style|object|textarea)(?:\s.*?)?>
262
+ .*?
263
+ <\s*\/\s*\\1\s*>
264
+ /isx",
265
+ ) as $regex ) {
266
+ $text = preg_replace_callback($regex, array($this, 'escape_callback'), $text);
267
+ }
268
+
269
+ return $text;
270
+ } # escape()
271
+
272
+
273
+ /**
274
+ * escape_callback()
275
+ *
276
+ * @param array $match
277
+ * @return string $text
278
+ **/
279
+
280
+ function escape_callback($match) {
281
+ global $escape_anchor_filter;
282
+
283
+ $tag_id = "----escape_anchor_utils:" . md5($match[0]) . "----";
284
+ $escape_anchor_filter[$tag_id] = $match[0];
285
+
286
+ return $tag_id;
287
+ } # escape_callback()
288
+
289
+
290
+ /**
291
+ * unescape()
292
+ *
293
+ * @param string $text
294
+ * @return string $text
295
+ **/
296
+
297
+ function unescape($text) {
298
+ global $escape_anchor_filter;
299
+
300
+ if ( !$escape_anchor_filter )
301
+ return $text;
302
+
303
+ $unescape = array_reverse($escape_anchor_filter);
304
+
305
+ return str_replace(array_keys($unescape), array_values($unescape), $text);
306
+ } # unescape()
307
+
308
+ /**
309
+ * Parse an attributes string into an array. If the string starts with a tag,
310
+ * then the attributes on the first tag are parsed. This parses via a manual
311
+ * loop and is designed to be safer than using DOMDocument.
312
+ *
313
+ * @param string|* $attrs
314
+ * @return array
315
+ *
316
+ * @example parse_attrs( 'src="example.jpg" alt="example"' )
317
+ * @example parse_attrs( '<img src="example.jpg" alt="example">' )
318
+ * @example parse_attrs( '<a href="example"></a>' )
319
+ * @example parse_attrs( '<a href="example">' )
320
+ */
321
+ function parse_attrs($attrs) {
322
+
323
+ if ( !is_scalar($attrs) )
324
+ return (array) $attrs;
325
+
326
+ $attrs = str_split( trim($attrs) );
327
+
328
+ if ( '<' === $attrs[0] ) # looks like a tag so strip the tagname
329
+ while ( $attrs && ! ctype_space($attrs[0]) && $attrs[0] !== '>' )
330
+ array_shift($attrs);
331
+
332
+ $arr = array(); # output
333
+ $name = ''; # for the current attr being parsed
334
+ $value = ''; # for the current attr being parsed
335
+ $mode = 0; # whether current char is part of the name (-), the value (+), or neither (0)
336
+ $stop = false; # delimiter for the current $value being parsed
337
+ $space = ' '; # a single space
338
+
339
+ foreach ( $attrs as $j => $curr ) {
340
+
341
+ if ( $mode < 0 ) {# name
342
+ if ( '=' === $curr ) {
343
+ $mode = 1;
344
+ $stop = false;
345
+ } elseif ( '>' === $curr ) {
346
+ '' === $name or $arr[ $name ] = $value;
347
+ break;
348
+ } elseif ( !ctype_space($curr) ) {
349
+ if ( ctype_space( $attrs[ $j - 1 ] ) ) { # previous char
350
+ '' === $name or $arr[ $name ] = ''; # previous name
351
+ $name = $curr; # initiate new
352
+ } else {
353
+ $name .= $curr;
354
+ }
355
+ }
356
+ } elseif ( $mode > 0 ) {# value
357
+ if ( $stop === false ) {
358
+ if ( !ctype_space($curr) ) {
359
+ if ( '"' === $curr || "'" === $curr ) {
360
+ $value = '';
361
+ $stop = $curr;
362
+ } else {
363
+ $value = $curr;
364
+ $stop = $space;
365
+ }
366
+ }
367
+ } elseif ( $stop === $space ? ctype_space($curr) : $curr === $stop ) {
368
+ $arr[ $name ] = $value;
369
+ $mode = 0;
370
+ $name = $value = '';
371
+ } else {
372
+ $value .= $curr;
373
+ }
374
+ } else {# neither
375
+
376
+ if ( '>' === $curr )
377
+ break;
378
+ if ( !ctype_space( $curr ) ) {
379
+ # initiate
380
+ $name = $curr;
381
+ $mode = -1;
382
+ }
383
+ }
384
+ }
385
+
386
+ # incl the final pair if it was quoteless
387
+ '' === $name or $arr[ $name ] = $value;
388
+
389
+ return $arr;
390
+ }
391
+ } # anchor_utils
readme.txt CHANGED
@@ -3,7 +3,7 @@ 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
 
@@ -36,13 +36,6 @@ If you require more dedicated assistance, consider using [Semiologic Pro](http:/
36
 
37
  == Change Log ==
38
 
39
- = 5.3 =
40
-
41
- - Fix: Conflict with Auto Thickbox plugin that would result in text widgets still being filtered even though option was turned off
42
- - Fix: Ensure this plugin filter is executed way back in the change to prevent other plugins/themes from reversing our changes
43
- - Code refactoring
44
- - WP 3.9 compat
45
-
46
  = 5.2.1 =
47
 
48
  - Checks for new sem_dofollow class to determine if Do Follow plugin is active
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.8
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
36
 
37
  == Change Log ==
38
 
 
 
 
 
 
 
 
39
  = 5.2.1 =
40
 
41
  - Checks for new sem_dofollow class to determine if Do Follow plugin is active
sem-external-links.php CHANGED
@@ -3,7 +3,7 @@
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.3
7
  Author: Denis de Bernardy & Mike Koepke
8
  Author URI: http://www.getsemiologic.com
9
  Text Domain: external-links
@@ -19,6 +19,10 @@ This software is copyright Denis de Bernardy & Mike Koepke, and is distributed u
19
 
20
  **/
21
 
 
 
 
 
22
  /**
23
  * external_links
24
  *
@@ -31,115 +35,33 @@ class external_links {
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
- $this->plugin_path . '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('anchor_utils') )
112
- include $this->plugin_path . '/anchor-utils/anchor-utils.php';
113
 
114
- $o = external_links::get_options();
115
 
116
- $inc_text_widgets = false;
117
- if ( isset( $o['text_widgets'] ) && $o['text_widgets'] )
118
- $inc_text_widgets = true;
119
 
120
- $this->anchor_utils = new anchor_utils( $inc_text_widgets );
121
 
122
- if ( $o['icon'] )
123
- add_action('wp_enqueue_scripts', array($this, 'styles'), 5);
124
 
125
- add_filter(($o['global'] ? 'ob_' : '' ) . 'filter_anchor', array($this, 'filter'));
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()
@@ -172,14 +94,8 @@ class external_links {
172
  # no icons for images
173
  $is_image = (bool) preg_match("/^\s*<\s*img\s.+?>\s*$/is", $anchor['body']);
174
 
175
- $current_filter = $anchor['current_filter'];
176
-
177
  $o = external_links::get_options();
178
 
179
- # is this is a widget callback and the option is off return;
180
- if ( 'widget_text' == $current_filter && (isset( $o['text_widgets'] ) && !$o['text_widgets']) )
181
- return $anchor;
182
-
183
  if ( !in_array('external', $anchor['attr']['class']) )
184
  $anchor['attr']['class'][] = 'external';
185
 
@@ -357,4 +273,11 @@ class external_links {
357
  } # admin_menu()
358
  } # external_links
359
 
360
- $external_links = external_links::get_instance();
 
 
 
 
 
 
 
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.2.1
7
  Author: Denis de Bernardy & Mike Koepke
8
  Author URI: http://www.getsemiologic.com
9
  Text Domain: external-links
19
 
20
  **/
21
 
22
+
23
+ load_plugin_textdomain('external-links', false, dirname(plugin_basename(__FILE__)) . '/lang');
24
+
25
+
26
  /**
27
  * external_links
28
  *
35
 
36
  protected $anchor_utils;
37
 
38
+ /**
39
+ * constructor()
40
+ */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  public function __construct() {
42
+ if ( !is_admin() ) {
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
+ if ( !class_exists('anchor_utils') )
45
+ include dirname(__FILE__) . '/anchor-utils/anchor-utils.php';
 
 
 
46
 
47
+ $o = external_links::get_options();
48
 
49
+ $inc_text_widgets = false;
50
+ if ( isset( $o['text_widgets'] ) && $o['text_widgets'] )
51
+ $inc_text_widgets = true;
52
 
53
+ $this->anchor_utils = new anchor_utils( $inc_text_widgets );
54
 
55
+ if ( $o['icon'] )
56
+ add_action('wp_enqueue_scripts', array($this, 'styles'), 5);
57
 
58
+ add_filter(($o['global'] ? 'ob_' : '' ) . 'filter_anchor', array($this, 'filter'));
59
 
60
+ unset($o);
61
+ } else {
62
+ add_action('admin_menu', array($this, 'admin_menu'));
63
+ }
64
+ }
 
 
 
 
 
 
 
 
 
 
 
65
 
66
  /**
67
  * styles()
94
  # no icons for images
95
  $is_image = (bool) preg_match("/^\s*<\s*img\s.+?>\s*$/is", $anchor['body']);
96
 
 
 
97
  $o = external_links::get_options();
98
 
 
 
 
 
99
  if ( !in_array('external', $anchor['attr']['class']) )
100
  $anchor['attr']['class'][] = 'external';
101
 
273
  } # admin_menu()
274
  } # external_links
275
 
276
+
277
+ function external_links_admin() {
278
+ include_once dirname(__FILE__) . '/sem-external-links-admin.php';
279
+ }
280
+
281
+ add_action('load-settings_page_external-links', 'external_links_admin');
282
+
283
+ $external_links = new external_links();