External Links - Version 6.0

Version Description

Download this release

Release Info

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

Code changes from version 4.2 to 6.0

anchor-utils/anchor-utils.php CHANGED
@@ -2,311 +2,8 @@
2
  /*
3
  * Anchor Utils
4
  * Author: Denis de Bernardy & Mike Koepke <http://www.semiologic.com>
5
- * Version: 1.2
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
- * anchor_utils
22
- */
23
- function anchor_utils() {
24
- add_filter('the_content', array($this, 'filter'), 100);
25
- add_filter('the_excerpt', array($this, 'filter'), 100);
26
- add_filter('widget_text', array($this, 'filter'), 100);
27
- add_filter('comment_text', array($this, 'filter'), 100);
28
-
29
- add_action('wp_head', array($this, 'ob_start'), 10000);
30
- } #anchor_utils
31
-
32
- /**
33
- * ob_start()
34
- *
35
- * @return void
36
- **/
37
-
38
- function ob_start() {
39
- static $done = false;
40
-
41
- if ( $done )
42
- return;
43
-
44
- if ( has_filter('ob_filter_anchor') ) {
45
- ob_start(array($this, 'ob_filter'));
46
- add_action('wp_footer', array($this, 'ob_flush'), 10000);
47
- $done = true;
48
- }
49
- } # ob_start()
50
-
51
-
52
- /**
53
- * ob_filter()
54
- *
55
- * @param string $text
56
- * @return string $text
57
- **/
58
-
59
- function ob_filter($text) {
60
- global $escape_anchor_filter;
61
- $escape_anchor_filter = array();
62
-
63
- $text = anchor_utils::escape($text);
64
-
65
- $text = preg_replace_callback("/
66
- <\s*a\s+
67
- ([^<>]+)
68
- >
69
- (.+?)
70
- <\s*\/\s*a\s*>
71
- /isx", array($this, 'ob_filter_callback'), $text);
72
-
73
- $text = anchor_utils::unescape($text);
74
-
75
- return $text;
76
- } # ob_filter()
77
-
78
-
79
- /**
80
- * ob_flush()
81
- *
82
- * @return void
83
- **/
84
-
85
- static function ob_flush() {
86
- static $done = true;
87
-
88
- if ( $done )
89
- return;
90
-
91
- ob_end_flush();
92
- $done = true;
93
- } # ob_flush()
94
-
95
-
96
- /**
97
- * ob_filter_callback()
98
- *
99
- * @param array $match
100
- * @return string $str
101
- **/
102
-
103
- function ob_filter_callback($match) {
104
- # skip empty anchors
105
- if ( !trim($match[2]) )
106
- return $match[0];
107
-
108
- # parse anchor
109
- $anchor = anchor_utils::parse_anchor($match);
110
-
111
- if ( !$anchor )
112
- return $match[0];
113
-
114
- # filter anchor
115
- $anchor = apply_filters('ob_filter_anchor', $anchor);
116
-
117
- # return anchor
118
- return anchor_utils::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
- if ( !has_filter('filter_anchor') )
131
- return $text;
132
-
133
- global $escape_anchor_filter;
134
- $escape_anchor_filter = array();
135
-
136
- $text = anchor_utils::escape($text);
137
-
138
- $text = preg_replace_callback("/
139
- <\s*a\s+
140
- ([^<>]+)
141
- >
142
- (.+?)
143
- <\s*\/\s*a\s*>
144
- /isx", array($this, 'filter_callback'), $text);
145
-
146
- $text = anchor_utils::unescape($text);
147
-
148
- return $text;
149
- } # filter()
150
-
151
-
152
- /**
153
- * filter_callback()
154
- *
155
- * @param array $match
156
- * @return string $str
157
- **/
158
-
159
- function filter_callback($match) {
160
- # skip empty anchors
161
- if ( !trim($match[2]) )
162
- return $match[0];
163
-
164
- # parse anchor
165
- $anchor = anchor_utils::parse_anchor($match);
166
-
167
- if ( !$anchor )
168
- return $match[0];
169
-
170
- # filter anchor
171
- $anchor = apply_filters('filter_anchor', $anchor);
172
-
173
- # return anchor
174
- return anchor_utils::build_anchor($anchor);
175
- } # filter_callback()
176
-
177
-
178
- /**
179
- * parse_anchor()
180
- *
181
- * @param array $match
182
- * @return array $anchor
183
- **/
184
-
185
- function parse_anchor($match) {
186
-
187
- // Fix links that have javascript onClick or similar code but uses =" " rather than =' ". Messes up attribute extraction especially for threaded comments
188
- $on_patterns = '/(onClick|onMouseOver|onMouseOut|onMouseDown|onMouseUp|onDblClick|onContextMenu|onLoad|onAbort|onError)="(.+?)"/iU';
189
- $match[1] = preg_replace($on_patterns, "$1='$2'", $match[1]);
190
-
191
- $anchor = array();
192
- $anchor['attr'] = shortcode_parse_atts($match[1]);
193
-
194
- if ( !is_array($anchor['attr']) || empty($anchor['attr']['href']) # parser error or no link
195
- || trim($anchor['attr']['href']) != esc_url($anchor['attr']['href'], null, 'db') ) # likely a script
196
- return false;
197
-
198
- foreach ( array('class', 'rel') as $attr ) {
199
- if ( !isset($anchor['attr'][$attr]) ) {
200
- $anchor['attr'][$attr] = array();
201
- } else {
202
- $anchor['attr'][$attr] = explode(' ', $anchor['attr'][$attr]);
203
- $anchor['attr'][$attr] = array_map('trim', $anchor['attr'][$attr]);
204
- }
205
- }
206
-
207
- $anchor['body'] = $match[2];
208
-
209
- $anchor['attr']['href'] = @html_entity_decode($anchor['attr']['href'], ENT_COMPAT, get_option('blog_charset'));
210
-
211
- return $anchor;
212
- } # parse_anchor()
213
-
214
-
215
- /**
216
- * build_anchor()
217
- *
218
- * @param array $anchor
219
- * @return string $anchor
220
- **/
221
-
222
- function build_anchor($anchor) {
223
- $anchor['attr']['href'] = esc_url($anchor['attr']['href']);
224
-
225
- $str = '<a';
226
- foreach ( $anchor['attr'] as $k => $v ) {
227
- if ( is_array($v) ) {
228
- $v = array_unique($v);
229
- if ( $v )
230
- $str .= ' ' . $k . '="' . implode(' ', $v) . '"';
231
- } else {
232
- if ($k)
233
- $str .= ' ' . $k . '="' . $v . '"';
234
- else
235
- $str .= ' ' . $v;
236
- }
237
- }
238
- $str .= '>' . $anchor['body'] . '</a>';
239
-
240
- return $str;
241
- } # build_anchor()
242
-
243
-
244
- /**
245
- * escape()
246
- *
247
- * @param string $text
248
- * @return string $text
249
- **/
250
-
251
- function escape($text) {
252
- global $escape_anchor_filter;
253
-
254
- if ( !isset($escape_anchor_filter) )
255
- $escape_anchor_filter = array();
256
-
257
- foreach ( array(
258
- 'head' => "/
259
- .*?
260
- <\s*\/\s*head\s*>
261
- /isx",
262
- 'blocks' => "/
263
- <\s*(script|style|object|textarea)(?:\s.*?)?>
264
- .*?
265
- <\s*\/\s*\\1\s*>
266
- /isx",
267
- ) as $regex ) {
268
- $text = preg_replace_callback($regex, array($this, 'escape_callback'), $text);
269
- }
270
-
271
- return $text;
272
- } # escape()
273
-
274
-
275
- /**
276
- * escape_callback()
277
- *
278
- * @param array $match
279
- * @return string $text
280
- **/
281
-
282
- function escape_callback($match) {
283
- global $escape_anchor_filter;
284
-
285
- $tag_id = "----escape_anchor_utils:" . md5($match[0]) . "----";
286
- $escape_anchor_filter[$tag_id] = $match[0];
287
-
288
- return $tag_id;
289
- } # escape_callback()
290
-
291
-
292
- /**
293
- * unescape()
294
- *
295
- * @param string $text
296
- * @return string $text
297
- **/
298
-
299
- function unescape($text) {
300
- global $escape_anchor_filter;
301
-
302
- if ( !$escape_anchor_filter )
303
- return $text;
304
-
305
- $unescape = array_reverse($escape_anchor_filter);
306
-
307
- return str_replace(array_keys($unescape), array_values($unescape), $text);
308
- } # unescape()
309
- } # anchor_utils
310
-
311
- $anchor_utils = new anchor_utils();
312
- ?>
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
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.6
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -16,16 +16,16 @@ The external links plugin for WordPress lets you process outgoing links differen
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.
20
  - Add an external link icon to outgoing links. You can use a class="no_icon" attribute on links to override this.
 
21
  - 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.
22
- - Add rel=nofollow to the links. You can use a rel="nofollow" attribute on links to override this.
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.getsemiologic.com).
29
 
30
 
31
  == Installation ==
@@ -36,6 +36,70 @@ If you require more dedicated assistance, consider using [Semiologic Pro](http:/
36
 
37
  == Change Log ==
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  = 4.2 =
40
 
41
  - WP 3.6 compat
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
 
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 ==
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
sem-external-links-admin.php CHANGED
@@ -9,7 +9,7 @@ class external_links_admin {
9
  /**
10
  * external_links_admin()
11
  */
12
- function external_links_admin() {
13
  add_action('settings_page_external-links', array($this, 'save_options'), 0);
14
  }
15
 
@@ -25,10 +25,10 @@ class external_links_admin {
25
 
26
  check_admin_referer('external_links');
27
 
28
- foreach ( array('global', 'icon', 'target', 'nofollow') as $var )
29
  $$var = isset($_POST[$var]);
30
 
31
- update_option('external_links', compact('global', 'icon', 'target', 'nofollow'));
32
 
33
  echo "<div class=\"updated fade\">\n"
34
  . "<p>"
@@ -54,7 +54,7 @@ class external_links_admin {
54
 
55
  $options = external_links::get_options();
56
 
57
- if ( $options['nofollow'] && function_exists('strip_nofollow') ) {
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')
@@ -62,12 +62,10 @@ class external_links_admin {
62
  . "</div>\n";
63
  }
64
 
65
- screen_icon();
66
-
67
  echo '<h2>' . __('External Links Settings', 'external-links') . '</h2>' . "\n";
68
 
69
  echo '<table class="form-table">' . "\n";
70
-
71
  echo '<tr>' . "\n"
72
  . '<th scope="row">'
73
  . __('Apply Globally', 'external-links')
@@ -78,11 +76,26 @@ class external_links_admin {
78
  . checked($options['global'], true, false)
79
  . ' />'
80
  . '&nbsp;'
81
- . __('Apply these settings to all outbound links, including those in sidebars, rather than to those in posts and comments.', 'external-links')
82
  . '</label>'
83
  . '</td>' . "\n"
84
  . '</tr>' . "\n";
85
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  echo '<tr>' . "\n"
87
  . '<th scope="row">'
88
  . __('Add Icons', 'external-links')
@@ -113,7 +126,7 @@ class external_links_admin {
113
  . __('Add a rel=nofollow attribute to outbound links.', 'external-links')
114
  . '</label>'
115
  . '<br />' . "\n"
116
- . __('Note: You can override this behavior by adding a rel="follow" to individual links.', 'external-links')
117
  . '</td>' . "\n"
118
  . '</tr>' . "\n";
119
 
@@ -148,4 +161,3 @@ class external_links_admin {
148
  } # external_links_admin
149
 
150
  $external_links_admin = new external_links_admin();
151
- ?>
9
  /**
10
  * external_links_admin()
11
  */
12
+ public function __construct() {
13
  add_action('settings_page_external-links', array($this, 'save_options'), 0);
14
  }
15
 
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>"
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')
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')
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')
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
 
161
  } # external_links_admin
162
 
163
  $external_links_admin = new external_links_admin();
 
sem-external-links.php CHANGED
@@ -3,26 +3,22 @@
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: 4.2
7
  Author: Denis de Bernardy & Mike Koepke
8
  Author URI: http://www.getsemiologic.com
9
  Text Domain: external-links
10
  Domain Path: /lang
 
11
  */
12
 
13
  /*
14
  Terms of use
15
  ------------
16
 
17
- This software is copyright Mesoconcepts (http://www.mesoconcepts.com), and is distributed under the terms of the GPL license, v.2.
18
 
19
- http://www.opensource.org/licenses/gpl-2.0.php
20
  **/
21
 
22
-
23
- load_plugin_textdomain('external-links', false, dirname(plugin_basename(__FILE__)) . '/lang');
24
-
25
-
26
  /**
27
  * external_links
28
  *
@@ -30,24 +26,121 @@ load_plugin_textdomain('external-links', false, dirname(plugin_basename(__FILE__
30
  **/
31
 
32
  class external_links {
33
- /**
34
- * external_links()
35
- */
36
- function external_links() {
37
- if ( !is_admin() ) {
38
- $o = external_links::get_options();
39
 
40
- if ( $o['icon'] )
41
- add_action('wp_print_styles', array($this, 'styles'), 5);
42
 
43
- add_filter(($o['global'] ? 'ob_' : '' ) . 'filter_anchor', array($this, 'filter'));
44
 
45
- unset($o);
46
- } else {
47
- add_action('admin_menu', array($this, 'admin_menu'));
48
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  }
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  /**
52
  * styles()
53
  *
@@ -58,49 +151,48 @@ class external_links {
58
  $folder = plugin_dir_url(__FILE__);
59
  wp_enqueue_style('external-links', $folder . 'sem-external-links.css', null, '20090903');
60
  } # styles()
61
-
62
-
63
  /**
64
  * filter()
65
  *
66
- * @param array $anchor
67
- * @return array $anchor
68
- **/
69
 
70
  function filter($anchor) {
71
  # disable in feeds
72
  if ( is_feed() )
73
  return $anchor;
74
-
75
  # ignore local urls
76
  if ( external_links::is_local_url($anchor['attr']['href']) )
77
  return $anchor;
78
-
79
  # no icons for images
80
  $is_image = (bool) preg_match("/^\s*<\s*img\s.+?>\s*$/is", $anchor['body']);
81
-
82
  $o = external_links::get_options();
83
-
84
  if ( !in_array('external', $anchor['attr']['class']) )
85
  $anchor['attr']['class'][] = 'external';
86
-
87
  if ( !$is_image && $o['icon'] && !in_array('external_icon', $anchor['attr']['class'])
88
  && !in_array('no_icon', $anchor['attr']['class'])
89
  && !in_array('noicon', $anchor['attr']['class']) )
90
  $anchor['attr']['class'][] = 'external_icon';
91
-
92
  if ( $o['nofollow'] && !function_exists('strip_nofollow')
93
  && !in_array('nofollow', $anchor['attr']['rel'])
94
  && !in_array('follow', $anchor['attr']['rel']) )
95
  $anchor['attr']['rel'][] = 'nofollow';
96
-
97
  if ( $o['target'] && empty($anchor['attr']['target']) )
98
  $anchor['attr']['target'] = '_blank';
99
-
100
  return $anchor;
101
  } # filter()
102
 
103
-
104
  /**
105
  * is_local_url()
106
  *
@@ -111,6 +203,8 @@ class external_links {
111
  function is_local_url($url) {
112
  if ( in_array(substr($url, 0, 1), array('?', '#')) || strpos($url, '://') === false )
113
  return true;
 
 
114
  elseif ( preg_match("~/go(/|\.)~i", $url) )
115
  return false;
116
 
@@ -162,9 +256,9 @@ class external_links {
162
  if ( !$site_domain )
163
  return false;
164
 
165
- $link_domain = parse_url($url);
166
- if ($link_domain == false)
167
- return false;
168
  elseif (is_array($link_domain)) {
169
  if (isset($link_domain['host']))
170
  $link_domain = $link_domain['host'];
@@ -206,13 +300,13 @@ class external_links {
206
 
207
  $o = get_option('external_links');
208
 
209
- if ( $o === false )
210
  $o = external_links::init_options();
211
-
212
  return $o;
213
  } # get_options()
214
-
215
-
216
  /**
217
  * init_options()
218
  *
@@ -220,23 +314,27 @@ class external_links {
220
  **/
221
 
222
  function init_options() {
223
- $o = get_option('sem_external_links_params');
224
-
225
- if ( $o !== false )
226
- delete_option('sem_external_links_params');
227
-
228
- $o = wp_parse_args($o, array(
229
- 'global' => true,
230
- 'icon' => true,
231
- 'target' => false,
232
- 'nofollow' => false,
233
- ));
234
-
 
 
 
235
  update_option('external_links', $o);
236
-
237
  return $o;
238
  } # init_options()
239
-
 
240
 
241
  /**
242
  * admin_menu()
@@ -255,17 +353,4 @@ class external_links {
255
  } # admin_menu()
256
  } # external_links
257
 
258
-
259
- function external_links_admin() {
260
- include_once dirname(__FILE__) . '/sem-external-links-admin.php';
261
- }
262
-
263
- add_action('load-settings_page_external-links', 'external_links_admin');
264
-
265
- if ( !is_admin() ) {
266
- if ( !class_exists('anchor_utils') )
267
- include dirname(__FILE__) . '/anchor-utils/anchor-utils.php';
268
- }
269
-
270
- $external_links = new external_links();
271
- ?>
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
  *
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
  *
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
  *
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
 
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'];
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
  *
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()
353
  } # admin_menu()
354
  } # external_links
355
 
356
+ $external_links = external_links::get_instance();