PluginProbe ʕ •ᴥ•ʔ
Broken Link Checker / 0.4.14
Broken Link Checker v0.4.14
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 / broken-link-checker.php
broken-link-checker Last commit date
broken-link-checker.php 17 years ago readme.txt 17 years ago wsblc_ajax.php 17 years ago
broken-link-checker.php
1104 lines
1 <?php
2 /*
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 */
10
11 /*
12 Created by Janis Elsts (email : whiteshadow@w-shadow.com)
13 MySQL 4.0 compatibility by Jeroen (www.yukka.eu)
14 */
15
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(){
82 ?>
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
96 }
97
98 function header_css(){
99 echo '<style type="text/css">',$this->options['broken_link_css'],'</style>';
100 }
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;
408 foreach($this->options['exclusion_list'] as $excluded_word){
409 if (stristr($url, $excluded_word)){
410 return true;
411 }
412 }
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){
442 global $wpdb;
443
444 $post = get_post($post_id);
445 //Only check links in posts, not revisions and attachments
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 /**
563 * plugin_action_links()
564 * Handler for the 'plugin_action_links' hook. Adds a "Settings" link to this plugin's entry
565 * on the plugin list.
566 *
567 * @param array $links
568 * @param string $file
569 * @return array
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
577 function mytruncate($str, $max_length=50){
578 if(strlen($str)<=$max_length) return $str;
579 return (substr($str, 0, $max_length-3).'...');
580 }
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>
679
680 </td>
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(
910 '<?php
911 echo get_option( "siteurl" ).'/wp-content/plugins/'.$this->myfolder.'/wsblc_ajax.php?';
912 ?>action=remove_link&id='+link_id,
913 {
914 method:'get',
915 onSuccess: function(transport){
916 var re = /OK:.*/i
917 var response = transport.responseText || "";
918 if (re.test(response)){
919 $('link-'+link_id).hide();
920 $('link-details-'+link_id).hide();
921 alterLinkCounter(-1);
922 } else {
923 $('unlink_button-'+link_id).innerHTML = 'Unlink';
924 alert(response);
925 }
926 }
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 ?>