External Links - Version 6.2

Version Description

  • Additional performance changes for long post/page when Apply Globally is off.
  • Links with no anchor text will have nofollow and/or target set, but no external icon
  • Improved detection and handling of anchor links as internal.
  • Initial support for AddThis placeholder links
Download this release

Release Info

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

Code changes from version 6.1 to 6.2

Files changed (2) hide show
  1. readme.txt +8 -0
  2. sem-external-links.php +176 -165
readme.txt CHANGED
@@ -60,6 +60,14 @@ The plugin supports a non-started rel="follow" attribute on links to override th
60
 
61
  == Change Log ==
62
 
 
 
 
 
 
 
 
 
63
  = 6.1 =
64
 
65
  - The nofollow attribute was not being set if a certain combination of the global, follow comments, set nofollow settings were set.
60
 
61
  == Change Log ==
62
 
63
+ = 6.2 =
64
+
65
+ - Additional performance changes for long post/page when Apply Globally is off.
66
+ - Links with no anchor text will have nofollow and/or target set, but no external icon
67
+ - Improved detection and handling of anchor links as internal.
68
+ - Initial support for AddThis placeholder links
69
+
70
+
71
  = 6.1 =
72
 
73
  - The nofollow attribute was not being set if a certain combination of the global, follow comments, set nofollow settings were set.
sem-external-links.php CHANGED
@@ -3,9 +3,9 @@
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: 6.1
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
@@ -19,7 +19,7 @@ This software is copyright Denis de Bernardy & Mike Koepke, and is distributed u
19
 
20
  **/
21
 
22
- define('sem_external_links_version', '6.1');
23
 
24
  /**
25
  * external_links
@@ -131,11 +131,11 @@ class sem_external_links {
131
  $this->anchor_utils = new external_links_anchor_utils( $this );
132
  }
133
  else {
134
- add_filter('the_content', array($this, 'process_content'), 1000000);
135
- add_filter('the_excerpt', array($this, 'process_content'), 1000000);
136
- add_filter('comment_text', array($this, 'process_content'), 1000000);
137
  if ( $inc_text_widgets )
138
- add_filter('widget_text', array($this, 'process_content'), 1000000);
139
  }
140
  }
141
  else {
@@ -169,19 +169,23 @@ class sem_external_links {
169
  * process_content()
170
  *
171
  * @param string $text
 
172
  * @return string $text
173
  **/
174
 
175
- function process_content($text) {
176
 
177
  // short circuit if there's no anchors at all in the text
178
  if ( false === stripos($text, '<a ') )
179
  return($text);
180
 
181
- global $escape_anchor_filter;
182
- $escape_anchor_filter = array();
 
 
183
 
184
- $text = $this->escape($text);
 
185
 
186
  // find all occurrences of anchors and fill matches with links
187
  preg_match_all("/
@@ -207,36 +211,87 @@ class sem_external_links {
207
  if ( !empty($raw_links) && !empty($processed_links) )
208
  $text = str_replace($raw_links, $processed_links, $text);
209
 
210
- $text = $this->unescape($text);
 
 
211
 
212
  return $text;
213
  } # process_content()
214
 
215
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  /**
217
  * escape()
218
  *
219
  * @param string $text
 
220
  * @return string $text
221
  **/
222
 
223
- function escape($text) {
224
  global $escape_anchor_filter;
225
 
226
  if ( !isset($escape_anchor_filter) )
227
  $escape_anchor_filter = array();
228
 
229
- foreach ( array(
230
- 'head' => "/
231
- .*?
232
- <\s*\/\s*head\s*>
233
- /isx",
234
- 'blocks' => "/
235
- <\s*(script|style|object|textarea)(?:\s.*?)?>
236
- .*?
237
- <\s*\/\s*\\1\s*>
238
- /isx",
239
- ) as $regex ) {
 
 
 
 
240
  $text = preg_replace_callback($regex, array($this, 'escape_callback'), $text);
241
  }
242
 
@@ -288,21 +343,17 @@ class sem_external_links {
288
  **/
289
 
290
  function process_link($match) {
291
- # skip empty anchors
292
- if ( !trim($match[2]) )
293
- return $match[0];
294
-
295
  # parse anchor
296
  $anchor = $this->parse_anchor($match);
297
 
298
  if ( !$anchor )
299
- return $match[0];
300
 
301
  # filter anchor
302
  $anchor = $this->filter_anchor( $anchor );
303
 
304
  if ( $anchor )
305
- $anchor = $this->build_anchor($anchor);
306
 
307
  return $anchor;
308
  } # process_link()
@@ -317,7 +368,8 @@ class sem_external_links {
317
 
318
  function parse_anchor($match) {
319
  $anchor = array();
320
- $anchor['attr'] = $this->parse_attrs( $match[1] );
 
321
 
322
  if ( !is_array($anchor['attr']) || empty($anchor['attr']['href']) # parser error or no link
323
  || trim($anchor['attr']['href']) != esc_url($anchor['attr']['href'], null, 'db') ) # likely a script
@@ -343,128 +395,35 @@ class sem_external_links {
343
  /**
344
  * build_anchor()
345
  *
 
346
  * @param array $anchor
347
  * @return string $anchor
348
  */
349
 
350
- function build_anchor($anchor) {
351
- $anchor['attr']['href'] = esc_url($anchor['attr']['href']);
352
 
353
- $str = '<a';
354
- foreach ( $anchor['attr'] as $k => $v ) {
355
- if ( is_array($v) ) {
356
- $v = array_unique($v);
357
- if ( $v )
358
- $str .= ' ' . $k . '="' . implode(' ', $v) . '"';
359
- } else {
360
- if ($k)
361
- $str .= ' ' . $k . '="' . $v . '"';
362
- else
363
- $str .= ' ' . $v;
 
 
 
 
 
364
  }
365
  }
366
- $str .= '>' . $anchor['body'] . '</a>';
367
 
368
- return $str;
369
  } # build_anchor()
370
 
371
- /**
372
- * Parse an attributes string into an array. If the string starts with a tag,
373
- * then the attributes on the first tag are parsed. This parses via a manual
374
- * loop and is designed to be safer than using DOMDocument.
375
- *
376
- * @param string|* $attrs
377
- * @return array
378
- *
379
- * @example parse_attrs( 'src="example.jpg" alt="example"' )
380
- * @example parse_attrs( '<img src="example.jpg" alt="example">' )
381
- * @example parse_attrs( '<a href="example"></a>' )
382
- * @example parse_attrs( '<a href="example">' )
383
- */
384
- function parse_attrs($attrs) {
385
-
386
- if ( !is_scalar($attrs) )
387
- return (array) $attrs;
388
-
389
- $attrs = str_split( trim($attrs) );
390
-
391
- if ( '<' === $attrs[0] ) # looks like a tag so strip the tagname
392
- while ( $attrs && ! ctype_space($attrs[0]) && $attrs[0] !== '>' )
393
- array_shift($attrs);
394
-
395
- $arr = array(); # output
396
- $name = ''; # for the current attr being parsed
397
- $value = ''; # for the current attr being parsed
398
- $mode = 0; # whether current char is part of the name (-), the value (+), or neither (0)
399
- $stop = false; # delimiter for the current $value being parsed
400
- $space = ' '; # a single space
401
- $paren = 0; # in parenthesis for js attrs
402
-
403
- foreach ( $attrs as $j => $curr ) {
404
-
405
- if ( $mode < 0 ) {# name
406
- if ( '=' === $curr ) {
407
- $mode = 1;
408
- $stop = false;
409
- } elseif ( '>' === $curr ) {
410
- '' === $name or $arr[ $name ] = $value;
411
- break;
412
- } elseif ( !ctype_space($curr) ) {
413
- if ( ctype_space( $attrs[ $j - 1 ] ) ) { # previous char
414
- '' === $name or $arr[ $name ] = ''; # previous name
415
- $name = $curr; # initiate new
416
- } else {
417
- $name .= $curr;
418
- }
419
- }
420
- } elseif ( $mode > 0 ) {# value
421
- if ( $paren ) {
422
- $value .= $curr;
423
- if ( $curr === "(")
424
- $paren += 1;
425
- elseif ( $curr === ")")
426
- $paren -= 1;
427
- }
428
- else {
429
- if ( $stop === false ) {
430
- if ( !ctype_space($curr) ) {
431
- if ( '"' === $curr || "'" === $curr ) {
432
- $value = '';
433
- $stop = $curr;
434
- } else {
435
- $value = $curr;
436
- $stop = $space;
437
- }
438
- }
439
- } elseif ( $stop === $space ? ctype_space($curr) : $curr === $stop ) {
440
- $arr[ $name ] = $value;
441
- $mode = 0;
442
- $name = $value = '';
443
- } else {
444
- $value .= $curr;
445
- if ( $curr === "(")
446
- $paren += 1;
447
- elseif ( $curr === ")")
448
- $paren -= 1;
449
- }
450
- }
451
- } else {# neither
452
-
453
- if ( '>' === $curr )
454
- break;
455
- if ( !ctype_space( $curr ) ) {
456
- # initiate
457
- $name = $curr;
458
- $mode = -1;
459
- }
460
- }
461
- }
462
-
463
- # incl the final pair if it was quoteless
464
- '' === $name or $arr[ $name ] = $value;
465
-
466
- return $arr;
467
- }
468
 
469
  /**
470
  * Updates attribute of an HTML tag.
@@ -479,8 +438,8 @@ class sem_external_links {
479
  $attr_value = false;
480
  $quote = false; // quotes to wrap attribute values
481
 
482
- if (preg_match('/\s' . $attr_name . '="([^"]*)"/iu', $html, $matches)
483
- || preg_match('/\s' . $attr_name . "='([^']*)'/iu", $html, $matches)
484
  ) {
485
  // two possible ways to get existing attributes
486
  $attr_value = $matches[1];
@@ -491,15 +450,19 @@ class sem_external_links {
491
  if ($attr_value)
492
  {
493
  //replace current attribute
494
- return str_ireplace("$attr_name=" . $quote . "$attr_value" . $quote,
495
  $attr_name . '="' . esc_attr($new_attr_value) . '"', $html);
496
  }
497
  else {
498
  // attribute does not currently exist, add it
499
- return str_ireplace('>', " $attr_name=\"" . esc_attr($new_attr_value) . '">', $html);
 
 
 
500
  }
501
- } # update_attribute()
502
 
 
 
503
 
504
  /**
505
  * filter_anchor()
@@ -513,12 +476,37 @@ class sem_external_links {
513
  if ( is_feed() )
514
  return null;
515
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
516
  # ignore local urls
517
- if ( sem_external_links::is_local_url($anchor['attr']['href']) )
518
  return null;
519
 
520
  # no icons for images
521
- $is_image = (bool) preg_match("/^\s*<\s*img\s.+?>\s*$/is", $anchor['body']);
522
 
523
  $updated = false;
524
  if ( !in_array('external', $anchor['attr']['class']) ) {
@@ -529,8 +517,11 @@ class sem_external_links {
529
  if ( !$is_image && $this->opts['icon'] && !in_array('external_icon', $anchor['attr']['class'])
530
  && !in_array('no_icon', $anchor['attr']['class'])
531
  && !in_array('noicon', $anchor['attr']['class']) ) {
532
- $anchor['attr']['class'][] = 'external_icon';
533
- $updated = true;
 
 
 
534
  }
535
 
536
  if ( $this->opts['nofollow'] && !in_array('nofollow', $anchor['attr']['rel'])
@@ -551,6 +542,28 @@ class sem_external_links {
551
  } # filter_anchor()
552
 
553
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
554
  /**
555
  * is_local_url()
556
  *
@@ -559,17 +572,15 @@ class sem_external_links {
559
  **/
560
 
561
  function is_local_url($url) {
562
- if ( in_array(substr($url, 0, 1), array('?', '#')) )
563
- return true;
564
- elseif ( (substr($url, 0, 2) != '//') && (strpos($url, 'http://') === false) && (strpos($url, 'https://') === false) )
565
  return true;
566
  elseif ( $url == 'http://' || $url == 'https://' )
567
  return true;
568
  elseif ( preg_match("~/go(/|\.)~i", $url) )
569
  return false;
570
-
571
  static $site_domain;
572
-
573
  if ( !isset($site_domain) ) {
574
  $site_domain = home_url();
575
  $site_domain = parse_url($site_domain);
@@ -583,7 +594,7 @@ class sem_external_links {
583
  return false;
584
  }
585
  $site_domain = preg_replace("/^www\./i", '', $site_domain);
586
-
587
  # The following is not bullet proof, but it's good enough for a WP site
588
  if ( $site_domain != 'localhost' && !preg_match("/\d+(\.\d+){3}/", $site_domain) ) {
589
  if ( preg_match("/\.([^.]+)$/", $site_domain, $tld) ) {
@@ -592,9 +603,9 @@ class sem_external_links {
592
  $site_domain = false;
593
  return false;
594
  }
595
-
596
  $site_domain = substr($site_domain, 0, strlen($site_domain) - 1 - strlen($tld));
597
-
598
  if ( preg_match("/\.([^.]+)$/", $site_domain, $subtld) ) {
599
  $subtld = end($subtld);
600
  if ( strlen($subtld) <= 4 ) {
@@ -606,16 +617,16 @@ class sem_external_links {
606
  $site_domain = $subtld;
607
  }
608
  }
609
-
610
  $site_domain .= ".$tld";
611
  }
612
-
613
  $site_domain = strtolower($site_domain);
614
  }
615
-
616
  if ( !$site_domain )
617
  return false;
618
-
619
  $link_domain = @parse_url($url);
620
  if ($link_domain === false)
621
  return true;
@@ -633,7 +644,7 @@ class sem_external_links {
633
  if ( $subdomains != '')
634
  $link_domain = str_replace($subdomains . '.', '', $link_domain);
635
  }
636
-
637
  if ( $site_domain == $link_domain ) {
638
  return true;
639
  } elseif ( function_exists('is_multisite') && is_multisite() ) {
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: 6.2
7
  Author: Denis de Bernardy & Mike Koepke
8
+ Author URI: https://www.semiologic.com
9
  Text Domain: external-links
10
  Domain Path: /lang
11
  License: Dual licensed under the MIT and GPLv2 licenses
19
 
20
  **/
21
 
22
+ define('sem_external_links_version', '6.2');
23
 
24
  /**
25
  * external_links
131
  $this->anchor_utils = new external_links_anchor_utils( $this );
132
  }
133
  else {
134
+ add_filter('the_content', array($this, 'process_content_content'), 100000);
135
+ // add_filter('the_excerpt', array($this, 'process_content_excerpt'), 100000);
136
+ add_filter('comment_text', array($this, 'process_content_comment'), 100000);
137
  if ( $inc_text_widgets )
138
+ add_filter('widget_text', array($this, 'process_content_widget'), 100000);
139
  }
140
  }
141
  else {
169
  * process_content()
170
  *
171
  * @param string $text
172
+ * @param string $context
173
  * @return string $text
174
  **/
175
 
176
+ function process_content($text, $context = "global") {
177
 
178
  // short circuit if there's no anchors at all in the text
179
  if ( false === stripos($text, '<a ') )
180
  return($text);
181
 
182
+ $escape_needed = array( 'global', 'content', 'widgets' );
183
+ if ( in_array($context, $escape_needed ) ) {
184
+ global $escape_anchor_filter;
185
+ $escape_anchor_filter = array();
186
 
187
+ $text = $this->escape($text, $context);
188
+ }
189
 
190
  // find all occurrences of anchors and fill matches with links
191
  preg_match_all("/
211
  if ( !empty($raw_links) && !empty($processed_links) )
212
  $text = str_replace($raw_links, $processed_links, $text);
213
 
214
+ if ( in_array($context, $escape_needed ) ) {
215
+ $text = $this->unescape($text);
216
+ }
217
 
218
  return $text;
219
  } # process_content()
220
 
221
 
222
+ /**
223
+ * process_content_comment()
224
+ *
225
+ * @param string $text
226
+ * @return string $text
227
+ **/
228
+
229
+ function process_content_comment($text) {
230
+ return $this->process_content( $text, 'comment' );
231
+ }
232
+
233
+ /**
234
+ * process_content_content()
235
+ *
236
+ * @param string $text
237
+ * @return string $text
238
+ **/
239
+
240
+ function process_content_content($text) {
241
+ return $this->process_content( $text, 'content' );
242
+ }
243
+
244
+ /**
245
+ * process_content_excerpt()
246
+ *
247
+ * @param string $text
248
+ * @return string $text
249
+ **/
250
+
251
+ function process_content_excerpt($text) {
252
+ return $this->process_content( $text, 'excerpt' );
253
+ }
254
+
255
+ /**
256
+ * process_content_widget()
257
+ *
258
+ * @param string $text
259
+ * @return string $text
260
+ **/
261
+
262
+ function process_content_widget($text) {
263
+ return $this->process_content( $text, 'widget' );
264
+ }
265
+
266
  /**
267
  * escape()
268
  *
269
  * @param string $text
270
+ * @param string $context
271
  * @return string $text
272
  **/
273
 
274
+ function escape($text, $context) {
275
  global $escape_anchor_filter;
276
 
277
  if ( !isset($escape_anchor_filter) )
278
  $escape_anchor_filter = array();
279
 
280
+ $exclusions = array();
281
+
282
+ if ( $context == 'global' )
283
+ $exclusions['head'] = "/
284
+ .*?
285
+ <\s*\/\s*head\s*>
286
+ /isx";
287
+
288
+ $exclusions['blocks'] = "/
289
+ <\s*(script|style|object|textarea)(?:\s.*?)?>
290
+ .*?
291
+ <\s*\/\s*\\1\s*>
292
+ /isx";
293
+
294
+ foreach ( $exclusions as $regex ) {
295
  $text = preg_replace_callback($regex, array($this, 'escape_callback'), $text);
296
  }
297
 
343
  **/
344
 
345
  function process_link($match) {
 
 
 
 
346
  # parse anchor
347
  $anchor = $this->parse_anchor($match);
348
 
349
  if ( !$anchor )
350
+ return false;
351
 
352
  # filter anchor
353
  $anchor = $this->filter_anchor( $anchor );
354
 
355
  if ( $anchor )
356
+ $anchor = $this->build_anchor($match[0], $anchor);
357
 
358
  return $anchor;
359
  } # process_link()
368
 
369
  function parse_anchor($match) {
370
  $anchor = array();
371
+ // $anchor['attr'] = $this->parse_attrs( $match[1] );
372
+ $anchor['attr'] = $this->parseAttributes( $match[1] );
373
 
374
  if ( !is_array($anchor['attr']) || empty($anchor['attr']['href']) # parser error or no link
375
  || trim($anchor['attr']['href']) != esc_url($anchor['attr']['href'], null, 'db') ) # likely a script
395
  /**
396
  * build_anchor()
397
  *
398
+ * @param $link
399
  * @param array $anchor
400
  * @return string $anchor
401
  */
402
 
403
+ function build_anchor($link, $anchor) {
 
404
 
405
+ $attrs = array( 'class', 'rel', 'target');
406
+
407
+ foreach ( $attrs as $attr ) {
408
+ if ( isset($anchor['attr'][$attr]) ) {
409
+ $new_attr_value = null;
410
+ $values = $anchor['attr'][$attr];
411
+ if ( is_array($values) ) {
412
+ $values = array_unique($values);
413
+ if ( $values )
414
+ $new_attr_value = implode(' ', $values );
415
+ } else {
416
+ $new_attr_value = $values;
417
+ }
418
+
419
+ if ( $new_attr_value )
420
+ $link = $this->update_attribute($link, $attr, $new_attr_value);
421
  }
422
  }
 
423
 
424
+ return $link;
425
  } # build_anchor()
426
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
 
428
  /**
429
  * Updates attribute of an HTML tag.
438
  $attr_value = false;
439
  $quote = false; // quotes to wrap attribute values
440
 
441
+ if (preg_match('/<\s*a\s+' . $attr_name . '="([^"]*)\s*>"/iu', $html, $matches)
442
+ || preg_match('/<\s*a\s+' . $attr_name . "='([^']*)\s*>'/iu", $html, $matches)
443
  ) {
444
  // two possible ways to get existing attributes
445
  $attr_value = $matches[1];
450
  if ($attr_value)
451
  {
452
  //replace current attribute
453
+ $html = str_ireplace("$attr_name=" . $quote . "$attr_value" . $quote,
454
  $attr_name . '="' . esc_attr($new_attr_value) . '"', $html);
455
  }
456
  else {
457
  // attribute does not currently exist, add it
458
+ $pos = strpos( $html, '>' );
459
+ if ($pos !== false) {
460
+ $html = substr_replace( $html, " $attr_name=\"" . esc_attr($new_attr_value) . '">', $pos, strlen('>') );
461
+ }
462
  }
 
463
 
464
+ return $html;
465
+ } # update_attribute()
466
 
467
  /**
468
  * filter_anchor()
476
  if ( is_feed() )
477
  return null;
478
 
479
+ // if we don't have a href or find a ? only obviously this some illformed or temp link
480
+ if ( empty( $anchor['attr']['href'] ) || (substr($anchor['attr']['href'], 0, 1) == '?' ) )
481
+ return null;
482
+
483
+ // we have a placeholder link. Normally treat as a local but there are exceptions like add this
484
+ if ( substr($anchor['attr']['href'], 0, 1) == '#' ) {
485
+ // now is this a full blown anchor?
486
+ if ( strlen( $anchor['attr']['href'] ) > 1 ) {
487
+ // yep, bail
488
+ return null;
489
+ }
490
+
491
+ // can we find addthis_button class? if so then don't bail and process external
492
+ $addthis = false;
493
+ foreach( $anchor['attr']['class'] as $c => $class) {
494
+ $pos = strpos( $class, "addthis_button" );
495
+ if ( null !== $pos ) {
496
+ $addthis = true;
497
+ break;
498
+ }
499
+ }
500
+ // no addthis. bail as we got just a an anchor only
501
+ if ( !$addthis )
502
+ return null;
503
+ }
504
  # ignore local urls
505
+ elseif ( sem_external_links::is_local_url($anchor['attr']['href']) )
506
  return null;
507
 
508
  # no icons for images
509
+ $is_image = ( false !== strpos($anchor['body'], "<img ") );
510
 
511
  $updated = false;
512
  if ( !in_array('external', $anchor['attr']['class']) ) {
517
  if ( !$is_image && $this->opts['icon'] && !in_array('external_icon', $anchor['attr']['class'])
518
  && !in_array('no_icon', $anchor['attr']['class'])
519
  && !in_array('noicon', $anchor['attr']['class']) ) {
520
+ // don't add an icon if there is no text in the link
521
+ if ($anchor['body'] != null) {
522
+ $anchor['attr']['class'][] = 'external_icon';
523
+ $updated = true;
524
+ }
525
  }
526
 
527
  if ( $this->opts['nofollow'] && !in_array('nofollow', $anchor['attr']['rel'])
542
  } # filter_anchor()
543
 
544
 
545
+ function parseAttributes($text) {
546
+ $attributes = array();
547
+ $pattern = '#(?(DEFINE)
548
+ (?<name>[a-zA-Z][a-zA-Z0-9-:]*)
549
+ (?<value_double>"[^"]+")
550
+ (?<value_single>\'[^\']+\')
551
+ (?<value_none>[^\s>]+)
552
+ (?<value>((?&value_double)|(?&value_single)|(?&value_none)))
553
+ )
554
+ (?<n>(?&name))(=(?<v>(?&value)))?#xs';
555
+
556
+ if (preg_match_all($pattern, $text, $matches, PREG_SET_ORDER)) {
557
+ foreach ($matches as $match) {
558
+ $attributes[$match['n']] = isset($match['v'])
559
+ ? trim($match['v'], '\'"')
560
+ : null;
561
+ }
562
+ }
563
+
564
+ return $attributes;
565
+ }
566
+
567
  /**
568
  * is_local_url()
569
  *
572
  **/
573
 
574
  function is_local_url($url) {
575
+ if ( (substr($url, 0, 2) != '//') && (strpos($url, 'http://') === false) && (strpos($url, 'https://') === false) )
 
 
576
  return true;
577
  elseif ( $url == 'http://' || $url == 'https://' )
578
  return true;
579
  elseif ( preg_match("~/go(/|\.)~i", $url) )
580
  return false;
581
+
582
  static $site_domain;
583
+
584
  if ( !isset($site_domain) ) {
585
  $site_domain = home_url();
586
  $site_domain = parse_url($site_domain);
594
  return false;
595
  }
596
  $site_domain = preg_replace("/^www\./i", '', $site_domain);
597
+
598
  # The following is not bullet proof, but it's good enough for a WP site
599
  if ( $site_domain != 'localhost' && !preg_match("/\d+(\.\d+){3}/", $site_domain) ) {
600
  if ( preg_match("/\.([^.]+)$/", $site_domain, $tld) ) {
603
  $site_domain = false;
604
  return false;
605
  }
606
+
607
  $site_domain = substr($site_domain, 0, strlen($site_domain) - 1 - strlen($tld));
608
+
609
  if ( preg_match("/\.([^.]+)$/", $site_domain, $subtld) ) {
610
  $subtld = end($subtld);
611
  if ( strlen($subtld) <= 4 ) {
617
  $site_domain = $subtld;
618
  }
619
  }
620
+
621
  $site_domain .= ".$tld";
622
  }
623
+
624
  $site_domain = strtolower($site_domain);
625
  }
626
+
627
  if ( !$site_domain )
628
  return false;
629
+
630
  $link_domain = @parse_url($url);
631
  if ($link_domain === false)
632
  return true;
644
  if ( $subdomains != '')
645
  $link_domain = str_replace($subdomains . '.', '', $link_domain);
646
  }
647
+
648
  if ( $site_domain == $link_domain ) {
649
  return true;
650
  } elseif ( function_exists('is_multisite') && is_multisite() ) {