PluginProbe ʕ •ᴥ•ʔ
Broken Link Checker / 0.5.8
Broken Link Checker v0.5.8
1.5.4 1.5.5 1.6 1.6.1 1.6.2 1.7 1.7.1 1.8 1.8.1 1.8.2 1.8.3 1.9 1.9.1 1.9.2 1.9.3 1.9.4 1.9.4.1 1.9.4.2 1.9.5 2.0.0 2.1.0 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.3.1 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.4.8 0.9.4 0.9.4.1 0.9.4.2 0.9.4.3 0.9.4.4 0.9.4.4-last-non-modular 0.9.5 0.9.6 0.9.7 0.9.7.1 0.9.7.2 1.10 1.10.1 1.10.10 1.10.11 1.10.2 1.10.3 1.10.4 1.10.5 1.10.6 1.10.7 1.10.8 1.10.9 1.11.1 1.11.10 1.11.11 1.11.12 1.11.13 1.11.14 1.11.15 1.11.17 1.11.18 1.11.19 1.11.2 1.11.20 1.11.21 1.11.3 1.11.4 1.11.5 1.11.8 1.11.9 1.2.2 1.2.3 1.2.4 1.2.5 1.3 1.3.1 1.4 1.5 1.5.1 1.5.2 1.5.3 trunk 0.1 0.2 0.2.2 0.2.2.1 0.2.3 0.2.4 0.2.5 0.3 0.3.1 0.3.2 0.3.3 0.3.4 0.3.5 0.3.6 0.3.7 0.3.8 0.3.9 0.4 0.4-i8n 0.4.1 0.4.10 0.4.11 0.4.12 0.4.13 0.4.14 0.4.2 0.4.3 0.4.4 0.4.5 0.4.6 0.4.7 0.4.8 0.4.9 0.5 0.5.1 0.5.10 0.5.10.1 0.5.11 0.5.12 0.5.13 0.5.14 0.5.15 0.5.16 0.5.16.1 0.5.17 0.5.18 0.5.2 0.5.3 0.5.4 0.5.5 0.5.6 0.5.7 0.5.8 0.5.8.1 0.5.9 0.6 0.6.1 0.6.2 0.6.3 0.6.4 0.6.5 0.7 0.7.1 0.7.2 0.7.3 0.7.4 0.8 0.8.1 0.9 0.9.1 0.9.2 0.9.3
broken-link-checker / link-classes.php
broken-link-checker Last commit date
images 16 years ago JSON.php 16 years ago broken-link-checker.php 16 years ago instance-classes.php 16 years ago link-classes.php 16 years ago readme.txt 16 years ago uninstall.php 16 years ago utility-class.php 16 years ago wsblc_ajax.php 16 years ago
link-classes.php
548 lines
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 = BLC_CHECKING;
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, blcUtility::urlencodefix($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, true);
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->maxlength = 1024*5; //load up to 5 kilobytes
224 $snoopy->fetch($url);
225
226 $this->request_duration = microtime_float(true) - $start_time;
227
228 $this->http_code = $snoopy->status; //HTTP status code (note : Snoopy returns -100 on timeout)
229 if ( $this->http_code == -100 ){
230 $this->http_code = BLC_TIMEOUT;
231 $this->timeout = true;
232 }
233
234 if ($snoopy->error)
235 $this->log .= $snoopy->error."\n";
236 if ($snoopy->timed_out)
237 $this->log .= "Request timed out.\n";
238
239 if ( is_array($snoopy->headers) )
240 $this->log .= implode("", $snoopy->headers)."\n"; //those headers already contain newlines
241
242 //Redirected?
243 if ( $snoopy->lastredirectaddr ) {
244 $this->final_url = $snoopy->lastredirectaddr;
245 $this->redirect_count = $snoopy->_redirectdepth;
246 } else {
247 $this->final_url = $this->url;
248 }
249 }
250
251 /*"Good" response codes are anything in the 2XX range (e.g "200 OK") and redirects - the 3XX range.
252 HTTP 401 Unauthorized is a special case that is considered OK as well. Other errors - the 4XX range -
253 are treated as "page doesn't exist'". */
254 //TODO: Treat circular redirects as broken links.
255 if ( (($this->http_code>=200) && ($this->http_code<400)) || ($this->http_code == 401) ) {
256 $this->log .= "Link is valid.";
257 //Reset the check count for valid links.
258 $this->check_count = 0;
259 return true;
260 } else {
261 $this->log .= "Link is broken.";
262 if ( $this->http_code == BLC_TIMEOUT ){
263 //This is probably a timeout
264 $this->timeout = true;
265 $this->log .= "\r\n(Most likely the connection timed out)";
266 }
267 return false;
268 }
269 }
270
271 function read_header($ch, $header){
272 $this->last_headers .= $header;
273 return strlen($header);
274 }
275
276 /**
277 * blcLink::save()
278 * Save link data to DB.
279 *
280 * @return bool True if saved successfully, false otherwise.
281 */
282 function save(){
283 global $wpdb;
284
285 if ( !$this->valid() ) return false;
286
287 if ( $this->is_new ){
288
289 //Insert a new row
290 $q = "
291 INSERT INTO {$wpdb->prefix}blc_links
292 ( url, last_check, check_count, final_url, redirect_count, log, http_code, request_duration, timeout )
293 VALUES( %s, %s, %d, %s, %d, %s, %d, %f, %d )";
294 $q = $wpdb->prepare($q, $this->url, $this->last_check, $this->check_count, $this->final_url,
295 $this->redirect_count, $this->log, $this->http_code, $this->request_duration, (integer)$this->timeout );
296 $rez = $wpdb->query($q);
297
298 $rez = $rez !== false;
299
300 if ($rez){
301 $this->link_id = $wpdb->insert_id;
302 //echo "Link added, ID : {$this->link_id}\r\n<br>";
303 //If the link was successfully saved then it's no longer "new"
304 $this->is_new = !$rez;
305 } else {
306 echo "Error adding link $url : {$wpdb->last_error}\r\n<br>";
307 }
308
309 return $rez;
310
311 } else {
312
313 //Update an existing DB record
314 $q = "UPDATE {$wpdb->prefix}blc_links SET url=%s, last_check=%s, check_count=%d, final_url=%s,
315 redirect_count=%d, log=%s, http_code=%d, request_duration=%f, timeout=%d
316 WHERE link_id=%d";
317
318 $q = $wpdb->prepare($q, $this->url, $this->last_check, $this->check_count, $this->final_url,
319 $this->redirect_count, $this->log, $this->http_code, $this->request_duration, (integer)$this->timeout, $this->link_id );
320
321 $rez = $wpdb->query($q);
322 if ( $rez !== false ){
323 //echo "Link updated, ID : {$this->link_id}\r\n<br>";
324 } else {
325 echo "Error updating link {$this->link_id} : {$wpdb->last_error}\r\n<br>";
326 }
327 return $rez !== false;
328 }
329 }
330
331 /**
332 * blcLink::edit()
333 * Edit all instances of the link by changing the URL.
334 *
335 * Here's how this really works : create a new link with the new URL. Then edit()
336 * all instances and point them to the new link record. If some instance can't be
337 * edited they will still point to the old record. The old record is deleted
338 * if all instances were edited successfully.
339 *
340 * @param string $new_url
341 * @return array An associative array with the new link ID, the number of successfully edited instances and the number of failed edits.
342 */
343 function edit($new_url){
344 if ( !$this->valid() ){
345 return false;
346 }
347
348 //FB::info('Changing link '.$this->link_id .' to URL "'.$new_url.'"');
349
350 $instances = $this->get_instances();
351 //Fail if there are no instances
352 if (empty($instances)) return false;
353
354 //Load or create a link with the URL = $new_url
355 $new_link = new blcLink($new_url);
356 $was_new = $new_link->is_new;
357 if ($new_link->is_new) {
358 //FB::log($new_link, 'Saving a new link');
359 $new_link->save(); //so that we get a valid link_id
360 }
361
362 if ( empty($new_link->link_id) ){
363 //FB::error("Failed to create a new link record");
364 return false;
365 }
366
367 //Edit each instance.
368 //FB::info('Editing ' . count($instances) . ' instances');
369 $cnt_okay = $cnt_error = 0;
370 foreach ( $instances as $instance ){
371 if ( $instance->edit( $this->url, $new_url ) ){
372 $cnt_okay++;
373 $instance->link_id = $new_link->link_id;
374 $instance->save();
375 //FB::info($instance, 'Successfully edited instance ' . $instance->instance_id);
376 } else {
377 $cnt_error++;
378 //FB::error($instance, 'Failed to edit instance ' . $instance->instance_id);
379 }
380 }
381
382 //If all instances were edited successfully we can delete the old link record.
383 //And copy the new link data into this object. UNLESS this link is equal to the new link
384 //(which should never happen, but whatever)
385 if ( ( $cnt_error == 0 ) && ( $cnt_okay > 0 ) && ( $this->link_id != $new_link->link_id ) ){
386 $this->forget( false );
387
388 $this->link_id = $new_link->link_id;
389 $this->url = $new_link->url;
390 $this->final_url = $new_link->url;
391 $this->log = $new_link->log;
392 $this->http_code = $new_link->http_code;
393 $this->redirect_count = $new_link->redirect_count;
394 $this->timeout = $new_link->timeout;
395 }
396
397 //On the other hand, if no instances could be edited and the $new_link was really new,
398 //then delete it.
399 if ( ( $cnt_okay == 0 ) && $was_new ){
400 $new_link->forget( false );
401 }
402
403 return array(
404 'new_link_id' => $this->link_id,
405 'cnt_okay' => $cnt_okay,
406 'cnt_error' => $cnt_error,
407 );
408 }
409
410 //Delete (unlink) all instances and the link itself
411 function unlink(){
412 if ( !$this->valid() ){
413 return false;
414 }
415
416 //FB::info($this, 'Removing link');
417
418 $instances = $this->get_instances();
419 //Fail if there are no instances
420 if (empty($instances)) {
421 //FB::warn("This link has no instances. Deleting the link.");
422 return $this->forget( false ) !== false;
423 }
424
425 //Unlink each instance.
426 //FB::info('Unlinking ' . count($instances) . ' instances');
427 $cnt_okay = $cnt_error = 0;
428 foreach ( $instances as $instance ){
429 if ( $instance->unlink( $this->url ) ){
430 $cnt_okay++;
431 //FB::info( $instance, 'Successfully unlinked instance' );
432 } else {
433 $cnt_error++;
434 //FB::error( $instance, 'Failed to unlink instance' );
435 }
436 }
437
438 //If all instances were unlinked successfully we can delete the link record.
439 if ( ( $cnt_error == 0 ) && ( $cnt_okay > 0 ) ){
440 //FB::log('Instances removed, deleting the link.');
441 return $this->forget() !== false;
442 } else {
443 //FB::error("Something went wrong. Unlinked instances : $cnt_okay, errors : $cnt_error");
444 return false;
445 }
446 }
447
448 /**
449 * blcLink::forget()
450 * Remove the link and instance records from the DB. Doesn't alter posts/etc.
451 *
452 * @return mixed 1 on success, 0 if link not found, false on error.
453 */
454 function forget($remove_instances = true){
455 global $wpdb;
456 if ( !$this->valid() ) return false;
457
458 if ( !empty($this->link_id) ){
459 //FB::info($this, 'Deleting link from DB');
460
461 if ( $remove_instances ){
462 //Remove instances, if any
463 $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_instances WHERE link_id=%d", $this->link_id) );
464 }
465
466 //Remove the link itself
467 $rez = $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_links WHERE link_id=%d", $this->link_id) );
468 $this->link_id = 0;
469
470 return $rez;
471 } else {
472 return false;
473 }
474
475 }
476
477 /**
478 * blcLink::get_instances()
479 * Get a list of the link's instances
480 *
481 * @param integer $max_count The maximum number of instances to return. The default is -1 (no limit)
482 * @return array An array of instance objects or FALSE on failure.
483 */
484 function get_instances($max_count = -1){
485 global $wpdb;
486 if ( !$this->valid() || empty($this->link_id) ) return false;
487
488 $limit = $max_count > 0 ? "LIMIT $max_count":'';
489
490 //Get all instances of this link
491 $q = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_instances WHERE link_id=%d $limit", $this->link_id);
492 $results = $wpdb->get_results($q, ARRAY_A);
493
494 if ( !empty($results) ) {
495 //Create an object for each instance
496 $instances = array();
497 foreach ($results as $result){
498 //Each source/link type combination has it's own subclass. E.g. _post_image or _blogroll_link.
499 $classname = 'blcLinkInstance_' . $result['source_type'] . '_' . $result['instance_type'];
500 $instances[] = new $classname($result);
501 }
502 return $instances;
503 } else {
504 return false;
505 }
506 }
507
508 /**
509 * blcLink::add_instance()
510 * Record a new instance of the link.
511 *
512 * @param int $source_id
513 * @param string $source_type
514 * @param string $link_text
515 * @param string $instance_type
516 * @return object The created instance or FALSE on error.
517 */
518 function add_instance($source_id, $source_type, $link_text, $instance_type){
519
520 //The link must be saved before an instance can be added
521 if ($this->is_new) {
522 if ( !$this->save()) return false;
523 }
524
525 //Create a new instance tied to this link
526 $classname = 'blcLinkInstance_' . $source_type . '_' . $instance_type;
527 if ( !class_exists($classname) ){
528 $classname = 'blcLinkInstance';
529 }
530 $inst = new $classname( array(
531 'link_id' => $this->link_id,
532 'source_id' => $source_id,
533 'source_type' => $source_type,
534 'link_text' => $link_text,
535 'instance_type' => $instance_type,
536 ) );
537
538 //Save the instance to the DB
539 if ( $inst->save() ){
540 return $inst;
541 } else {
542 return false;
543 };
544 }
545 }
546 } //class_exists
547
548 ?>