PluginProbe ʕ •ᴥ•ʔ
Broken Link Checker / 0.5.1
Broken Link Checker v0.5.1
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
images 17 years ago JSON.php 17 years ago broken-link-checker.php 17 years ago instance-classes.php 17 years ago link-classes.php 17 years ago readme.txt 17 years ago uninstall.php 17 years ago utility-class.php 17 years ago wsblc_ajax.php 17 years ago
broken-link-checker.php
2201 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.5.1
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 /**
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(){
140 ?>
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
164 }
165
166 function header_css(){
167 echo '<style type="text/css">',$this->options['broken_link_css'],'</style>';
168 }
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;
214 foreach($this->options['exclusion_list'] as $excluded_word){
215 if (stristr($url, $excluded_word)){
216 return true;
217 }
218 }
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){
314 global $wpdb;
315
316 $post = get_post($post_id);
317 //Only check links in posts, not revisions and attachments
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 {$wpdb->prefix}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 /**
496 * plugin_action_links()
497 * Handler for the 'plugin_action_links' hook. Adds a "Settings" link to this plugin's entry
498 * on the plugin list.
499 *
500 * @param array $links
501 * @param string $file
502 * @return array
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
510 function mytruncate($str, $max_length=50){
511 if(strlen($str)<=$max_length) return $str;
512 return (substr($str, 0, $max_length-3).'...');
513 }
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>
631
632 </td>
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(
1252 '<?php
1253 echo get_option( "siteurl" ).'/wp-content/plugins/'.$this->myfolder.'/wsblc_ajax.php?';
1254 ?>action=remove_link&id='+link_id,
1255 {
1256 method:'get',
1257 onSuccess: function(transport){
1258 var re = /OK:.*/i
1259 var response = transport.responseText || "";
1260 if (re.test(response)){
1261 $('link-'+link_id).hide();
1262 $('link-details-'+link_id).hide();
1263 alterLinkCounter(-1);
1264 } else {
1265 $('unlink_button-'+link_id).innerHTML = 'Unlink';
1266 alert(response);
1267 }
1268 }
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 ?>