Broken Link Checker - Version 0.5

Version Description

Download this release

Release Info

Developer whiteshadow
Plugin Icon 128x128 Broken Link Checker
Version 0.5
Comparing to
See all releases

Code changes from version 0.4.14 to 0.5

JSON.php ADDED
@@ -0,0 +1,805 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
+
4
+ /**
5
+ * Converts to and from JSON format.
6
+ *
7
+ * JSON (JavaScript Object Notation) is a lightweight data-interchange
8
+ * format. It is easy for humans to read and write. It is easy for machines
9
+ * to parse and generate. It is based on a subset of the JavaScript
10
+ * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
11
+ * This feature can also be found in Python. JSON is a text format that is
12
+ * completely language independent but uses conventions that are familiar
13
+ * to programmers of the C-family of languages, including C, C++, C#, Java,
14
+ * JavaScript, Perl, TCL, and many others. These properties make JSON an
15
+ * ideal data-interchange language.
16
+ *
17
+ * This package provides a simple encoder and decoder for JSON notation. It
18
+ * is intended for use with client-side Javascript applications that make
19
+ * use of HTTPRequest to perform server communication functions - data can
20
+ * be encoded into JSON notation for use in a client-side javascript, or
21
+ * decoded from incoming Javascript requests. JSON format is native to
22
+ * Javascript, and can be directly eval()'ed with no further parsing
23
+ * overhead
24
+ *
25
+ * All strings should be in ASCII or UTF-8 format!
26
+ *
27
+ * LICENSE: Redistribution and use in source and binary forms, with or
28
+ * without modification, are permitted provided that the following
29
+ * conditions are met: Redistributions of source code must retain the
30
+ * above copyright notice, this list of conditions and the following
31
+ * disclaimer. Redistributions in binary form must reproduce the above
32
+ * copyright notice, this list of conditions and the following disclaimer
33
+ * in the documentation and/or other materials provided with the
34
+ * distribution.
35
+ *
36
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
37
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
38
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
39
+ * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
40
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
41
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
42
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
44
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
45
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
46
+ * DAMAGE.
47
+ *
48
+ * @category
49
+ * @package Services_JSON
50
+ * @author Michal Migurski <mike-json@teczno.com>
51
+ * @author Matt Knapp <mdknapp[at]gmail[dot]com>
52
+ * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
53
+ * @copyright 2005 Michal Migurski
54
+ * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
55
+ * @license http://www.opensource.org/licenses/bsd-license.php
56
+ * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
57
+ */
58
+
59
+ /**
60
+ * Marker constant for Services_JSON::decode(), used to flag stack state
61
+ */
62
+ define('SERVICES_JSON_SLICE', 1);
63
+
64
+ /**
65
+ * Marker constant for Services_JSON::decode(), used to flag stack state
66
+ */
67
+ define('SERVICES_JSON_IN_STR', 2);
68
+
69
+ /**
70
+ * Marker constant for Services_JSON::decode(), used to flag stack state
71
+ */
72
+ define('SERVICES_JSON_IN_ARR', 3);
73
+
74
+ /**
75
+ * Marker constant for Services_JSON::decode(), used to flag stack state
76
+ */
77
+ define('SERVICES_JSON_IN_OBJ', 4);
78
+
79
+ /**
80
+ * Marker constant for Services_JSON::decode(), used to flag stack state
81
+ */
82
+ define('SERVICES_JSON_IN_CMT', 5);
83
+
84
+ /**
85
+ * Behavior switch for Services_JSON::decode()
86
+ */
87
+ define('SERVICES_JSON_LOOSE_TYPE', 16);
88
+
89
+ /**
90
+ * Behavior switch for Services_JSON::decode()
91
+ */
92
+ define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
93
+
94
+ /**
95
+ * Converts to and from JSON format.
96
+ *
97
+ * Brief example of use:
98
+ *
99
+ * <code>
100
+ * // create a new instance of Services_JSON
101
+ * $json = new Services_JSON();
102
+ *
103
+ * // convert a complexe value to JSON notation, and send it to the browser
104
+ * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
105
+ * $output = $json->encode($value);
106
+ *
107
+ * print($output);
108
+ * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
109
+ *
110
+ * // accept incoming POST data, assumed to be in JSON notation
111
+ * $input = file_get_contents('php://input', 1000000);
112
+ * $value = $json->decode($input);
113
+ * </code>
114
+ */
115
+ class Services_JSON
116
+ {
117
+ /**
118
+ * constructs a new JSON instance
119
+ *
120
+ * @param int $use object behavior flags; combine with boolean-OR
121
+ *
122
+ * possible values:
123
+ * - SERVICES_JSON_LOOSE_TYPE: loose typing.
124
+ * "{...}" syntax creates associative arrays
125
+ * instead of objects in decode().
126
+ * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
127
+ * Values which can't be encoded (e.g. resources)
128
+ * appear as NULL instead of throwing errors.
129
+ * By default, a deeply-nested resource will
130
+ * bubble up with an error, so all return values
131
+ * from encode() should be checked with isError()
132
+ */
133
+ function Services_JSON($use = 0)
134
+ {
135
+ $this->use = $use;
136
+ }
137
+
138
+ /**
139
+ * convert a string from one UTF-16 char to one UTF-8 char
140
+ *
141
+ * Normally should be handled by mb_convert_encoding, but
142
+ * provides a slower PHP-only method for installations
143
+ * that lack the multibye string extension.
144
+ *
145
+ * @param string $utf16 UTF-16 character
146
+ * @return string UTF-8 character
147
+ * @access private
148
+ */
149
+ function utf162utf8($utf16)
150
+ {
151
+ // oh please oh please oh please oh please oh please
152
+ if(function_exists('mb_convert_encoding')) {
153
+ return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
154
+ }
155
+
156
+ $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
157
+
158
+ switch(true) {
159
+ case ((0x7F & $bytes) == $bytes):
160
+ // this case should never be reached, because we are in ASCII range
161
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
162
+ return chr(0x7F & $bytes);
163
+
164
+ case (0x07FF & $bytes) == $bytes:
165
+ // return a 2-byte UTF-8 character
166
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
167
+ return chr(0xC0 | (($bytes >> 6) & 0x1F))
168
+ . chr(0x80 | ($bytes & 0x3F));
169
+
170
+ case (0xFFFF & $bytes) == $bytes:
171
+ // return a 3-byte UTF-8 character
172
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
173
+ return chr(0xE0 | (($bytes >> 12) & 0x0F))
174
+ . chr(0x80 | (($bytes >> 6) & 0x3F))
175
+ . chr(0x80 | ($bytes & 0x3F));
176
+ }
177
+
178
+ // ignoring UTF-32 for now, sorry
179
+ return '';
180
+ }
181
+
182
+ /**
183
+ * convert a string from one UTF-8 char to one UTF-16 char
184
+ *
185
+ * Normally should be handled by mb_convert_encoding, but
186
+ * provides a slower PHP-only method for installations
187
+ * that lack the multibye string extension.
188
+ *
189
+ * @param string $utf8 UTF-8 character
190
+ * @return string UTF-16 character
191
+ * @access private
192
+ */
193
+ function utf82utf16($utf8)
194
+ {
195
+ // oh please oh please oh please oh please oh please
196
+ if(function_exists('mb_convert_encoding')) {
197
+ return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
198
+ }
199
+
200
+ switch(strlen($utf8)) {
201
+ case 1:
202
+ // this case should never be reached, because we are in ASCII range
203
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
204
+ return $utf8;
205
+
206
+ case 2:
207
+ // return a UTF-16 character from a 2-byte UTF-8 char
208
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
209
+ return chr(0x07 & (ord($utf8{0}) >> 2))
210
+ . chr((0xC0 & (ord($utf8{0}) << 6))
211
+ | (0x3F & ord($utf8{1})));
212
+
213
+ case 3:
214
+ // return a UTF-16 character from a 3-byte UTF-8 char
215
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
216
+ return chr((0xF0 & (ord($utf8{0}) << 4))
217
+ | (0x0F & (ord($utf8{1}) >> 2)))
218
+ . chr((0xC0 & (ord($utf8{1}) << 6))
219
+ | (0x7F & ord($utf8{2})));
220
+ }
221
+
222
+ // ignoring UTF-32 for now, sorry
223
+ return '';
224
+ }
225
+
226
+ /**
227
+ * encodes an arbitrary variable into JSON format
228
+ *
229
+ * @param mixed $var any number, boolean, string, array, or object to be encoded.
230
+ * see argument 1 to Services_JSON() above for array-parsing behavior.
231
+ * if var is a strng, note that encode() always expects it
232
+ * to be in ASCII or UTF-8 format!
233
+ *
234
+ * @return mixed JSON string representation of input var or an error if a problem occurs
235
+ * @access public
236
+ */
237
+ function encode($var)
238
+ {
239
+ switch (gettype($var)) {
240
+ case 'boolean':
241
+ return $var ? 'true' : 'false';
242
+
243
+ case 'NULL':
244
+ return 'null';
245
+
246
+ case 'integer':
247
+ return (int) $var;
248
+
249
+ case 'double':
250
+ case 'float':
251
+ return (float) $var;
252
+
253
+ case 'string':
254
+ // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
255
+ $ascii = '';
256
+ $strlen_var = strlen($var);
257
+
258
+ /*
259
+ * Iterate over every character in the string,
260
+ * escaping with a slash or encoding to UTF-8 where necessary
261
+ */
262
+ for ($c = 0; $c < $strlen_var; ++$c) {
263
+
264
+ $ord_var_c = ord($var{$c});
265
+
266
+ switch (true) {
267
+ case $ord_var_c == 0x08:
268
+ $ascii .= '\b';
269
+ break;
270
+ case $ord_var_c == 0x09:
271
+ $ascii .= '\t';
272
+ break;
273
+ case $ord_var_c == 0x0A:
274
+ $ascii .= '\n';
275
+ break;
276
+ case $ord_var_c == 0x0C:
277
+ $ascii .= '\f';
278
+ break;
279
+ case $ord_var_c == 0x0D:
280
+ $ascii .= '\r';
281
+ break;
282
+
283
+ case $ord_var_c == 0x22:
284
+ case $ord_var_c == 0x2F:
285
+ case $ord_var_c == 0x5C:
286
+ // double quote, slash, slosh
287
+ $ascii .= '\\'.$var{$c};
288
+ break;
289
+
290
+ case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
291
+ // characters U-00000000 - U-0000007F (same as ASCII)
292
+ $ascii .= $var{$c};
293
+ break;
294
+
295
+ case (($ord_var_c & 0xE0) == 0xC0):
296
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
297
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
298
+ $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
299
+ $c += 1;
300
+ $utf16 = $this->utf82utf16($char);
301
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
302
+ break;
303
+
304
+ case (($ord_var_c & 0xF0) == 0xE0):
305
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
306
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
307
+ $char = pack('C*', $ord_var_c,
308
+ ord($var{$c + 1}),
309
+ ord($var{$c + 2}));
310
+ $c += 2;
311
+ $utf16 = $this->utf82utf16($char);
312
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
313
+ break;
314
+
315
+ case (($ord_var_c & 0xF8) == 0xF0):
316
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
317
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
318
+ $char = pack('C*', $ord_var_c,
319
+ ord($var{$c + 1}),
320
+ ord($var{$c + 2}),
321
+ ord($var{$c + 3}));
322
+ $c += 3;
323
+ $utf16 = $this->utf82utf16($char);
324
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
325
+ break;
326
+
327
+ case (($ord_var_c & 0xFC) == 0xF8):
328
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
329
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
330
+ $char = pack('C*', $ord_var_c,
331
+ ord($var{$c + 1}),
332
+ ord($var{$c + 2}),
333
+ ord($var{$c + 3}),
334
+ ord($var{$c + 4}));
335
+ $c += 4;
336
+ $utf16 = $this->utf82utf16($char);
337
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
338
+ break;
339
+
340
+ case (($ord_var_c & 0xFE) == 0xFC):
341
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
342
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
343
+ $char = pack('C*', $ord_var_c,
344
+ ord($var{$c + 1}),
345
+ ord($var{$c + 2}),
346
+ ord($var{$c + 3}),
347
+ ord($var{$c + 4}),
348
+ ord($var{$c + 5}));
349
+ $c += 5;
350
+ $utf16 = $this->utf82utf16($char);
351
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
352
+ break;
353
+ }
354
+ }
355
+
356
+ return '"'.$ascii.'"';
357
+
358
+ case 'array':
359
+ /*
360
+ * As per JSON spec if any array key is not an integer
361
+ * we must treat the the whole array as an object. We
362
+ * also try to catch a sparsely populated associative
363
+ * array with numeric keys here because some JS engines
364
+ * will create an array with empty indexes up to
365
+ * max_index which can cause memory issues and because
366
+ * the keys, which may be relevant, will be remapped
367
+ * otherwise.
368
+ *
369
+ * As per the ECMA and JSON specification an object may
370
+ * have any string as a property. Unfortunately due to
371
+ * a hole in the ECMA specification if the key is a
372
+ * ECMA reserved word or starts with a digit the
373
+ * parameter is only accessible using ECMAScript's
374
+ * bracket notation.
375
+ */
376
+
377
+ // treat as a JSON object
378
+ if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
379
+ $properties = array_map(array($this, 'name_value'),
380
+ array_keys($var),
381
+ array_values($var));
382
+
383
+ foreach($properties as $property) {
384
+ if(Services_JSON::isError($property)) {
385
+ return $property;
386
+ }
387
+ }
388
+
389
+ return '{' . join(',', $properties) . '}';
390
+ }
391
+
392
+ // treat it like a regular array
393
+ $elements = array_map(array($this, 'encode'), $var);
394
+
395
+ foreach($elements as $element) {
396
+ if(Services_JSON::isError($element)) {
397
+ return $element;
398
+ }
399
+ }
400
+
401
+ return '[' . join(',', $elements) . ']';
402
+
403
+ case 'object':
404
+ $vars = get_object_vars($var);
405
+
406
+ $properties = array_map(array($this, 'name_value'),
407
+ array_keys($vars),
408
+ array_values($vars));
409
+
410
+ foreach($properties as $property) {
411
+ if(Services_JSON::isError($property)) {
412
+ return $property;
413
+ }
414
+ }
415
+
416
+ return '{' . join(',', $properties) . '}';
417
+
418
+ default:
419
+ return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
420
+ ? 'null'
421
+ : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
422
+ }
423
+ }
424
+
425
+ /**
426
+ * array-walking function for use in generating JSON-formatted name-value pairs
427
+ *
428
+ * @param string $name name of key to use
429
+ * @param mixed $value reference to an array element to be encoded
430
+ *
431
+ * @return string JSON-formatted name-value pair, like '"name":value'
432
+ * @access private
433
+ */
434
+ function name_value($name, $value)
435
+ {
436
+ $encoded_value = $this->encode($value);
437
+
438
+ if(Services_JSON::isError($encoded_value)) {
439
+ return $encoded_value;
440
+ }
441
+
442
+ return $this->encode(strval($name)) . ':' . $encoded_value;
443
+ }
444
+
445
+ /**
446
+ * reduce a string by removing leading and trailing comments and whitespace
447
+ *
448
+ * @param $str string string value to strip of comments and whitespace
449
+ *
450
+ * @return string string value stripped of comments and whitespace
451
+ * @access private
452
+ */
453
+ function reduce_string($str)
454
+ {
455
+ $str = preg_replace(array(
456
+
457
+ // eliminate single line comments in '// ...' form
458
+ '#^\s*//(.+)$#m',
459
+
460
+ // eliminate multi-line comments in '/* ... */' form, at start of string
461
+ '#^\s*/\*(.+)\*/#Us',
462
+
463
+ // eliminate multi-line comments in '/* ... */' form, at end of string
464
+ '#/\*(.+)\*/\s*$#Us'
465
+
466
+ ), '', $str);
467
+
468
+ // eliminate extraneous space
469
+ return trim($str);
470
+ }
471
+
472
+ /**
473
+ * decodes a JSON string into appropriate variable
474
+ *
475
+ * @param string $str JSON-formatted string
476
+ *
477
+ * @return mixed number, boolean, string, array, or object
478
+ * corresponding to given JSON input string.
479
+ * See argument 1 to Services_JSON() above for object-output behavior.
480
+ * Note that decode() always returns strings
481
+ * in ASCII or UTF-8 format!
482
+ * @access public
483
+ */
484
+ function decode($str)
485
+ {
486
+ $str = $this->reduce_string($str);
487
+
488
+ switch (strtolower($str)) {
489
+ case 'true':
490
+ return true;
491
+
492
+ case 'false':
493
+ return false;
494
+
495
+ case 'null':
496
+ return null;
497
+
498
+ default:
499
+ $m = array();
500
+
501
+ if (is_numeric($str)) {
502
+ // Lookie-loo, it's a number
503
+
504
+ // This would work on its own, but I'm trying to be
505
+ // good about returning integers where appropriate:
506
+ // return (float)$str;
507
+
508
+ // Return float or int, as appropriate
509
+ return ((float)$str == (integer)$str)
510
+ ? (integer)$str
511
+ : (float)$str;
512
+
513
+ } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
514
+ // STRINGS RETURNED IN UTF-8 FORMAT
515
+ $delim = substr($str, 0, 1);
516
+ $chrs = substr($str, 1, -1);
517
+ $utf8 = '';
518
+ $strlen_chrs = strlen($chrs);
519
+
520
+ for ($c = 0; $c < $strlen_chrs; ++$c) {
521
+
522
+ $substr_chrs_c_2 = substr($chrs, $c, 2);
523
+ $ord_chrs_c = ord($chrs{$c});
524
+
525
+ switch (true) {
526
+ case $substr_chrs_c_2 == '\b':
527
+ $utf8 .= chr(0x08);
528
+ ++$c;
529
+ break;
530
+ case $substr_chrs_c_2 == '\t':
531
+ $utf8 .= chr(0x09);
532
+ ++$c;
533
+ break;
534
+ case $substr_chrs_c_2 == '\n':
535
+ $utf8 .= chr(0x0A);
536
+ ++$c;
537
+ break;
538
+ case $substr_chrs_c_2 == '\f':
539
+ $utf8 .= chr(0x0C);
540
+ ++$c;
541
+ break;
542
+ case $substr_chrs_c_2 == '\r':
543
+ $utf8 .= chr(0x0D);
544
+ ++$c;
545
+ break;
546
+
547
+ case $substr_chrs_c_2 == '\\"':
548
+ case $substr_chrs_c_2 == '\\\'':
549
+ case $substr_chrs_c_2 == '\\\\':
550
+ case $substr_chrs_c_2 == '\\/':
551
+ if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
552
+ ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
553
+ $utf8 .= $chrs{++$c};
554
+ }
555
+ break;
556
+
557
+ case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
558
+ // single, escaped unicode character
559
+ $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
560
+ . chr(hexdec(substr($chrs, ($c + 4), 2)));
561
+ $utf8 .= $this->utf162utf8($utf16);
562
+ $c += 5;
563
+ break;
564
+
565
+ case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
566
+ $utf8 .= $chrs{$c};
567
+ break;
568
+
569
+ case ($ord_chrs_c & 0xE0) == 0xC0:
570
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
571
+ //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
572
+ $utf8 .= substr($chrs, $c, 2);
573
+ ++$c;
574
+ break;
575
+
576
+ case ($ord_chrs_c & 0xF0) == 0xE0:
577
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
578
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
579
+ $utf8 .= substr($chrs, $c, 3);
580
+ $c += 2;
581
+ break;
582
+
583
+ case ($ord_chrs_c & 0xF8) == 0xF0:
584
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
585
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
586
+ $utf8 .= substr($chrs, $c, 4);
587
+ $c += 3;
588
+ break;
589
+
590
+ case ($ord_chrs_c & 0xFC) == 0xF8:
591
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
592
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
593
+ $utf8 .= substr($chrs, $c, 5);
594
+ $c += 4;
595
+ break;
596
+
597
+ case ($ord_chrs_c & 0xFE) == 0xFC:
598
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
599
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
600
+ $utf8 .= substr($chrs, $c, 6);
601
+ $c += 5;
602
+ break;
603
+
604
+ }
605
+
606
+ }
607
+
608
+ return $utf8;
609
+
610
+ } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
611
+ // array, or object notation
612
+
613
+ if ($str{0} == '[') {
614
+ $stk = array(SERVICES_JSON_IN_ARR);
615
+ $arr = array();
616
+ } else {
617
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
618
+ $stk = array(SERVICES_JSON_IN_OBJ);
619
+ $obj = array();
620
+ } else {
621
+ $stk = array(SERVICES_JSON_IN_OBJ);
622
+ $obj = new stdClass();
623
+ }
624
+ }
625
+
626
+ array_push($stk, array('what' => SERVICES_JSON_SLICE,
627
+ 'where' => 0,
628
+ 'delim' => false));
629
+
630
+ $chrs = substr($str, 1, -1);
631
+ $chrs = $this->reduce_string($chrs);
632
+
633
+ if ($chrs == '') {
634
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
635
+ return $arr;
636
+
637
+ } else {
638
+ return $obj;
639
+
640
+ }
641
+ }
642
+
643
+ //print("\nparsing {$chrs}\n");
644
+
645
+ $strlen_chrs = strlen($chrs);
646
+
647
+ for ($c = 0; $c <= $strlen_chrs; ++$c) {
648
+
649
+ $top = end($stk);
650
+ $substr_chrs_c_2 = substr($chrs, $c, 2);
651
+
652
+ if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
653
+ // found a comma that is not inside a string, array, etc.,
654
+ // OR we've reached the end of the character list
655
+ $slice = substr($chrs, $top['where'], ($c - $top['where']));
656
+ array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
657
+ //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
658
+
659
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
660
+ // we are in an array, so just push an element onto the stack
661
+ array_push($arr, $this->decode($slice));
662
+
663
+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
664
+ // we are in an object, so figure
665
+ // out the property name and set an
666
+ // element in an associative array,
667
+ // for now
668
+ $parts = array();
669
+
670
+ if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
671
+ // "name":value pair
672
+ $key = $this->decode($parts[1]);
673
+ $val = $this->decode($parts[2]);
674
+
675
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
676
+ $obj[$key] = $val;
677
+ } else {
678
+ $obj->$key = $val;
679
+ }
680
+ } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
681
+ // name:value pair, where name is unquoted
682
+ $key = $parts[1];
683
+ $val = $this->decode($parts[2]);
684
+
685
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
686
+ $obj[$key] = $val;
687
+ } else {
688
+ $obj->$key = $val;
689
+ }
690
+ }
691
+
692
+ }
693
+
694
+ } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
695
+ // found a quote, and we are not inside a string
696
+ array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
697
+ //print("Found start of string at {$c}\n");
698
+
699
+ } elseif (($chrs{$c} == $top['delim']) &&
700
+ ($top['what'] == SERVICES_JSON_IN_STR) &&
701
+ ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
702
+ // found a quote, we're in a string, and it's not escaped
703
+ // we know that it's not escaped becase there is _not_ an
704
+ // odd number of backslashes at the end of the string so far
705
+ array_pop($stk);
706
+ //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
707
+
708
+ } elseif (($chrs{$c} == '[') &&
709
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
710
+ // found a left-bracket, and we are in an array, object, or slice
711
+ array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
712
+ //print("Found start of array at {$c}\n");
713
+
714
+ } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
715
+ // found a right-bracket, and we're in an array
716
+ array_pop($stk);
717
+ //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
718
+
719
+ } elseif (($chrs{$c} == '{') &&
720
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
721
+ // found a left-brace, and we are in an array, object, or slice
722
+ array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
723
+ //print("Found start of object at {$c}\n");
724
+
725
+ } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
726
+ // found a right-brace, and we're in an object
727
+ array_pop($stk);
728
+ //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
729
+
730
+ } elseif (($substr_chrs_c_2 == '/*') &&
731
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
732
+ // found a comment start, and we are in an array, object, or slice
733
+ array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
734
+ $c++;
735
+ //print("Found start of comment at {$c}\n");
736
+
737
+ } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
738
+ // found a comment end, and we're in one now
739
+ array_pop($stk);
740
+ $c++;
741
+
742
+ for ($i = $top['where']; $i <= $c; ++$i)
743
+ $chrs = substr_replace($chrs, ' ', $i, 1);
744
+
745
+ //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
746
+
747
+ }
748
+
749
+ }
750
+
751
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
752
+ return $arr;
753
+
754
+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
755
+ return $obj;
756
+
757
+ }
758
+
759
+ }
760
+ }
761
+ }
762
+
763
+ /**
764
+ * @todo Ultimately, this should just call PEAR::isError()
765
+ */
766
+ function isError($data, $code = null)
767
+ {
768
+ if (class_exists('pear')) {
769
+ return PEAR::isError($data, $code);
770
+ } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
771
+ is_subclass_of($data, 'services_json_error'))) {
772
+ return true;
773
+ }
774
+
775
+ return false;
776
+ }
777
+ }
778
+
779
+ if (class_exists('PEAR_Error')) {
780
+
781
+ class Services_JSON_Error extends PEAR_Error
782
+ {
783
+ function Services_JSON_Error($message = 'unknown error', $code = null,
784
+ $mode = null, $options = null, $userinfo = null)
785
+ {
786
+ parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
787
+ }
788
+ }
789
+
790
+ } else {
791
+
792
+ /**
793
+ * @todo Ultimately, this class shall be descended from PEAR_Error
794
+ */
795
+ class Services_JSON_Error
796
+ {
797
+ function Services_JSON_Error($message = 'unknown error', $code = null,
798
+ $mode = null, $options = null, $userinfo = null)
799
+ {
800
+
801
+ }
802
+ }
803
+
804
+ }
805
+ ?>
broken-link-checker.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: Broken Link Checker
4
  Plugin URI: http://w-shadow.com/blog/2007/08/05/broken-link-checker-for-wordpress/
5
  Description: Checks your posts for broken links and missing images and notifies you on the dashboard if any are found.
6
- Version: 0.4.14
7
  Author: Janis Elsts
8
  Author URI: http://w-shadow.com/blog/
9
  */
@@ -16,66 +16,124 @@ MySQL 4.0 compatibility by Jeroen (www.yukka.eu)
16
  //The plugin will use Snoopy in case CURL is not available
17
  if (!class_exists('Snoopy')) require_once(ABSPATH.'/wp-includes/class-snoopy.php');
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  if (!class_exists('ws_broken_link_checker')) {
20
 
21
  class ws_broken_link_checker {
22
  var $options;
23
  var $options_name='wsblc_options';
24
- var $postdata_name;
25
- var $linkdata_name;
26
- var $version='0.4.14';
27
  var $myfile='';
28
  var $myfolder='';
29
  var $mybasename='';
30
  var $siteurl;
31
  var $defaults;
 
 
 
32
 
33
  function ws_broken_link_checker() {
34
  global $wpdb;
35
 
36
  //set default options
37
  $this->defaults = array(
38
- 'version' => $this->version,
39
- 'max_work_session' => 27,
40
- 'check_treshold' => 72,
41
- 'mark_broken_links' => true,
42
  'broken_link_css' => ".broken_link, a.broken_link {\n\ttext-decoration: line-through;\n}",
43
- 'exclusion_list' => array(),
44
- 'delete_post_button' => false
 
 
 
 
 
 
 
 
 
 
 
 
45
  );
46
- //load options
47
- $this->options=get_option($this->options_name);
48
- if(!is_array($this->options)){
49
- $this->options = $this->defaults;
50
- } else {
51
- $this->options = array_merge($this->defaults, $this->options);
52
- }
53
 
54
- $this->postdata_name=$wpdb->prefix . "blc_postdata";
55
- $this->linkdata_name=$wpdb->prefix . "blc_linkdata";
56
  $this->siteurl = get_option('siteurl');
57
 
58
  $my_file = str_replace('\\', '/',__FILE__);
59
  $my_file = preg_replace('/^.*wp-content[\\\\\/]plugins[\\\\\/]/', '', $my_file);
60
- add_action('activate_'.$my_file, array(&$this,'activation'));
61
  $this->myfile=$my_file;
62
  $this->myfolder=basename(dirname(__FILE__));
63
  $this->mybasename=plugin_basename(__FILE__);
 
 
64
 
65
- add_action('admin_menu', array(&$this,'options_menu'));
66
-
67
- add_action('delete_post', array(&$this,'post_deleted'));
68
  add_action('save_post', array(&$this,'post_saved'));
 
 
 
 
 
 
69
  add_action('admin_footer', array(&$this,'admin_footer'));
70
  add_action('admin_print_scripts', array(&$this,'admin_print_scripts'));
71
- add_action('activity_box_end', array(&$this,'activity_box'));
 
72
 
73
- if (!empty($this->options['mark_broken_links'])){
74
- add_filter('the_content', array(&$this,'the_content'));
75
- if (!empty($this->options['broken_link_css'])){
76
- add_action('wp_head', array(&$this,'header_css'));
77
  }
78
  }
 
 
 
 
 
 
 
 
 
 
79
  }
80
 
81
  function admin_footer(){
@@ -83,13 +141,23 @@ class ws_broken_link_checker {
83
  <!-- wsblc admin footer -->
84
  <div id='wsblc_updater_div'></div>
85
  <script type='text/javascript'>
86
- new Ajax.PeriodicalUpdater('wsblc_updater_div', '<?php
87
- echo get_option( "siteurl" ).'/wp-content/plugins/'.$this->myfolder.'/wsblc_ajax.php?action=run_check' ; ?>',
88
- {
89
- method: 'get',
90
- frequency: <?php echo ($this->options['max_work_session']-1); ?>,
91
- decay: 1.3
92
- });
 
 
 
 
 
 
 
 
 
 
93
  </script>
94
  <!-- /wsblc admin footer -->
95
  <?php
@@ -101,307 +169,45 @@ class ws_broken_link_checker {
101
 
102
  function the_content($content){
103
  global $post, $wpdb;
104
- if (empty($post)) return $content;
105
-
106
- $sql="SELECT url from $this->linkdata_name WHERE post_id = $post->ID AND broken<>0";
107
- $rows=$wpdb->get_results($sql, ARRAY_A);
108
- if($rows && (count($rows)>0)){
109
- //some rows found
110
- $this->links_to_remove = array_map(
111
- create_function('$elem', 'return $elem["url"];'),
112
- $rows);
113
- $url_pattern='/(<a[\s]+[^>]*href\s*=\s*[\"\']?)([^\'\" >]+)([\'\"]+[^<>]*)(>)((?sU).*)(<\/a>)/i';
114
- $content = preg_replace_callback($url_pattern, array(&$this,'mark_broken_links'), $content);
 
 
 
 
 
 
 
 
 
 
 
 
115
  };
116
-
117
- //print_r($post);
118
  return $content;
119
  }
120
 
121
  function mark_broken_links($matches){
122
- $url = $this->normalize_url(html_entity_decode($matches[2])) ;
123
- if(in_array($url, $this->links_to_remove)){
124
- return $matches[1].$matches[2].$matches[3].' class="broken_link"'.$matches[4].
125
- $matches[5].$matches[6];
 
126
  } else {
127
  return $matches[0];
128
  }
129
  }
130
 
131
- /**
132
- * ws_broken_link_checker::normalize_url()
133
- *
134
- *
135
- * @param string $url
136
- * @return string or FALSE for invalid/unsupported URLs
137
- */
138
- function normalize_url($url){
139
- $parts=@parse_url($url);
140
- if(!$parts) return false;
141
-
142
- if(isset($parts['scheme'])) {
143
- //Only HTTP(S) links are checked. Other protocols are not supported.
144
- if ( ($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') )
145
- return false;
146
- }
147
-
148
- $url = html_entity_decode($url);
149
- $url = preg_replace(
150
- array('/([\?&]PHPSESSID=\w+)$/i',
151
- '/(#[^\/]*)$/',
152
- '/&amp;/',
153
- '/^(javascript:.*)/i',
154
- '/([\?&]sid=\w+)$/i'
155
- ),
156
- array('','','&','',''),
157
- $url);
158
- $url=trim($url);
159
-
160
- if($url=='') return false;
161
-
162
- // turn relative URLs into absolute URLs
163
- $url = $this->relative2absolute($this->siteurl, $url);
164
- return $url;
165
- }
166
-
167
- function relative2absolute($absolute, $relative) {
168
- $p = @parse_url($relative);
169
- if(!$p) {
170
- //WTF? $relative is a seriously malformed URL
171
- return false;
172
- }
173
- if(isset($p["scheme"])) return $relative;
174
-
175
- $parts=(parse_url($absolute));
176
-
177
- if(substr($relative,0,1)=='/') {
178
- $cparts = (explode("/", $relative));
179
- array_shift($cparts);
180
- } else {
181
- if(isset($parts['path'])){
182
- $aparts=explode('/',$parts['path']);
183
- array_pop($aparts);
184
- $aparts=array_filter($aparts);
185
- } else {
186
- $aparts=array();
187
- }
188
-
189
- $rparts = (explode("/", $relative));
190
-
191
- $cparts = array_merge($aparts, $rparts);
192
- foreach($cparts as $i => $part) {
193
- if($part == '.') {
194
- unset($cparts[$i]);
195
- } else if($part == '..') {
196
- unset($cparts[$i]);
197
- unset($cparts[$i-1]);
198
- }
199
- }
200
- }
201
- $path = implode("/", $cparts);
202
-
203
- $url = '';
204
- if($parts['scheme']) {
205
- $url = "$parts[scheme]://";
206
- }
207
- if(isset($parts['user'])) {
208
- $url .= $parts['user'];
209
- if(isset($parts['pass'])) {
210
- $url .= ":".$parts['pass'];
211
- }
212
- $url .= "@";
213
- }
214
- if(isset($parts['host'])) {
215
- $url .= $parts['host']."/";
216
- }
217
- $url .= $path;
218
-
219
- return $url;
220
- }
221
-
222
- function page_exists($url){
223
- //echo "Checking $url...<br/>";
224
- $result = array('final_url'=>$url, 'log'=>'', 'http_code'=>'', 'okay' => false);
225
-
226
- $parts=parse_url($url);
227
- if(!$parts) {
228
- $result['log'] .= "Invalid link URL (doesn't parse).";
229
- return $result;
230
- }
231
-
232
- if(!isset($parts['scheme'])) {
233
- $url='http://'.$url;
234
- $parts['scheme'] = 'http';
235
- $result['log'] .= "Protocol not specified, assuming HTTP.\n";
236
- }
237
-
238
- //Only HTTP links are checked. All others are automatically considered okay.
239
- if ( ($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') ) {
240
- $result['log'] .= "URL protocol ($parts[scheme]) is not HTTP. This link won't be checked.\n";
241
- return $result;
242
- }
243
-
244
- //Kill the #anchor if it's present
245
- $anchor_start = strpos($url, '#');
246
- if ( $anchor_start !== false ){
247
- $url = substr($url, 0, $anchor_start);
248
- }
249
-
250
- //******* Use CURL if available ***********
251
- if (function_exists('curl_init')) {
252
-
253
- $ch = curl_init();
254
- curl_setopt($ch, CURLOPT_URL, $url);
255
- //Masquerade as Internet explorer
256
- curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)');
257
- //Add a semi-plausible referer header to avoid tripping up some bot traps
258
- curl_setopt($ch, CURLOPT_REFERER, get_option('home'));
259
-
260
- curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
261
-
262
- @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
263
- curl_setopt($ch, CURLOPT_MAXREDIRS, 10);
264
-
265
- curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
266
- curl_setopt($ch, CURLOPT_TIMEOUT, 30);
267
-
268
- curl_setopt($ch, CURLOPT_FAILONERROR, false);
269
-
270
- $nobody=false;
271
- if($parts['scheme']=='https'){
272
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
273
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
274
- } else {
275
- $nobody=true;
276
- curl_setopt($ch, CURLOPT_NOBODY, true);
277
- //curl_setopt($ch, CURLOPT_RANGE, '0-1023');
278
- }
279
- curl_setopt($ch, CURLOPT_HEADER, true);
280
-
281
- $response = curl_exec($ch);
282
- //echo 'Response 1 : <pre>',$response,'</pre>';
283
- $code=intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
284
- //echo "Code 1 : $code<br/>";
285
-
286
- $header = '';
287
- if (preg_match('/(.+?)\r\n\r\n/s', $response, $matches)){
288
- $header = $matches[1];
289
- }
290
-
291
- $result['log'] .= "=== First try : ".($response?"$code ===\n$header":"No response. ===")."\n\n";
292
-
293
- if ( (($code<200) || ($code>=400)) && $nobody) {
294
- $result['log'] .= "Trying a second time with different settings...\n";
295
- curl_setopt($ch, CURLOPT_NOBODY, false);
296
- curl_setopt($ch, CURLOPT_HTTPGET, true);
297
- curl_setopt($ch, CURLOPT_RANGE, '0-2047');
298
- $response = curl_exec($ch);
299
- //echo 'Response 2 : <pre>',$response,'</pre>';
300
- $code=intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
301
- //echo "Code 2 : $code<br/>";
302
- if (preg_match('/(.+?)\r\n\r\n/s', $response, $matches)){
303
- $header = $matches[1];
304
- }
305
-
306
- $result['log'] .= "=== Second try : ".($response?"$code ===\n$header":"No response. ===")."\n\n";
307
- }
308
-
309
- $result['final_url'] = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
310
-
311
- curl_close($ch);
312
-
313
- } elseif (class_exists('Snoopy')) {
314
- //******** Use Snoopy if CURL is not available *********
315
- //Note : Snoopy doesn't work too well with HTTPS URLs.
316
- $result['log'] .= "<em>(Using Snoopy)</em>\n";
317
-
318
- $snoopy = new Snoopy;
319
- $snoopy->read_timeout = 60; //read timeout in seconds
320
- $snoopy->fetch($url);
321
-
322
- $code = $snoopy->status; //HTTP status code
323
-
324
- if ($snoopy->error)
325
- $result['log'] .= $snoopy->error."\n";
326
- if ($snoopy->timed_out)
327
- $result['log'] .= "Request timed out.\n";
328
-
329
- if (is_array($snoopy->headers))
330
- $result['log'] .= implode("", $snoopy->headers)."\n"; //those headers already contain newlines
331
-
332
- //$result['log'] .= print_r($snoopy, true);
333
-
334
- if ($snoopy->lastredirectaddr)
335
- $result['final_url'] = $snoopy->lastredirectaddr;
336
- }
337
-
338
- $result['http_code'] = $code;
339
-
340
- /*"Good" response codes are anything in the 2XX range (e.g "200 OK") and redirects - the 3XX range.
341
- HTTP 401 Unauthorized is a special case that is considered OK as well. Other errors - the 4XX range -
342
- are treated as "page doesn't exist'". */
343
- $result['okay'] = (($code>=200) && ($code<400)) || ($code == 401);
344
- $result['log'] .= "Link is ".($result['okay']?'valid':'broken').".";
345
-
346
- return $result;
347
- }
348
-
349
- /**
350
- * ws_broken_link_checker::check_link()
351
- * Checks the link described by the object $link and udpates the DB accordingly.
352
- *
353
- * @param object $link
354
- * @return boolean
355
- */
356
- function check_link($link){
357
- global $wpdb;
358
-
359
- /*
360
- Check for problematic (though not necessarily "broken") links.
361
- If a link has been checked multiple times and still hasn't been marked as broken
362
- or removed from the queue then probably the checking algorithm is having problems
363
- with that link. Mark it as broken and hope the user sorts it out.
364
- */
365
- if ($link->check_count >=5){
366
- $wpdb->query("UPDATE {$this->linkdata_name}
367
- SET broken=1, log=CONCAT(log, '\nProblematic link, checking times out.')
368
- WHERE id={$link->id}");
369
- //can afford to skip the $max_execution_time check here, the above op. should be very fast.
370
- return false;
371
- }
372
-
373
- //Update the check_count & last_check fields before actually performing the check.
374
- //Useful if something goes terribly wrong in page_exists() with this particular URL.
375
- $wpdb->query("UPDATE $this->linkdata_name SET last_check=NOW(), check_count=check_count+1
376
- WHERE id=$link->id");
377
-
378
- //Verify that the link should be checked
379
- if ( !$this->is_excluded($link->url) ){
380
- $rez = $this->page_exists($link->url);
381
-
382
- if ( $rez['okay'] ) {
383
- //Link is fine; remove it from the queue.
384
- $wpdb->query("DELETE FROM $this->linkdata_name WHERE id=$link->id");
385
- return true;
386
- } else {
387
- //Link is broken.
388
- $wpdb->query(
389
- "UPDATE $this->linkdata_name
390
- SET broken=1, http_code=$rez[http_code], log='".$wpdb->escape($rez['log'])."',
391
- final_url = '".$wpdb->escape($rez['final_url'])."'
392
- WHERE id=$link->id"
393
- );
394
-
395
- return false;
396
- }
397
-
398
- } else {
399
- //This link doesn't need to be checked because it is excluded.
400
- $wpdb->query("DELETE FROM $this->linkdata_name WHERE id=$link->id");
401
- return true;
402
- }
403
-
404
- }
405
 
406
  function is_excluded($url){
407
  if (!is_array($this->options['exclusion_list'])) return false;
@@ -413,29 +219,95 @@ class ws_broken_link_checker {
413
  return false;
414
  }
415
 
416
- function activity_box(){
417
  ?>
418
- <!-- wsblc activity box -->
419
- <div id='wsblc_activity_box'></div>
420
  <script type='text/javascript'>
421
- new Ajax.Updater('wsblc_activity_box', '<?php
422
- echo get_option( "siteurl" ).'/wp-content/plugins/'.$this->myfolder.'/wsblc_ajax.php?action=dashboard_status' ; ?>');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  </script>
424
- <!-- /wsblc activity box -->
425
  <?php
426
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
 
428
  function admin_print_scripts(){
429
- // use JavaScript Prototype library for AJAX
430
- wp_enqueue_script('prototype');
 
431
  }
432
 
 
 
 
 
 
 
 
433
  function post_deleted($post_id){
434
  global $wpdb;
435
- $sql="DELETE FROM ".$this->linkdata_name." WHERE post_id=$post_id";
436
- $wpdb->query($sql);
437
- $sql="DELETE FROM ".$this->postdata_name." WHERE post_id=$post_id";
438
- $wpdb->query($sql);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
439
  }
440
 
441
  function post_saved($post_id){
@@ -446,117 +318,178 @@ class ws_broken_link_checker {
446
  if ( ($post->post_type != 'post') && ($post->post_type != 'page') ) return null;
447
  //Only check published posts
448
  if ( $post->post_status != 'publish' ) return null;
449
-
450
- $found=$wpdb->get_var("SELECT post_id FROM $this->postdata_name WHERE post_id=$post_id LIMIT 1");
451
- if($found===NULL){
452
- //this post hasn't been saved previously, save the additional data now
453
- $wpdb->query("INSERT INTO $this->postdata_name (post_id, last_check) VALUES($post_id, '00-00-0000 00:00:00')");
454
- } else {
455
- //mark the post as not checked
456
- $wpdb->query("UPDATE $this->postdata_name SET last_check='00-00-0000 00:00:00' WHERE post_id=$post_id");
457
- //delete the previously extracted links - they are possibly no longer in the post
458
- $wpdb->query("DELETE FROM $this->linkdata_name WHERE post_id=$post_id");
459
- }
460
- }
461
-
462
-
463
- function sync_posts_to_db(){
464
- global $wpdb;
465
-
466
- /* JHS: This query does not work on mySQL 4.0 (4.0 does not support subqueries).
467
- // However, this one is faster, so I'll leave it here (forward compatibility)
468
- $sql="INSERT INTO ".$this->postdata_name."( post_id, last_check )
469
- SELECT id, '00-00-0000 00:00:00'
470
- FROM $wpdb->posts b
471
- WHERE NOT EXISTS (
472
- SELECT post_id
473
- FROM ".$this->postdata_name." a
474
- WHERE a.post_id = b.id
475
- )"; */
476
- //JHS: This one also works on mySQL 4.0:
477
- $sql="INSERT INTO ".$this->postdata_name."(post_id, last_check)
478
- SELECT ".$wpdb->posts.".id, '00-00-0000 00:00:00' FROM ".$wpdb->posts."
479
- LEFT JOIN ".$this->postdata_name." ON ".$wpdb->posts.".id=".$this->postdata_name.".post_id
480
- WHERE
481
- ".$this->postdata_name.".post_id IS NULL
482
- AND ".$wpdb->posts.".post_status = 'publish'
483
- AND ".$wpdb->posts.".post_type IN ('post', 'page') ";
484
- $wpdb->query($sql);
485
- }
486
-
487
- //JHS: Clears all blc tables and initiates a new fresh recheck
488
- function recheck_all_posts(){
489
- global $wpdb;
490
-
491
- //Empty blc_linkdata
492
- $sql="TRUNCATE TABLE ".$this->linkdata_name;
493
- $wpdb->query($sql);
494
-
495
- //Empty table [aggressive approach]
496
- $sql="TRUNCATE TABLE ".$this->postdata_name;
497
- //Reset all dates to zero [less aggressive approach, I like the above one better, it's cleaner ;)]
498
- //$sql="UPDATE $this->postdata_name SET last_check='00-00-0000 00:00:00' WHERE 1";
499
-
500
- $wpdb->query($sql);
501
-
502
- $this->sync_posts_to_db();
503
  }
504
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  function activation(){
506
- global $wpdb;
507
-
508
- require_once(ABSPATH . 'wp-admin/upgrade-functions.php');
509
 
510
- if (($wpdb->get_var("show tables like '".($this->postdata_name)."'") != $this->postdata_name)
511
- || ($this->options['version'] != $this->version ) ) {
512
- $sql="CREATE TABLE ".$this->postdata_name." (
513
- post_id BIGINT( 20 ) NOT NULL ,
514
- last_check DATETIME NOT NULL ,
515
- UNIQUE KEY post_id (post_id)
516
- );";
517
-
518
- dbDelta($sql);
519
- }
520
 
521
- if (($wpdb->get_var("show tables like '".($this->linkdata_name)."'") != $this->linkdata_name)
522
- || ($this->options['version'] != $this->version ) ) {
523
- $sql="CREATE TABLE ".$this->linkdata_name." (
524
- id BIGINT( 20 ) UNSIGNED NOT NULL AUTO_INCREMENT ,
525
- post_id BIGINT( 20 ) NOT NULL ,
526
- url TEXT NOT NULL ,
527
- final_url TEXT NOT NULL,
528
- link_text VARCHAR( 50 ) NOT NULL ,
529
- broken TINYINT( 1 ) UNSIGNED DEFAULT '0' NOT NULL,
530
- last_check DATETIME NOT NULL ,
531
- check_count TINYINT( 2 ) UNSIGNED DEFAULT '0' NOT NULL,
532
- type ENUM('link', 'image') DEFAULT 'link' NOT NULL,
533
- log TEXT NOT NULL,
534
- http_code SMALLINT NOT NULL,
535
- PRIMARY KEY id (id)
536
- );";
537
-
538
- dbDelta($sql);
539
-
540
- //upgrade to 0.4.8
541
- $wpdb->query("UPDATE ".$this->linkdata_name." SET type='image' WHERE link_text='[image]'");
542
- $wpdb->query("UPDATE ".$this->linkdata_name." SET final_url=url WHERE final_url=''");
543
- }
544
-
545
- $this->sync_posts_to_db();
546
-
547
- //Update the options
548
- $this->options['version'] = $this->version;
549
- update_option($this->options_name,$this->options);
550
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
551
 
552
- function options_menu(){
553
  add_options_page('Link Checker Settings', 'Link Checker', 'manage_options',
554
- __FILE__,array(&$this, 'options_page'));
555
  if (current_user_can('manage_options'))
556
  add_filter('plugin_action_links', array(&$this, 'plugin_action_links'), 10, 2);
557
 
558
  add_management_page('View Broken Links', 'Broken Links', 'edit_others_posts',
559
- __FILE__,array(&$this, 'broken_links_page'));
560
  }
561
 
562
  /**
@@ -570,7 +503,7 @@ class ws_broken_link_checker {
570
  */
571
  function plugin_action_links($links, $file) {
572
  if ($file == $this->mybasename)
573
- $links[] = "<a href='options-general.php?page=broken-link-checker/broken-link-checker.php'>" . __('Settings') . "</a>";
574
  return $links;
575
  }
576
 
@@ -581,98 +514,117 @@ class ws_broken_link_checker {
581
 
582
  function options_page(){
583
 
584
- $this->options = get_option('wsblc_options');
585
- $reminder = '';
586
- //JHS: recheck all posts if asked for:
587
  if (isset($_GET['recheck']) && ($_GET['recheck'] == 'true')) {
588
- $this->recheck_all_posts();
589
  }
590
  if (isset($_GET['updated']) && ($_GET['updated'] == 'true')) {
591
- if(isset($_POST['Submit'])) {
592
 
593
- $new_session_length=intval($_POST['max_work_session']);
594
- if( $new_session_length >0 ){
595
- $this->options['max_work_session']=$new_session_length;
596
  }
597
 
598
- $new_check_treshold=intval($_POST['check_treshold']);
599
- if( $new_check_treshold > 0 ){
600
- $this->options['check_treshold']=$new_check_treshold;
601
  }
602
-
 
603
  $new_broken_link_css = trim($_POST['broken_link_css']);
604
  $this->options['broken_link_css'] = $new_broken_link_css;
605
 
606
- $this->options['mark_broken_links'] = !empty($_POST['mark_broken_links']);
607
-
608
- $this->options['delete_post_button'] = !empty($_POST['delete_post_button']);
609
-
610
- $this->options['exclusion_list']=array_filter(preg_split('/[\s,\r\n]+/',
611
- $_POST['exclusion_list']));
612
-
613
- update_option($this->options_name,$this->options);
 
 
 
 
 
 
 
 
 
 
 
 
 
614
  }
615
 
616
  }
617
- echo $reminder;
618
  ?>
619
  <div class="wrap"><h2>Broken Link Checker Options</h2>
620
- <?php
621
- //This check isn't required anymore. Can now use Snoopy when CURL is not available.
622
- /*
623
- if(!function_exists('curl_init')){ ?>
624
- <strong>Error: <a href='http://curl.haxx.se/libcurl/php/'>CURL library</a>
625
- is not installed. This plugin won't work.</strong><br/>
626
- <?php };
627
- */
628
- ?>
629
- <form name="link_checker_options" method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>?page=<?php echo plugin_basename(__FILE__); ?>&amp;updated=true">
630
- <p class="submit"><input type="submit" name="Submit" value="Update Options &raquo;" /></p>
631
 
632
- <table class="optiontable">
 
 
633
 
634
  <tr valign="top">
635
- <th scope="row">Status:</th>
636
  <td>
637
 
638
 
639
  <div id='wsblc_full_status'>
640
- <br/><br/>
641
  </div>
642
  <script type='text/javascript'>
643
- new Ajax.PeriodicalUpdater('wsblc_full_status', '<?php
644
- echo get_option( "siteurl" ).'/wp-content/plugins/'.$this->myfolder.'/wsblc_ajax.php?action=full_status' ; ?>',
645
- {
646
- method: 'get',
647
- frequency: 10,
648
- decay: 2
649
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
650
  </script>
651
  <?php //JHS: Recheck all posts link: ?>
652
- <p><input class="button" type="button" name="recheckbutton" value="Re-check all pages" onclick="location.replace('<?php echo $_SERVER['PHP_SELF']; ?>?page=<?php echo plugin_basename(__FILE__); ?>&amp;recheck=true')" /></p>
653
  </td>
654
  </tr>
655
 
656
  <tr valign="top">
657
- <th scope="row">Check Every Post:</th>
658
  <td>
659
 
660
- Every <input type="text" name="check_treshold" id="check_treshold"
661
- value="<?php echo $this->options['check_treshold']; ?>" size='5' maxlength='3'/>
662
  hours
663
  <br/>
664
- Links in old posts will be re-checked this often. New posts will be usually checked ASAP.
 
 
665
 
666
  </td>
667
  </tr>
668
 
669
  <tr valign="top">
670
- <th scope="row">Broken Link CSS:</th>
671
  <td>
672
  <input type="checkbox" name="mark_broken_links" id="mark_broken_links"
673
  <?php if ($this->options['mark_broken_links']) echo ' checked="checked"'; ?>/>
674
  <label for='mark_broken_links'>Apply <em>class="broken_link"</em> to broken links</label><br/>
675
- <textarea type="text" name="broken_link_css" id="broken_link_css" cols='40' rows='4'/><?php
676
  if( isset($this->options['broken_link_css']) )
677
  echo $this->options['broken_link_css'];
678
  ?></textarea>
@@ -681,229 +633,619 @@ class ws_broken_link_checker {
681
  </tr>
682
 
683
  <tr valign="top">
684
- <th scope="row">Exclusion list:</th>
685
- <td>Don't check links where the URL contains any of these words (one per line):<br/>
686
- <textarea type="text" name="exclusion_list" id="exclusion_list" cols='40' rows='4'/><?php
687
  if( isset($this->options['exclusion_list']) )
688
  echo implode("\n", $this->options['exclusion_list']);
689
  ?></textarea>
690
 
691
  </td>
692
  </tr>
693
-
694
  <tr valign="top">
695
- <th scope="row">Work Session Length:</th>
696
- <td>
697
-
698
- <input type="text" name="max_work_session" id="max_work_session"
699
- value="<?php echo $this->options['max_work_session']; ?>" size='5' maxlength='3'/>
700
- seconds
701
- <br/>
702
- The link checker does its work in short "sessions" while any page of the WP admin panel is open.
703
- Typically you won't need to change this value.
704
 
705
  </td>
706
  </tr>
707
 
708
  <tr valign="top">
709
- <th scope="row">"Delete Post" option:</th>
710
  <td>
711
 
712
- <input type="checkbox" name="delete_post_button" id="delete_post_button"
713
- <?php if ($this->options['delete_post_button']) echo " checked='checked'"; ?>/>
714
- <label for='delete_post_button'>
715
- Display a "Delete Post" link in every row at the broken link list
716
- (<em>Manage -&gt; Broken Links</em>). Not recommended.</label>
 
 
 
717
 
718
  </td>
719
  </tr>
720
 
721
  </table>
722
 
723
- <p class="submit"><input type="submit" name="Submit" value="Update Options &raquo;" /></p>
724
  </form>
725
  </div>
726
  <?php
727
  }
728
 
729
- function broken_links_page(){
730
  global $wpdb;
731
- $sql="SELECT count(*) FROM $this->linkdata_name WHERE broken=1";
732
- $broken_links=$wpdb->get_var($sql);
733
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
734
  ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
735
  <div class="wrap">
736
  <h2><?php
737
- echo ($broken_links>0)?"<span id='broken_link_count'>$broken_links</span> Broken Links":
738
- "No broken links found";
 
 
 
 
739
  ?></h2>
740
- <form style="font-size: x-small;" action="javascript: void(0);" method="">
741
- <label for="sort_order">Sort by:</label><select id="sort_order" name="sort_order"
742
- onchange="sortTheList(this.options[this.selectedIndex].value); return false;">
743
- <option value="check_date" selected="selected">Default (Last checked)</option>
744
- <option value="post_date">By Date</option>
745
- </select>
746
- </form>
747
- <br style="clear:both;" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
748
  <?php
749
- $sql="SELECT b.post_title, b.post_date, a.*, b.guid FROM $this->linkdata_name a, $wpdb->posts b
750
- WHERE a.post_id=b.id AND a.broken=1 ORDER BY a.last_check DESC";
751
- $links=$wpdb->get_results($sql, OBJECT);
752
  if($links && (count($links)>0)){
753
  ?>
754
  <table class="widefat">
755
  <thead>
756
  <tr>
757
 
758
- <th scope="col"><div style="text-align: center">#</div></th>
759
-
760
- <th scope="col">Post
761
  </th>
762
  <th scope="col">Link Text</th>
763
  <th scope="col">URL</th>
764
 
765
- <th scope="col" colspan='<?php echo ($this->options['delete_post_button'])?'5':'4';x ?>'>Action</th>
 
 
766
 
767
  </tr>
768
  </thead>
769
  <tbody id="the-list">
770
  <?php
771
-
772
- $rownumber=0;
773
- /* reformatted this section - changed a few ids, made the php excerpts
774
- explicit to take advantage of syntax highlighting. Most notably, the
775
- link details section is changed here.*/
776
  foreach ($links as $link) {
777
- $rownumber++;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
778
  ?>
779
- <tr id='<?php print "link-$link->id" ?>' class='alternate'>
780
- <th scope='row' style='text-align: center'><?php print $rownumber; ?></th>
781
- <td>
782
- <a href='<?php print get_permalink($link->post_id); ?>'
783
- title='View post'><?php print $link->post_title; ?></a></td>
784
-
785
- <td><?php print $link->link_text; ?></td>
786
- <td>
787
- <a href='<?php print $link->url; ?>' target='_blank'>
788
- <?php print $this->mytruncate($link->url); ?></a>
789
- | <a href='javascript:editBrokenLink(<?php print "$link->id, \"$link->url\""; ?>)'
790
- id='link-editor-button-<?php print $link->id; ?>'>Edit</a>
791
- <br />
792
- <input type='text' size='50' id='link-editor-<?php print $link->id; ?>'
793
- value='<?php print $link->url; ?>'
794
- class='link-editor' style='display:none' />
795
  </td>
796
- <td><a href='javascript:void(0);' onclick='
797
- toggleLinkDetails(<?php print $link->id; ?>);
798
- return false;' class='edit'>Details</a></td>
799
-
800
- <td><a href='post.php?action=edit&amp;post=<?php print $link->post_id; ?>'
801
- class='edit'>Edit Post</a></td>
802
- <?php
803
- //the ""Delete Post"" button - optional
804
- if ($this->options['delete_post_button']){
805
- $deletion_url = "post.php?action=delete&post=$link->post_id";
806
- $deletion_url = wp_nonce_url($deletion_url, "delete-post_$link->post_id");
807
- echo "<td><a href='$deletion_url'>Delete Post</a></td>";
808
- }
809
- ?><td><a href='javascript:void(0);' class='delete'
810
- id='discard_button-<?php print $link->id; ?>'
811
- onclick='discardLinkMessage(<?php print $link->id; ?>);return false;'
812
- title='Discard This Message'>Discard</a></td>
813
-
814
- <td><a href='javascript:void(0);' class='delete' id='unlink_button-<?php print $link->id; ?>'
815
- onclick='removeLinkFromPost(<?php print $link->id; ?>);return false;'
816
- title='Remove the link from the post'>Unlink</a></td>
817
  </tr>
818
  <!-- Link details -->
819
- <tr id='<?php print "link-details-$link->id"; ?>' style='display:none;'>
820
- <span id='post_date_full' style='display:none;'><?php
821
- print $link->post_date;
822
- ?></span>
823
- <span id='check_date_full' style='display:none;'><?php
824
- print $link->last_check;
825
- ?></span>
826
- <td colspan='8'>
827
- <ol style='list-style-type: none; width: 50%; float: right;'>
828
- <li><strong>Log :</strong>
829
- <span id='blc_log'><?php
830
- print nl2br($link->log);
831
- ?></span></li></ol>
832
- <ol style='list-style-type: none; padding-left: 2px;'>
833
- <li><strong>Published On :</strong>
834
- <span id='post_date'><?php
835
- print strftime("%B %d, %Y",strtotime($link->post_date));
836
- ?></span></li>
837
- <li><strong>Last Checked :</strong>
838
- <span id='check_date'><?php
839
- print strftime("%B %d, %Y",strtotime($link->last_check));
840
- ?></span></li>
841
- <li><strong>Final URL :</strong>
842
- <span id='final_url'><?php
843
- print "$link->final_url";
844
- ?></span></li>
845
- <li><strong>HTTP Code :</strong>
846
- <span id='http_code'><?php
847
- print "$link->http_code";
848
- ?></span></li></ol>
849
- </td></tr><?php
850
  }
851
  ?></tbody></table><?php
 
 
 
 
 
 
 
 
 
 
 
 
 
852
  };
853
  ?>
854
- <style type='text/css'>
855
- .link-editor {
856
- font-size: 1em;
 
 
 
 
 
 
 
 
 
 
857
  }
858
- </style>
859
 
860
- <script type='text/javascript'>
861
- /** for debugging only **/
862
- var flg = 1;
863
- function msg(text){
864
- if(flg){
865
- if(window.dump !== undefined){
866
- dump(text);
867
- }
868
- if(window.console !== undefined){
869
- console.log(text);
870
- }
871
- flg++;
872
- if(flg > 200){
873
- flg = false;
874
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
875
  }
876
- }
877
- function alterLinkCounter(factor){
878
- cnt = parseInt($('broken_link_count').innerHTML);
879
- cnt = cnt + factor;
880
- $('broken_link_count').innerHTML = cnt;
881
- }
882
-
883
- function discardLinkMessage(link_id){
884
- $('discard_button-'+link_id).innerHTML = 'Wait...';
885
- new Ajax.Request('<?php
886
- echo get_option( "siteurl" ).'/wp-content/plugins/'.$this->myfolder.'/wsblc_ajax.php?';
887
- ?>action=discard_link&id='+link_id,
888
- {
889
- method:'get',
890
- onSuccess: function(transport){
891
- var re = /OK:.*/i
892
- var response = transport.responseText || "";
893
- if (re.test(response)){
894
- $('link-'+link_id).hide();
895
- $('link-details-'+link_id).hide();
896
- alterLinkCounter(-1);
897
- } else {
898
- $('discard_button-'+link_id).innerHTML = 'Discard';
899
- alert(response);
900
- }
901
- }
902
- }
903
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
904
 
905
- }
906
  function removeLinkFromPost(link_id){
 
 
907
  $('unlink_button-'+link_id).innerHTML = 'Wait...';
908
 
909
  new Ajax.Request(
@@ -927,178 +1269,933 @@ class ws_broken_link_checker {
927
  }
928
  );
929
  }
930
- /* sorting function added here */
931
- function sortTheList(sort_type){
932
- var sort_types = {
933
- "post_date": true,
934
- "check_date": true
935
- };
936
- if(sort_types[sort_type] === undefined){
937
- return false;
938
- }
939
- function rSearch(e, sTarget){
940
- if(e === null){
941
- return;
942
- }
943
- if(e.id !== undefined){
944
- if(e.id.toString() == sTarget){
945
- return e;
946
- }
947
- }
948
- return rSearch(e.firstChild, sTarget) || rSearch(e.nextSibling, sTarget);
949
- }
950
- function mergesort(list, i){
951
- i = (i === undefined) ? 0 : i;
952
- var left, right, result;
953
- if (list.length <= 1){
954
- return list;
955
- }
956
- var middle = Math.floor(list.length / 2);
957
- left = mergesort(list.slice(0, middle), (i+1));
958
- right = mergesort(list.slice(middle), (i+1));
959
- result = merge(left, right);
960
- return result;
961
- }
962
- function merge(left, right){
963
- var result = [];
964
- var lindex = 0;
965
- var rindex = 0;
966
- var str = '';
967
- for(var item in left){
968
- if(left.hasOwnProperty(item)){
969
- str += " " + left[item].id + ", ";
970
- }
971
- }
972
- var str2 = '';
973
- for(var item in right){
974
- if(right.hasOwnProperty(item)){
975
- str += " " + right[item].id + ", ";
976
- }
977
- }
978
- while(left.length > lindex && right.length > rindex){
979
- if(left[lindex].date < right[rindex].date){
980
- result.push(left[lindex]);
981
- lindex++;
982
- }
983
- else{
984
- result.push(right[rindex]);
985
- rindex++;
986
- }
987
- }
988
- if(left.length > lindex){
989
- result = result.concat(left.slice(lindex));
990
- }
991
- else{
992
- result = result.concat(right.slice(rindex));
993
- }
994
- return result;
995
- }
996
- var theList = document.getElementById('the-list');
997
- if (theList === null || theList.hasChildNodes() === false){
998
- return;
999
- }
1000
- var re = /^link-(\d+)$/;
1001
- var myid = 0;
1002
- var links = [];
1003
- var children = theList.childNodes;
1004
- var child = null;
1005
- for (var i = 0; i < children.length; i++){
1006
- child = children[i];
1007
- if(child === undefined){
1008
- continue;
1009
- }
1010
- if(child === undefined || child.nodeType !== Node.ELEMENT_NODE ||
1011
- child.nodeName !== 'TR'){
1012
- continue;
1013
- }
1014
- if(child.id && child.id.match(re)){
1015
- myid = (child.id.match(re))[1];
1016
- mydetails = child;
1017
- var mydate = false;
1018
- while ( mydetails !== undefined &&
1019
- mydetails !== null &&
1020
- mydetails.id != 'link-details-'+myid){
1021
- mydetails = mydetails.nextSibling;
1022
- }
1023
- var node = rSearch(child, sort_type+'_full').firstChild;
1024
- if(node){
1025
- mydate = new Date(node.nodeValue.toString());
1026
- links.push({
1027
- id: myid,
1028
- date: mydate,
1029
- details: mydetails === null ? null : mydetails.cloneNode(true),
1030
- link: child === null ? null : child.cloneNode(true)
1031
- });
1032
- }
1033
- }
1034
- }
1035
- while(theList.hasChildNodes()){
1036
- try{
1037
- theList.removeChild(theList.firstChild);
1038
- }
1039
- catch(e){
1040
- break;
1041
- }
1042
- }
1043
- links = mergesort(links);
1044
- for (var i = 0; i < links.length; i++){
1045
- var obj = links[i];
1046
- theList.appendChild(obj.link);
1047
- theList.appendChild(obj.details);
1048
- }
1049
- }
1050
- /* end of js changes */
1051
 
1052
- function editBrokenLink(link_id, orig_link){
1053
- if ($('link-editor-button-'+link_id).innerHTML == 'Edit'){
1054
- $('link-editor-'+link_id).show();
1055
- $('link-editor-'+link_id).focus();
1056
- $('link-editor-'+link_id).select();
1057
- $('link-editor-button-'+link_id).innerHTML = 'Save';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1058
  } else {
1059
- $('link-editor-'+link_id).hide();
1060
- new_url = $('link-editor-'+link_id).value;
1061
- if (new_url != orig_link){
1062
- //Save the changed link
1063
- new Ajax.Request(
1064
- '<?php
1065
- echo get_option( "siteurl" ).'/wp-content/plugins/'.$this->myfolder.'/wsblc_ajax.php?';
1066
- ?>action=edit_link&id='+link_id+'&new_url='+escape(new_url),
1067
- {
1068
- method:'post',
1069
- onSuccess: function(transport){
1070
- var re = /OK:.*/i
1071
- var response = transport.responseText || "";
1072
- if (re.test(response)){
1073
- $('link-'+link_id).hide();
1074
- $('link-details-'+link_id).hide();
1075
- alterLinkCounter(-1);
1076
- //alert(response);
1077
- } else {
1078
- alert(response);
1079
- }
1080
- }
1081
- }
1082
- );
1083
-
1084
- }
1085
- $('link-editor-button-'+link_id).innerHTML = 'Edit';
1086
  }
1087
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1088
 
1089
- function toggleLinkDetails(link_id){
1090
- //alert('showlinkdetails '+link_id);
1091
- $('link-details-'+link_id).toggle();
1092
- }
1093
- </script>
1094
- </div>
1095
- <?php
1096
- }
 
 
 
 
 
 
 
 
 
1097
 
1098
  }//class ends here
1099
 
1100
  } // if class_exists...
1101
 
1102
- $ws_link_checker = new ws_broken_link_checker();
 
1103
 
1104
  ?>
3
  Plugin Name: Broken Link Checker
4
  Plugin URI: http://w-shadow.com/blog/2007/08/05/broken-link-checker-for-wordpress/
5
  Description: Checks your posts for broken links and missing images and notifies you on the dashboard if any are found.
6
+ Version: 0.5
7
  Author: Janis Elsts
8
  Author URI: http://w-shadow.com/blog/
9
  */
16
  //The plugin will use Snoopy in case CURL is not available
17
  if (!class_exists('Snoopy')) require_once(ABSPATH.'/wp-includes/class-snoopy.php');
18
 
19
+ /**
20
+ * Simple function to replicate PHP 5 behaviour
21
+ */
22
+ if ( !function_exists('microtime_float') ) {
23
+ function microtime_float()
24
+ {
25
+ list($usec, $sec) = explode(" ", microtime());
26
+ return ((float)$usec + (float)$sec);
27
+ }
28
+ }
29
+
30
+ //Make sure some useful constants are defined
31
+ if ( ! defined( 'WP_CONTENT_URL' ) )
32
+ define( 'WP_CONTENT_URL', get_option( 'siteurl' ) . '/wp-content' );
33
+ if ( ! defined( 'WP_CONTENT_DIR' ) )
34
+ define( 'WP_CONTENT_DIR', ABSPATH . 'wp-content' );
35
+ if ( ! defined( 'WP_PLUGIN_URL' ) )
36
+ define( 'WP_PLUGIN_URL', WP_CONTENT_URL. '/plugins' );
37
+ if ( ! defined( 'WP_PLUGIN_DIR' ) )
38
+ define( 'WP_PLUGIN_DIR', WP_CONTENT_DIR . '/plugins' );
39
+
40
+ /*
41
+ //FirePHP for debugging
42
+ if ( !class_exists('FB') ) {
43
+ require 'FirePHPCore/fb.php';
44
+ }
45
+ //FB::setEnabled(false);
46
+
47
+ //to comment out all calls : (^[^\/]*)(FB::) -> $1\/\/$2
48
+ //to uncomment : \/\/(\s*FB::) -> $1
49
+ //*/
50
+
51
+ require 'utility-class.php';
52
+ require 'instance-classes.php';
53
+ require 'link-classes.php';
54
+
55
  if (!class_exists('ws_broken_link_checker')) {
56
 
57
  class ws_broken_link_checker {
58
  var $options;
59
  var $options_name='wsblc_options';
 
 
 
60
  var $myfile='';
61
  var $myfolder='';
62
  var $mybasename='';
63
  var $siteurl;
64
  var $defaults;
65
+
66
+ var $execution_start_time;
67
+ var $lockfile_handle = null;
68
 
69
  function ws_broken_link_checker() {
70
  global $wpdb;
71
 
72
  //set default options
73
  $this->defaults = array(
74
+ 'max_execution_time' => 5*60, //How long the worker instance may run, at most.
75
+ 'check_threshold' => 72, //Check each link every 72 hours.
76
+ 'mark_broken_links' => true, //Whether to add the broken_link class to broken links in posts.
 
77
  'broken_link_css' => ".broken_link, a.broken_link {\n\ttext-decoration: line-through;\n}",
78
+ 'exclusion_list' => array(), //Links that contain a substring listed in this array won't be checked.
79
+ 'recheck_count' => 3, //[Internal] How many times a broken link should be re-checked (slightly buggy)
80
+
81
+ //These are currently ignored. Everything is checked by default.
82
+ 'check_posts' => true,
83
+ 'check_custom_fields' => true,
84
+ 'check_blogroll' => true,
85
+
86
+ 'custom_fields' => array(), //List of custom fields that can contain URLs and should be checked.
87
+
88
+ 'autoexpand_widget' => true,
89
+
90
+ 'need_resynch' => false, //[Internal flag]
91
+
92
  );
93
+
94
+ $this->load_options();
 
 
 
 
 
95
 
 
 
96
  $this->siteurl = get_option('siteurl');
97
 
98
  $my_file = str_replace('\\', '/',__FILE__);
99
  $my_file = preg_replace('/^.*wp-content[\\\\\/]plugins[\\\\\/]/', '', $my_file);
100
+ add_action('activate_' . plugin_basename(__FILE__), array(&$this,'activation'));
101
  $this->myfile=$my_file;
102
  $this->myfolder=basename(dirname(__FILE__));
103
  $this->mybasename=plugin_basename(__FILE__);
104
+
105
+ add_action('admin_menu', array(&$this,'admin_menu'));
106
 
107
+ //These hooks update the plugin's internal records when posts are added, deleted or modified.
108
+ add_action('delete_post', array(&$this,'post_deleted'));
 
109
  add_action('save_post', array(&$this,'post_saved'));
110
+
111
+ //These do the same for (blogroll) links.
112
+ add_action('add_link', array(&$this,'hook_add_link'));
113
+ add_action('edit_link', array(&$this,'hook_edit_link'));
114
+ add_action('delete_link', array(&$this,'hook_delete_link'));
115
+
116
  add_action('admin_footer', array(&$this,'admin_footer'));
117
  add_action('admin_print_scripts', array(&$this,'admin_print_scripts'));
118
+ //The dashboard widget
119
+ add_action('wp_dashboard_setup', array(&$this, 'hook_wp_dashboard_setup'));
120
 
121
+ if ( $this->options['mark_broken_links'] ){
122
+ add_filter( 'the_content', array(&$this,'the_content') );
123
+ if ( !empty($this->options['broken_link_css']) ){
124
+ add_action( 'wp_head', array(&$this,'header_css') );
125
  }
126
  }
127
+
128
+ //AJAXy hooks
129
+ add_action( 'wp_ajax_blc_full_status', array(&$this,'ajax_full_status') );
130
+ add_action( 'wp_ajax_blc_dashboard_status', array(&$this,'ajax_dashboard_status') );
131
+ add_action( 'wp_ajax_blc_work', array(&$this,'ajax_work') );
132
+ add_action( 'wp_ajax_blc_discard', array(&$this,'ajax_discard') );
133
+ add_action( 'wp_ajax_blc_edit', array(&$this,'ajax_edit') );
134
+ add_action( 'wp_ajax_blc_link_details', array(&$this,'ajax_link_details') );
135
+ add_action( 'wp_ajax_blc_exclude_link', array(&$this,'ajax_exclude_link') );
136
+ add_action( 'wp_ajax_blc_unlink', array(&$this,'ajax_unlink') );
137
  }
138
 
139
  function admin_footer(){
141
  <!-- wsblc admin footer -->
142
  <div id='wsblc_updater_div'></div>
143
  <script type='text/javascript'>
144
+ (function($){
145
+
146
+ function blcDoWork(){
147
+ $.post(
148
+ "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
149
+ {
150
+ 'action' : 'blc_work'
151
+ },
152
+ function (data, textStatus){}
153
+ );
154
+ }
155
+ //Call it the first time
156
+ blcDoWork();
157
+ //...and every max_execution_time seconds
158
+ setInterval(blcDoWork, <?php echo ($this->options['max_execution_time'] + 1 )*1000; ?>);
159
+
160
+ })(jQuery);
161
  </script>
162
  <!-- /wsblc admin footer -->
163
  <?php
169
 
170
  function the_content($content){
171
  global $post, $wpdb;
172
+ if ( empty($post) ) return $content;
173
+
174
+ $q = "
175
+ SELECT instances.link_text, links.*
176
+
177
+ FROM {$wpdb->prefix}blc_instances AS instances, {$wpdb->prefix}blc_links AS links
178
+
179
+ WHERE
180
+ instances.source_id = %d
181
+ AND instances.source_type = 'post'
182
+ AND instances.instance_type = 'link'
183
+
184
+ AND instances.link_id = links.link_id
185
+ AND links.check_count > 0
186
+ AND ( links.http_code < 200 OR links.http_code >= 400 OR links.timeout = 1 )";
187
+
188
+ $rows = $wpdb->get_results( $wpdb->prepare( $q, $post->ID ), ARRAY_A );
189
+ if( $rows ){
190
+ $this->links_to_remove = array();
191
+ foreach($rows as $row){
192
+ $this->links_to_remove[$row['url']] = $row;
193
+ }
194
+ $content = preg_replace_callback( blcUtility::link_pattern(), array(&$this,'mark_broken_links'), $content );
195
  };
196
+
 
197
  return $content;
198
  }
199
 
200
  function mark_broken_links($matches){
201
+ //TODO:Tooltip-style popups with more info
202
+ $url = blcUtility::normalize_url( html_entity_decode( $matches[3] ) );
203
+ if( isset( $this->links_to_remove[$url] ) ){
204
+ return $matches[1].$matches[2].$matches[3].$matches[2].' class="broken_link" '.$matches[4].
205
+ $matches[5].$matches[6];
206
  } else {
207
  return $matches[0];
208
  }
209
  }
210
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
 
212
  function is_excluded($url){
213
  if (!is_array($this->options['exclusion_list'])) return false;
219
  return false;
220
  }
221
 
222
+ function dashboard_widget(){
223
  ?>
224
+ <div id='wsblc_activity_box' style="line-height : 140%">Loading...</div>
 
225
  <script type='text/javascript'>
226
+ jQuery(function($){
227
+ var blc_was_autoexpanded = false;
228
+
229
+ function blcDashboardStatus(){
230
+ $.getJSON(
231
+ "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
232
+ {
233
+ 'action' : 'blc_dashboard_status'
234
+ },
235
+ function (data, textStatus){
236
+ if ( typeof(data['text']) != 'undefined'){
237
+ $('#wsblc_activity_box').html(data.text);
238
+ <?php if ( $this->options['autoexpand_widget'] ) { ?>
239
+ //Expand the widget if there are broken links.
240
+ //Do this only once per pageload so as not to annoy the user.
241
+ if ( !blc_was_autoexpanded && ( data.status.broken_links > 0 ) ){
242
+ $('#blc_dashboard_widget.postbox').removeClass('closed');
243
+ blc_was_autoexpanded = true;
244
+ };
245
+ <?php } ?>
246
+ } else {
247
+ $('#wsblc_activity_box').html('[ Network error ]');
248
+ }
249
+
250
+ setTimeout( blcDashboardStatus, 120*1000 ); //...update every two minutes
251
+ }
252
+ );
253
+ }
254
+ blcDashboardStatus();//Call it the first time
255
+
256
+ });
257
  </script>
 
258
  <?php
259
  }
260
+
261
+ function dashboard_widget_control( $widget_id, $form_inputs = array() ){
262
+ if ( 'POST' == $_SERVER['REQUEST_METHOD'] && 'blc_dashboard_widget' == $_POST['widget_id'] ) {
263
+ //It appears $form_inputs isn't used in the current WP version, so lets just use $_POST
264
+ $this->options['autoexpand_widget'] = !empty($_POST['blc-autoexpand']);
265
+ $this->save_options();
266
+ }
267
+
268
+ ?>
269
+ <p><label for="blc-autoexpand">
270
+ <input id="blc-autoexpand" name="blc-autoexpand" type="checkbox" value="1" <?php if ( $this->options['autoexpand_widget'] ) echo 'checked="checked"'; ?> />
271
+ Automatically expand the widget if broken links have been detected
272
+ </label></p>
273
+ <?php
274
+ }
275
 
276
  function admin_print_scripts(){
277
+ //jQuery is used for AJAX and effects
278
+ wp_enqueue_script('jquery');
279
+ wp_enqueue_script('jquery-ui-core');
280
  }
281
 
282
+ /**
283
+ * ws_broken_link_checker::post_deleted()
284
+ * A hook for post_deleted. Remove link instances associated with that post.
285
+ *
286
+ * @param int $post_id
287
+ * @return void
288
+ */
289
  function post_deleted($post_id){
290
  global $wpdb;
291
+
292
+ //FB::log($post_id, "Post deleted");
293
+ //Remove this post's instances
294
+ $q = "DELETE FROM {$wpdb->prefix}blc_instances
295
+ WHERE source_id = %d AND (source_type = 'post' OR source_type='custom_field')";
296
+ $q = $wpdb->prepare($q, intval($post_id) );
297
+
298
+ //FB::log($q, 'Executing query');
299
+
300
+ if ( $wpdb->query( $q ) === false ){
301
+ //FB::error($wpdb->last_error, "Database error");
302
+ }
303
+
304
+ //Remove the synch record
305
+ $q = "DELETE FROM {$wpdb->prefix}blc_synch
306
+ WHERE source_id = %d AND source_type = 'post'";
307
+ $wpdb->query( $wpdb->prepare($q, intval($post_id)) );
308
+
309
+ //Remove any dangling link records
310
+ $this->cleanup_links();
311
  }
312
 
313
  function post_saved($post_id){
318
  if ( ($post->post_type != 'post') && ($post->post_type != 'page') ) return null;
319
  //Only check published posts
320
  if ( $post->post_status != 'publish' ) return null;
321
+
322
+ $this->mark_unsynched( $post_id, 'post' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
  }
324
+
325
+ function initiate_recheck(){
326
+ global $wpdb;
327
+
328
+ //Delete all discovered instances
329
+ $wpdb->query("TRUNCATE {$wpdb->prefix}blc_instances");
330
+
331
+ //Delete all discovered links
332
+ $wpdb->query("TRUNCATE {$wpdb->prefix}blc_links");
333
+
334
+ //Mark all posts, custom fields and bookmarks for processing.
335
+ $this->resynch();
336
+ }
337
+
338
+ function resynch(){
339
+ global $wpdb;
340
+
341
+ //Drop all synchronization records
342
+ $wpdb->query("TRUNCATE {$wpdb->prefix}blc_synch");
343
+
344
+
345
+ //Create new synchronization records for posts
346
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch(source_id, source_type, synched)
347
+ SELECT id, 'post', 0
348
+ FROM {$wpdb->posts}
349
+ WHERE
350
+ {$wpdb->posts}.post_status = 'publish'
351
+ AND {$wpdb->posts}.post_type IN ('post', 'page')";
352
+ $wpdb->query( $q );
353
+
354
+ //Create new synchronization records for bookmarks (the blogroll)
355
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch(source_id, source_type, synched)
356
+ SELECT link_id, 'blogroll', 0
357
+ FROM {$wpdb->links}
358
+ WHERE 1";
359
+ $wpdb->query( $q );
360
+
361
+ //Delete invalid instances
362
+ $this->cleanup_instances();
363
+ //Delete orphaned links
364
+ $this->cleanup_links();
365
+
366
+ $this->options['need_resynch'] = true;
367
+ $this->save_options();
368
+ }
369
+
370
+ function mark_unsynched( $source_id, $source_type ){
371
+ global $wpdb;
372
+
373
+ $q = "REPLACE INTO {$wpdb->prefix}blc_synch( source_id, source_type, synched, last_synch)
374
+ VALUES( %d, %s, %d, NOW() )";
375
+ $rez = $wpdb->query( $wpdb->prepare( $q, $source_id, $source_type, 0 ) );
376
+
377
+ if ( !$this->options['need_resynch'] ){
378
+ $this->options['need_resynch'] = true;
379
+ $this->save_options();
380
+ }
381
+
382
+ return $rez;
383
+ }
384
+
385
+ function mark_synched( $source_id, $source_type ){
386
+ global $wpdb;
387
+ //FB::log("Marking $source_type $source_id as synched.");
388
+ $q = "REPLACE INTO {$wpdb->prefix}blc_synch( source_id, source_type, synched, last_synch)
389
+ VALUES( %d, %s, %d, NOW() )";
390
+ return $wpdb->query( $wpdb->prepare( $q, $source_id, $source_type, 1 ) );
391
+ }
392
+
393
  function activation(){
394
+ //Prepare the database.
395
+ $this->upgrade_database();
 
396
 
397
+ //Clear the instance table and mark all posts and other parse-able objects as unsynchronized.
398
+ $this->resynch();
 
 
 
 
 
 
 
 
399
 
400
+ //Save the default options.
401
+ $this->save_options();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  }
403
+
404
+ /**
405
+ * ws_broken_link_checker::upgrade_database()
406
+ * Create and/or upgrade database tables
407
+ *
408
+ * @return bool
409
+ */
410
+ function upgrade_database(){
411
+ global $wpdb;
412
+
413
+ //Delete tables used by older versions of the plugin
414
+ $rez = $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}blc_linkdata, {$wpdb->prefix}blc_postdata" );
415
+ if ( $rez === false ){
416
+ //FB::error($wpdb->last_error, "Database error");
417
+ return false;
418
+ }
419
+
420
+ //Create the link table if it doesn't exist yet.
421
+ $rez = $wpdb->query(
422
+ "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}blc_links (
423
+ link_id int(20) unsigned NOT NULL auto_increment,
424
+ url text NOT NULL,
425
+ last_check datetime NOT NULL default '0000-00-00 00:00:00',
426
+ check_count int(2) unsigned NOT NULL default '0',
427
+ final_url text NOT NULL,
428
+ redirect_count smallint(5) unsigned NOT NULL,
429
+ log text NOT NULL,
430
+ http_code smallint(6) NOT NULL,
431
+ request_duration float NOT NULL default '0',
432
+ timeout tinyint(1) unsigned NOT NULL default '0',
433
+
434
+ PRIMARY KEY (link_id),
435
+ KEY url (url(150)),
436
+ KEY final_url (final_url(150)),
437
+ KEY http_code (http_code),
438
+ KEY timeout (timeout)
439
+ )"
440
+ );
441
+ if ( $rez === false ){
442
+ //FB::error($wpdb->last_error, "Database error");
443
+ return false;
444
+ }
445
+
446
+ //Create the instance table if it doesn't exist yet.
447
+ $wpdb->query(
448
+ "CREATE TABLE IF NOT EXISTS wp_blc_instances (
449
+ instance_id int(10) unsigned NOT NULL auto_increment,
450
+ link_id int(10) unsigned NOT NULL,
451
+ source_id int(10) unsigned NOT NULL,
452
+ source_type enum('post','blogroll','custom_field') NOT NULL default 'post',
453
+ link_text varchar(250) NOT NULL,
454
+ instance_type enum('link','image') NOT NULL default 'link',
455
+
456
+ PRIMARY KEY (instance_id),
457
+ KEY link_id (link_id),
458
+ KEY source_id (source_id,source_type)
459
+ )"
460
+ );
461
+ if ( $rez === false ){
462
+ //FB::error($wpdb->last_error, "Database error");
463
+ return false;
464
+ }
465
+
466
+ //....
467
+ $wpdb->query(
468
+ "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}blc_synch (
469
+ source_id int(20) unsigned NOT NULL,
470
+ source_type enum('post','blogroll') NOT NULL,
471
+ synched tinyint(3) unsigned NOT NULL,
472
+ last_synch datetime NOT NULL,
473
+ PRIMARY KEY (source_id, source_type),
474
+ KEY synched (synched)
475
+ )"
476
+ );
477
+ if ( $rez === false ){
478
+ //FB::error($wpdb->last_error, "Database error");
479
+ return false;
480
+ }
481
+
482
+ return true;
483
+ }
484
 
485
+ function admin_menu(){
486
  add_options_page('Link Checker Settings', 'Link Checker', 'manage_options',
487
+ 'link-checker-settings',array(&$this, 'options_page'));
488
  if (current_user_can('manage_options'))
489
  add_filter('plugin_action_links', array(&$this, 'plugin_action_links'), 10, 2);
490
 
491
  add_management_page('View Broken Links', 'Broken Links', 'edit_others_posts',
492
+ 'view-broken-links',array(&$this, 'links_page'));
493
  }
494
 
495
  /**
503
  */
504
  function plugin_action_links($links, $file) {
505
  if ($file == $this->mybasename)
506
+ $links[] = "<a href='options-general.php?page=link-checker-settings'>" . __('Settings') . "</a>";
507
  return $links;
508
  }
509
 
514
 
515
  function options_page(){
516
 
 
 
 
517
  if (isset($_GET['recheck']) && ($_GET['recheck'] == 'true')) {
518
+ $this->initiate_recheck();
519
  }
520
  if (isset($_GET['updated']) && ($_GET['updated'] == 'true')) {
521
+ if(isset($_POST['submit'])) {
522
 
523
+ $new_execution_time = intval($_POST['max_execution_time']);
524
+ if( $new_execution_time > 0 ){
525
+ $this->options['max_execution_time'] = $new_execution_time;
526
  }
527
 
528
+ $new_check_threshold=intval($_POST['check_threshold']);
529
+ if( $new_check_threshold > 0 ){
530
+ $this->options['check_threshold'] = $new_check_threshold;
531
  }
532
+
533
+ $this->options['mark_broken_links'] = !empty($_POST['mark_broken_links']);
534
  $new_broken_link_css = trim($_POST['broken_link_css']);
535
  $this->options['broken_link_css'] = $new_broken_link_css;
536
 
537
+ $this->options['exclusion_list']=array_filter( preg_split( '/[\s\r\n]+/',
538
+ $_POST['exclusion_list'], -1, PREG_SPLIT_NO_EMPTY ) );
539
+ //TODO: Maybe update affected links when exclusion list changes (expensive).
540
+
541
+
542
+ $new_custom_fields = array_filter( preg_split( '/[\s\r\n]+/',
543
+ $_POST['blc_custom_fields'], -1, PREG_SPLIT_NO_EMPTY ) );
544
+ $diff1 = array_diff( $new_custom_fields, $this->options['custom_fields'] );
545
+ $diff2 = array_diff( $this->options['custom_fields'], $new_custom_fields );
546
+ $this->options['custom_fields'] = $new_custom_fields;
547
+
548
+ $this->save_options();
549
+
550
+ /*
551
+ If the list of custom fields was modified then we MUST resynchronize or
552
+ custom fields linked with existing posts may not be detected. This is somewhat
553
+ inefficient.
554
+ */
555
+ if ( ( count($diff1) > 0 ) || ( count($diff2) > 0 ) ){
556
+ $this->resynch();
557
+ }
558
  }
559
 
560
  }
561
+
562
  ?>
563
  <div class="wrap"><h2>Broken Link Checker Options</h2>
 
 
 
 
 
 
 
 
 
 
 
564
 
565
+ <form name="link_checker_options" method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>?page=link-checker-settings&amp;updated=true">
566
+
567
+ <table class="form-table">
568
 
569
  <tr valign="top">
570
+ <th scope="row">Status</th>
571
  <td>
572
 
573
 
574
  <div id='wsblc_full_status'>
575
+ <br/><br/><br/>
576
  </div>
577
  <script type='text/javascript'>
578
+ (function($){
579
+
580
+ function blcUpdateStatus(){
581
+ $.getJSON(
582
+ "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
583
+ {
584
+ 'action' : 'blc_full_status'
585
+ },
586
+ function (data, textStatus){
587
+ if ( typeof(data['text']) != 'undefined'){
588
+ $('#wsblc_full_status').html(data.text);
589
+ } else {
590
+ $('#wsblc_full_status').html('[ Network error ]');
591
+ }
592
+
593
+ setTimeout(blcUpdateStatus, 10000); //...update every 10 seconds
594
+ }
595
+ );
596
+ }
597
+ blcUpdateStatus();//Call it the first time
598
+
599
+ })(jQuery);
600
  </script>
601
  <?php //JHS: Recheck all posts link: ?>
602
+ <p><input class="button" type="button" name="recheckbutton" value="Re-check all pages" onclick="location.replace('<?php echo $_SERVER['PHP_SELF']; ?>?page=link-checker-settings&amp;recheck=true')" /></p>
603
  </td>
604
  </tr>
605
 
606
  <tr valign="top">
607
+ <th scope="row">Check each link</th>
608
  <td>
609
 
610
+ Every <input type="text" name="check_threshold" id="check_threshold"
611
+ value="<?php echo $this->options['check_threshold']; ?>" size='5' maxlength='3'/>
612
  hours
613
  <br/>
614
+ <span class="description">
615
+ Existing links will be checked this often. New links will usually be checked ASAP.
616
+ </span>
617
 
618
  </td>
619
  </tr>
620
 
621
  <tr valign="top">
622
+ <th scope="row">Broken link CSS</th>
623
  <td>
624
  <input type="checkbox" name="mark_broken_links" id="mark_broken_links"
625
  <?php if ($this->options['mark_broken_links']) echo ' checked="checked"'; ?>/>
626
  <label for='mark_broken_links'>Apply <em>class="broken_link"</em> to broken links</label><br/>
627
+ <textarea name="broken_link_css" id="broken_link_css" cols='45' rows='4'/><?php
628
  if( isset($this->options['broken_link_css']) )
629
  echo $this->options['broken_link_css'];
630
  ?></textarea>
633
  </tr>
634
 
635
  <tr valign="top">
636
+ <th scope="row">Exclusion list</th>
637
+ <td>Don't check links where the URL contains any of these words (one per line) :<br/>
638
+ <textarea name="exclusion_list" id="exclusion_list" cols='45' rows='4' wrap='off'/><?php
639
  if( isset($this->options['exclusion_list']) )
640
  echo implode("\n", $this->options['exclusion_list']);
641
  ?></textarea>
642
 
643
  </td>
644
  </tr>
645
+
646
  <tr valign="top">
647
+ <th scope="row">Custom fields</th>
648
+ <td>Check URLs entered in these custom fields (one per line) : <br/>
649
+ <textarea name="blc_custom_fields" id="blc_custom_fields" cols='45' rows='4' /><?php
650
+ if( isset($this->options['custom_fields']) )
651
+ echo implode("\n", $this->options['custom_fields']);
652
+ ?></textarea>
 
 
 
653
 
654
  </td>
655
  </tr>
656
 
657
  <tr valign="top">
658
+ <th scope="row">Max. execution time (advanced)</th>
659
  <td>
660
 
661
+ <input type="text" name="max_execution_time" id="max_execution_time"
662
+ value="<?php echo $this->options['max_execution_time']; ?>" size='5' maxlength='3'/>
663
+ seconds
664
+ <br/><span class="description">
665
+ The plugin works by periodically creating a background worker instance that parses your posts looking for links,
666
+ checks the discovered URLs, and performs other time-consuming tasks. Here you can set for how long, at most,
667
+ the background instance may run each time before stopping.
668
+ </span>
669
 
670
  </td>
671
  </tr>
672
 
673
  </table>
674
 
675
+ <p class="submit"><input type="submit" name="submit" class='button-primary' value="<?php _e('Save Changes') ?>" /></p>
676
  </form>
677
  </div>
678
  <?php
679
  }
680
 
681
+ function links_page(){
682
  global $wpdb;
683
+
684
+ //Available filters by link type + the appropriate WHERE expressions
685
+ $filters = array(
686
+ 'broken' => array(
687
+ 'where_expr' => '( http_code < 200 OR http_code >= 400 OR timeout = 1 ) AND ( check_count > 0 )',
688
+ 'name' => 'Broken',
689
+ 'heading' => 'Broken Links',
690
+ 'heading_zero' => 'No broken links found'
691
+ ),
692
+ 'redirects' => array(
693
+ 'where_expr' => '( redirect_count > 0 )',
694
+ 'name' => 'Redirects',
695
+ 'heading' => 'Redirected Links',
696
+ 'heading_zero' => 'No redirects found'
697
+ ),
698
+
699
+ 'all' => array(
700
+ 'where_expr' => '1',
701
+ 'name' => 'All',
702
+ 'heading' => 'Detected Links',
703
+ 'heading_zero' => 'No links found (yet)'
704
+ ),
705
+ );
706
+
707
+ $link_type = isset($_GET['link_type'])?$_GET['link_type']:'broken';
708
+ if ( !isset($filters[$link_type]) ){
709
+ $link_type = 'broken';
710
+ }
711
+
712
+ //Get the desired page number (must be > 0)
713
+ $page = isset($_GET['paged'])?intval($_GET['paged']):'1';
714
+ if ($page < 1) $page = 1;
715
+
716
+ //Links per page [1 - 200]
717
+ $per_page = isset($_GET['per_page'])?intval($_GET['per_page']):'30';
718
+ if ($per_page < 1){
719
+ $per_page = 30;
720
+ } else if ($per_page > 200){
721
+ $per_page = 200;
722
+ }
723
+
724
+ //calculate the number of various links
725
+ foreach ($filters as $filter => $data){
726
+ $filters[$filter]['count'] = $wpdb->get_var(
727
+ "SELECT COUNT(*) FROM {$wpdb->prefix}blc_links WHERE ".$data['where_expr'] );
728
+ }
729
+ $current_filter = $filters[$link_type];
730
+ $max_pages = ceil($current_filter['count'] / $per_page);
731
+
732
+
733
+ //Select the required links + 1 instance per link.
734
+ //Note : The query might be somewhat inefficient, but I can't think of any better way to do this.
735
+ $q = "SELECT
736
+ links.*,
737
+ instances.instance_id, instances.source_id, instances.source_type,
738
+ instances.link_text, instances.instance_type,
739
+ COUNT(*) as instance_count,
740
+ posts.post_title,
741
+ posts.post_date
742
+
743
+ FROM
744
+ {$wpdb->prefix}blc_links AS links,
745
+ {$wpdb->prefix}blc_instances as instances LEFT JOIN {$wpdb->posts} as posts ON instances.source_id = posts.ID
746
+
747
+ WHERE
748
+ links.link_id = instances.link_id
749
+ AND ". $current_filter['where_expr'] ."
750
+
751
+ GROUP BY links.link_id
752
+ LIMIT ".( ($page-1) * $per_page ).", $per_page";
753
+ //echo "<pre>$q</pre>";
754
+
755
+ $links = $wpdb->get_results($q, ARRAY_A);
756
+ if ($links){
757
+ /*
758
+ echo '<pre>';
759
+ print_r($links);
760
+ echo '</pre>';
761
+ //*/
762
+ } else {
763
+ echo $wpdb->last_error;
764
+ }
765
  ?>
766
+
767
+ <script type='text/javascript'>
768
+ var blc_current_filter = '<?php echo $link_type; ?>';
769
+ </script>
770
+
771
+ <style type='text/css'>
772
+ .blc-link-editor {
773
+ font-size: 1em;
774
+ width: 95%;
775
+ }
776
+
777
+ .blc-excluded-link {
778
+ background-color: #E2E2E2;
779
+ }
780
+
781
+ .blc-small-image {
782
+ display : block;
783
+ float: left;
784
+ padding-top: 2px;
785
+ margin-right: 3px;
786
+ }
787
+ </style>
788
+
789
  <div class="wrap">
790
  <h2><?php
791
+ //Output a header matching the current filter
792
+ if ( $current_filter['count'] > 0 ){
793
+ echo "<span class='current-link-count'>{$current_filter[count]}</span> " . $current_filter['heading'];
794
+ } else {
795
+ echo "<span class='current-link-count'></span>" . $current_filter['heading_zero'];
796
+ }
797
  ?></h2>
798
+
799
+ <div class='tablenav'>
800
+ <ul class="subsubsub">
801
+ <?php
802
+ //Construct a submenu of filter types
803
+ $items = array();
804
+ foreach ($filters as $filter => $data){
805
+ $class = $number_class = '';
806
+
807
+ if ( $link_type == $filter ) $class = 'class="current"';
808
+ if ( $link_type == $filter ) $number_class = 'current-link-count';
809
+
810
+ $items[] = "<li><a href='tools.php?page=view-broken-links&link_type=$filter' $class>
811
+ {$data[name]}</a> <span class='count'>(<span class='$number_class'>{$data[count]}</span>)</span>";
812
+ }
813
+ echo implode(' |</li>', $items);
814
+ unset($items);
815
+ ?>
816
+ </ul>
817
+ <?php
818
+ //Display pagination links
819
+ $page_links = paginate_links( array(
820
+ 'base' => add_query_arg( 'paged', '%#%' ),
821
+ 'format' => '',
822
+ 'prev_text' => __('&laquo;'),
823
+ 'next_text' => __('&raquo;'),
824
+ 'total' => $max_pages,
825
+ 'current' => $page
826
+ ));
827
+
828
+ if ( $page_links ) {
829
+ echo '<div class="tablenav-pages">';
830
+ $page_links_text = sprintf( '<span class="displaying-num">' . __( 'Displaying %s&#8211;%s of <span class="current-link-count">%s</span>' ) . '</span>%s',
831
+ number_format_i18n( ( $page - 1 ) * $per_page + 1 ),
832
+ number_format_i18n( min( $page * $per_page, count($links) ) ),
833
+ number_format_i18n( $current_filter['count'] ),
834
+ $page_links
835
+ );
836
+ echo $page_links_text;
837
+ echo '</div>';
838
+ }
839
+ ?>
840
+
841
+ </div>
842
+
843
+
844
+
845
  <?php
 
 
 
846
  if($links && (count($links)>0)){
847
  ?>
848
  <table class="widefat">
849
  <thead>
850
  <tr>
851
 
852
+ <th scope="col">Source
 
 
853
  </th>
854
  <th scope="col">Link Text</th>
855
  <th scope="col">URL</th>
856
 
857
+ <?php if ( 'broken' == $link_type ) { ?>
858
+ <th scope="col"> </th>
859
+ <?php } ?>
860
 
861
  </tr>
862
  </thead>
863
  <tbody id="the-list">
864
  <?php
865
+ $rowclass = ''; $rownum = 0;
 
 
 
 
866
  foreach ($links as $link) {
867
+ $rownum++;
868
+
869
+ $rowclass = 'alternate' == $rowclass ? '' : 'alternate';
870
+ $excluded = $this->is_excluded( $link['url'] );
871
+ if ( $excluded ) $rowclass .= ' blc-excluded-link';
872
+
873
+ ?>
874
+ <tr id='<?php echo "blc-row-$rownum"; ?>' class='blc-row <?php echo $rowclass; ?>'>
875
+ <td class='post-title column-title'>
876
+ <span class='blc-link-id' style='display:none;'><?php echo $link['link_id']; ?></span>
877
+ <?php
878
+ if ( ('post' == $link['source_type']) || ('custom_field' == $link['source_type']) ){
879
+
880
+ echo "<a class='row-title' href='post.php?action=edit&amp;post=$link[source_id]' title='Edit this post'>{$link[post_title]}</a>";
881
+
882
+ //Output inline action links (copied from edit-post-rows.php)
883
+ $actions = array();
884
+ if ( current_user_can('edit_post', $link['source_id']) ) {
885
+ $actions['edit'] = '<span class="edit"><a href="' . get_edit_post_link($link['source_id'], true) . '" title="' . attribute_escape(__('Edit this post')) . '">' . __('Edit') . '</a>';
886
+ $actions['delete'] = "<span class='delete'><a class='submitdelete' title='" . attribute_escape(__('Delete this post')) . "' href='" . wp_nonce_url("post.php?action=delete&amp;post=".$link['source_id'], 'delete-post_' . $link['source_id']) . "' onclick=\"if ( confirm('" . js_escape(sprintf( __("You are about to delete the post '%s'\n 'Cancel' to stop, 'OK' to delete."), $link['post_title'] )) . "') ) { return true;}return false;\">" . __('Delete') . "</a>";
887
+ }
888
+ $actions['view'] = '<span class="view"><a href="' . get_permalink($link['source_id']) . '" title="' . attribute_escape(sprintf(__('View "%s"'), $link['post_title'])) . '" rel="permalink">' . __('View') . '</a>';
889
+ echo '<div class="row-actions">';
890
+ echo implode(' | </span>', $actions);
891
+ echo '</div>';
892
+
893
+ } elseif ( 'blogroll' == $link['source_type'] ) {
894
+
895
+ echo "<a class='row-title' href='link.php?action=edit&amp;link_id=$link[source_id]' title='Edit this bookmark'>{$link[link_text]}</a>";
896
+
897
+ //Output inline action links
898
+ $actions = array();
899
+ if ( current_user_can('manage_links') ) {
900
+ $actions['edit'] = '<span class="edit"><a href="link.php?action=edit&amp;link_id=' . $link['source_id'] . '" title="' . attribute_escape(__('Edit this bookmark')) . '">' . __('Edit') . '</a>';
901
+ $actions['delete'] = "<span class='delete'><a class='submitdelete' href='" . wp_nonce_url("link.php?action=delete&amp;link_id={$link[source_id]}", 'delete-bookmark_' . $link['source_id']) . "' onclick=\"if ( confirm('" . js_escape(sprintf( __("You are about to delete this link '%s'\n 'Cancel' to stop, 'OK' to delete."), $link['link_text'])) . "') ) { return true;}return false;\">" . __('Delete') . "</a>";
902
+ }
903
+
904
+ echo '<div class="row-actions">';
905
+ echo implode(' | </span>', $actions);
906
+ echo '</div>';
907
+
908
+ } elseif ( empty($link['source_type']) ){
909
+
910
+ echo "[An orphaned link! This is a bug.]";
911
+
912
+ }
913
+ ?>
914
+ </td>
915
+ <td class='blc-link-text'><?php
916
+ if ( 'post' == $link['source_type'] ){
917
+
918
+ if ( 'link' == $link['instance_type'] ) {
919
+ print strip_tags($link['link_text']);
920
+ } elseif ( 'image' == $link['instance_type'] ){
921
+ echo "<img src='" . WP_PLUGIN_URL . "/broken-link-checker/images/image.png' class='blc-small-image' alt='Image' title='Image'> Image";
922
+ } else {
923
+ echo '[ ??? ]';
924
+ }
925
+
926
+ } elseif ( 'custom_field' == $link['source_type'] ){
927
+
928
+ echo "<img src='" . WP_PLUGIN_URL . "/broken-link-checker/images/script_code.png' class='blc-small-image' title='Custom field' alt='Custom field'> ";
929
+ echo "<code>".$link['link_text']."</code>";
930
+
931
+ } elseif ( 'blogroll' == $link['source_type'] ){
932
+ //echo $link['link_text'];
933
+ echo "<img src='" . WP_PLUGIN_URL . "/broken-link-checker/images/link.png' class='blc-small-image' title='Bookmark' alt='Bookmark'> Bookmark";
934
+ }
935
+ ?>
936
+ </td>
937
+ <td class='column-url'>
938
+ <a href='<?php print $link['url']; ?>' target='_blank' class='blc-link-url'>
939
+ <?php print $this->mytruncate($link['url']); ?></a>
940
+ <input type='text' id='link-editor-<?php print $rownum; ?>'
941
+ value='<?php print attribute_escape($link['url']); ?>'
942
+ class='blc-link-editor' style='display:none' />
943
+ <?php
944
+ //Output inline action links for the link/URL
945
+ $actions = array();
946
+
947
+ $actions['details'] = "<span class='view'><a class='blc-details-button' href='javascript:void(0)' title='Show more info about this link'>Details</a>";
948
+
949
+ $actions['delete'] = "<span class='delete'><a class='submitdelete blc-unlink-button' title='Remove this link from all posts' ".
950
+ "id='unlink-button-$rownum' href='javascript:void(0);'>Unlink</a>";
951
+
952
+ if ( $excluded ){
953
+ $actions['exclude'] = "<span class='delete'>Excluded";
954
+ } else {
955
+ $actions['exclude'] = "<span class='delete'><a class='submitdelete blc-exclude-button' title='Add this URL to the exclusion list' ".
956
+ "id='exclude-button-$rownum' href='javascript:void(0);'>Exclude</a>";
957
+ }
958
+
959
+ $actions['edit'] = "<span class='edit'><a href='javascript:void(0)' class='blc-edit-button' title='Edit link URL'>Edit URL</a>";
960
+
961
+ echo '<div class="row-actions">';
962
+ echo implode(' | </span>', $actions);
963
+
964
+ echo "<span style='display:none' class='blc-cancel-button-container'> ",
965
+ "| <a href='javascript:void(0)' class='blc-cancel-button' title='Cancel URL editing'>Cancel</a></span>";
966
+
967
+ echo '</div>';
968
  ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
969
  </td>
970
+ <?php if ( 'broken' == $link_type ) { ?>
971
+ <td><a href='javascript:void(0);'
972
+ id='discard_button-<?php print $rownum; ?>'
973
+ class='blc-discard-button'
974
+ title='Remove this message and mark the link as valid'>Discard</a>
975
+ </td>
976
+ <?php } ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
977
  </tr>
978
  <!-- Link details -->
979
+ <tr id='<?php print "link-details-$rownum"; ?>' style='display:none;' class='blc-link-details'>
980
+ <td colspan='4'><?php $this->link_details_row($link); ?></td>
981
+ </tr><?php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
982
  }
983
  ?></tbody></table><?php
984
+
985
+ //Also display pagination links at the bottom
986
+ if ( $page_links ) {
987
+ echo '<div class="tablenav"><div class="tablenav-pages">';
988
+ $page_links_text = sprintf( '<span class="displaying-num">' . __( 'Displaying %s&#8211;%s of <span class="current-link-count">%s</span>' ) . '</span>%s',
989
+ number_format_i18n( ( $page - 1 ) * $per_page + 1 ),
990
+ number_format_i18n( min( $page * $per_page, count($links) ) ),
991
+ number_format_i18n( $current_filter['count'] ),
992
+ $page_links
993
+ );
994
+ echo $page_links_text;
995
+ echo '</div></div>';
996
+ }
997
  };
998
  ?>
999
+ <?php $this->links_page_js(); ?>
1000
+ </div>
1001
+ <?php
1002
+ }
1003
+
1004
+ function links_page_js(){
1005
+ ?>
1006
+ <script type='text/javascript'>
1007
+
1008
+ function alterLinkCounter(factor){
1009
+ cnt = parseInt(jQuery('.current-link-count').eq(0).html());
1010
+ cnt = cnt + factor;
1011
+ jQuery('.current-link-count').html(cnt);
1012
  }
 
1013
 
1014
+ jQuery(function($){
1015
+
1016
+ //The discard button - manually mark the link as valid. The link will be checked again later.
1017
+ $(".blc-discard-button").click(function () {
1018
+ var me = this;
1019
+ $(me).html('Wait...');
1020
+
1021
+ var link_id = $(me).parents('.blc-row').find('.blc-link-id').html();
1022
+
1023
+ $.post(
1024
+ "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
1025
+ {
1026
+ 'action' : 'blc_discard',
1027
+ 'link_id' : link_id
1028
+ },
1029
+ function (data, textStatus){
1030
+ if (data == 'OK'){
1031
+ var master = $(me).parents('.blc-row');
1032
+ var details = master.next('.blc-link-details');
1033
+
1034
+ details.hide();
1035
+ //Flash the main row green to indicate success, then hide it.
1036
+ var oldColor = master.css('background-color');
1037
+ master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300, function(){
1038
+ master.hide();
1039
+ });
1040
+
1041
+ alterLinkCounter(-1);
1042
+ } else {
1043
+ $(me).html('Discard');
1044
+ alert(data);
1045
+ }
1046
+ }
1047
+ );
1048
+ });
1049
+
1050
+ //The details button - display/hide detailed info about a link
1051
+ $(".blc-details-button, .blc-link-text").click(function () {
1052
+ $(this).parents('.blc-row').next('.blc-link-details').toggle();
1053
+ });
1054
+
1055
+ //The edit button - edit/save the link's URL
1056
+ $(".blc-edit-button").click(function () {
1057
+ var edit_button = $(this);
1058
+ var master = $(edit_button).parents('.blc-row');
1059
+ var editor = $(master).find('.blc-link-editor');
1060
+ var url_el = $(master).find('.blc-link-url');
1061
+ var cancel_button_container = $(master).find('.blc-cancel-button-container');
1062
+
1063
+ //Find the current/original URL
1064
+ var orig_url = url_el.attr('href');
1065
+ //Find the link ID
1066
+ var link_id = $(master).find('.blc-link-id').html();
1067
+
1068
+ if ( !$(editor).is(':visible') ){
1069
+ //Begin editing
1070
+ url_el.hide();
1071
+ editor.show();
1072
+ cancel_button_container.show();
1073
+ editor.focus();
1074
+ editor.select();
1075
+ edit_button.html('Save URL');
1076
+ } else {
1077
+ editor.hide();
1078
+ cancel_button_container.hide();
1079
+ url_el.show();
1080
+
1081
+ new_url = editor.val();
1082
+
1083
+ if (new_url != orig_url){
1084
+ //Save the changed link
1085
+ url_el.html('Saving changes...');
1086
+
1087
+ $.getJSON(
1088
+ "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
1089
+ {
1090
+ 'action' : 'blc_edit',
1091
+ 'link_id' : link_id,
1092
+ 'new_url' : new_url
1093
+ },
1094
+ function (data, textStatus){
1095
+ var display_url = '';
1096
+
1097
+ if ( typeof(data['error']) != 'undefined'){
1098
+ //data.error is an error message
1099
+ alert(data.error);
1100
+ display_url = orig_url;
1101
+ } else {
1102
+ //data contains info about the performed edit
1103
+ if ( data.cnt_okay > 0 ){
1104
+ display_url = new_url;
1105
+
1106
+ url_el.attr('href', new_url);
1107
+
1108
+ if ( data.cnt_error > 0 ){
1109
+ var msg = "The link was successfully modifed.";
1110
+ msg = msg + "\nHowever, "+data.cnt_error+" instances couldn't be edited and still point to the old URL."
1111
+ alert(msg);
1112
+ } else {
1113
+ //Flash the row green to indicate success
1114
+ var oldColor = master.css('background-color');
1115
+ master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300);
1116
+
1117
+ //Save the new ID
1118
+ master.find('.blc-link-id').html(data.new_link_id);
1119
+ //Load up the new link info (so sue me)
1120
+ master.next('.blc-link-details').find('td').html('<center>Loading...</center>').load(
1121
+ "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
1122
+ {
1123
+ 'action' : 'blc_link_details',
1124
+ 'link_id' : data.new_link_id
1125
+ }
1126
+ );
1127
+ }
1128
+ } else {
1129
+ alert("Something went wrong. The plugin failed to edit "+
1130
+ data.cnt_error + ' instance(s) of this link.');
1131
+
1132
+ display_url = orig_url;
1133
+ }
1134
+ };
1135
+
1136
+ //Shorten the displayed URL if it's > 50 characters
1137
+ if ( display_url.length > 50 ){
1138
+ display_url = display_url.substr(0, 47) + '...';
1139
+ }
1140
+ url_el.html(display_url);
1141
+ }
1142
+ );
1143
+
1144
+ } else {
1145
+ //It's the same URL, so do nothing.
1146
+ }
1147
+ edit_button.html('Edit URL');
1148
  }
1149
+ });
1150
+
1151
+ $(".blc-cancel-button").click(function () {
1152
+ var master = $(this).parents('.blc-row');
1153
+ var url_el = $(master).find('.blc-link-url');
1154
+
1155
+ //Hide the cancel button
1156
+ $(this).parent().hide();
1157
+ //Show the un-editable URL again
1158
+ url_el.show();
1159
+ //reset and hide the editor
1160
+ master.find('.blc-link-editor').hide().val(url_el.attr('href'));
1161
+ //Set the edit button to say "Edit URL"
1162
+ master.find('.blc-edit-button').html('Edit URL');
1163
+ });
1164
+
1165
+ //The unlink button - remove the link/image from all posts, custom fields, etc.
1166
+ $(".blc-unlink-button").click(function () {
1167
+ var me = this;
1168
+ var master = $(me).parents('.blc-row');
1169
+ $(me).html('Wait...');
1170
+
1171
+ var link_id = $(me).parents('.blc-row').find('.blc-link-id').html();
1172
+
1173
+ $.post(
1174
+ "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
1175
+ {
1176
+ 'action' : 'blc_unlink',
1177
+ 'link_id' : link_id
1178
+ },
1179
+ function (data, textStatus){
1180
+ eval('data = ' + data);
1181
+
1182
+ if ( typeof(data['ok']) != 'undefined'){
1183
+ //Hide the details
1184
+ master.next('.blc-link-details').hide();
1185
+ //Flash the main row green to indicate success, then hide it.
1186
+ var oldColor = master.css('background-color');
1187
+ master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300, function(){
1188
+ master.hide();
1189
+ });
1190
+
1191
+ alterLinkCounter(-1);
1192
+ } else {
1193
+ $(me).html('Unlink');
1194
+ //Show the error message
1195
+ alert(data.error);
1196
+ }
1197
+ }
1198
+ );
1199
+ });
1200
+
1201
+ //The exclude button - Add this link to the exclusion list
1202
+ $(".blc-exclude-button").click(function () {
1203
+ var me = this;
1204
+ var master = $(me).parents('.blc-row');
1205
+ var details = master.next('.blc-link-details');
1206
+ $(me).html('Wait...');
1207
+
1208
+ var link_id = $(me).parents('.blc-row').find('.blc-link-id').html();
1209
+
1210
+ $.post(
1211
+ "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
1212
+ {
1213
+ 'action' : 'blc_exclude_link',
1214
+ 'link_id' : link_id
1215
+ },
1216
+ function (data, textStatus){
1217
+ eval('data = ' + data);
1218
+
1219
+ if ( typeof(data['ok']) != 'undefined'){
1220
+
1221
+ if ( 'broken' == blc_current_filter ){
1222
+ //Flash the row green to indicate success, then hide it.
1223
+ $(me).replaceWith('Excluded');
1224
+ master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: '#E2E2E2' }, 200, function(){
1225
+ details.hide();
1226
+ master.hide();
1227
+ alterLinkCounter(-1);
1228
+ });
1229
+ master.addClass('blc-excluded-link');
1230
+ } else {
1231
+ //Flash the row green to indicate success and fade to the "excluded link" color
1232
+ master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: '#E2E2E2' }, 300);
1233
+ master.addClass('blc-excluded-link');
1234
+ $(me).replaceWith('Excluded');
1235
+ }
1236
+ } else {
1237
+ $(me).html('Exclude');
1238
+ alert(data.error);
1239
+ }
1240
+ }
1241
+ );
1242
+ });
1243
+
1244
+ });
1245
 
 
1246
  function removeLinkFromPost(link_id){
1247
+ if (!confirm('Do you really want to remove this link from all posts, custom fields and the blogroll?')) return;
1248
+
1249
  $('unlink_button-'+link_id).innerHTML = 'Wait...';
1250
 
1251
  new Ajax.Request(
1269
  }
1270
  );
1271
  }
1272
+ </script>
1273
+ <?php
1274
+ }
1275
+
1276
+ function link_details_row($link){
1277
+ ?>
1278
+ <span id='post_date_full' style='display:none;'><?php
1279
+ print $link['post_date'];
1280
+ ?></span>
1281
+ <span id='check_date_full' style='display:none;'><?php
1282
+ print $link['last_check'];
1283
+ ?></span>
1284
+ <ol style='list-style-type: none; width: 50%; float: right;'>
1285
+ <li><strong>Log :</strong>
1286
+ <span class='blc_log'><?php
1287
+ print nl2br($link['log']);
1288
+ ?></span></li>
1289
+ </ol>
1290
+
1291
+ <ol style='list-style-type: none; padding-left: 2px;'>
1292
+ <?php if ( !empty($link['post_date']) ) { ?>
1293
+ <li><strong>Post published on :</strong>
1294
+ <span class='post_date'><?php
1295
+ print strftime("%B %d, %Y",strtotime($link['post_date']));
1296
+ ?></span></li>
1297
+ <?php } ?>
1298
+ <li><strong>Link last checked :</strong>
1299
+ <span class='check_date'><?php
1300
+ $last_check = strtotime($link['last_check']);
1301
+ if ( $last_check < strtotime('-10 years') ){
1302
+ echo 'Never';
1303
+ } else {
1304
+ echo strftime( "%B %d, %Y", $last_check );
1305
+ }
1306
+ ?></span></li>
1307
+
1308
+ <li><strong>HTTP code :</strong>
1309
+ <span class='http_code'><?php
1310
+ print $link['http_code'];
1311
+ ?></span></li>
1312
+
1313
+ <li><strong>Response time :</strong>
1314
+ <span class='request_duration'><?php
1315
+ printf('%2.3f seconds', $link['request_duration']);
1316
+ ?></span></li>
1317
+
1318
+ <li><strong>Final URL :</strong>
1319
+ <span class='final_url'><?php
1320
+ print $link['final_url'];
1321
+ ?></span></li>
1322
+
1323
+ <li><strong>Redirect count :</strong>
1324
+ <span class='redirect_count'><?php
1325
+ print $link['redirect_count'];
1326
+ ?></span></li>
1327
+
1328
+ <li><strong>Instance count :</strong>
1329
+ <span class='instance_count'><?php
1330
+ print $link['instance_count'];
1331
+ ?></span></li>
1332
+
1333
+ <?php if ( intval( $link['check_count'] ) > 0 ){ ?>
1334
+ <li><br/>This link has failed
1335
+ <span class='check_count'><?php
1336
+ echo $link['check_count'];
1337
+ if ( intval($link['check_count'])==1 ){
1338
+ echo ' time';
1339
+ } else {
1340
+ echo ' times';
1341
+ }
1342
+ ?></span>.</li>
1343
+ <?php } ?>
1344
+ </ol>
1345
+ <?php
1346
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1347
 
1348
+ /**
1349
+ * ws_broken_link_checker::cleanup_links()
1350
+ * Remove orphaned links that have no corresponding instances
1351
+ *
1352
+ * @param int $link_id (optional) Only check this link
1353
+ * @return bool
1354
+ */
1355
+ function cleanup_links( $link_id = null ){
1356
+ global $wpdb;
1357
+
1358
+ $q = "DELETE FROM {$wpdb->prefix}blc_links
1359
+ USING {$wpdb->prefix}blc_links LEFT JOIN {$wpdb->prefix}blc_instances
1360
+ ON {$wpdb->prefix}blc_instances.link_id = {$wpdb->prefix}blc_links.link_id
1361
+ WHERE
1362
+ {$wpdb->prefix}blc_instances.link_id IS NULL";
1363
+
1364
+ if ( $link_id !==null ) {
1365
+ $q .= " AND {$wpdb->prefix}blc_links.link_id = " . intval( $link_id );
1366
+ }
1367
+
1368
+ return $wpdb->query( $q );
1369
+ }
1370
+
1371
+ /**
1372
+ * ws_broken_link_checker::cleanup_instances()
1373
+ * Remove instances that reference invalid posts or bookmarks
1374
+ *
1375
+ * @return bool
1376
+ */
1377
+ function cleanup_instances(){
1378
+ global $wpdb;
1379
+
1380
+ //Delete all instances that reference non-existent posts
1381
+ $q = "DELETE FROM {$wpdb->prefix}blc_instances
1382
+ USING {$wpdb->prefix}blc_instances LEFT JOIN {$wpdb->posts} ON {$wpdb->prefix}blc_instances.source_id = {$wpdb->posts}.ID
1383
+ WHERE
1384
+ {$wpdb->posts}.ID IS NULL
1385
+ AND ( ( {$wpdb->prefix}blc_instances.source_type = 'post' ) OR ( {$wpdb->prefix}blc_instances.source_type = 'custom_field' ) )";
1386
+ $rez = $wpdb->query($q);
1387
+
1388
+ //Delete all instances that reference non-existant bookmarks
1389
+ $q = "DELETE FROM {$wpdb->prefix}blc_instances
1390
+ USING {$wpdb->prefix}blc_instances LEFT JOIN {$wpdb->links} ON {$wpdb->prefix}blc_instances.source_id = {$wpdb->links}.link_id
1391
+ WHERE
1392
+ {$wpdb->links}.link_id IS NULL
1393
+ AND {$wpdb->prefix}blc_instances.source_type = 'blogroll' ";
1394
+ $rez2 = $wpdb->query($q);
1395
+
1396
+ return $rez and $rez2;
1397
+ }
1398
+
1399
+ function load_options(){
1400
+ $this->options = get_option($this->options_name);
1401
+ if(!is_array($this->options)){
1402
+ $this->options = $this->defaults;
1403
  } else {
1404
+ $this->options = array_merge($this->defaults, $this->options);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1405
  }
1406
+ }
1407
+
1408
+ function save_options(){
1409
+ update_option($this->options_name,$this->options);
1410
+ }
1411
+
1412
+ /**
1413
+ * ws_broken_link_checker::parse_post()
1414
+ * Parse a post for links and save them to the DB.
1415
+ *
1416
+ * @param string $content Post content
1417
+ * @param int $post_id Post ID
1418
+ * @return void
1419
+ */
1420
+ function parse_post($content, $post_id){
1421
+ //remove all <code></code> blocks first
1422
+ $content = preg_replace('/<code>.+?<\/code>/i', ' ', $content);
1423
+
1424
+ //Find links
1425
+ if(preg_match_all(blcUtility::link_pattern(), $content, $matches, PREG_SET_ORDER)){
1426
+ foreach($matches as $link){
1427
+ $url = $link[3];
1428
+ $text = strip_tags( $link[5] );
1429
+ //FB::log($url, "Found link");
1430
+
1431
+ $url = blcUtility::normalize_url($url);
1432
+ //Skip invalid links
1433
+ if ( !$url || (strlen($url)<6) ) continue;
1434
+
1435
+ //Create or load the link
1436
+ $link_obj = new blcLink($url);
1437
+ //Add & save a new instance
1438
+ $link_obj->add_instance($post_id, 'post', $text, 'link');
1439
+ }
1440
+ };
1441
+
1442
+ //Find images (<img src=...>)
1443
+ if(preg_match_all(blcUtility::img_pattern(), $content, $matches, PREG_SET_ORDER)){
1444
+ foreach($matches as $img){
1445
+ $url = $img[3];
1446
+ //FB::log($url, "Found image");
1447
+
1448
+ $url = blcUtility::normalize_url($url);
1449
+ if ( !$url || (strlen($url)<6) ) continue; //skip invalid URLs
1450
+
1451
+ //Create or load the link
1452
+ $link = new blcLink($url);
1453
+ //Add & save a new image instance
1454
+ $link->add_instance($post_id, 'post', '', 'image');
1455
+ }
1456
+ };
1457
+ }
1458
+
1459
+ /**
1460
+ * ws_broken_link_checker::parse_post_meta()
1461
+ * Parse a post's custom fields for links and save them in the DB.
1462
+ *
1463
+ * @param id $post_id
1464
+ * @return void
1465
+ */
1466
+ function parse_post_meta($post_id){
1467
+ //Get all custom fields of this post
1468
+ $custom_fields = get_post_custom( $post_id );
1469
+ //FB::log($custom_fields, "Custom fields loaded");
1470
+
1471
+ //Parse the enabled fields
1472
+ foreach( $this->options['custom_fields'] as $field ){
1473
+ if ( !isset($custom_fields[$field]) ) continue;
1474
+
1475
+ //FB::log($field, "Parsing field");
1476
+
1477
+ $values = $custom_fields[$field];
1478
+ if ( !is_array( $values ) ) $values = array($values);
1479
+
1480
+ foreach( $values as $value ){
1481
+
1482
+ //Attempt to parse the $value as URL
1483
+ $url = blcUtility::normalize_url($value);
1484
+ if ( empty($url) ){
1485
+ //FB::warn($value, "Invalid URL in custom field ".$field);
1486
+ continue;
1487
+ }
1488
+
1489
+ //FB::log($url, "Found URL");
1490
+ $link = new blcLink( $url );
1491
+ //FB::log($link, 'Created/loaded link');
1492
+ $inst = $link->add_instance( $post_id, 'custom_field', $field, 'link' );
1493
+ //FB::log($inst, 'Created instance');
1494
+ }
1495
+ }
1496
+
1497
+ }
1498
+
1499
+ function parse_blogroll_link( $the_link ){
1500
+ //FB::log($the_link, "Parsing blogroll link");
1501
+
1502
+ //Attempt to parse the URL
1503
+ $url = blcUtility::normalize_url( $the_link['link_url'] );
1504
+ if ( empty($url) ){
1505
+ //FB::warn( $the_link['link_url'], "Invalid URL in for a blogroll link".$the_link['link_name'] );
1506
+ return false;
1507
+ }
1508
+
1509
+ //FB::log($url, "Found URL");
1510
+ $link = new blcLink( $url );
1511
+ return $link->add_instance( $the_link['link_id'], 'blogroll', $the_link['link_name'], 'link' );
1512
+ }
1513
+
1514
+ function start_timer(){
1515
+ $this->execution_start_time = microtime_float();
1516
+ }
1517
+
1518
+ function execution_time(){
1519
+ return microtime_float() - $this->execution_start_time;
1520
+ }
1521
+
1522
+ /**
1523
+ * ws_broken_link_checker::work()
1524
+ * The main worker function that does all kinds of things.
1525
+ *
1526
+ * @return void
1527
+ */
1528
+ function work(){
1529
+ global $wpdb;
1530
+
1531
+ if ( !$this->acquire_lock() ){
1532
+ //FB::warn("Another instance of BLC is already working. Stop.");
1533
+ return false;
1534
+ }
1535
+
1536
+ $this->start_timer();
1537
+
1538
+ $max_execution_time = $this->options['max_execution_time'];
1539
+
1540
+ /*****************************************
1541
+ Preparation
1542
+ ******************************************/
1543
+ // Check for safe mode
1544
+ if( ini_get('safe_mode') ){
1545
+ // Do it the safe mode way
1546
+ $t=ini_get('max_execution_time');
1547
+ if ($t && ($t < $max_execution_time))
1548
+ $max_execution_time = $t-1;
1549
+ } else {
1550
+ // Do it the regular way
1551
+ @set_time_limit( $max_execution_time * 2 ); //x2 should be plenty, running any longer would mean a glitch.
1552
+ }
1553
+ @ignore_user_abort(true);
1554
+
1555
+ $check_threshold = date('Y-m-d H:i:s', strtotime('-'.$this->options['check_threshold'].' hours'));
1556
+ $recheck_threshold = date('Y-m-d H:i:s', strtotime('-20 minutes'));
1557
+
1558
+ $orphans_possible = false;
1559
+
1560
+ $still_need_resynch = false;
1561
+
1562
+ /*****************************************
1563
+ Parse posts and bookmarks
1564
+ ******************************************/
1565
+
1566
+ if ( $this->options['need_resynch'] ) {
1567
+
1568
+ //FB::log("Looking for posts and bookmarks that need parsing...");
1569
+
1570
+ $tsynch = $wpdb->prefix.'blc_synch';
1571
+ $tposts = $wpdb->posts;
1572
+ $tlinks = $wpdb->links;
1573
+
1574
+ $synch_q = "SELECT $tsynch.source_id, $tsynch.source_type, $tposts.post_content, $tlinks.link_url, $tlinks.link_id, $tlinks.link_name
1575
+
1576
+ FROM
1577
+ $tsynch LEFT JOIN $tposts
1578
+ ON ($tposts.id = $tsynch.source_id AND $tsynch.source_type='post')
1579
+ LEFT JOIN wp_links
1580
+ ON ($tlinks.link_id = $tsynch.source_id AND $tsynch.source_type='blogroll')
1581
+
1582
+ WHERE
1583
+ $tsynch.synched = 0
1584
+
1585
+ LIMIT 50";
1586
+
1587
+ while ( $rows = $wpdb->get_results($synch_q, ARRAY_A) ) {
1588
+
1589
+ //FB::log("Found ".count($rows)." items to analyze.");
1590
+
1591
+ foreach ($rows as $row) {
1592
+
1593
+ if ( $row['source_type'] == 'post' ){
1594
+
1595
+ //FB::log("Parsing post ".$row['source_id']);
1596
+
1597
+ //Remove instances associated with this post
1598
+ $q = "DELETE FROM {$wpdb->prefix}blc_instances
1599
+ WHERE source_id = %d AND (source_type = 'post' OR source_type='custom_field')";
1600
+ $q = $wpdb->prepare($q, intval($row['source_id']));
1601
+
1602
+ //FB::log($q, "Executing query");
1603
+
1604
+ if ( $wpdb->query( $q ) === false ){
1605
+ //FB::error($wpdb->last_error, "Database error");
1606
+ }
1607
+
1608
+ //Gather links and images from the post
1609
+ $this->parse_post( $row['post_content'], $row['source_id'] );
1610
+ //Gather links from custom fields
1611
+ $this->parse_post_meta( $row['source_id'] );
1612
+
1613
+ //Some link records might be orhpaned now
1614
+ $orphans_possible = true;
1615
+
1616
+ } else {
1617
+
1618
+ //FB::log("Parsing bookmark ".$row['source_id']);
1619
+
1620
+ //Remove instances associated with this bookmark
1621
+ $q = "DELETE FROM {$wpdb->prefix}blc_instances
1622
+ WHERE source_id = %d AND source_type = 'blogroll'";
1623
+ $q = $wpdb->prepare($q, intval($row['source_id']));
1624
+ //FB::log($q, "Executing query");
1625
+
1626
+ if ( $wpdb->query( $q ) === false ){
1627
+ //FB::error($wpdb->last_error, "Database error");
1628
+ }
1629
+
1630
+ //(Re)add the instance and link
1631
+ $this->parse_blogroll_link( $row );
1632
+
1633
+ //Some link records might be orhpaned now
1634
+ $orphans_possible = true;
1635
+
1636
+ }
1637
+
1638
+ //Update the table to indicate the item has been parsed
1639
+ $this->mark_synched( $row['source_id'], $row['source_type'] );
1640
+
1641
+ //Check if we still have some execution time left
1642
+ if( $this->execution_time() > $max_execution_time ){
1643
+ //FB::log('The alloted execution time has run out');
1644
+ $this->cleanup_links();
1645
+ $this->release_lock();
1646
+ return;
1647
+ }
1648
+
1649
+ }
1650
+
1651
+ }
1652
+
1653
+ //FB::log('No unparsed items found.');
1654
+ $still_need_resynch = false;
1655
+
1656
+ if ( $wpdb->last_error ){
1657
+ //FB::error($wpdb->last_error, "Database error");
1658
+ }
1659
+
1660
+ } else {
1661
+ //FB::log('Resynch not required.');
1662
+ }
1663
+
1664
+ /******************************************
1665
+ Resynch done?
1666
+ *******************************************/
1667
+ if ( $this->options['need_resynch'] && !$still_need_resynch ){
1668
+ $this->options['need_resynch'] = $still_need_resynch;
1669
+ $this->save_options();
1670
+ }
1671
+
1672
+ /******************************************
1673
+ Remove orphaned links
1674
+ *******************************************/
1675
+
1676
+ if ( $orphans_possible ) {
1677
+ //FB::log('Cleaning up the link table.');
1678
+ $this->cleanup_links();
1679
+ }
1680
+
1681
+ //Check if we still have some execution time left
1682
+ if( $this->execution_time() > $max_execution_time ){
1683
+ //FB::log('The alloted execution time has run out');
1684
+ $this->release_lock();
1685
+ return;
1686
+ }
1687
+
1688
+ /*****************************************
1689
+ Check links
1690
+ ******************************************/
1691
+ //FB::log('Looking for links to check (threshold : '.$check_threshold.')...');
1692
+
1693
+ //Select some links that haven't been checked for a long time or
1694
+ //that are broken and need to be re-checked again.
1695
+
1696
+ //Note : This is a slow query, but AFAIK there is no way to speed it up.
1697
+ //I could put an index on last_check, but that value is almost certainly unique
1698
+ //for each row so it wouldn't be much better than a full table scan.
1699
+ $q = "SELECT *, ( last_check < %s ) AS meets_check_threshold
1700
+ FROM {$wpdb->prefix}blc_links
1701
+ WHERE
1702
+ ( last_check < %s )
1703
+ OR
1704
+ (
1705
+ ( http_code >= 400 OR http_code < 200 OR timeout = 1)
1706
+ AND check_count < %d
1707
+ AND check_count > 0
1708
+ AND last_check < %s
1709
+ )
1710
+ ORDER BY last_check ASC
1711
+ LIMIT 50";
1712
+ $link_q = $wpdb->prepare($q, $check_threshold, $check_threshold, $this->options['recheck_count'], $recheck_threshold);
1713
+ //FB::log($link_q);
1714
+
1715
+ while ( $links = $wpdb->get_results($link_q, ARRAY_A) ){
1716
+
1717
+ //some unchecked links found
1718
+ //FB::log("Checking ".count($links)." link(s)");
1719
+
1720
+ foreach ($links as $link) {
1721
+ $link_obj = new blcLink($link);
1722
+
1723
+ //Does this link need to be checked?
1724
+ if ( !$this->is_excluded( $link['url'] ) ) {
1725
+ //Yes, do it
1726
+ //FB::log("Checking link {$link[link_id]}");
1727
+ $link_obj->check();
1728
+ $link_obj->save();
1729
+ } else {
1730
+ //Nope, mark it as already checked.
1731
+ //FB::info("The URL {$link_obj->url} is excluded, marking link {$link_obj->link_id} as already checked.");
1732
+ $link_obj->last_check = date('Y-m-d H:i:s');
1733
+ $link_obj->http_code = 200; //Use a fake code so that the link doesn't show up in queries looking for broken links.
1734
+ $link_obj->timeout = false;
1735
+ $link_obj->request_duration = 0;
1736
+ $link_obj->log = "This link wasn't checked because a matching keyword was found on your exclusion list.";
1737
+ $link_obj->save();
1738
+ }
1739
+
1740
+ //Check if we still have some execution time left
1741
+ if( $this->execution_time() > $max_execution_time ){
1742
+ //FB::log('The alloted execution time has run out');
1743
+ $this->release_lock();
1744
+ return;
1745
+ }
1746
+ }
1747
+ }
1748
+ //FB::log('No links need to be checked right now.');
1749
+
1750
+ $this->release_lock();
1751
+ //FB::log('All done.');
1752
+ }
1753
+
1754
+ function ajax_full_status( ){
1755
+ $status = $this->get_status();
1756
+ $text = $this->status_text( $status );
1757
+
1758
+ echo json_encode( array(
1759
+ 'text' => $text,
1760
+ 'status' => $status,
1761
+ ) );
1762
+
1763
+ die();
1764
+ }
1765
+
1766
+ function status_text( $status ){
1767
+ $text = '';
1768
+
1769
+ if( $status['broken_links'] > 0 ){
1770
+ $text .= sprintf( "<a href='%stools.php?page=view-broken-links' title='View broken links'><strong>Found %d broken link%s</strong></a>",
1771
+ get_option('wpurl'), $status['broken_links'], ( $status['broken_links'] == 1 )?'':'s' );
1772
+ } else {
1773
+ $text .= "No broken links found.";
1774
+ }
1775
+
1776
+ $text .= "<br/>";
1777
+
1778
+ if( $status['unchecked_links'] > 0) {
1779
+ $text .= sprintf( '%d URL%s in the work queue', $status['unchecked_links'], ($status['unchecked_links'] == 1)?'':'s' );
1780
+ } else {
1781
+ $text .= "No URLs in the work queue.";
1782
+ }
1783
+
1784
+ $text .= "<br/>";
1785
+ if ( $status['known_links'] > 0 ){
1786
+ $text .= sprintf( "Detected %d unique URL%s in %d link%s",
1787
+ $status['known_links'], $status['known_links'] == 1 ? '' : 's',
1788
+ $status['known_instances'], $status['known_instances'] == 1 ? '' : 's'
1789
+ );
1790
+ if ($this->options['need_resynch']){
1791
+ $text .= ' and still searching...';
1792
+ } else {
1793
+ $text .= '.';
1794
+ }
1795
+ } else {
1796
+ if ($this->options['need_resynch']){
1797
+ $text .= 'Searching your blog for links...';
1798
+ } else {
1799
+ $text .= 'No links detected.';
1800
+ }
1801
+ }
1802
+
1803
+ return $text;
1804
+ }
1805
+
1806
+ function ajax_dashboard_status(){
1807
+ //Just display the full status.
1808
+ $this->ajax_full_status( false );
1809
+ /*
1810
+ global $wpdb;
1811
+
1812
+ //displays a notification if broken links have been found
1813
+ $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links
1814
+ WHERE check_count > 0 AND ( http_code < 200 OR http_code >= 400 OR timeout = 1 )";
1815
+ $broken_links = $wpdb->get_var($q);
1816
+
1817
+
1818
+ if($broken_links>0){
1819
+ printf( "<a href='%stools.php?page=view-broken-links' title='View broken links'><strong>Found %d broken link%s</strong></a>",
1820
+ get_option('wpurl'), $broken_links, ($broken_links==1)?'':'s' );
1821
+ } else {
1822
+ echo "No broken links found.";
1823
+ }
1824
+ die();
1825
+ */
1826
+ }
1827
+
1828
+ function get_status(){
1829
+ global $wpdb;
1830
+
1831
+ $check_threshold=date('Y-m-d H:i:s', strtotime('-'.$this->options['check_threshold'].' hours'));
1832
+ $recheck_threshold=date('Y-m-d H:i:s', strtotime('-20 minutes'));
1833
+
1834
+ $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links WHERE 1";
1835
+ $known_links = $wpdb->get_var($q);
1836
+
1837
+ $q = "SELECT count(*) FROM {$wpdb->prefix}blc_instances WHERE 1";
1838
+ $known_instances = $wpdb->get_var($q);
1839
+
1840
+ $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links
1841
+ WHERE check_count > 0 AND ( http_code < 200 OR http_code >= 400 OR timeout = 1 )";
1842
+ $broken_links = $wpdb->get_var($q);
1843
+
1844
+ $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links
1845
+ WHERE
1846
+ ( ( last_check < '$check_threshold' ) OR
1847
+ (
1848
+ ( http_code >= 400 OR http_code < 200 )
1849
+ AND check_count < 3
1850
+ AND last_check < '$recheck_threshold' )
1851
+ )";
1852
+ $unchecked_links = $wpdb->get_var($q);
1853
+
1854
+ return array(
1855
+ 'check_threshold' => $check_threshold,
1856
+ 'recheck_threshold' => $recheck_threshold,
1857
+ 'known_links' => $known_links,
1858
+ 'known_instances' => $known_instances,
1859
+ 'broken_links' => $broken_links,
1860
+ 'unchecked_links' => $unchecked_links,
1861
+ );
1862
+ }
1863
+
1864
+ function ajax_work(){
1865
+ //Run the worker function
1866
+ $this->work();
1867
+ die();
1868
+ }
1869
+
1870
+ function ajax_discard(){
1871
+ //TODO:Rewrite to use JSON instead of plaintext
1872
+ if (!current_user_can('edit_others_posts')){
1873
+ die( "You're not allowed to do that!" );
1874
+ }
1875
+
1876
+ if ( isset($_POST['link_id']) ){
1877
+ //Load the link
1878
+ $link = new blcLink( intval($_POST['link_id']) );
1879
+
1880
+ if ( !$link->valid() ){
1881
+ die("Oops, I can't find the link ".intval($_POST['link_id']) );
1882
+ }
1883
+ //Make it appear "not broken"
1884
+ $link->last_check = date('Y-m-d H:i:s');
1885
+ $link->http_code = 200;
1886
+ $link->timeout = 0;
1887
+ $link->check_count = 0;
1888
+ $link->log = "This link was manually marked as working by the user.";
1889
+
1890
+ //Save the changes
1891
+ if ( $link->save() ){
1892
+ die("OK");
1893
+ } else {
1894
+ die("Oops, couldn't modify the link!");
1895
+ }
1896
+ } else {
1897
+ die("Error : link_id not specified");
1898
+ }
1899
+ }
1900
+
1901
+ function ajax_edit(){
1902
+ if (!current_user_can('edit_others_posts')){
1903
+ die( json_encode( array(
1904
+ 'error' => "You're not allowed to do that!"
1905
+ )));
1906
+ }
1907
+
1908
+ if ( isset($_GET['link_id']) && !empty($_GET['new_url']) ){
1909
+ //Load the link
1910
+ $link = new blcLink( intval($_GET['link_id']) );
1911
+
1912
+ if ( !$link->valid() ){
1913
+ die( json_encode( array(
1914
+ 'error' => "Oops, I can't find the link ".intval($_GET['link_id'])
1915
+ )));
1916
+ }
1917
+
1918
+ $new_url = blcUtility::normalize_url($_GET['new_url']);
1919
+ if ( !$new_url ){
1920
+ die( json_encode( array(
1921
+ 'error' => "Oops, the new URL is invalid!"
1922
+ )));
1923
+ }
1924
+
1925
+ //Try and edit the link
1926
+ $rez = $link->edit($new_url);
1927
+
1928
+ if ( $rez == false ){
1929
+ die( json_encode( array(
1930
+ 'error' => "An unexpected error occured!"
1931
+ )));
1932
+ } else {
1933
+ $rez['ok'] = 'OK';
1934
+ die( json_encode($rez) );
1935
+ }
1936
+
1937
+ } else {
1938
+ die( json_encode( array(
1939
+ 'error' => "Error : link_id or new_url not specified"
1940
+ )));
1941
+ }
1942
+ }
1943
+
1944
+ function ajax_unlink(){
1945
+ if (!current_user_can('edit_others_posts')){
1946
+ die( json_encode( array(
1947
+ 'error' => "You're not allowed to do that!"
1948
+ )));
1949
+ }
1950
+
1951
+ if ( isset($_POST['link_id']) ){
1952
+ //Load the link
1953
+ $link = new blcLink( intval($_POST['link_id']) );
1954
+
1955
+ if ( !$link->valid() ){
1956
+ die( json_encode( array(
1957
+ 'error' => "Oops, I can't find the link ".intval($_POST['link_id'])
1958
+ )));
1959
+ }
1960
+
1961
+ //Try and unlink it
1962
+ if ( $link->unlink() ){
1963
+ die( json_encode( array(
1964
+ 'ok' => "URL {$link->url} was removed."
1965
+ )));
1966
+ } else {
1967
+ die( json_encode( array(
1968
+ 'error' => "The plugin failed to remove the link."
1969
+ )));
1970
+ }
1971
+
1972
+ } else {
1973
+ die( json_encode( array(
1974
+ 'error' => "Error : link_id not specified"
1975
+ )));
1976
+ }
1977
+ }
1978
+
1979
+ function ajax_link_details(){
1980
+ global $wpdb;
1981
+
1982
+ if (!current_user_can('edit_others_posts')){
1983
+ die("You don't have sufficient privileges to access this information!");
1984
+ }
1985
+
1986
+ //FB::log("Loading link details via AJAX");
1987
+
1988
+ if ( isset($_GET['link_id']) ){
1989
+ //FB::info("Link ID found in GET");
1990
+ $link_id = intval($_GET['link_id']);
1991
+ } else if ( isset($_POST['link_id']) ){
1992
+ //FB::info("Link ID found in POST");
1993
+ $link_id = intval($_POST['link_id']);
1994
+ } else {
1995
+ //FB::error('Link ID not specified, you hacking bastard.');
1996
+ die('Error : link ID not specified');
1997
+ }
1998
+
1999
+ //Load the link. link_details_rwo needs it as an array, so
2000
+ //we'll have to do this the long way.
2001
+ $q = "SELECT
2002
+ links.*,
2003
+ COUNT(*) as instance_count
2004
+
2005
+ FROM
2006
+ {$wpdb->prefix}blc_links AS links,
2007
+ {$wpdb->prefix}blc_instances as instances
2008
+
2009
+ WHERE
2010
+ links.link_id = %d
2011
+
2012
+ GROUP BY links.link_id";
2013
+
2014
+ $link = $wpdb->get_row( $wpdb->prepare($q, $link_id), ARRAY_A );
2015
+ if ( is_array($link) ){
2016
+ //FB::info($link, 'Link loaded');
2017
+ $this->link_details_row($link);
2018
+ die();
2019
+ } else {
2020
+ die ("Failed to load link details (" . $wpdb->last_error . ")");
2021
+ }
2022
+ }
2023
+
2024
+ function ajax_exclude_link(){
2025
+ if ( !current_user_can('manage_options') ){
2026
+ die( json_encode( array(
2027
+ 'error' => "You're not allowed to do that!"
2028
+ )));
2029
+ }
2030
+
2031
+ if ( isset($_POST['link_id']) ){
2032
+ //Load the link
2033
+ $link = new blcLink( intval($_POST['link_id']) );
2034
+
2035
+ if ( !$link->valid() ){
2036
+ die( json_encode( array(
2037
+ 'error' => "Oops, I can't find the link ".intval($_POST['link_id'])
2038
+ )));
2039
+ }
2040
+
2041
+ //Add the URL to the exclusion list
2042
+ if ( !in_array( $link->url, $this->options['exclusion_list'] ) ){
2043
+ $this->options['exclusion_list'][] = $link->url;
2044
+ //Also mark it as already checked so that it doesn't show up with other broken links.
2045
+ //FB::info("The URL {$link->url} is excluded, marking link {$link->link_id} as already checked.");
2046
+ $link->last_check = date('Y-m-d H:i:s');
2047
+ $link->http_code = 200; //Use a fake code so that the link doesn't show up in queries looking for broken links.
2048
+ $link->timeout = false;
2049
+ $link->request_duration = 0;
2050
+ $link->log = "This link wasn't checked because a matching keyword was found on your exclusion list.";
2051
+ $link->save();
2052
+ }
2053
+
2054
+ $this->save_options();
2055
+
2056
+ die( json_encode( array(
2057
+ 'ok' => "URL {$link->url} added to the exclusion list"
2058
+ )));
2059
+ } else {
2060
+ die( json_encode( array(
2061
+ 'error' => "Link ID not specified"
2062
+ )));
2063
+ }
2064
+ }
2065
+
2066
+ /**
2067
+ * ws_broken_link_checker::acquire_lock()
2068
+ * Create and lock a temporary file.
2069
+ *
2070
+ * @return bool
2071
+ */
2072
+ function acquire_lock(){
2073
+ //Maybe we already have the lock?
2074
+ if ( $this->lockfile_handle ){
2075
+ return true;
2076
+ }
2077
+
2078
+ $fn = $this->lockfile_name();
2079
+ if ( $fn ){
2080
+ //Open the lockfile
2081
+ $this->lockfile_handle = fopen($fn, 'w+');
2082
+ if ( $this->lockfile_handle ){
2083
+ //Do an exclusive lock
2084
+ if (flock($this->lockfile_handle, LOCK_EX | LOCK_NB)) {
2085
+ //File locked successfully
2086
+ return true;
2087
+ } else {
2088
+ //Something went wrong
2089
+ fclose($this->lockfile_handle);
2090
+ $this->lockfile_handle = null;
2091
+ return false;
2092
+ }
2093
+ } else {
2094
+ //Can't open the file, fail.
2095
+ return false;
2096
+ }
2097
+ } else {
2098
+ //Uh oh, can't generate a lockfile name. Locking will be disabled.
2099
+ return true;
2100
+ };
2101
+ }
2102
+
2103
+ /**
2104
+ * ws_broken_link_checker::release_lock()
2105
+ * Unlock and delete the temporary file
2106
+ *
2107
+ * @return bool
2108
+ */
2109
+ function release_lock(){
2110
+ if ( $this->lockfile_handle ){
2111
+ //Close the file (implicitly releasing the lock)
2112
+ fclose( $this->lockfile_handle );
2113
+ //Delete the file
2114
+ $fn = $this->lockfile_name();
2115
+ if ( file_exists( $fn ) ) {
2116
+ unlink( $fn );
2117
+ }
2118
+ $this->lockfile_handle = null;
2119
+ return true;
2120
+ } else {
2121
+ //We didn't have the lock anyway...
2122
+ return false;
2123
+ }
2124
+ }
2125
+
2126
+ /**
2127
+ * ws_broken_link_checker::lockfile_name()
2128
+ * Generate system-specific lockfile filename
2129
+ *
2130
+ * @return string A filename or FALSE on error
2131
+ */
2132
+ function lockfile_name(){
2133
+ //Try the plugin's directory first.
2134
+ if ( is_writable( dirname(__FILE__) ) ){
2135
+ return dirname(__FILE__) . '/wp_blc_lock';
2136
+ } else {
2137
+ //Try to find the temp directory.
2138
+ $dummy = tempnam('asdf', 'blc');
2139
+ if ( $dummy ) {
2140
+ $path = dirname($dummy);
2141
+ //Kill the dummy file
2142
+ if ( file_exists( $dummy ) ) unlink($dummy);
2143
+ return $path . '/wp_blc_lock';
2144
+ } else {
2145
+ //Try /tmp as a last resort
2146
+ if ( is_writable('/tmp') ){
2147
+ return '/tmp/wp_blc_lock';
2148
+ } else {
2149
+ return false;
2150
+ }
2151
+ }
2152
+ }
2153
+ }
2154
+
2155
+ function hook_add_link( $link_id ){
2156
+ $this->mark_unsynched( $link_id, 'blogroll' );
2157
+ }
2158
+
2159
+ function hook_edit_link( $link_id ){
2160
+ $this->mark_unsynched( $link_id, 'blogroll' );
2161
+ }
2162
+
2163
+ function hook_delete_link( $link_id ){
2164
+ global $wpdb;
2165
+ //Delete the synch record
2166
+ $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}blc_synch WHERE source_id = %d AND source_type='blogroll'", $link_id ) );
2167
+
2168
+ //Get the matching instance record.
2169
+ $inst = $wpdb->get_row( $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_instances WHERE source_id = %d AND source_type = 'blogroll'", $link_id), ARRAY_A );
2170
+
2171
+ if ( !$inst ) {
2172
+ //No instance record? No problem.
2173
+ return;
2174
+ }
2175
 
2176
+ //Remove it
2177
+ $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_instances WHERE instance_id = %d", $inst['instance_id']) );
2178
+
2179
+ //Remove the link that was associated with this instance if it has no more related instances.
2180
+ $this->cleanup_links( $inst['link_id'] );
2181
+ }
2182
+
2183
+ function hook_wp_dashboard_setup(){
2184
+ if ( function_exists( 'wp_add_dashboard_widget' ) ) {
2185
+ wp_add_dashboard_widget(
2186
+ 'blc_dashboard_widget',
2187
+ 'Broken Link Checker',
2188
+ array( &$this, 'dashboard_widget' ),
2189
+ array( &$this, 'dashboard_widget_control' )
2190
+ );
2191
+ }
2192
+ }
2193
 
2194
  }//class ends here
2195
 
2196
  } // if class_exists...
2197
 
2198
+ if ( !isset($ws_link_checker) )
2199
+ $ws_link_checker = new ws_broken_link_checker();
2200
 
2201
  ?>
images/image.png ADDED
Binary file
images/link.png ADDED
Binary file
images/script_code.png ADDED
Binary file
instance-classes.php ADDED
@@ -0,0 +1,465 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @author W-Shadow
5
+ * @copyright 2009
6
+ */
7
+
8
+ if (!class_exists('blcLinkInstance')) {
9
+ class blcLinkInstance {
10
+
11
+ //Object state
12
+ var $is_new = false;
13
+
14
+ //DB fields
15
+ var $instance_id = 0;
16
+ var $link_id = 0;
17
+ var $source_id = 0;
18
+ var $source_type = '';
19
+ var $link_text = '';
20
+ var $instance_type = '';
21
+
22
+ //These are used to pass info to callbacks when editing an instance
23
+ var $old_url = null;
24
+ var $new_url = null;
25
+
26
+ /**
27
+ * blcLinkInstance::__construct()
28
+ * Class constructor
29
+ *
30
+ * @param mixed $arg XXXXX look up how to do a multiline doc here (phpdoc)
31
+ * @return void
32
+ */
33
+ function __construct($arg = null){
34
+
35
+ if (is_int($arg)){
36
+ //Load an instance with ID = $arg from the DB.
37
+ $q = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_instances WHERE instance_id=%d LIMIT 1", $arg);
38
+ $arr = $wpdb->get_row( $q, ARRAY_A );
39
+
40
+ if ( is_array($arr) ){ //Loaded successfully
41
+ $this->set_values($arr);
42
+ } else {
43
+ //Link instance not found. The object is invalid.
44
+ }
45
+
46
+ } else if (is_array($arg)){
47
+ $this->set_values($arg);
48
+
49
+ //Is this a new instance?
50
+ $this->is_new = empty($this->instance_id);
51
+
52
+ } else {
53
+ $this->is_new = true;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * blcLinkInstance::blcLinkInstance()
59
+ * Old-style constructor for PHP 4. Do not use.
60
+ *
61
+ * @param mixed $arg
62
+ * @return void
63
+ */
64
+ function blcLinkInstance($arg = null){
65
+ $this->__construct($arg);
66
+ }
67
+
68
+ /**
69
+ * blcLinkInstance::valid()
70
+ * Verifies whether the object represents a valid link instance
71
+ *
72
+ * @return bool
73
+ */
74
+ function valid(){
75
+ //Some basic validation to ensure the required properties are set.
76
+ return !empty($this->link_id) && !empty($this->instance_type) && !empty($this->source_id)
77
+ && !empty($this->source_type) && (!empty($this->instance_id) || $this->is_new);
78
+ }
79
+
80
+ /**
81
+ * blcLinkInstance::set_values()
82
+ * Set property values to the ones provided in an array (doesn't sanitize).
83
+ *
84
+ * @param array $arr An associative array
85
+ * @return void
86
+ */
87
+ function set_values($arr){
88
+ foreach( $arr as $key => $value ){
89
+ $this->$key = $value;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * blcLinkInstance::edit()
95
+ * Replace this instance's URL with a new one.
96
+ * Warning : this shouldn't be called directly. Use blcLink->edit() instead.
97
+ *
98
+ * @param string $new_url
99
+ * @return bool
100
+ */
101
+ function edit($old_url, $new_url){
102
+ echo "Error : The stub function blcLinkInstance->edit() was executed!\r\n";
103
+ return false;
104
+ }
105
+
106
+ /**
107
+ * blcLinkInstance::unlink()
108
+ * Remove this instance from the post/blogroll/etc. Also deletes the appropriate DB record(s).
109
+ *
110
+ * @return bool
111
+ */
112
+ function unlink( $url = null ) {
113
+ //FB::warn("The stub function blcLinkInstance->unlink() was executed!");
114
+ return false;
115
+ }
116
+
117
+ /**
118
+ * blcLinkInstance::forget()
119
+ * Remove the link instance record from database. Doesn't affect the post/link/whatever.
120
+ *
121
+ * @return mixed 1 on success, 0 if the instance wasn't found, false on error
122
+ */
123
+ function forget(){
124
+ global $wpdb;
125
+
126
+ if ( !$this->valid() ) return false;
127
+
128
+ if ( !empty($this->link_id) ) {
129
+ $rez = $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_instances WHERE instance_id=%d", $this->instance_id) );
130
+ $this->link_id = 0;
131
+ return $rez;
132
+ } else {
133
+ return false;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * blcLinkInstance::save()
139
+ * Store the instance in the database.
140
+ *
141
+ * @return bool TRUE on success, FALSE on failure
142
+ */
143
+ function save(){
144
+ global $wpdb;
145
+
146
+ if ( !$this->valid() ) return false;
147
+
148
+ if ( $this->is_new ){
149
+
150
+ //Insert a new row
151
+ $q = "
152
+ INSERT INTO {$wpdb->prefix}blc_instances
153
+ ( link_id, source_id, source_type, link_text, instance_type )
154
+ VALUES( %d, %d, %s, %s, %s )";
155
+
156
+ $q = $wpdb->prepare($q, $this->link_id, $this->source_id, $this->source_type,
157
+ $this->link_text, $this->instance_type );
158
+
159
+ $rez = $wpdb->query($q);
160
+ $rez = $rez !== false;
161
+
162
+ if ($rez){
163
+ $this->instance_id = $wpdb->insert_id;
164
+ //If the instance was successfully saved then it's no longer "new".
165
+ $this->is_new = !$rez;
166
+ }
167
+
168
+ return $rez;
169
+
170
+ } else {
171
+
172
+ //Create a new DB record
173
+ $q = "UPDATE {$wpdb->prefix}blc_instances
174
+ SET link_id = %d, source_id = %d, source_type = %s, link_text = %s, instance_type = %s
175
+ WHERE instance_id = %d";
176
+
177
+ $q = $wpdb->prepare($q, $this->link_id, $this->source_id, $this->source_type,
178
+ $this->link_text, $this->instance_type, $this->instance_id );
179
+
180
+ $rez = $wpdb->query($q) !== false;
181
+
182
+ if ($rez){
183
+ //FB::info($this, "Instance updated");
184
+ } else {
185
+ //FB::error("DB error while updating instance {$this->instance_id} : {$wpdb->last_error}");
186
+ }
187
+
188
+ return $rez;
189
+
190
+ }
191
+ }
192
+
193
+ /**
194
+ * blcLinkInstance::get_url()
195
+ * Get the URL associated with this instance
196
+ *
197
+ * @return string
198
+ */
199
+ function get_url(){
200
+ if ( !$this->valid() ){
201
+ return false;
202
+ }
203
+
204
+ //If the URL isn't specified get it from the link record
205
+ $link = new blcLink( intval($this->link_id) );
206
+ return $link->url;
207
+ }
208
+ }
209
+
210
+ class blcLinkInstance_post_link extends blcLinkInstance {
211
+
212
+ function edit($old_url, $new_url){
213
+ global $wpdb;
214
+
215
+ if ( !$this->valid() ){
216
+ return false;
217
+ }
218
+
219
+ //If the old URL isn't specified get it from the link record
220
+ if ( empty($old_url) ){
221
+ $old_url = $this->get_url();
222
+ }
223
+
224
+ //Load the post
225
+ $post = get_post($this->source_id, ARRAY_A);
226
+ if (!$post){
227
+ //FB::error('Can\'t load post ' . $this->source_id);
228
+ return false;
229
+ }
230
+ //FB::info('Post ' . $this->source_id . ' loaded successfully');
231
+
232
+ $this->old_url = $old_url;
233
+ $this->new_url = $new_url;
234
+
235
+ //Find all links and replace those that match $old_url.
236
+ $content = preg_replace_callback(blcUtility::link_pattern(), array(&$this, 'edit_callback'), $post['post_content']);
237
+ $q = $wpdb->prepare("UPDATE $wpdb->posts SET post_content = %s WHERE id = %d", $content, $this->source_id);
238
+
239
+ return $wpdb->query($q) !== false;
240
+ }
241
+
242
+ function edit_callback($matches){
243
+ $url = blcUtility::normalize_url($matches[3]);
244
+
245
+ if ($url == $this->old_url){
246
+ return $matches[1].$matches[2].$this->new_url.$matches[2].$matches[4].$matches[5].$matches[6];
247
+ } else {
248
+ return $matches[0];
249
+ }
250
+ }
251
+
252
+ function unlink( $url = null ){
253
+ global $wpdb;
254
+
255
+ if ( !$this->valid() ){
256
+ return false;
257
+ }
258
+
259
+ //If the URL isn't specified get it from the link record
260
+ if ( empty($url) ){
261
+ $url = $this->get_url();
262
+ }
263
+
264
+ //Load the post
265
+ $post = get_post($this->source_id, ARRAY_A);
266
+ if (!$post){
267
+ //FB::error('Can\'t load post ' . $this->source_id);
268
+ return false;
269
+ }
270
+ //FB::info('Post ' . $this->source_id . ' loaded successfully');
271
+
272
+ //Find all links and remove those that match $url.
273
+ $this->old_url = $url; //used by the callback
274
+ $content = preg_replace_callback(blcUtility::link_pattern(), array(&$this, 'unlink_callback'), $post['post_content']);
275
+ $q = $wpdb->prepare("UPDATE $wpdb->posts SET post_content = %s WHERE id = %d", $content, $this->source_id);
276
+
277
+ if ( $wpdb->query($q) !== false ){
278
+ //Delete the instance record
279
+ //FB::info("Post updated, deleting instance from DB");
280
+ return $this->forget() !== false;
281
+ } else {
282
+ //FB::error("Failed to update the post");
283
+ return false;
284
+ };
285
+ }
286
+
287
+ function unlink_callback($matches){
288
+ $url = blcUtility::normalize_url($matches[3]);
289
+
290
+ if ($url == $this->old_url){
291
+ return $matches[5]; //just the anchor text
292
+ } else {
293
+ return $matches[0]; //return the link unchanged
294
+ }
295
+ }
296
+
297
+ }
298
+
299
+ class blcLinkInstance_post_image extends blcLinkInstance {
300
+
301
+ function edit($old_url, $new_url){
302
+ global $wpdb;
303
+
304
+ if ( !$this->valid() ){
305
+ return false;
306
+ }
307
+
308
+ //If the URL isn't specified get it from the link record
309
+ if ( empty($old_url) ){
310
+ $old_url = $this->get_url();
311
+ }
312
+
313
+ //Load the post
314
+ $post = get_post($this->source_id, ARRAY_A);
315
+ if (!$post){
316
+ return false;
317
+ }
318
+
319
+ $this->old_url = $old_url;
320
+ $this->new_url = $new_url;
321
+
322
+ //Find all images and change the URL of those that match $old_url.
323
+ //Note : this might be inefficient if there's more than one instance of the sme link
324
+ //in one post, as each instances would be called when editing the link.
325
+ //Either way, I thing the overhead is small enough to ignore for now.
326
+ $content = preg_replace_callback(blcUtility::img_pattern(), array(&$this, 'edit_callback'), $post['post_content']);
327
+ $q = $wpdb->prepare("UPDATE $wpdb->posts SET post_content = %s WHERE id = %d", $content, $this->source_id);
328
+
329
+ return $wpdb->query($q) !== false;
330
+ }
331
+
332
+ function edit_callback($matches){
333
+ $url = blcUtility::normalize_url($matches[3]);
334
+
335
+ if ($url == $this->old_url){
336
+ return $matches[1].$matches[2].$this->new_url.$matches[2].$matches[4].$matches[5];
337
+ } else {
338
+ return $matches[0];
339
+ }
340
+ }
341
+
342
+ function unlink( $url = null ){
343
+ global $wpdb;
344
+
345
+ if ( !$this->valid() ){
346
+ return false;
347
+ }
348
+
349
+ //If the URL isn't specified get it from the link record
350
+ if ( empty($url) ){
351
+ $url = $this->get_url();
352
+ }
353
+
354
+ //Load the post
355
+ $post = get_post($this->source_id, ARRAY_A);
356
+ if (!$post){
357
+ //FB::error('Can\'t load post ' . $this->source_id);
358
+ return false;
359
+ }
360
+ //FB::info('Post ' . $this->source_id . ' loaded successfully');
361
+
362
+ //Find all links and remove those that match $url.
363
+ $this->old_url = $url; //used by the callback
364
+ $content = preg_replace_callback(blcUtility::img_pattern(), array(&$this, 'unlink_callback'), $post['post_content']);
365
+ $q = $wpdb->prepare("UPDATE $wpdb->posts SET post_content = %s WHERE id = %d", $content, $this->source_id);
366
+
367
+ if ( $wpdb->query($q) !== false ){
368
+ //Delete the instance record
369
+ //FB::info("Post updated, deleting instance from DB");
370
+ return $this->forget() !== false;
371
+ } else {
372
+ //FB::error("Failed to update the post");
373
+ return false;
374
+ };
375
+ }
376
+
377
+ function unlink_callback($matches){
378
+ $url = blcUtility::normalize_url($matches[3]);
379
+
380
+ if ($url == $this->old_url){
381
+ return ''; //remove the image completely
382
+ } else {
383
+ return $matches[0]; //return the image unchanged
384
+ }
385
+ }
386
+
387
+ }
388
+
389
+ class blcLinkInstance_custom_field_link extends blcLinkInstance {
390
+
391
+ function edit($old_url, $new_url){
392
+ if ( !$this->valid() ){
393
+ return false;
394
+ }
395
+
396
+ //If the URL isn't specified get it from the link record
397
+ if ( empty($old_url) ){
398
+ $old_url = $this->old_url;
399
+ }
400
+
401
+ //FB::log("Changing [{$this->link_text}] to '$new_url' on post {$this->source_id}");
402
+ //Change the meta value
403
+ return update_post_meta( $this->source_id, $this->link_text, $new_url, $old_url );
404
+ }
405
+
406
+ function unlink( $url = null ){
407
+ //Get the URL from the link record if it wasn't specified
408
+ if ( empty($url) ){
409
+ $url = $this->get_url();
410
+ }
411
+
412
+ //FB::log("Removing [{$this->link_text}] from post {$this->source_id} where value is '$url'");
413
+ delete_post_meta( $this->source_id, $this->link_text, $url );
414
+
415
+ return $this->forget() !== false;
416
+ }
417
+
418
+ }
419
+
420
+ class blcLinkInstance_blogroll_link extends blcLinkInstance {
421
+
422
+ function edit($old_url, $new_url){
423
+ if ( !$this->valid() ){
424
+ return false;
425
+ }
426
+
427
+ //FB::log("Changing the bookmark [{$this->link_text}] to '$new_url'");
428
+ //Update the bookmark. Note : wp_update_link calls the edit_link hook, which is also
429
+ //hooked by the plugin for maintaining bookmark->instance integrity... Conclusion :
430
+ //don't ever call $instance->edit() in that hook!
431
+ return wp_update_link( array(
432
+ 'link_id' => $this->source_id,
433
+ 'link_url' => $new_url
434
+ ) );
435
+ }
436
+
437
+ function unlink( $url = null ){
438
+ if ( !$this->valid() ){
439
+ return false;
440
+ }
441
+
442
+ //Get the URL from the link record if it wasn't specified
443
+ if ( empty($url) ){
444
+ $url = $this->get_url();
445
+ }
446
+
447
+ //FB::log("Removing bookmark [{$this->link_text}] ( ID : {$this->source_id} )");
448
+ //Note : wp_delete_link calls the delete_link hook, which is also used by the plugin
449
+ //for removing instances associated with links deleted through the WP link manager.
450
+ //This means that when you delete a bookmark via the plugin's interface, the plugin will
451
+ //attempt to delete it twice. Anybody have a better idea?
452
+ if ( wp_delete_link( $this->source_id ) ){
453
+ return $this->forget() !== false;
454
+ } else {
455
+ //FB::error("Failed to delete the bookmark.");
456
+ return false;
457
+ };
458
+
459
+ }
460
+
461
+ }
462
+
463
+ }//class_exists
464
+
465
+ ?>
link-classes.php ADDED
@@ -0,0 +1,541 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @author W-Shadow
5
+ * @copyright 2009
6
+ */
7
+
8
+ if (!class_exists('blcLink')){
9
+ class blcLink {
10
+
11
+ //Object state
12
+ var $is_new = false;
13
+ var $last_headers = '';
14
+ var $meets_check_threshold = false; //currently unused
15
+
16
+ //DB fields
17
+ var $link_id = 0;
18
+ var $url = '';
19
+ var $last_check='0000-00-00 00:00:00';
20
+ var $check_count = 0;
21
+ var $final_url = '';
22
+ var $log = '';
23
+ var $http_code = 0;
24
+ var $request_duration = 0;
25
+ var $timeout = false;
26
+ var $redirect_count = 0;
27
+
28
+ function __construct($arg = null){
29
+ global $wpdb;
30
+
31
+ if (is_int($arg)){
32
+ //Load a link with ID = $arg from the DB.
33
+ $q = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_links WHERE link_id=%d LIMIT 1", $arg);
34
+ $arr = $wpdb->get_row( $q, ARRAY_A );
35
+
36
+ if ( is_array($arr) ){ //Loaded successfully
37
+ $this->set_values($arr);
38
+ } else {
39
+ //Link not found. The object is invalid.
40
+ //I'd throw an error, but that wouldn't be PHP 4 compatible...
41
+ }
42
+
43
+ } else if (is_string($arg)){
44
+ //Load a link with URL = $arg from the DB. Create a new one if the record isn't found.
45
+ $q = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_links WHERE url=%s LIMIT 1", $arg);
46
+ $arr = $wpdb->get_row( $q, ARRAY_A );
47
+
48
+ if ( is_array($arr) ){ //Loaded successfully
49
+ $this->set_values($arr);
50
+ } else { //Link not found, treat as new
51
+ $this->url = $arg;
52
+ $this->is_new = true;
53
+ }
54
+
55
+ } else if (is_array($arg)){
56
+ $this->set_values($arg);
57
+ //Is this a new link?
58
+ $this->is_new = empty($this->link_id);
59
+ } else {
60
+ $this->is_new = true;
61
+ }
62
+ }
63
+
64
+ function blcLink($arg = null){
65
+ $this->__construct($arg);
66
+ }
67
+
68
+ /**
69
+ * blcLink::set_values()
70
+ * Set the internal values to the ones provided in an array (doesn't sanitize).
71
+ *
72
+ * @param array $arr An associative array of values
73
+ * @return void
74
+ */
75
+ function set_values($arr){
76
+ foreach( $arr as $key => $value ){
77
+ $this->$key = $value;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * blcLink::valid()
83
+ * Verifies whether the object represents a valid link
84
+ *
85
+ * @return bool
86
+ */
87
+ function valid(){
88
+ return !empty( $this->url ) && ( !empty($this->link_id) || $this->is_new );
89
+ }
90
+
91
+ /**
92
+ * blcLink::check()
93
+ * Check if the link is working.
94
+ *
95
+ * @return bool
96
+ */
97
+ function check(){
98
+ if ( !$this->valid() ) return false;
99
+ /*
100
+ Check for problematic (though not necessarily "broken") links.
101
+ If a link has been checked multiple times and still hasn't been marked as
102
+ timed-out or broken then probably the checking algorithm is having problems with
103
+ that link. Mark it as timed-out and hope the user sorts it out.
104
+ */
105
+ if ( ($this->check_count >= 3) && ( !$this->timeout ) && ( !$this->http_code ) ) {
106
+ $this->timeout = 1;
107
+ $this->last_check = date('Y-m-d H:i:s');
108
+ $this->log .= "\r\n[A weird error was detected. This should never happen.]";
109
+ $this->save();
110
+ return false;
111
+ }
112
+
113
+ //Update the DB record before actually performing the check.
114
+ //Useful if something goes terribly wrong while checkint this particular URL.
115
+ //Note : might be unnecessary.
116
+ $this->check_count++;
117
+ $this->last_check = date('Y-m-d H:i:s');
118
+ $this->log = '';
119
+ $this->final_url = '';
120
+ $this->http_code = 0;
121
+ $this->request_duration = 0;
122
+ $this->timeout = false;
123
+ $this->redirect_count = 0;
124
+ $this->save();
125
+
126
+ //Empty some variables before running the check
127
+ $this->last_headers = '';
128
+
129
+ //Save the URL into a local var; we'll need it later.
130
+ $url = $this->url;
131
+
132
+ $parts = parse_url($url);
133
+ //Only HTTP links are checked. All others are automatically considered okay.
134
+ if ( ($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') ) {
135
+ $this->log .= "URL protocol ($parts[scheme]) is not HTTP. This link won't be checked.\n";
136
+ return true;
137
+ }
138
+
139
+ //Kill the #anchor if it's present
140
+ $anchor_start = strpos($url, '#');
141
+ if ( $anchor_start !== false ){
142
+ $url = substr($url, 0, $anchor_start);
143
+ }
144
+
145
+ //******* Use CURL if available ***********
146
+ if (function_exists('curl_init')) {
147
+ $ch = curl_init();
148
+ curl_setopt($ch, CURLOPT_URL, $url);
149
+ //Masquerade as Internet explorer
150
+ curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)');
151
+ //Add a semi-plausible referer header to avoid tripping up some bot traps
152
+ curl_setopt($ch, CURLOPT_REFERER, get_option('home'));
153
+
154
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
155
+
156
+ @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
157
+ curl_setopt($ch, CURLOPT_MAXREDIRS, 10);
158
+
159
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
160
+ curl_setopt($ch, CURLOPT_TIMEOUT, 30);
161
+
162
+ curl_setopt($ch, CURLOPT_FAILONERROR, false);
163
+
164
+ $nobody=false;
165
+ if($parts['scheme']=='https'){
166
+ //TODO: Redirects don't work with HTTPS
167
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
168
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
169
+ } else {
170
+ $nobody=true;
171
+ curl_setopt($ch, CURLOPT_NOBODY, true);
172
+ //curl_setopt($ch, CURLOPT_RANGE, '0-1023');
173
+ }
174
+
175
+ //We definitely want headers.
176
+ curl_setopt($ch, CURLOPT_HEADER, true);
177
+ //register a callback function which will process the headers
178
+ //this assumes your code is into a class method, and uses $this->readHeader
179
+ //as the callback function.
180
+ curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$this,'read_header'));
181
+
182
+ //Execute the request
183
+ $response = curl_exec($ch);
184
+
185
+ $info = curl_getinfo($ch);
186
+ $code = intval( $info['http_code'] );
187
+
188
+ $this->log .= "=== First try : $code ".(!$code?'(No response) ':'')."===\n\n";
189
+ $this->log .= $this->last_headers."\n";
190
+
191
+ if ( (($code<200) || ($code>=400)) && $nobody) {
192
+ $this->log .= "Trying a second time with different settings...\n";
193
+ $this->last_headers = '';
194
+
195
+ curl_setopt($ch, CURLOPT_NOBODY, false);
196
+ curl_setopt($ch, CURLOPT_HTTPGET, true);
197
+ curl_setopt($ch, CURLOPT_RANGE, '0-2047');
198
+ $response = curl_exec($ch);
199
+
200
+ $info = curl_getinfo($ch);
201
+ $code = intval( $info['http_code'] );
202
+
203
+ $this->log .= "=== Second try : $code ".(!$code?'(No response) ':'')."===\n\n";
204
+ $this->log .= $this->last_headers."\n";
205
+ }
206
+
207
+ $this->http_code = $code;
208
+ $this->final_url = $info['url'];
209
+ $this->request_duration = $info['total_time'];
210
+ $this->redirect_count = $info['redirect_count'];
211
+
212
+ curl_close($ch);
213
+
214
+ } elseif (class_exists('Snoopy')) {
215
+ //******** Use Snoopy if CURL is not available *********
216
+ //Note : Snoopy doesn't work too well with HTTPS URLs.
217
+ $this->log .= "<em>(Using Snoopy)</em>\n";
218
+
219
+ $start_time = microtime_float(true);
220
+
221
+ $snoopy = new Snoopy;
222
+ $snoopy->read_timeout = 60; //read timeout in seconds
223
+ $snoopy->fetch($url);
224
+
225
+ $this->request_duration = $start_time - microtime_float(true);
226
+
227
+ $this->http_code = $snoopy->status; //HTTP status code
228
+
229
+ if ($snoopy->error)
230
+ $this->log .= $snoopy->error."\n";
231
+ if ($snoopy->timed_out)
232
+ $this->log .= "Request timed out.\n";
233
+
234
+ if ( is_array($snoopy->headers) )
235
+ $this->log .= implode("", $snoopy->headers)."\n"; //those headers already contain newlines
236
+
237
+ if ($snoopy->lastredirectaddr) {
238
+ $this->final_url = $snoopy->lastredirectaddr;
239
+ $this->redirect_count = $snoopy->_redirectdepth;
240
+ }
241
+ }
242
+
243
+ /*"Good" response codes are anything in the 2XX range (e.g "200 OK") and redirects - the 3XX range.
244
+ HTTP 401 Unauthorized is a special case that is considered OK as well. Other errors - the 4XX range -
245
+ are treated as "page doesn't exist'". */
246
+ //TODO: Treat circular redirects as broken links.
247
+ if ( (($this->http_code>=200) && ($this->http_code<400)) || ($this->http_code == 401) ) {
248
+ $this->log .= "Link is valid.";
249
+ //Reset the check count for valid links.
250
+ $this->check_count = 0;
251
+ return true;
252
+ } else {
253
+ $this->log .= "Link is broken.";
254
+ if ($this->http_code == 0){
255
+ //This is probably a timeout
256
+ $this->timeout = true;
257
+ $this->log .= "\r\n(Most likely the connection timed out)";
258
+ }
259
+ return false;
260
+ }
261
+ }
262
+
263
+ function read_header($ch, $header){
264
+ //extracting example data: filename from header field Content-Disposition
265
+ $this->last_headers .= $header;
266
+ return strlen($header);
267
+ }
268
+
269
+ /**
270
+ * blcLink::save()
271
+ * Save link data to DB.
272
+ *
273
+ * @return bool True if saved successfully, false otherwise.
274
+ */
275
+ function save(){
276
+ global $wpdb;
277
+
278
+ if ( !$this->valid() ) return false;
279
+
280
+ if ( $this->is_new ){
281
+
282
+ //Insert a new row
283
+ $q = "
284
+ INSERT INTO {$wpdb->prefix}blc_links
285
+ ( url, last_check, check_count, final_url, redirect_count, log, http_code, request_duration, timeout )
286
+ VALUES( %s, %s, %d, %s, %d, %s, %d, %f, %d )";
287
+ $q = $wpdb->prepare($q, $this->url, $this->last_check, $this->check_count, $this->final_url,
288
+ $this->redirect_count, $this->log, $this->http_code, $this->request_duration, (integer)$this->timeout );
289
+ $rez = $wpdb->query($q);
290
+
291
+ $rez = $rez !== false;
292
+
293
+ if ($rez){
294
+ $this->link_id = $wpdb->insert_id;
295
+ //echo "Link added, ID : {$this->link_id}\r\n<br>";
296
+ //If the link was successfully saved then it's no longer "new"
297
+ $this->is_new = !$rez;
298
+ } else {
299
+ echo "Error adding link $url : {$wpdb->last_error}\r\n<br>";
300
+ }
301
+
302
+ return $rez;
303
+
304
+ } else {
305
+
306
+ //Update an existing DB record
307
+ $q = "UPDATE {$wpdb->prefix}blc_links SET url=%s, last_check=%s, check_count=%d, final_url=%s,
308
+ redirect_count=%d, log=%s, http_code=%d, request_duration=%f, timeout=%d
309
+ WHERE link_id=%d";
310
+
311
+ $q = $wpdb->prepare($q, $this->url, $this->last_check, $this->check_count, $this->final_url,
312
+ $this->redirect_count, $this->log, $this->http_code, $this->request_duration, (integer)$this->timeout, $this->link_id );
313
+
314
+ $rez = $wpdb->query($q);
315
+ if ( $rez !== false ){
316
+ //echo "Link updated, ID : {$this->link_id}\r\n<br>";
317
+ } else {
318
+ echo "Error updating link {$this->link_id} : {$wpdb->last_error}\r\n<br>";
319
+ }
320
+ return $rez !== false;
321
+ }
322
+ }
323
+
324
+ /**
325
+ * blcLink::edit()
326
+ * Edit all instances of the link by changing the URL.
327
+ *
328
+ * Here's how this really works : create a new link with the new URL. Then edit()
329
+ * all instances and point them to the new link record. If some instance can't be
330
+ * edited they will still point to the old record. The old record is deleted
331
+ * if all instances were edited successfully.
332
+ *
333
+ * @param string $new_url
334
+ * @return array An associative array with the new link ID, the number of successfully edited instances and the number of failed edits.
335
+ */
336
+ function edit($new_url){
337
+ if ( !$this->valid() ){
338
+ return false;
339
+ }
340
+
341
+ //FB::info('Changing link '.$this->link_id .' to URL "'.$new_url.'"');
342
+
343
+ $instances = $this->get_instances();
344
+ //Fail if there are no instances
345
+ if (empty($instances)) return false;
346
+
347
+ //Load or create a link with the URL = $new_url
348
+ $new_link = new blcLink($new_url);
349
+ $was_new = $new_link->is_new;
350
+ if ($new_link->is_new) {
351
+ //FB::log($new_link, 'Saving a new link');
352
+ $new_link->save(); //so that we get a valid link_id
353
+ }
354
+
355
+ if ( empty($new_link->link_id) ){
356
+ //FB::error("Failed to create a new link record");
357
+ return false;
358
+ }
359
+
360
+ //Edit each instance.
361
+ //FB::info('Editing ' . count($instances) . ' instances');
362
+ $cnt_okay = $cnt_error = 0;
363
+ foreach ( $instances as $instance ){
364
+ if ( $instance->edit( $this->url, $new_url ) ){
365
+ $cnt_okay++;
366
+ $instance->link_id = $new_link->link_id;
367
+ $instance->save();
368
+ //FB::info($instance, 'Successfully edited instance ' . $instance->instance_id);
369
+ } else {
370
+ $cnt_error++;
371
+ //FB::error($instance, 'Failed to edit instance ' . $instance->instance_id);
372
+ }
373
+ }
374
+
375
+ //If all instances were edited successfully we can delete the old link record.
376
+ //And copy the new link data into this object. UNLESS this link is equal to the new link
377
+ //(which should never happen, but whatever)
378
+ if ( ( $cnt_error == 0 ) && ( $cnt_okay > 0 ) && ( $this->link_id != $new_link->link_id ) ){
379
+ $this->forget( false );
380
+
381
+ $this->link_id = $new_link->link_id;
382
+ $this->url = $new_link->url;
383
+ $this->final_url = $new_link->url;
384
+ $this->log = $new_link->log;
385
+ $this->http_code = $new_link->http_code;
386
+ $this->redirect_count = $new_link->redirect_count;
387
+ $this->timeout = $new_link->timeout;
388
+ }
389
+
390
+ //On the other hand, if no instances could be edited and the $new_link was really new,
391
+ //then delete it.
392
+ if ( ( $cnt_okay == 0 ) && $was_new ){
393
+ $new_link->forget( false );
394
+ }
395
+
396
+ return array(
397
+ 'new_link_id' => $this->link_id,
398
+ 'cnt_okay' => $cnt_okay,
399
+ 'cnt_error' => $cnt_error,
400
+ );
401
+ }
402
+
403
+ //Delete (unlink) all instances and the link itself
404
+ function unlink(){
405
+ if ( !$this->valid() ){
406
+ return false;
407
+ }
408
+
409
+ //FB::info($this, 'Removing link');
410
+
411
+ $instances = $this->get_instances();
412
+ //Fail if there are no instances
413
+ if (empty($instances)) {
414
+ //FB::warn("This link has no instances. Deleting the link.");
415
+ return $this->forget( false ) !== false;
416
+ }
417
+
418
+ //Unlink each instance.
419
+ //FB::info('Unlinking ' . count($instances) . ' instances');
420
+ $cnt_okay = $cnt_error = 0;
421
+ foreach ( $instances as $instance ){
422
+ if ( $instance->unlink( $this->url ) ){
423
+ $cnt_okay++;
424
+ //FB::info( $instance, 'Successfully unlinked instance' );
425
+ } else {
426
+ $cnt_error++;
427
+ //FB::error( $instance, 'Failed to unlink instance' );
428
+ }
429
+ }
430
+
431
+ //If all instances were unlinked successfully we can delete the link record.
432
+ if ( ( $cnt_error == 0 ) && ( $cnt_okay > 0 ) ){
433
+ //FB::log('Instances removed, deleting the link.');
434
+ return $this->forget() !== false;
435
+ } else {
436
+ //FB::error("Something went wrong. Unlinked instances : $cnt_okay, errors : $cnt_error");
437
+ return false;
438
+ }
439
+ }
440
+
441
+ /**
442
+ * blcLink::forget()
443
+ * Remove the link and instance records from the DB. Doesn't alter posts/etc.
444
+ *
445
+ * @return mixed 1 on success, 0 if link not found, false on error.
446
+ */
447
+ function forget($remove_instances = true){
448
+ global $wpdb;
449
+ if ( !$this->valid() ) return false;
450
+
451
+ if ( !empty($this->link_id) ){
452
+ //FB::info($this, 'Deleting link from DB');
453
+
454
+ if ( $remove_instances ){
455
+ //Remove instances, if any
456
+ $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_instances WHERE link_id=%d", $this->link_id) );
457
+ }
458
+
459
+ //Remove the link itself
460
+ $rez = $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_links WHERE link_id=%d", $this->link_id) );
461
+ $this->link_id = 0;
462
+
463
+ return $rez;
464
+ } else {
465
+ return false;
466
+ }
467
+
468
+ }
469
+
470
+ /**
471
+ * blcLink::get_instances()
472
+ * Get a list of the link's instances
473
+ *
474
+ * @param integer $max_count The maximum number of instances to return. The default is -1 (no limit)
475
+ * @return array An array of instance objects or FALSE on failure.
476
+ */
477
+ function get_instances($max_count = -1){
478
+ global $wpdb;
479
+ if ( !$this->valid() || empty($this->link_id) ) return false;
480
+
481
+ $limit = $max_count > 0 ? "LIMIT $max_count":'';
482
+
483
+ //Get all instances of this link
484
+ $q = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_instances WHERE link_id=%d $limit", $this->link_id);
485
+ $results = $wpdb->get_results($q, ARRAY_A);
486
+
487
+ if ( !empty($results) ) {
488
+ //Create an object for each instance
489
+ $instances = array();
490
+ foreach ($results as $result){
491
+ //Each source/link type combination has it's own subclass. E.g. _post_image or _blogroll_link.
492
+ $classname = 'blcLinkInstance_' . $result['source_type'] . '_' . $result['instance_type'];
493
+ $instances[] = new $classname($result);
494
+ }
495
+ return $instances;
496
+ } else {
497
+ return false;
498
+ }
499
+ }
500
+
501
+ /**
502
+ * blcLink::add_instance()
503
+ * Record a new instance of the link.
504
+ *
505
+ * @param int $source_id
506
+ * @param string $source_type
507
+ * @param string $link_text
508
+ * @param string $instance_type
509
+ * @return object The created instance or FALSE on error.
510
+ */
511
+ function add_instance($source_id, $source_type, $link_text, $instance_type){
512
+
513
+ //The link must be saved before an instance can be added
514
+ if ($this->is_new) {
515
+ if ( !$this->save()) return false;
516
+ }
517
+
518
+ //Create a new instance tied to this link
519
+ $classname = 'blcLinkInstance_' . $source_type . '_' . $instance_type;
520
+ if ( !class_exists($classname) ){
521
+ $classname = 'blcLinkInstance';
522
+ }
523
+ $inst = new $classname( array(
524
+ 'link_id' => $this->link_id,
525
+ 'source_id' => $source_id,
526
+ 'source_type' => $source_type,
527
+ 'link_text' => $link_text,
528
+ 'instance_type' => $instance_type,
529
+ ) );
530
+
531
+ //Save the instance to the DB
532
+ if ( $inst->save() ){
533
+ return $inst;
534
+ } else {
535
+ return false;
536
+ };
537
+ }
538
+ }
539
+ } //class_exists
540
+
541
+ ?>
readme.txt CHANGED
@@ -1,30 +1,43 @@
1
  === Broken Link Checker ===
2
  Contributors: whiteshadow
3
- Tags: links, broken, maintenance
4
- Requires at least: 2.0.2
5
- Tested up to: 2.7.1
6
- Stable tag: 0.4.14
7
 
8
- This plugin will check your posts for broken links and missing images in background and notify you on the dashboard if any are found.
9
 
10
  == Description ==
11
- This plugin is will monitor your blog looking for broken links and let you know if any are found.
12
 
13
- * Checks your posts (and pages) in the background.
 
 
14
  * Detects links that don't work and missing images.
15
  * Notifies you on the Dashboard if any are found.
 
16
  * Makes broken links display differently in posts (optional).
17
  * Link checking intervals can be configured.
18
  * New/modified posts are checked ASAP.
19
- * You can unlink or edit broken links in the *Manage -> Broken Links* tab.
 
 
 
 
 
 
20
 
21
- **How To Use It**
22
- The broken links, if any are found, will show up in a new tab of WP admin panel - Manage -> Broken Links. A notification will also appear on the Dashboard.
23
 
24
- There are several buttons for each broken link - "Details" shows more info about why the link is considered "broken", "Edit Post" does exactly what it says and "Discard" will remove the message about a broken link, but not the link itself (so it will show up again later unless you fix it). Use "Unlink" to actually remove the link from the post. If references to missing images are found, they will be listed along with the links, with "[image]" in place of link text.
25
 
26
- You can modify the available options at Options -> Link Checker. You can also see the current checking status there - e.g. how many posts need to be checked and how many links are in the queue. The plugin runs while you have any page of the WordPress admin panel open.
27
 
 
 
 
 
 
28
 
29
  == Installation ==
30
 
1
  === Broken Link Checker ===
2
  Contributors: whiteshadow
3
+ Tags: links, broken, maintenance, blogroll, custom fields, admin
4
+ Requires at least: 2.7.0
5
+ Tested up to: 2.8
6
+ Stable tag: 0.5
7
 
8
+ This plugin will check your posts, custom fields and the blogroll for broken links and missing images and notify you on the dashboard if any are found.
9
 
10
  == Description ==
11
+ This plugin will monitor your blog looking for broken links and let you know if any are found.
12
 
13
+ **Features**
14
+
15
+ * Monitors links in your posts, pages, the blogroll, and custom fields (optional).
16
  * Detects links that don't work and missing images.
17
  * Notifies you on the Dashboard if any are found.
18
+ * Also detects redirected links.
19
  * Makes broken links display differently in posts (optional).
20
  * Link checking intervals can be configured.
21
  * New/modified posts are checked ASAP.
22
+ * You view broken links, redirects, and a complete list of links used on your site, in the *Tools -> Broken Links* tab.
23
+ * Each link can be edited or unlinked directly via the plugin's page, without manually editing each post.
24
+
25
+ **Basic Usage**
26
+ Once installed, the plugin will begin parsing your posts, bookmarks (AKA blogroll), etc and looking for links. Depending on the size of your site this can take a few minutes or even several hours. When parsing is complete the plugin will start checking each link to see if it works. Again, how long this takes depends on how big your site is and how many links there are. You can monitor the progress and set various link checking options in *Settings -> Link Checker*.
27
+
28
+ Note : Currently the plugin only runs when you have at least one tab of the Dashboard open. Cron support will likely be added in a later version.
29
 
30
+ The broken links, if any are found, will show up in a new tab of WP admin panel - *Tools -> Broken Links*. A notification will also appear in the "Broken Link Checker" widget on the Dashboard. To save display space, you can keep the widget closed and configure it to expand automatically when problematic links are detected.
 
31
 
32
+ The "Broken Links" tab will by default display broken links that have been detected so far. However, you can use the subnavigation links on that page to view redirects or see a listing of all links - working or not - instead.
33
 
34
+ There are several actions associated with each link listed -
35
 
36
+ * "Details" shows more info about the link. You can also toggle link details by clicking on the "link text" cell.
37
+ * "Edit URL" lets you change the URL of that link. If the link is present in more than one place (e.g. both in a post and in the blogroll) then all instances of that URL will be changed.
38
+ * "Unlink" removes the link but leaves the link text intact.
39
+ * "Exclude" adds the link's URL to the exclusion list. Excluded URLs won't be checked again.
40
+ * "Discard" lets you manually mark the link as valid. This is useful if you know it was detected as broken only due to a temporary glitch or similar. The link will still be checked normally later.
41
 
42
  == Installation ==
43
 
uninstall.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @author W-Shadow
5
+ * @copyright 2009
6
+ *
7
+ * The terrifying uninstallation script.
8
+ */
9
+
10
+ if( !defined( 'ABSPATH') && !defined('WP_UNINSTALL_PLUGIN') && !current_user_can('delete_plugins') )
11
+ exit();
12
+
13
+ if( !isset($wpdb) )
14
+ exit();
15
+
16
+ //Remove the plugin's settings
17
+ delete_option('wsblc_options');
18
+
19
+ //EXTERMINATE!
20
+ $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}blc_linkdata, {$wpdb->prefix}blc_postdata, {$wpdb->prefix}blc_instances, {$wpdb->prefix}blc_links, {$wpdb->prefix}blc_synch" );
21
+
22
+ ?>
utility-class.php ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @author W-Shadow
5
+ * @copyright 2009
6
+ */
7
+
8
+
9
+ if (!function_exists('json_encode')){
10
+ //Load JSON functions for PHP < 5.2
11
+ if (!class_exists('Services_JSON')){
12
+ require 'JSON.php';
13
+ }
14
+
15
+ //Backwards fompatible json_encode.
16
+ function json_encode($data) {
17
+ $json = new Services_JSON();
18
+ return( $json->encode($data) );
19
+ }
20
+ }
21
+
22
+ if ( !class_exists('blcUtility') ){
23
+
24
+ class blcUtility {
25
+
26
+ //A regxp for images
27
+ function img_pattern(){
28
+ // \1 \2 \3 URL \4
29
+ return '/(<img[\s]+[^>]*src\s*=\s*)([\"\']?)([^\'\">]+)\2([^<>]*>)/i';
30
+ }
31
+
32
+ //A regexp for links
33
+ function link_pattern(){
34
+ // \1 \2 \3 URL \4 \5 Text \6
35
+ return '/(<a[\s]+[^>]*href\s*=\s*)([\"\']+)([^\'\">]+)\2([^<>]*>)((?sU).*)(<\/a>)/i';
36
+ }
37
+
38
+ /**
39
+ * blcUtility::normalize_url()
40
+ *
41
+ * @param string $url
42
+ * @return string A normalized URL or FALSE if the URL is invalid
43
+ */
44
+ function normalize_url($url){
45
+ $parts=@parse_url($url);
46
+ if(!$parts) return false;
47
+
48
+ if(isset($parts['scheme'])) {
49
+ //Only HTTP(S) links are checked. Other protocols are not supported.
50
+ if ( ($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') )
51
+ return false;
52
+ }
53
+
54
+ $url = html_entity_decode($url);
55
+ $url = preg_replace(
56
+ array('/([\?&]PHPSESSID=\w+)$/i',
57
+ '/(#[^\/]*)$/',
58
+ '/&amp;/',
59
+ '/^(javascript:.*)/i',
60
+ '/([\?&]sid=\w+)$/i'
61
+ ),
62
+ array('','','&','',''),
63
+ $url);
64
+ $url=trim($url);
65
+
66
+ if($url=='') return false;
67
+
68
+ // turn relative URLs into absolute URLs
69
+ $url = blcUtility::relative2absolute( get_option('siteurl'), $url);
70
+ return $url;
71
+ }
72
+
73
+ /**
74
+ * blcUtility::relative2absolute()
75
+ * Turns a relative URL into an absolute one given a base URL.
76
+ *
77
+ * @param string $absolute Base URL
78
+ * @param string $relative A relative URL
79
+ * @return string
80
+ */
81
+ function relative2absolute($absolute, $relative) {
82
+ $p = @parse_url($relative);
83
+ if(!$p) {
84
+ //WTF? $relative is a seriously malformed URL
85
+ return false;
86
+ }
87
+ if(isset($p["scheme"])) return $relative;
88
+
89
+ $parts=(parse_url($absolute));
90
+
91
+ if(substr($relative,0,1)=='/') {
92
+ $cparts = (explode("/", $relative));
93
+ array_shift($cparts);
94
+ } else {
95
+ if(isset($parts['path'])){
96
+ $aparts=explode('/',$parts['path']);
97
+ array_pop($aparts);
98
+ $aparts=array_filter($aparts);
99
+ } else {
100
+ $aparts=array();
101
+ }
102
+
103
+ $rparts = (explode("/", $relative));
104
+
105
+ $cparts = array_merge($aparts, $rparts);
106
+ foreach($cparts as $i => $part) {
107
+ if($part == '.') {
108
+ unset($cparts[$i]);
109
+ } else if($part == '..') {
110
+ unset($cparts[$i]);
111
+ unset($cparts[$i-1]);
112
+ }
113
+ }
114
+ }
115
+ $path = implode("/", $cparts);
116
+
117
+ $url = '';
118
+ if($parts['scheme']) {
119
+ $url = "$parts[scheme]://";
120
+ }
121
+ if(isset($parts['user'])) {
122
+ $url .= $parts['user'];
123
+ if(isset($parts['pass'])) {
124
+ $url .= ":".$parts['pass'];
125
+ }
126
+ $url .= "@";
127
+ }
128
+ if(isset($parts['host'])) {
129
+ $url .= $parts['host']."/";
130
+ }
131
+ $url .= $path;
132
+
133
+ return $url;
134
+ }
135
+
136
+ }//class
137
+
138
+ }//class_exists
139
+
140
+ ?>
wsblc_ajax.php CHANGED
@@ -1,451 +1,5 @@
1
  <?php
2
  /*
3
- The AJAX-y part of the link checker.
4
- */
5
- require_once("../../../wp-config.php");
6
- if(!isset($wpdb)) {
7
- require_once("../../../wp-includes/wp-db.php");
8
- }
9
-
10
- //error_reporting(E_ALL);
11
-
12
- $execution_start_time=microtime(true);
13
-
14
- function execution_time(){
15
- global $execution_start_time;
16
- return microtime(true)-$execution_start_time;
17
- }
18
-
19
- if (!current_user_can('read')) {
20
- die("Error: You can't do that. Access denied.");
21
- }
22
-
23
- if(!is_object($ws_link_checker)) {
24
- die('Fatal error : undefined object; plugin may not be active.');
25
- };
26
-
27
- //Regexp for HTML links
28
- // \1 \2 \3 \4 \5 \6
29
- $url_pattern='/(<a[\s]+[^>]*href\s*=\s*)([\"\']+)([^\'\">]+)\2([^<>]*>)((?sU).*)(<\/a>)/i';
30
- //Regexp for IMG tags
31
- // \1 \2 \3 \4
32
- $img_pattern='/(<img[\s]+[^>]*src\s*=\s*)([\"\']?)([^\'\">]+)\2([^<>]*>)/i';
33
-
34
- $url_to_replace = '';
35
-
36
- $postdata_name=$wpdb->prefix . "blc_postdata";
37
- $linkdata_name=$wpdb->prefix . "blc_linkdata";
38
-
39
- $options=$ws_link_checker->options; //get_option('wsblc_options');
40
- $max_execution_time=isset($options['max_work_session'])?intval($options['max_work_session']):27;
41
-
42
- // Check for safe mode
43
- if( ini_get('safe_mode') ){
44
- // Do it the safe mode way
45
- $t=ini_get('max_execution_time');
46
- if ($t && ($t < $max_execution_time))
47
- $max_execution_time = $t-1;
48
- } else {
49
- // Do it the regular way
50
- @set_time_limit(0);
51
- }
52
- @ignore_user_abort(true);
53
-
54
- $check_treshold=date('Y-m-d H:i:s', strtotime('-'.$options['check_treshold'].' hours'));
55
- $recheck_treshold=date('Y-m-d H:i:s', strtotime('-20 minutes'));
56
-
57
- if (!empty($_POST['action'])){
58
- $action = $_POST['action'];
59
- } else {
60
- $action=isset($_GET['action'])?$_GET['action']:'run_check';
61
- }
62
-
63
- if($action=='dashboard_status'){
64
- /* displays a notification if broken links have been found */
65
- $sql="SELECT count(*) FROM $linkdata_name WHERE broken=1";
66
- $broken_links=$wpdb->get_var($sql);
67
- if($broken_links>0){
68
- echo "<div>
69
- <h3>Broken Links</h3>
70
- <p><a href='".get_option('siteurl')."/wp-admin/edit.php?page=".
71
- $ws_link_checker->mybasename."' title='View broken links'>Found $broken_links broken links</a></p>
72
- </div>";
73
- };
74
-
75
- } else if($action=='full_status'){
76
- /* give some stats about the current situation */
77
- $sql="SELECT count(*) FROM $postdata_name WHERE last_check<'$check_treshold'";
78
- $posts_unchecked=$wpdb->get_var($sql);
79
-
80
- $sql="SELECT count(*) FROM $linkdata_name WHERE last_check<'$check_treshold'";
81
- $links_unchecked=$wpdb->get_var($sql);
82
-
83
- $sql="SELECT count(*) FROM $linkdata_name WHERE broken=1";
84
- $broken_links=$wpdb->get_var($sql);
85
-
86
- if($broken_links>0){
87
- echo "<a href='".get_option('siteurl')."/wp-admin/edit.php?page=".
88
- $ws_link_checker->mybasename."' title='View broken links'><strong>Found $broken_links broken links</strong></a>";
89
- } else {
90
- echo "No broken links found.";
91
- }
92
-
93
- echo "<br/>";
94
-
95
- if($posts_unchecked || $links_unchecked) {
96
- echo "$posts_unchecked posts and $links_unchecked links in the work queue.";
97
- } else {
98
- echo "The work queue is empty.";
99
- }
100
-
101
-
102
- } else if($action=='run_check'){
103
- /* check for posts that haven't been checked for a long time & parse them for links, put the links in queue */
104
- echo "<!-- run_check -->";
105
-
106
- $sql="SELECT b.* FROM $postdata_name a, $wpdb->posts b
107
- WHERE a.last_check<'$check_treshold' AND a.post_id=b.id
108
- AND b.post_status = 'publish' AND b.post_type IN ('post', 'page')
109
- ORDER BY a.last_check ASC LIMIT 40";
110
-
111
- $rows=$wpdb->get_results($sql, OBJECT);
112
- if($rows && (count($rows)>0)){
113
- //some rows found
114
- echo "<!-- parsing pages (rand : ".rand(1,1000).") -->";
115
- foreach ($rows as $post) {
116
- $wpdb->query("DELETE FROM $linkdata_name WHERE post_id=$post->ID");
117
- gather_and_save_links($post->post_content, $post->ID);
118
- $wpdb->query("UPDATE $postdata_name SET last_check=NOW() WHERE post_id=$post->ID");
119
- }
120
- };
121
-
122
- if(execution_time()>$max_execution_time){
123
- die('<!-- general timeout -->');
124
- }
125
-
126
- /* check the queue and process any links unchecked */
127
- $sql="SELECT * FROM $linkdata_name WHERE ".
128
- " ((last_check<'$check_treshold') OR ".
129
- " (broken=1 AND check_count<5 AND last_check<'$recheck_treshold')) ".
130
- " LIMIT 100";
131
-
132
- $links=$wpdb->get_results($sql, OBJECT);
133
- if($links && (count($links)>0)){
134
- //some unchecked links found
135
- echo "<!-- checking ".count($links)." links (rand : ".rand(1,1000).") -->";
136
- foreach ($links as $link) {
137
- $ws_link_checker->check_link($link);
138
-
139
- if(execution_time()>$max_execution_time){
140
- die('<!-- url loop timeout -->');
141
- }
142
- }
143
- };
144
-
145
- die('<!-- /run_check -->');
146
-
147
- } else if ($action=='discard_link'){
148
- if (!current_user_can('edit_others_posts')) {
149
- die("Error: You can't do that. Access denied.");
150
- }
151
- $id=intval($_GET['id']);
152
- $wpdb->query("DELETE FROM $linkdata_name WHERE id=$id LIMIT 1");
153
- if($wpdb->rows_affected<1){
154
- die('Error: Couldn\'t remove the link record (DB error).');
155
- }
156
- die('OK: Link discarded');
157
-
158
- } else if ($action=='remove_link'){
159
-
160
- //actually deletes the link from the post
161
- if (!current_user_can('edit_others_posts')) {
162
- die("Error: You can't do that. Access denied.");
163
- }
164
-
165
- $id=intval($_GET['id']);
166
- $sql="SELECT * FROM $linkdata_name WHERE id = $id LIMIT 1";
167
- $the_link=$wpdb->get_row($sql, OBJECT, 0);
168
- if (!$the_link){
169
- die('Error: Link not found');
170
- }
171
- $the_post = get_post($the_link->post_id, ARRAY_A);
172
- if (!$the_post){
173
- die('Error: Post not found');
174
- }
175
-
176
- //Remove a link or an image from the post HTML
177
- if ($the_link->type == 'link')
178
- $new_content = unlink_the_link($the_post['post_content'], $the_link->url);
179
- elseif ($the_link->type == 'image')
180
- $new_content = unlink_image($the_post['post_content'], $the_link->url);
181
-
182
- //Update database
183
- $new_content = $wpdb->escape($new_content);
184
- $wpdb->query("UPDATE $wpdb->posts SET post_content = '$new_content' WHERE id = $the_link->post_id");
185
- if($wpdb->rows_affected<1){
186
- die('Error: Couldn\'t update the post ('.mysql_error().').');
187
- }
188
- $wpdb->query("DELETE FROM $linkdata_name WHERE id=$id LIMIT 1");
189
- if($wpdb->rows_affected<1){
190
- die('Error: Couldn\'t remove the link record (DB error).');
191
- }
192
-
193
- die('OK: Link deleted');
194
-
195
- } else if ($action == 'edit_link'){
196
- //edits the link's URL inside the post
197
- if (!current_user_can('edit_others_posts')) {
198
- die("Error: You can't do that. Access denied.");
199
- }
200
-
201
- $id = intval($_GET['id']);
202
- $new_url = $_GET['new_url'];
203
-
204
- $sql="SELECT * FROM $linkdata_name WHERE id = $id LIMIT 1";
205
- $the_link=$wpdb->get_row($sql, OBJECT, 0);
206
- if (!$the_link){
207
- die('Error: Link not found');
208
- }
209
- $the_post = get_post($the_link->post_id, ARRAY_A);
210
- if (!$the_post){
211
- die('Error: Post not found');
212
- }
213
-
214
- if ($the_link->type == 'link')
215
- $new_content = edit_the_link($the_post['post_content'], $the_link->url, $new_url);
216
- elseif ($the_link->type == 'image')
217
- $new_content = edit_image($the_post['post_content'], $the_link->url, $new_url);
218
-
219
-
220
- if (function_exists('mysql_real_escape_string')){
221
- $new_content = mysql_real_escape_string($new_content);
222
- } else {
223
- $new_content = $wpdb->escape($new_content);
224
- }
225
- $q = "UPDATE $wpdb->posts SET post_content = '$new_content' WHERE id = $the_link->post_id";
226
- //@file_put_contents('q.txt', $q);
227
- $wpdb->query($q);
228
- if($wpdb->rows_affected<1){
229
- die('Error: Couldn\'t update the post ('.mysql_error().').');
230
- }
231
- $wpdb->query("DELETE FROM $linkdata_name WHERE id=$id LIMIT 1");
232
- if($wpdb->rows_affected<1){
233
- die('Error: Couldn\'t remove the link record (DB error).');
234
- }
235
-
236
- die('OK: Link changed and deleted from the list of broken links.');
237
- };
238
-
239
- function parse_link($matches, $post_id){
240
- global $wpdb, $linkdata_name, $ws_link_checker;
241
-
242
- $url = $matches[3];
243
- $text = $matches[5];
244
-
245
- $url = $ws_link_checker->normalize_url($url);
246
- if (!$url) return false;
247
-
248
- if(strlen($url)>5){
249
- $wpdb->query(
250
- "INSERT INTO $linkdata_name(post_id, url, link_text, type, final_url)
251
- VALUES($post_id, '".$wpdb->escape($url)."',
252
- '".$wpdb->escape(strip_tags($text))."', 'link',
253
- '".$wpdb->escape($url)."')"
254
- );
255
- };
256
-
257
- return true;
258
- }
259
-
260
- function parse_image($matches, $post_id){
261
- global $wpdb, $linkdata_name, $ws_link_checker;
262
-
263
- $url=$matches[3];
264
- $url = $ws_link_checker->normalize_url($url);
265
- if(!$url) return false;
266
-
267
- if(strlen($url)>3){
268
- $wpdb->query(
269
- "INSERT INTO $linkdata_name(post_id, url, link_text, type, final_url)
270
- VALUES($post_id, '".$wpdb->escape($url)."', '[image]', 'image','".$wpdb->escape($url)."')"
271
- );
272
- };
273
-
274
- return true;
275
- }
276
-
277
- function gather_and_save_links($content, $post_id){
278
- //gather links (<a href=...>)
279
- global $url_pattern, $img_pattern;
280
-
281
- //remove all <code></code> blocks first
282
- $content = preg_replace('/<code>.+?<\/code>/i', ' ', $content);
283
-
284
- //echo "Analyzing post $post_id<br>Content = ".htmlspecialchars($content)."<br>";
285
-
286
- if(preg_match_all($url_pattern, $content, $matches, PREG_SET_ORDER)){
287
- foreach($matches as $link){
288
- //echo "Found link : ".print_r($link,true)."<br>";
289
- parse_link($link, $post_id);
290
- }
291
- };
292
-
293
- //gather images (<img src=...>)
294
- if(preg_match_all($img_pattern, $content, $matches, PREG_SET_ORDER)){
295
- foreach($matches as $img){
296
- parse_image($img, $post_id);
297
- }
298
- };
299
-
300
- return $content;
301
- }
302
-
303
- function page_exists_simple($url){
304
- //echo "Checking $url...<br/>";
305
-
306
- $parts=parse_url($url);
307
- if(!$parts) return false;
308
-
309
- if(!isset($parts['scheme'])) {
310
- $url='http://'.$url;
311
- $parts['scheme'] = 'http';
312
- }
313
-
314
- //Only HTTP links are checked. All others are automatically considered okay.
315
- if ( ($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') )
316
- return true;
317
-
318
- $ch = curl_init();
319
- curl_setopt($ch, CURLOPT_URL, $url);
320
- curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)');
321
- //curl_setopt($ch, CURLOPT_USERAGENT, 'WordPress/Broken Link Checker (bot)');
322
- curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
323
-
324
- @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
325
- curl_setopt($ch, CURLOPT_MAXREDIRS, 10);
326
-
327
- curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
328
- curl_setopt($ch, CURLOPT_TIMEOUT, 30);
329
-
330
- curl_setopt($ch, CURLOPT_FAILONERROR, false);
331
-
332
- $nobody=false;
333
- if($parts['scheme']=='https'){
334
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
335
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
336
- } else {
337
- $nobody=true;
338
- curl_setopt($ch, CURLOPT_NOBODY, true);
339
- //curl_setopt($ch, CURLOPT_RANGE, '0-1023');
340
- }
341
- curl_setopt($ch, CURLOPT_HEADER, true);
342
-
343
- $response = curl_exec($ch);
344
- //echo 'Response 1 : <pre>',$response,'</pre>';
345
- $code=intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
346
- //echo "Code 1 : $code<br/>";
347
-
348
- if ( (($code<200) || ($code>=400)) && $nobody) {
349
- curl_setopt($ch, CURLOPT_NOBODY, false);
350
- curl_setopt($ch, CURLOPT_HTTPGET, true);
351
- curl_setopt($ch, CURLOPT_RANGE, '0-2047');
352
- $response = curl_exec($ch);
353
- //echo 'Response 2 : <pre>',$response,'</pre>';
354
- $code=intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
355
- //echo "Code 2 : $code<br/>";
356
- }
357
-
358
- curl_close($ch);
359
-
360
- /*"Good" response codes are anything in the 2XX range (e.g "200 OK") and redirects - the 3XX range.
361
- HTTP 401 Unauthorized is a special case that is considered OK as well. Other errors - the 4XX range
362
- are treated as "page doesn't exist'". */
363
- return (($code>=200) && ($code<400)) || ($code == 401);
364
- }
365
-
366
- function unlink_image($content, $url){
367
- global $img_pattern, $url_to_replace;
368
- $url_to_replace = $url;
369
- //$url_pattern='/(<a[\s]+[^>]*href\s*=\s*[\"\']?)([^\'\">]+)([\'\"]+[^<>]*>)((?sU).*)(<\/a>)/i';
370
- $content = preg_replace_callback($img_pattern, unlink_image_callback, $content);
371
- return $content;
372
- }
373
-
374
- function unlink_image_callback($matches){
375
- global $url_to_replace, $ws_link_checker;
376
- $url = $ws_link_checker->normalize_url($matches[3]);
377
-
378
- if ($url == $url_to_replace){
379
- return ''; //completely remove the IMG tag
380
- } else {
381
- return $matches[0];
382
- }
383
- }
384
-
385
- function unlink_the_link($content, $url){
386
- global $url_pattern, $url_to_replace;
387
- $url_to_replace = $url;
388
- //$url_pattern='/(<a[\s]+[^>]*href\s*=\s*[\"\']?)([^\'\">]+)([\'\"]+[^<>]*>)((?sU).*)(<\/a>)/i';
389
- $content = preg_replace_callback($url_pattern, unlink_link_callback, $content);
390
- return $content;
391
- }
392
-
393
- function unlink_link_callback($matches){
394
- global $url_to_replace, $ws_link_checker;
395
- $url = $ws_link_checker->normalize_url($matches[3]);
396
- $text = $matches[5];
397
-
398
- //echo "$url || $url_to_replace\n";
399
- if ($url == $url_to_replace){
400
- //echo "Removed '$text' - '$url'\n";
401
- return $text;
402
- //return "<span class='broken_link'>$text</span>";
403
- } else {
404
- return $matches[0];
405
- }
406
- }
407
-
408
- function edit_the_link($content, $url, $newurl){
409
- global $url_pattern, $url_to_replace, $new_url;
410
- $url_to_replace = $url;
411
- $new_url = $newurl;
412
- //$url_pattern='/(<a[\s]+[^>]*href\s*=\s*[\"\']?)([^\'\" >]+)\2([^<>]*>)((?sU).*)(<\/a>)/i';
413
- $content = preg_replace_callback($url_pattern, edit_link_callback, $content);
414
- return $content;
415
- }
416
-
417
- function edit_link_callback($matches){
418
- global $url_to_replace, $new_url, $ws_link_checker;
419
- $url = $ws_link_checker->normalize_url($matches[3]);
420
- $text = $matches[5];
421
-
422
- //echo "$url || $url_to_replace\n";
423
- if ($url == $url_to_replace){
424
- //return $text;
425
- // \<a.. \",' \",' \...> \</a>
426
- return $matches[1].$matches[2].$new_url.$matches[2].$matches[4].$text.$matches[6];
427
- } else {
428
- return $matches[0];
429
- }
430
- }
431
-
432
- function edit_image($content, $url, $newurl){
433
- global $img_pattern, $url_to_replace, $new_url;
434
- $url_to_replace = $url;
435
- $new_url = $newurl;
436
- $content = preg_replace_callback($img_pattern, edit_link_callback, $content);
437
- return $content;
438
- }
439
-
440
- function edit_image_callback($matches){
441
- global $url_to_replace, $new_url, $ws_link_checker;
442
- $url = $ws_link_checker->normalize_url($matches[3]);
443
-
444
- if ($url == $url_to_replace){
445
- // \<img... \",' \url \",' \...>
446
- return $matches[1].$matches[2].$new_url.$matches[3].$matches[4];
447
- } else {
448
- return $matches[0];
449
- }
450
- }
451
  ?>
1
  <?php
2
  /*
3
+ This file isn't used anymore.
4
+ */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  ?>