PluginProbe ʕ •ᴥ•ʔ
Broken Link Checker / 0.7.1
Broken Link Checker v0.7.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 / core.php
broken-link-checker Last commit date
images 17 years ago languages 16 years ago JSON.php 17 years ago broken-link-checker.php 16 years ago config-manager.php 16 years ago core.php 16 years ago highlighter-class.php 16 years ago instance-classes.php 16 years ago link-classes.php 16 years ago readme.txt 16 years ago uninstall.php 16 years ago utility-class.php 16 years ago
core.php
3107 lines
1 <?php
2
3 //The plugin will use Snoopy in case CURL is not available
4 if (!class_exists('Snoopy')) require_once(ABSPATH. WPINC . '/class-snoopy.php');
5
6 /**
7 * Simple function to replicate PHP 5 behaviour
8 */
9 if ( !function_exists( 'microtime_float' ) ) {
10 function microtime_float()
11 {
12 list($usec, $sec) = explode(" ", microtime());
13 return ((float)$usec + (float)$sec);
14 }
15 }
16
17 if (!class_exists('wsBrokenLinkChecker')) {
18
19 class wsBrokenLinkChecker {
20 var $conf;
21
22 var $loader;
23 var $my_basename = '';
24
25 var $db_version = 3;
26
27 var $execution_start_time; //Used for a simple internal execution timer in start_timer()/execution_time()
28 var $lockfile_handle = null;
29
30 var $native_filters = null;
31
32 /**
33 * wsBrokenLinkChecker::wsBrokenLinkChecker()
34 * Class constructor
35 *
36 * @param string $loader The fully qualified filename of the loader script that WP identifies as the "main" plugin file.
37 * @param blcConfigurationManager $conf An instance of the configuration manager
38 * @return void
39 */
40 function wsBrokenLinkChecker ( $loader, $conf ) {
41 global $wpdb;
42
43 $this->loader = $loader;
44 $this->conf = $conf;
45
46 add_action('activate_' . plugin_basename( $this->loader ), array(&$this,'activation'));
47 $this->my_basename = plugin_basename( $this->loader );
48
49 add_action('init', array(&$this,'load_language'));
50
51 add_action('admin_menu', array(&$this,'admin_menu'));
52
53 //These hooks update the plugin's internal records when posts are added, deleted or modified.
54 add_action('delete_post', array(&$this,'post_deleted'));
55 add_action('save_post', array(&$this,'post_saved'));
56
57 //These do the same for (blogroll) links.
58 add_action('add_link', array(&$this,'hook_add_link'));
59 add_action('edit_link', array(&$this,'hook_edit_link'));
60 add_action('delete_link', array(&$this,'hook_delete_link'));
61
62 //Load jQuery on Dashboard pages (probably redundant as WP already does that)
63 add_action('admin_print_scripts', array(&$this,'admin_print_scripts'));
64
65 //The dashboard widget
66 add_action('wp_dashboard_setup', array(&$this, 'hook_wp_dashboard_setup'));
67
68 //AJAXy hooks
69 //TODO: Check nonces in AJAX hooks
70 add_action( 'wp_ajax_blc_full_status', array(&$this,'ajax_full_status') );
71 add_action( 'wp_ajax_blc_dashboard_status', array(&$this,'ajax_dashboard_status') );
72 add_action( 'wp_ajax_blc_work', array(&$this,'ajax_work') );
73 add_action( 'wp_ajax_blc_discard', array(&$this,'ajax_discard') );
74 add_action( 'wp_ajax_blc_edit', array(&$this,'ajax_edit') );
75 add_action( 'wp_ajax_blc_link_details', array(&$this,'ajax_link_details') );
76 add_action( 'wp_ajax_blc_exclude_link', array(&$this,'ajax_exclude_link') );
77 add_action( 'wp_ajax_blc_unlink', array(&$this,'ajax_unlink') );
78
79 //Check if it's possible to create a lockfile and nag the user about it if not.
80 if ( $this->lockfile_name() ){
81 //Lockfiles work, so it's safe to enable the footer hook that will call the worker
82 //function via AJAX.
83 add_action('admin_footer', array(&$this,'admin_footer'));
84 } else {
85 //No lockfiles, nag nag nag!
86 add_action( 'admin_notices', array( &$this, 'lockfile_warning' ) );
87 }
88
89 //Initialize the built-in link filters
90 add_action('init', array(&$this,'init_native_filters'));
91 }
92
93 function admin_footer(){
94 ?>
95 <!-- wsblc admin footer -->
96 <div id='wsblc_updater_div'></div>
97 <script type='text/javascript'>
98 (function($){
99
100 //(Re)starts the background worker thread
101 function blcDoWork(){
102 $.post(
103 "<?php echo admin_url('admin-ajax.php'); ?>",
104 {
105 'action' : 'blc_work'
106 }
107 );
108 }
109 //Call it the first time
110 blcDoWork();
111
112 //Then call it periodically every X seconds
113 setInterval(blcDoWork, <?php echo (intval($this->conf->options['max_execution_time']) + 1 )*1000; ?>);
114
115 })(jQuery);
116 </script>
117 <!-- /wsblc admin footer -->
118 <?php
119 }
120
121 function is_excluded($url){
122 if (!is_array($this->conf->options['exclusion_list'])) return false;
123 foreach($this->conf->options['exclusion_list'] as $excluded_word){
124 if (stristr($url, $excluded_word)){
125 return true;
126 }
127 }
128 return false;
129 }
130
131 function dashboard_widget(){
132 ?>
133 <p id='wsblc_activity_box'><?php _e('Loading...', 'broken-link-checker'); ?></p>
134 <script type='text/javascript'>
135 jQuery( function($){
136 var blc_was_autoexpanded = false;
137
138 function blcDashboardStatus(){
139 $.getJSON(
140 "<?php echo admin_url('admin-ajax.php'); ?>",
141 {
142 'action' : 'blc_dashboard_status'
143 },
144 function (data, textStatus){
145 if ( data && ( typeof(data.text) != 'undefined' ) ) {
146 $('#wsblc_activity_box').html(data.text);
147 <?php if ( $this->conf->options['autoexpand_widget'] ) { ?>
148 //Expand the widget if there are broken links.
149 //Do this only once per pageload so as not to annoy the user.
150 if ( !blc_was_autoexpanded && ( data.status.broken_links > 0 ) ){
151 $('#blc_dashboard_widget.postbox').removeClass('closed');
152 blc_was_autoexpanded = true;
153 };
154 <?php } ?>
155 } else {
156 $('#wsblc_activity_box').html('<?php _e('[ Network error ]', 'broken-link-checker'); ?>');
157 }
158
159 setTimeout( blcDashboardStatus, 120*1000 ); //...update every two minutes
160 }
161 );
162 }
163
164 blcDashboardStatus();//Call it the first time
165
166 } );
167 </script>
168 <?php
169 }
170
171 function dashboard_widget_control( $widget_id, $form_inputs = array() ){
172 if ( 'POST' == $_SERVER['REQUEST_METHOD'] && 'blc_dashboard_widget' == $_POST['widget_id'] ) {
173 //It appears $form_inputs isn't used in the current WP version, so lets just use $_POST
174 $this->conf->options['autoexpand_widget'] = !empty($_POST['blc-autoexpand']);
175 $this->conf->save_options();
176 }
177
178 ?>
179 <p><label for="blc-autoexpand">
180 <input id="blc-autoexpand" name="blc-autoexpand" type="checkbox" value="1" <?php if ( $this->conf->options['autoexpand_widget'] ) echo 'checked="checked"'; ?> />
181 <?php _e('Automatically expand the widget if broken links have been detected', 'broken-link-checker'); ?>
182 </label></p>
183 <?php
184 }
185
186 function admin_print_scripts(){
187 //jQuery is used for AJAX and effects
188 wp_enqueue_script('jquery');
189 }
190
191 function load_ui_scripts(){
192 //jQuery UI is used on the settings page and in the link listings
193 wp_enqueue_script('jquery-ui-core');
194 wp_enqueue_script('jquery-ui-dialog');
195 }
196
197 /**
198 * ws_broken_link_checker::post_deleted()
199 * A hook for post_deleted. Remove link instances associated with that post.
200 *
201 * @param int $post_id
202 * @return void
203 */
204 function post_deleted($post_id){
205 global $wpdb;
206
207 //FB::log($post_id, "Post deleted");
208 //Remove this post's instances
209 $q = "DELETE FROM {$wpdb->prefix}blc_instances
210 WHERE source_id = %d AND (source_type = 'post' OR source_type='custom_field')";
211 $q = $wpdb->prepare($q, intval($post_id) );
212
213 //FB::log($q, 'Executing query');
214
215 if ( $wpdb->query( $q ) === false ){
216 //FB::error($wpdb->last_error, "Database error");
217 }
218
219 //Remove the synch record
220 $q = "DELETE FROM {$wpdb->prefix}blc_synch
221 WHERE source_id = %d AND source_type = 'post'";
222 $wpdb->query( $wpdb->prepare($q, intval($post_id)) );
223
224 //Remove any dangling link records
225 $this->cleanup_links();
226 }
227
228 function post_saved($post_id){
229 global $wpdb;
230
231 $post = get_post($post_id);
232 //Only check links in posts, not revisions and attachments
233 if ( ($post->post_type != 'post') && ($post->post_type != 'page') ) return null;
234 //Only check published posts
235 if ( $post->post_status != 'publish' ) return null;
236
237 $this->mark_unsynched( $post_id, 'post' );
238 }
239
240 function initiate_recheck(){
241 global $wpdb;
242
243 //Delete all discovered instances
244 $wpdb->query("TRUNCATE {$wpdb->prefix}blc_instances");
245
246 //Delete all discovered links
247 $wpdb->query("TRUNCATE {$wpdb->prefix}blc_links");
248
249 //Mark all posts, custom fields and bookmarks for processing.
250 $this->resynch();
251 }
252
253 function resynch(){
254 global $wpdb;
255
256 //Drop all synchronization records
257 $wpdb->query("TRUNCATE {$wpdb->prefix}blc_synch");
258
259 //Create new synchronization records for posts
260 $q = "INSERT INTO {$wpdb->prefix}blc_synch(source_id, source_type, synched)
261 SELECT id, 'post', 0
262 FROM {$wpdb->posts}
263 WHERE
264 {$wpdb->posts}.post_status = 'publish'
265 AND {$wpdb->posts}.post_type IN ('post', 'page')";
266 $wpdb->query( $q );
267
268 //Create new synchronization records for bookmarks (the blogroll)
269 $q = "INSERT INTO {$wpdb->prefix}blc_synch(source_id, source_type, synched)
270 SELECT link_id, 'blogroll', 0
271 FROM {$wpdb->links}
272 WHERE 1";
273 $wpdb->query( $q );
274
275 //Delete invalid instances
276 $this->cleanup_instances();
277 //Delete orphaned links
278 $this->cleanup_links();
279
280 $this->conf->options['need_resynch'] = true;
281 $this->conf->save_options();
282 }
283
284 function mark_unsynched( $source_id, $source_type ){
285 global $wpdb;
286
287 $q = "REPLACE INTO {$wpdb->prefix}blc_synch( source_id, source_type, synched, last_synch)
288 VALUES( %d, %s, %d, NOW() )";
289 $rez = $wpdb->query( $wpdb->prepare( $q, $source_id, $source_type, 0 ) );
290
291 if ( !$this->conf->options['need_resynch'] ){
292 $this->conf->options['need_resynch'] = true;
293 $this->conf->save_options();
294 }
295
296 return $rez;
297 }
298
299 function mark_synched( $source_id, $source_type ){
300 global $wpdb;
301 //FB::log("Marking $source_type $source_id as synched.");
302 $q = "REPLACE INTO {$wpdb->prefix}blc_synch( source_id, source_type, synched, last_synch)
303 VALUES( %d, %s, %d, NOW() )";
304 return $wpdb->query( $wpdb->prepare( $q, $source_id, $source_type, 1 ) );
305 }
306
307 function activation(){
308 //Prepare the database.
309 $this->upgrade_database();
310
311 //Clear the instance table and mark all posts and other parse-able objects as unsynchronized.
312 $this->resynch();
313
314 //Save the default options.
315 $this->conf->save_options();
316
317 //And optimize my DB tables, too (for good measure)
318 $this->optimize_database();
319 }
320
321 /**
322 * ws_broken_link_checker::upgrade_database()
323 * Create and/or upgrade database tables
324 *
325 * @param bool $die_on_error Whether the function should stop the script and display an error message if a DB error is encountered.
326 * @return void
327 */
328 function upgrade_database( $die_on_error = true ){
329 global $wpdb;
330
331 //Do we need to upgrade?
332 //[ Disabled for now, was causing issues when the user manually deletes the plugin ]
333 //if ( $this->db_version == $this->conf->options['current_db_version'] ) return;
334
335 //Delete tables used by older versions of the plugin
336 $rez = $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}blc_linkdata, {$wpdb->prefix}blc_postdata" );
337 if ( $rez === false ){
338 //FB::error($wpdb->last_error, "Database error");
339 return false;
340 }
341
342 require_once (ABSPATH . 'wp-admin/includes/upgrade.php');
343
344 //Create the link table if it doesn't exist yet.
345 $q = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}blc_links (
346 link_id int(20) unsigned NOT NULL auto_increment,
347 url text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
348 last_check datetime NOT NULL default '0000-00-00 00:00:00',
349 check_count int(2) unsigned NOT NULL default '0',
350 final_url text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
351 redirect_count smallint(5) unsigned NOT NULL,
352 log text NOT NULL,
353 http_code smallint(6) NOT NULL,
354 request_duration float NOT NULL default '0',
355 timeout tinyint(1) unsigned NOT NULL default '0',
356
357 PRIMARY KEY (link_id),
358 KEY url (url(150)),
359 KEY final_url (final_url(150)),
360 KEY http_code (http_code),
361 KEY timeout (timeout)
362 )";
363 if ( $wpdb->query( $q ) === false ){
364 if ( $die_on_error )
365 die( sprintf( __('Database error : %s', 'broken-link-checker'), $wpdb->last_error) );
366 };
367
368 //Fix URL fields so that they are collated as case-sensitive (this can't be done via dbDelta)
369 $q = "ALTER TABLE {$wpdb->prefix}blc_links
370 MODIFY url text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
371 MODIFY final_url text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL";
372 if ( $wpdb->query( $q ) === false ){
373 if ( $die_on_error )
374 die( sprintf( __('Database error : %s', 'broken-link-checker'), $wpdb->last_error) );
375 };
376
377 //Create the instance table
378 $q = "CREATE TABLE {$wpdb->prefix}blc_instances (
379 instance_id int(10) unsigned NOT NULL auto_increment,
380 link_id int(10) unsigned NOT NULL,
381 source_id int(10) unsigned NOT NULL,
382 source_type enum('post','blogroll','custom_field') NOT NULL default 'post',
383 link_text varchar(250) NOT NULL,
384 instance_type enum('link','image') NOT NULL default 'link',
385
386 PRIMARY KEY (instance_id),
387 KEY link_id (link_id),
388 KEY source_id (source_id,source_type),
389 FULLTEXT KEY link_text (link_text)
390 )";
391 dbDelta($q);
392
393 //Create the synchronization table
394 $q = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}blc_synch (
395 source_id int(20) unsigned NOT NULL,
396 source_type enum('post','blogroll') NOT NULL,
397 synched tinyint(3) unsigned NOT NULL,
398 last_synch datetime NOT NULL,
399 PRIMARY KEY (source_id, source_type),
400 KEY synched (synched)
401 )";
402 if ( $wpdb->query( $q ) === false ){
403 if ( $die_on_error )
404 die( sprintf( __('Database error : %s', 'broken-link-checker'), $wpdb->last_error) );
405 };
406
407 //Create the custom filter table
408 $q = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}blc_filters (
409 id int(10) unsigned NOT NULL AUTO_INCREMENT,
410 `name` varchar(100) NOT NULL,
411 params text NOT NULL,
412 PRIMARY KEY (id)
413 )";
414 if ( $wpdb->query( $q ) === false ){
415 if ( $die_on_error )
416 die( sprintf( __('Database error : %s', 'broken-link-checker'), $wpdb->last_error) );
417 };
418
419 $this->conf->options['current_db_version'] = $this->db_version;
420 $this->conf->save_options();
421
422 return true;
423 }
424
425 /**
426 * wsBrokenLinkChecker::optimize_database()
427 * Optimize the plugin's tables
428 *
429 * @return void
430 */
431 function optimize_database(){
432 global $wpdb;
433
434 $wpdb->query("OPTIMIZE TABLE {$wpdb->prefix}blc_links, {$wpdb->prefix}blc_instances, {$wpdb->prefix}blc_synch");
435 }
436
437 function admin_menu(){
438 if (current_user_can('manage_options'))
439 add_filter('plugin_action_links', array(&$this, 'plugin_action_links'), 10, 2);
440
441 $options_page_hook = add_options_page(
442 __('Link Checker Settings', 'broken-link-checker'),
443 __('Link Checker', 'broken-link-checker'),
444 'manage_options',
445 'link-checker-settings',array(&$this, 'options_page')
446 );
447
448 $links_page_hook = add_management_page(
449 __('View Broken Links', 'broken-link-checker'),
450 __('Broken Links', 'broken-link-checker'),
451 'edit_others_posts',
452 'view-broken-links',array(&$this, 'links_page')
453 );
454
455 //Add plugin-specific scripts and CSS only to the it's own pages
456 add_action( 'admin_print_styles-' . $options_page_hook, array(&$this, 'options_page_css') );
457 add_action( 'admin_print_styles-' . $links_page_hook, array(&$this, 'links_page_css') );
458 add_action( 'admin_print_scripts-' . $options_page_hook, array(&$this, 'load_ui_scripts') );
459 add_action( 'admin_print_scripts-' . $links_page_hook, array(&$this, 'load_ui_scripts') );
460 }
461
462 /**
463 * plugin_action_links()
464 * Handler for the 'plugin_action_links' hook. Adds a "Settings" link to this plugin's entry
465 * on the plugin list.
466 *
467 * @param array $links
468 * @param string $file
469 * @return array
470 */
471 function plugin_action_links($links, $file) {
472 if ($file == $this->my_basename)
473 $links[] = "<a href='options-general.php?page=link-checker-settings'>" . __('Settings') . "</a>";
474 return $links;
475 }
476
477 function mytruncate($str, $max_length=50){
478 if(strlen($str)<=$max_length) return $str;
479 return (substr($str, 0, $max_length-3).'...');
480 }
481
482 function options_page(){
483 if (isset($_GET['recheck']) && ($_GET['recheck'] == 'true')) {
484 $this->initiate_recheck();
485 }
486 if(isset($_POST['submit'])) {
487 check_admin_referer('link-checker-options');
488
489 //The execution time limit must be above zero
490 $new_execution_time = intval($_POST['max_execution_time']);
491 if( $new_execution_time > 0 ){
492 $this->conf->options['max_execution_time'] = $new_execution_time;
493 }
494
495 //The check threshold also must be > 0
496 $new_check_threshold=intval($_POST['check_threshold']);
497 if( $new_check_threshold > 0 ){
498 $this->conf->options['check_threshold'] = $new_check_threshold;
499 }
500
501 $this->conf->options['mark_broken_links'] = !empty($_POST['mark_broken_links']);
502 $new_broken_link_css = trim($_POST['broken_link_css']);
503 $this->conf->options['broken_link_css'] = $new_broken_link_css;
504
505 $this->conf->options['mark_removed_links'] = !empty($_POST['mark_removed_links']);
506 $new_removed_link_css = trim($_POST['removed_link_css']);
507 $this->conf->options['removed_link_css'] = $new_removed_link_css;
508
509 //TODO: Maybe update affected links when exclusion list changes (could be expensive resource-wise).
510 $this->conf->options['exclusion_list'] = array_filter(
511 preg_split(
512 '/[\s\r\n]+/', //split on newlines and whitespace
513 $_POST['exclusion_list'],
514 -1,
515 PREG_SPLIT_NO_EMPTY //skip empty values
516 )
517 );
518
519 //Parse the custom field list
520 $new_custom_fields = array_filter(
521 preg_split( '/[\s\r\n]+/', $_POST['blc_custom_fields'], -1, PREG_SPLIT_NO_EMPTY )
522 );
523
524 //Calculate the difference between the old custom field list and the new one (used later)
525 $diff1 = array_diff( $new_custom_fields, $this->conf->options['custom_fields'] );
526 $diff2 = array_diff( $this->conf->options['custom_fields'], $new_custom_fields );
527 $this->conf->options['custom_fields'] = $new_custom_fields;
528
529 //Temporary file directory
530 $this->conf->options['custom_tmp_dir'] = trailingslashit( trim(stripslashes(strval($_POST['custom_tmp_dir']))) );
531
532 //HTTP timeout
533 $new_timeout = intval($_POST['timeout']);
534 if( $new_timeout > 0 ){
535 $this->conf->options['timeout'] = $new_timeout ;
536 }
537
538 $this->conf->save_options();
539
540 /*
541 If the list of custom fields was modified then we MUST resynchronize or
542 custom fields linked with existing posts may not be detected. This is somewhat
543 inefficient.
544 */
545 if ( ( count($diff1) > 0 ) || ( count($diff2) > 0 ) ){
546 $this->resynch();
547 }
548
549 $base_url = remove_query_arg( array('_wpnonce', 'noheader', 'updated', 'error', 'action', 'message') );
550 wp_redirect( add_query_arg( array( 'updated' => 1), $base_url ) );
551 }
552
553 $debug = $this->get_debug_info();
554
555 ?>
556
557 <div class="wrap"><h2><?php _e('Broken Link Checker Options', 'broken-link-checker'); ?></h2>
558
559 <form name="link_checker_options" method="post" action="<?php
560 echo admin_url('options-general.php?page=link-checker-settings&noheader=1');
561 ?>">
562 <?php
563 wp_nonce_field('link-checker-options');
564 ?>
565
566 <table class="form-table">
567
568 <tr valign="top">
569 <th scope="row">
570 <?php _e('Status','broken-link-checker'); ?>
571 <br>
572 <a href="javascript:void(0)" id="blc-debug-info-toggle"><?php _e('Show debug info', 'broken-link-checker'); ?></a>
573 </th>
574 <td>
575
576
577 <div id='wsblc_full_status'>
578 <br/><br/><br/>
579 </div>
580 <script type='text/javascript'>
581 (function($){
582
583 function blcUpdateStatus(){
584 $.getJSON(
585 "<?php echo admin_url('admin-ajax.php'); ?>",
586 {
587 'action' : 'blc_full_status'
588 },
589 function (data, textStatus){
590 if ( data && ( typeof(data['text']) != 'undefined' ) ){
591 $('#wsblc_full_status').html(data.text);
592 } else {
593 $('#wsblc_full_status').html('<?php _e('[ Network error ]', 'broken-link-checker'); ?>');
594 }
595
596 setTimeout(blcUpdateStatus, 10000); //...update every 10 seconds
597 }
598 );
599 }
600 blcUpdateStatus();//Call it the first time
601
602 })(jQuery);
603 </script>
604 <?php //JHS: Recheck all posts link: ?>
605 <p><input class="button" type="button" name="recheckbutton"
606 value="<?php _e('Re-check all pages', 'broken-link-checker'); ?>"
607 onclick="location.replace('<?php echo basename($_SERVER['PHP_SELF']); ?>?page=link-checker-settings&amp;recheck=true')" />
608 </p>
609
610 <table id="blc-debug-info">
611 <?php
612
613 //Output the debug info in a table
614 foreach( $debug as $key => $value ){
615 printf (
616 '<tr valign="top" class="blc-debug-item-%s"><th scope="row">%s</th><td>%s<div class="blc-debug-message">%s</div></td></tr>',
617 $value['state'],
618 $key,
619 $value['value'],
620 ( array_key_exists('message', $value)?$value['message']:'')
621 );
622 }
623 ?>
624 </table>
625
626 </td>
627 </tr>
628
629 <tr valign="top">
630 <th scope="row"><?php _e('Check each link','broken-link-checker'); ?></th>
631 <td>
632
633 <?php
634 printf(
635 __('Every %s hours','broken-link-checker'),
636 sprintf(
637 '<input type="text" name="check_threshold" id="check_threshold" value="%d" size="5" maxlength="5" />',
638 $this->conf->options['check_threshold']
639 )
640 );
641 ?>
642 <br/>
643 <span class="description">
644 <?php _e('Existing links will be checked this often. New links will usually be checked ASAP.', 'broken-link-checker'); ?>
645 </span>
646
647 </td>
648 </tr>
649
650 <tr valign="top">
651 <th scope="row"><?php _e('Broken link CSS','broken-link-checker'); ?></th>
652 <td>
653 <label for='mark_broken_links'>
654 <input type="checkbox" name="mark_broken_links" id="mark_broken_links"
655 <?php if ($this->conf->options['mark_broken_links']) echo ' checked="checked"'; ?>/>
656 <?php _e('Apply <em>class="broken_link"</em> to broken links', 'broken-link-checker'); ?>
657 </label>
658 <br/>
659 <textarea name="broken_link_css" id="broken_link_css" cols='45' rows='4'/><?php
660 if( isset($this->conf->options['broken_link_css']) )
661 echo $this->conf->options['broken_link_css'];
662 ?></textarea>
663
664 </td>
665 </tr>
666
667 <tr valign="top">
668 <th scope="row"><?php _e('Removed link CSS','broken-link-checker'); ?></th>
669 <td>
670 <label for='mark_removed_links'>
671 <input type="checkbox" name="mark_removed_links" id="mark_removed_links"
672 <?php if ($this->conf->options['mark_removed_links']) echo ' checked="checked"'; ?>/>
673 <?php _e('Apply <em>class="removed_link"</em> to unlinked links', 'broken-link-checker'); ?>
674 </label>
675 <br/>
676 <textarea name="removed_link_css" id="removed_link_css" cols='45' rows='4'/><?php
677 if( isset($this->conf->options['removed_link_css']) )
678 echo $this->conf->options['removed_link_css'];
679 ?></textarea>
680
681 </td>
682 </tr>
683
684 <tr valign="top">
685 <th scope="row"><?php _e('Exclusion list', 'broken-link-checker'); ?></th>
686 <td><?php _e("Don't check links where the URL contains any of these words (one per line) :", 'broken-link-checker'); ?><br/>
687 <textarea name="exclusion_list" id="exclusion_list" cols='45' rows='4' wrap='off'/><?php
688 if( isset($this->conf->options['exclusion_list']) )
689 echo implode("\n", $this->conf->options['exclusion_list']);
690 ?></textarea>
691
692 </td>
693 </tr>
694
695 <tr valign="top">
696 <th scope="row"><?php _e('Custom fields', 'broken-link-checker'); ?></th>
697 <td><?php _e('Check URLs entered in these custom fields (one per line) :', 'broken-link-checker'); ?><br/>
698 <textarea name="blc_custom_fields" id="blc_custom_fields" cols='45' rows='4' /><?php
699 if( isset($this->conf->options['custom_fields']) )
700 echo implode("\n", $this->conf->options['custom_fields']);
701 ?></textarea>
702
703 </td>
704 </tr>
705
706 </table>
707
708 <h3><?php _e('Advanced','broken-link-checker'); ?></h3>
709
710 <table class="form-table">
711
712
713 <tr valign="top">
714 <th scope="row"><?php _e('Timeout', 'broken-link-checker'); ?></th>
715 <td>
716
717 <?php
718
719 printf(
720 __('%s seconds', 'broken-link-checker'),
721 sprintf(
722 '<input type="text" name="timeout" id="blc_timeout" value="%d" size="5" maxlength="3" />',
723 $this->conf->options['timeout']
724 )
725 );
726
727 ?>
728 <br/><span class="description">
729 <?php _e('Links that take longer than this to load will be marked as broken.','broken-link-checker'); ?>
730 </span>
731
732 </td>
733 </tr>
734
735
736 <tr valign="top">
737 <th scope="row">
738 <a name='lockfile_directory'></a><?php _e('Custom temporary directory', 'broken-link-checker'); ?></th>
739 <td>
740
741 <input type="text" name="custom_tmp_dir" id="custom_tmp_dir"
742 value="<?php echo htmlspecialchars( $this->conf->options['custom_tmp_dir'] ); ?>" size='53' maxlength='500'/>
743 <?php
744 if ( !empty( $this->conf->options['custom_tmp_dir'] ) ) {
745 if ( @is_dir( $this->conf->options['custom_tmp_dir'] ) ){
746 if ( blcUtility::is_writable( $this->conf->options['custom_tmp_dir'] ) ){
747 echo "<strong>", __('OK', 'broken-link-checker'), "</strong>";
748 } else {
749 echo '<span class="error">';
750 _e("Error : This directory isn't writable by PHP.", 'broken-link-checker');
751 echo '</span>';
752 }
753 } else {
754 echo '<span class="error">';
755 _e("Error : This directory doesn't exist.", 'broken-link-checker');
756 echo '</span>';
757 }
758 }
759
760 ?>
761 <br/>
762 <span class="description">
763 <?php _e('Set this field if you want the plugin to use a custom directory for its lockfiles. Otherwise, leave it blank.','broken-link-checker'); ?>
764 </span>
765
766 </td>
767 </tr>
768
769 <tr valign="top">
770 <th scope="row"><?php _e('Max. execution time', 'broken-link-checker'); ?></th>
771 <td>
772
773 <?php
774
775 printf(
776 __('%s seconds', 'broken-link-checker'),
777 sprintf(
778 '<input type="text" name="max_execution_time" id="max_execution_time" value="%d" size="5" maxlength="5" />',
779 $this->conf->options['max_execution_time']
780 )
781 );
782
783 ?>
784 <br/><span class="description">
785 <?php
786
787 _e('The plugin works by periodically creating a background worker instance that parses your posts looking for links, checks the discovered URLs, and performs other time-consuming tasks. Here you can set for how long, at most, the background instance may run each time before stopping.', 'broken-link-checker');
788
789 ?>
790 </span>
791
792 </td>
793 </tr>
794
795 </table>
796
797 <p class="submit"><input type="submit" name="submit" class='button-primary' value="<?php _e('Save Changes') ?>" /></p>
798 </form>
799 </div>
800
801 <script type='text/javascript'>
802 jQuery(function($){
803 var toggleButton = $('#blc-debug-info-toggle');
804
805 toggleButton.click(function(){
806
807 var box = $('#blc-debug-info');
808 box.toggle();
809 if( box.is(':visible') ){
810 toggleButton.text('<?php _e('Hide debug info', 'broken-link-checker'); ?>');
811 } else {
812 toggleButton.text('<?php _e('Show debug info', 'broken-link-checker'); ?>');
813 }
814
815 });
816 });
817 </script>
818 <?php
819 }
820
821 function options_page_css(){
822 ?>
823 <style type='text/css'>
824 #blc-debug-info-toggle {
825 font-size: smaller;
826 }
827
828 .blc-debug-item-ok {
829 background-color: #d7ffa2;
830 }
831 .blc-debug-item-warning {
832 background-color: #fcffa2;
833 }
834 .blc-debug-item-error {
835 background-color: #ffc4c4;
836 }
837
838 #blc-debug-info {
839 display: none;
840
841 text-align: left;
842
843 border-width: 1px;
844 border-color: gray;
845 border-style: solid;
846
847 border-spacing: 0px;
848 border-collapse: collapse;
849 }
850
851 #blc-debug-info th, #blc-debug-info td {
852 padding: 6px;
853 font-weight: normal;
854 text-shadow: none;
855
856 border-width: 1px ;
857 border-color: silver;
858 border-style: solid;
859
860 border-collapse: collapse;
861 }
862 </style>
863 <?php
864 }
865
866 /**
867 * wsBrokenLinkChecker::init_native_filters()
868 * Initializes (if necessary) and returns the list of built-in link filters
869 *
870 * @return array
871 */
872 function init_native_filters(){
873 if ( !empty($this->native_filters) ){
874 return $this->native_filters;
875 } else {
876 //Available filters by link type + the appropriate WHERE expressions
877 $this->native_filters = array(
878 'broken' => array(
879 'where_expr' => '( http_code < 200 OR http_code >= 400 OR timeout = 1 ) AND ( check_count > 0 ) AND ( http_code <> ' . BLC_CHECKING . ')',
880 'name' => __('Broken', 'broken-link-checker'),
881 'heading' => __('Broken Links', 'broken-link-checker'),
882 'heading_zero' => __('No broken links found', 'broken-link-checker')
883 ),
884 'redirects' => array(
885 'where_expr' => '( redirect_count > 0 )',
886 'name' => __('Redirects', 'broken-link-checker'),
887 'heading' => __('Redirected Links', 'broken-link-checker'),
888 'heading_zero' => __('No redirects found', 'broken-link-checker')
889 ),
890
891 'all' => array(
892 'where_expr' => '1',
893 'name' => __('All', 'broken-link-checker'),
894 'heading' => __('Detected Links', 'broken-link-checker'),
895 'heading_zero' => __('No links found (yet)', 'broken-link-checker')
896 ),
897 );
898
899 return $this->native_filters;
900 }
901 }
902
903 /**
904 * wsBrokenLinkChecker::get_custom_filters()
905 * Returns a list of user-defined link filters.
906 *
907 * @return array An array of custom filter definitions. If there are no custom filters defined returns an empty array.
908 */
909 function get_custom_filters(){
910 global $wpdb;
911
912 $filter_data = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}blc_filters ORDER BY name ASC", ARRAY_A);
913 $filters = array();
914
915 if ( !empty($filter_data) ) {
916 foreach($filter_data as $data){
917 $filters[ 'f'.$data['id'] ] = array(
918 'name' => $data['name'],
919 'params' => $data['params'],
920 'is_search' => true,
921 'heading' => ucwords($data['name']),
922 'heading_zero' => __('No links found for your query', 'broken-link-checker'),
923 );
924 }
925 }
926
927 return $filters;
928 }
929
930 function get_search_params( $filter = null ){
931 //If present, the filter's parameters may be saved either as an array or a string.
932 $params = array();
933 if ( !empty($filter) && !empty($filter['params']) ){
934 $params = $filter['params'];
935 if ( is_string( $params ) ){
936 wp_parse_str($params, $params);
937 }
938 } else {
939 //If the filter doesn't have it's own search query, use the URL parameters
940 $params = array_merge($params, $_GET);
941 }
942
943 //Only leave valid search query parameters
944 $search_param_names = array( 's_link_text', 's_link_url', 's_http_code', 's_filter', 's_link_type' );
945 $output = array();
946 foreach ( $params as $name => $value ){
947 if ( in_array($name, $search_param_names) ){
948 $output[$name] = $value;
949 }
950 }
951
952 return $output;
953 }
954
955 function links_page(){
956 global $wpdb;
957
958 $action = !empty($_POST['action'])?$_POST['action']:'';
959
960 if ( $action == 'create-custom-filter' ){
961 //Create a custom filter!
962
963 check_admin_referer( $action );
964
965 $message = '';
966 $msg_class = 'updated';
967
968 //Filter name must be set
969 if ( empty($_POST['name']) ){
970 $message = __("You must enter a filter name!", 'broken-link-checker');
971 $msg_class = 'error';
972 //Filter parameters (a search query) must also be set
973 } elseif ( empty($_POST['params']) ){
974 $message = __("Invalid search query.", 'broken-link-checker');
975 $msg_class = 'error';
976 } else {
977 //Save the new filter
978 $q = $wpdb->prepare(
979 "INSERT INTO {$wpdb->prefix}blc_filters(name, params) VALUES (%s, %s)",
980 $_POST['name'], $_POST['params']
981 );
982
983 if ( $wpdb->query($q) ){
984 //Saved
985 $message = sprintf( __('Filter "%s" created', 'broken-link-checker'), $_POST['name']);
986 //A little hack to make the filter active immediately
987 $_GET['filter_id'] = 'f' . $wpdb->insert_id;
988 } else {
989 //Error
990 $message = sprintf( __("Database error : %s", 'broken-link-checker'), $wpdb->last_error);
991 $msg_class = 'error';
992 }
993 }
994
995 echo '<div id="message" class="'.$msg_class.' fade"><p><strong>'.$message.'</strong></p></div>';
996
997 } elseif ( $action == 'delete-custom-filter' ){
998 //Delete an existing custom filter!
999
1000 check_admin_referer( $action );
1001
1002 $message = '';
1003 $msg_class = 'updated';
1004
1005 //Filter ID must be set
1006 if ( empty($_POST['filter_id']) ){
1007 $message = __("Filter ID not specified.", 'broken-link-checker');
1008 $msg_class = 'error';
1009 } else {
1010 //Remove the "f" character from the filter ID to get its database key
1011 $filter_id = intval(ltrim($_POST['filter_id'], 'f'));
1012 //Try to delete the filter
1013 $q = $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_filters WHERE id = %d", $filter_id);
1014 if ( $wpdb->query($q) ){
1015 //Success
1016 $message = __('Filter deleted', 'broken-link-checker');
1017 } else {
1018 //Either the ID is wrong or there was some other error
1019 $message = __('Database error : %s', 'broken-link-checker');
1020 $msg_class = 'error';
1021 }
1022 }
1023 echo '<div id="message" class="'.$msg_class.' fade"><p><strong>'.$message.'</strong></p></div>';
1024 }
1025
1026 //Build the filter list
1027 $filters = array_merge($this->native_filters, $this->get_custom_filters());
1028
1029 //Add the special "search" filter
1030 $filters['search'] = array(
1031 'name' => __('Search', 'broken-link-checker'),
1032 'heading' => __('Search Results', 'broken-link-checker'),
1033 'heading_zero' => __('No links found for your query', 'broken-link-checker'),
1034 'is_search' => true,
1035 'where_expr' => 1,
1036 'hidden' => true,
1037 );
1038
1039 //Calculate the number of links for each filter
1040 foreach ($filters as $filter => $data){
1041 $filters[$filter]['count'] = $this->get_links($data, 0, 0, true);
1042 }
1043
1044 //Get the selected filter (defaults to displaying broken links)
1045 $filter_id = isset($_GET['filter_id'])?$_GET['filter_id']:'broken';
1046 if ( !isset($filters[$filter_id]) ){
1047 $filter_id = 'broken';
1048 }
1049
1050 //Get the desired page number (must be > 0)
1051 $page = isset($_GET['paged'])?intval($_GET['paged']):'1';
1052 if ($page < 1) $page = 1;
1053
1054 //Links per page [1 - 200]
1055 $per_page = isset($_GET['per_page'])?intval($_GET['per_page']):'30';
1056 if ($per_page < 1){
1057 $per_page = 30;
1058 } else if ($per_page > 200){
1059 $per_page = 200;
1060 }
1061
1062 $current_filter = $filters[$filter_id];
1063 $max_pages = ceil($current_filter['count'] / $per_page);
1064
1065 //Select the required links + 1 instance per link.
1066 $links = $this->get_links( $current_filter, ( ($page-1) * $per_page ), $per_page );
1067 if ( is_null($links) && !empty($wpdb->last_error) ){
1068 printf( __('Database error : %s', 'broken-link-checker'), $wpdb->last_error);
1069 }
1070
1071 //Save the search params (if any) in a handy array for later
1072 if ( !empty($current_filter['is_search']) ){
1073 $search_params = $this->get_search_params($current_filter);
1074 } else {
1075 $search_params = array();
1076 }
1077
1078 //Display the "Discard" button when listing broken links
1079 $show_discard_button = ('broken' == $filter_id) || (!empty($search_params['s_filter']) && ($search_params['s_filter'] == 'broken'));
1080
1081 ?>
1082
1083 <script type='text/javascript'>
1084 var blc_current_filter = '<?php echo $filter_id; ?>';
1085 </script>
1086
1087 <div class="wrap">
1088 <h2><?php
1089 //Output a header matching the current filter
1090 if ( $current_filter['count'] > 0 ){
1091 echo $current_filter['heading'] . " (<span class='current-link-count'>{$current_filter[count]}</span>)";
1092 } else {
1093 echo $current_filter['heading_zero'] . "<span class='current-link-count'></span>";
1094 }
1095 ?>
1096 </h2>
1097 <ul class="subsubsub">
1098 <?php
1099 //Construct a submenu of filter types
1100 $items = array();
1101 foreach ($filters as $filter => $data){
1102 if ( !empty($data['hidden']) ) continue; //skip hidden filters
1103
1104 $class = $number_class = '';
1105
1106 if ( $filter_id == $filter ) {
1107 $class = 'class="current"';
1108 $number_class = 'current-link-count';
1109 }
1110
1111 $items[] = "<li><a href='tools.php?page=view-broken-links&filter_id=$filter' $class>
1112 {$data[name]}</a> <span class='count'>(<span class='$number_class'>{$data[count]}</span>)</span>";
1113 }
1114 echo implode(' |</li>', $items);
1115 unset($items);
1116 ?>
1117 </ul>
1118
1119 <div class="search-box">
1120
1121 <?php
1122 //If we're currently displaying search results offer the user the option to s
1123 //save the search query as a custom filter.
1124 if ( $filter_id == 'search' ){
1125 ?>
1126 <form name="save-search-query" id="custom-filter-form" action="<?php echo admin_url("tools.php?page=view-broken-links"); ?>" method="post" class="blc-inline-form">
1127 <?php wp_nonce_field('create-custom-filter'); ?>
1128 <input type="hidden" name="name" id="blc-custom-filter-name" value="" />
1129 <input type="hidden" name="params" id="blc-custom-filter-params" value="<?php echo http_build_query($search_params, null, '&'); ?>" />
1130 <input type="hidden" name="action" value="create-custom-filter" />
1131 <input type="button" value="<?php esc_attr_e( 'Save This Search As a Filter', 'broken-link-checker' ); ?>" id="blc-create-filter" class="button" />
1132 </form>
1133 <?php
1134 } elseif ( !empty($current_filter['is_search']) ){
1135 //If we're displaying a custom filter give an option to delete it.
1136 ?>
1137 <form name="save-search-query" id="custom-filter-form" action="<?php echo admin_url("tools.php?page=view-broken-links"); ?>" method="post" class="blc-inline-form">
1138 <?php wp_nonce_field('delete-custom-filter'); ?>
1139 <input type="hidden" name="filter_id" id="blc-custom-filter-id" value="<?php echo $filter_id; ?>" />
1140 <input type="hidden" name="action" value="delete-custom-filter" />
1141 <input type="submit" value="<?php esc_attr_e( 'Delete This Filter', 'broken-link-checker' ); ?>" id="blc-delete-filter" class="button" />
1142 </form>
1143 <?php
1144 }
1145 ?>
1146
1147 <input type="button" value="<?php esc_attr_e( 'Search', 'broken-link-checker' ); ?> &raquo;" id="blc-open-search-box" class="button" />
1148 </div>
1149
1150 <!-- The search dialog -->
1151 <div id='search-links-dialog' title='Search'>
1152 <form class="search-form" action="<?php echo admin_url('tools.php?page=view-broken-links'); ?>" method="get">
1153 <input type="hidden" name="page" value="view-broken-links" />
1154 <input type="hidden" name="filter_id" value="search" />
1155 <fieldset>
1156
1157 <label for="s_link_text"><?php _e('Link text', 'broken-link-checker'); ?></label>
1158 <input type="text" name="s_link_text" value="<?php if(!empty($search_params['s_link_text'])) echo esc_attr($search_params['s_link_text']); ?>" id="s_link_text" class="text ui-widget-content" />
1159
1160 <label for="s_link_url"><?php _e('URL', 'broken-link-checker'); ?></label>
1161 <input type="text" name="s_link_url" id="s_link_url" value="<?php if(!empty($search_params['s_link_url'])) echo esc_attr($search_params['s_link_url']); ?>" class="text ui-widget-content" />
1162
1163 <label for="s_http_code"><?php _e('HTTP code', 'broken-link-checker'); ?></label>
1164 <input type="text" name="s_http_code" id="s_http_code" value="<?php if(!empty($search_params['s_http_code'])) echo esc_attr($search_params['s_http_code']); ?>" class="text ui-widget-content" />
1165
1166 <label for="s_filter"><?php _e('Link status', 'broken-link-checker'); ?></label>
1167 <select name="s_filter" id="s_filter">
1168 <?php
1169 if ( !empty($search_params['s_filter']) ){
1170 $search_subfilter = $search_params['s_filter'];
1171 } else {
1172 $search_subfilter = $filter_id;
1173 }
1174
1175 foreach ($this->native_filters as $filter => $data){
1176 $selected = ($search_subfilter == $filter)?' selected="selected"':'';
1177 printf('<option value="%s"%s>%s</option>', $filter, $selected, $data['name']);
1178 }
1179 ?>
1180 </select>
1181
1182 <label for="s_link_type"><?php _e('Link type', 'broken-link-checker'); ?></label>
1183 <select name="s_link_type" id="s_link_type">
1184 <?php
1185 $link_types = array(
1186 __('Any', 'broken-link-checker') => '',
1187 __('Normal link', 'broken-link-checker') => 'link',
1188 __('Image', 'broken-link-checker') => 'image',
1189 __('Custom field', 'broken-link-checker') => 'custom_field',
1190 __('Bookmark', 'broken-link-checker') => 'blogroll',
1191 );
1192
1193 foreach ($link_types as $name => $value){
1194 $selected = ( isset($search_params['s_link_type']) && $search_params['s_link_type'] == $value )?' selected="selected"':'';
1195 printf('<option value="%s"%s>%s</option>', $value, $selected, $name);
1196 }
1197 ?>
1198 </select>
1199
1200 </fieldset>
1201
1202 <div id="blc-search-button-row">
1203 <input type="submit" value="<?php esc_attr_e( 'Search Links', 'broken-link-checker' ); ?>" id="blc-search-button" name="search_button" class="button-primary" />
1204 <input type="button" value="<?php esc_attr_e( 'Cancel', 'broken-link-checker' ); ?>" id="blc-cancel-search" class="button" />
1205 </div>
1206
1207 </form>
1208 </div>
1209
1210 <div class='tablenav'>
1211
1212 <div class="alignleft actions">
1213 <span class='description'>
1214 <?php
1215 /*
1216 //If this is a search filter, display the search query here
1217 if ( !empty($current_filter['is_search']) ){
1218 $params = $search_params;
1219 $search_query = array();
1220
1221 if ( !empty($params['s_link_text']) ){
1222 $search_query[] = sprintf('link text is like "%s"', $params['s_link_text']);
1223 }
1224 if ( !empty($params['s_link_url']) ){
1225 $search_query[] = sprintf('the URL contains "%s"', $params['s_link_url']);
1226 }
1227 if ( !empty($params['s_http_code']) ){
1228 $search_query[] = 'HTTP code matches ' . $params['s_http_code'];
1229 }
1230 if ( !empty($params['s_link_type']) ){
1231 $search_query[] = sprintf('link type is "%s"', $params['s_link_type']);
1232 }
1233
1234 echo sprintf("Showing %s links where ", $params['s_filter']) , implode(', ', $search_query);
1235 }
1236 */
1237 ?>
1238 </span>
1239 </div>
1240 <?php
1241 //Display pagination links
1242 $page_links = paginate_links( array(
1243 'base' => add_query_arg( 'paged', '%#%' ),
1244 'format' => '',
1245 'prev_text' => __('&laquo;'),
1246 'next_text' => __('&raquo;'),
1247 'total' => $max_pages,
1248 'current' => $page
1249 ));
1250
1251 if ( $page_links ) {
1252 echo '<div class="tablenav-pages">';
1253 $page_links_text = sprintf( '<span class="displaying-num">' . __( 'Displaying %s&#8211;%s of <span class="current-link-count">%s</span>', 'broken-link-checker' ) . '</span>%s',
1254 number_format_i18n( ( $page - 1 ) * $per_page + 1 ),
1255 number_format_i18n( min( $page * $per_page, count($links) ) ),
1256 number_format_i18n( $current_filter['count'] ),
1257 $page_links
1258 );
1259 echo $page_links_text;
1260 echo '</div>';
1261 }
1262 ?>
1263
1264 </div>
1265
1266 <?php
1267 if($links && (count($links)>0)){
1268 ?>
1269 <table class="widefat">
1270 <thead>
1271 <tr>
1272
1273 <th scope="col"><?php _e('Source', 'broken-link-checker'); ?>
1274 </th>
1275 <th scope="col"><?php _e('Link Text', 'broken-link-checker'); ?></th>
1276 <th scope="col"><?php _e('URL', 'broken-link-checker'); ?></th>
1277
1278 <?php if ( $show_discard_button ) { ?>
1279 <th scope="col"> </th>
1280 <?php } ?>
1281
1282 </tr>
1283 </thead>
1284 <tbody id="the-list">
1285 <?php
1286 $rowclass = ''; $rownum = 0;
1287 foreach ($links as $link) {
1288 $rownum++;
1289
1290 $rowclass = 'alternate' == $rowclass ? '' : 'alternate';
1291 $excluded = $this->is_excluded( $link['url'] );
1292 if ( $excluded ) $rowclass .= ' blc-excluded-link';
1293
1294 ?>
1295 <tr id='<?php echo "blc-row-$rownum"; ?>' class='blc-row <?php echo $rowclass; ?>'>
1296 <td class='post-title column-title'>
1297 <span class='blc-link-id' style='display:none;'><?php echo $link['link_id']; ?></span>
1298 <?php
1299 if ( ('post' == $link['source_type']) || ('custom_field' == $link['source_type']) ){
1300
1301 echo "<a class='row-title' href='post.php?action=edit&amp;post=$link[source_id]' title='",
1302 attribute_escape(__('Edit this post')),
1303 "'>{$link[post_title]}</a>";
1304
1305 //Output inline action links (copied from edit-post-rows.php)
1306 $actions = array();
1307 if ( current_user_can('edit_post', $link['source_id']) ) {
1308 $actions['edit'] = '<span class="edit"><a href="' . get_edit_post_link($link['source_id'], true) . '" title="' . attribute_escape(__('Edit this post')) . '">' . __('Edit') . '</a>';
1309 $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 this post '%s'\n 'Cancel' to stop, 'OK' to delete."), $link['post_title'] )) . "') ) { return true;}return false;\">" . __('Delete') . "</a>";
1310 }
1311 $actions['view'] = '<span class="view"><a href="' . get_permalink($link['source_id']) . '" title="' . attribute_escape(sprintf(__('View "%s"', 'broken-link-checker'), $link['post_title'])) . '" rel="permalink">' . __('View') . '</a>';
1312 echo '<div class="row-actions">';
1313 echo implode(' | </span>', $actions);
1314 echo '</div>';
1315
1316 } elseif ( 'blogroll' == $link['source_type'] ) {
1317
1318 echo "<a class='row-title' href='link.php?action=edit&amp;link_id=$link[source_id]' title='" . __('Edit this bookmark', 'broken-link-checker') . "'>{$link[link_text]}</a>";
1319
1320 //Output inline action links
1321 $actions = array();
1322 if ( current_user_can('manage_links') ) {
1323 $actions['edit'] = '<span class="edit"><a href="link.php?action=edit&amp;link_id=' . $link['source_id'] . '" title="' . attribute_escape(__('Edit this bookmark', 'broken-link-checker')) . '">' . __('Edit') . '</a>';
1324 $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>";
1325 }
1326
1327 echo '<div class="row-actions">';
1328 echo implode(' | </span>', $actions);
1329 echo '</div>';
1330
1331 } elseif ( empty($link['source_type']) ){
1332
1333 _e("[An orphaned link! This is a bug.]", 'broken-link-checker');
1334
1335 }
1336 ?>
1337 </td>
1338 <td class='blc-link-text'><?php
1339 if ( 'post' == $link['source_type'] ){
1340
1341 if ( 'link' == $link['instance_type'] ) {
1342 print strip_tags($link['link_text']);
1343 } elseif ( 'image' == $link['instance_type'] ){
1344 printf(
1345 '<img src="%s/broken-link-checker/images/image.png" class="blc-small-image" alt="%2$s" title="%2$s"> %2$s',
1346 WP_PLUGIN_URL,
1347 __('Image', 'broken-link-checker')
1348 );
1349 } else {
1350 echo '[ ??? ]';
1351 }
1352
1353 } elseif ( 'custom_field' == $link['source_type'] ){
1354
1355 printf(
1356 '<img src="%s/broken-link-checker/images/script_code.png" class="blc-small-image" title="%2$s" alt="%2$s"> ',
1357 WP_PLUGIN_URL,
1358 __('Custom field', 'broken-link-checker')
1359 );
1360 echo "<code>".$link['link_text']."</code>";
1361
1362 } elseif ( 'blogroll' == $link['source_type'] ){
1363 printf(
1364 '<img src="%s/broken-link-checker/images/link.png" class="blc-small-image" title="%2$s" alt="%2$s"> %2$s',
1365 WP_PLUGIN_URL,
1366 __('Bookmark', 'broken-link-checker')
1367 );
1368 }
1369 ?>
1370 </td>
1371 <td class='column-url'>
1372 <a href='<?php print $link['url']; ?>' target='_blank' class='blc-link-url'>
1373 <?php print $this->mytruncate($link['url']); ?></a>
1374 <input type='text' id='link-editor-<?php print $rownum; ?>'
1375 value='<?php print attribute_escape($link['url']); ?>'
1376 class='blc-link-editor' style='display:none' />
1377 <?php
1378 //Output inline action links for the link/URL
1379 $actions = array();
1380
1381 $actions['details'] = "<span class='view'><a class='blc-details-button' href='javascript:void(0)' title='". attribute_escape(__('Show more info about this link', 'broken-link-checker')) . "'>". __('Details', 'broken-link-checker') ."</a>";
1382
1383 $actions['delete'] = "<span class='delete'><a class='submitdelete blc-unlink-button' title='" . attribute_escape( __('Remove this link from all posts', 'broken-link-checker') ). "' ".
1384 "id='unlink-button-$rownum' href='javascript:void(0);'>" . __('Unlink', 'broken-link-checker') . "</a>";
1385
1386 if ( $excluded ){
1387 $actions['exclude'] = "<span class='delete'>" . __('Excluded', 'broken-link-checker');
1388 } else {
1389 $actions['exclude'] = "<span class='delete'><a class='submitdelete blc-exclude-button' title='" . attribute_escape( __('Add this URL to the exclusion list' , 'broken-link-checker') ) . "' ".
1390 "id='exclude-button-$rownum' href='javascript:void(0);'>" . __('Exclude' , 'broken-link-checker'). "</a>";
1391 }
1392
1393 $actions['edit'] = "<span class='edit'><a href='javascript:void(0)' class='blc-edit-button' title='" . attribute_escape( __('Edit link URL' , 'broken-link-checker') ) . "'>". __('Edit URL' , 'broken-link-checker') ."</a>";
1394
1395 echo '<div class="row-actions">';
1396 echo implode(' | </span>', $actions);
1397
1398 echo "<span style='display:none' class='blc-cancel-button-container'> ",
1399 "| <a href='javascript:void(0)' class='blc-cancel-button' title='". attribute_escape(__('Cancel URL editing' , 'broken-link-checker')) ."'>". __('Cancel' , 'broken-link-checker') ."</a></span>";
1400
1401 echo '</div>';
1402 ?>
1403 </td>
1404 <?php
1405 //Display the "Discard" button when listing broken links
1406 if ( $show_discard_button ) {
1407 ?>
1408 <td><a href='javascript:void(0);'
1409 id='discard_button-<?php print $rownum; ?>'
1410 class='blc-discard-button'
1411 title='<?php
1412 echo attribute_escape(
1413 __('Remove this link from the list of broken links and mark it as valid', 'broken-link-checker')
1414 );
1415 ?>'><?php _e('Discard', 'broken-link-checker'); ?></a>
1416 </td>
1417 <?php } ?>
1418 </tr>
1419 <!-- Link details -->
1420 <tr id='<?php print "link-details-$rownum"; ?>' style='display:none;' class='blc-link-details'>
1421 <td colspan='4'><?php $this->link_details_row($link); ?></td>
1422 </tr><?php
1423 }
1424 ?></tbody></table><?php
1425
1426 //Also display pagination links at the bottom
1427 if ( $page_links ) {
1428 echo '<div class="tablenav"><div class="tablenav-pages">';
1429 $page_links_text = sprintf( '<span class="displaying-num">' . __( 'Displaying %s&#8211;%s of <span class="current-link-count">%s</span>', 'broken-link-checker' ) . '</span>%s',
1430 number_format_i18n( ( $page - 1 ) * $per_page + 1 ),
1431 number_format_i18n( min( $page * $per_page, count($links) ) ),
1432 number_format_i18n( $current_filter['count'] ),
1433 $page_links
1434 );
1435 echo $page_links_text;
1436 echo '</div></div>';
1437 }
1438 };
1439 ?>
1440 <?php $this->links_page_js(); ?>
1441 </div>
1442 <?php
1443 }
1444
1445 function links_page_js(){
1446 ?>
1447 <script type='text/javascript'>
1448
1449 function alterLinkCounter(factor){
1450 cnt = parseInt(jQuery('.current-link-count').eq(0).html());
1451 cnt = cnt + factor;
1452 jQuery('.current-link-count').html(cnt);
1453 }
1454
1455 jQuery(function($){
1456
1457 //The discard button - manually mark the link as valid. The link will be checked again later.
1458 $(".blc-discard-button").click(function () {
1459 var me = this;
1460 $(me).html('<?php echo js_escape(__('Wait...', 'broken-link-checker')); ?>');
1461
1462 var link_id = $(me).parents('.blc-row').find('.blc-link-id').html();
1463
1464 $.post(
1465 "<?php echo admin_url('admin-ajax.php'); ?>",
1466 {
1467 'action' : 'blc_discard',
1468 'link_id' : link_id
1469 },
1470 function (data, textStatus){
1471 if (data == 'OK'){
1472 var master = $(me).parents('.blc-row');
1473 var details = master.next('.blc-link-details');
1474
1475 details.hide();
1476 //Flash the main row green to indicate success, then hide it.
1477 var oldColor = master.css('background-color');
1478 master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300, function(){
1479 master.hide();
1480 });
1481
1482 alterLinkCounter(-1);
1483 } else {
1484 $(me).html('<?php echo js_escape(__('Discard' , 'broken-link-checker')); ?>');
1485 alert(data);
1486 }
1487 }
1488 );
1489 });
1490
1491 //The details button - display/hide detailed info about a link
1492 $(".blc-details-button, .blc-link-text").click(function () {
1493 $(this).parents('.blc-row').next('.blc-link-details').toggle();
1494 });
1495
1496 //The edit button - edit/save the link's URL
1497 $(".blc-edit-button").click(function () {
1498 var edit_button = $(this);
1499 var master = $(edit_button).parents('.blc-row');
1500 var editor = $(master).find('.blc-link-editor');
1501 var url_el = $(master).find('.blc-link-url');
1502 var cancel_button_container = $(master).find('.blc-cancel-button-container');
1503
1504 //Find the current/original URL
1505 var orig_url = url_el.attr('href');
1506 //Find the link ID
1507 var link_id = $(master).find('.blc-link-id').html();
1508
1509 if ( !$(editor).is(':visible') ){
1510 //Begin editing
1511 url_el.hide();
1512 //Reset the edit box to the actual URL value in case the user has already tried and failed to edit this link.
1513 editor.val( url_el.attr('href') );
1514 editor.show();
1515 cancel_button_container.show();
1516 editor.focus();
1517 editor.select();
1518 edit_button.html('<?php echo js_escape(__('Save URL' , 'broken-link-checker')); ?>');
1519 } else {
1520 editor.hide();
1521 cancel_button_container.hide();
1522 url_el.show();
1523
1524 new_url = editor.val();
1525
1526 if (new_url != orig_url){
1527 //Save the changed link
1528 url_el.html('<?php echo js_escape(__('Saving changes...' , 'broken-link-checker')); ?>');
1529
1530 $.getJSON(
1531 "<?php echo admin_url('admin-ajax.php'); ?>",
1532 {
1533 'action' : 'blc_edit',
1534 'link_id' : link_id,
1535 'new_url' : new_url
1536 },
1537 function (data, textStatus){
1538 var display_url = '';
1539
1540 if ( data && (typeof(data['error']) != 'undefined') ){
1541 //data.error is an error message
1542 alert(data.error);
1543 display_url = orig_url;
1544 } else {
1545 //data contains info about the performed edit
1546 if ( data.cnt_okay > 0 ){
1547 display_url = new_url;
1548
1549 url_el.attr('href', new_url);
1550
1551 if ( data.cnt_error > 0 ){
1552 //TODO: Interationalize this error message
1553 var msg = "The link was successfully modifed.";
1554 msg = msg + "\nHowever, "+data.cnt_error+" instances couldn't be edited and still point to the old URL."
1555 alert(msg);
1556 } else {
1557 //Flash the row green to indicate success
1558 var oldColor = master.css('background-color');
1559 master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300);
1560
1561 //Save the new ID
1562 master.find('.blc-link-id').html(data.new_link_id);
1563 //Load up the new link info (so sue me)
1564 master.next('.blc-link-details').find('td').html('<center><?php echo js_escape(__('Loading...' , 'broken-link-checker')); ?></center>').load(
1565 "<?php echo admin_url('admin-ajax.php'); ?>",
1566 {
1567 'action' : 'blc_link_details',
1568 'link_id' : data.new_link_id
1569 }
1570 );
1571 }
1572 } else {
1573 //TODO: Internationalize this error message
1574 alert("Something went wrong. The plugin failed to edit "+
1575 data.cnt_error + ' instance(s) of this link.');
1576
1577 display_url = orig_url;
1578 }
1579 };
1580
1581 //Shorten the displayed URL if it's > 50 characters
1582 if ( display_url.length > 50 ){
1583 display_url = display_url.substr(0, 47) + '...';
1584 }
1585 url_el.html(display_url);
1586 }
1587 );
1588
1589 } else {
1590 //It's the same URL, so do nothing.
1591 }
1592 edit_button.html('<?php echo js_escape(__('Edit URL', 'broken-link-checker')); ?>');
1593 }
1594 });
1595
1596 //Let the user use Enter and Esc as shortcuts for "Save URL" and "Cancel"
1597 $('input.blc-link-editor').keypress(function (e) {
1598 if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
1599 $(this).parents('.blc-row').find('.blc-edit-button').click();
1600 return false;
1601 } else if ((e.which && e.which == 27) || (e.keyCode && e.keyCode == 27)) {
1602 $(this).parents('.blc-row').find('.blc-cancel-button').click();
1603 return false;
1604 } else {
1605 return true;
1606 }
1607 });
1608
1609 $(".blc-cancel-button").click(function () {
1610 var master = $(this).parents('.blc-row');
1611 var url_el = $(master).find('.blc-link-url');
1612
1613 //Hide the cancel button
1614 $(this).parent().hide();
1615 //Show the un-editable URL again
1616 url_el.show();
1617 //reset and hide the editor
1618 master.find('.blc-link-editor').hide().val(url_el.attr('href'));
1619 //Set the edit button to say "Edit URL"
1620 master.find('.blc-edit-button').html('<?php echo js_escape(__('Edit URL' , 'broken-link-checker')); ?>');
1621 });
1622
1623 //The unlink button - remove the link/image from all posts, custom fields, etc.
1624 $(".blc-unlink-button").click(function () {
1625 var me = this;
1626 var master = $(me).parents('.blc-row');
1627 $(me).html('<?php echo js_escape(__('Wait...' , 'broken-link-checker')); ?>');
1628
1629 var link_id = $(me).parents('.blc-row').find('.blc-link-id').html();
1630
1631 $.post(
1632 "<?php echo admin_url('admin-ajax.php'); ?>",
1633 {
1634 'action' : 'blc_unlink',
1635 'link_id' : link_id
1636 },
1637 function (data, textStatus){
1638 eval('data = ' + data);
1639
1640 if ( data && ( typeof(data['ok']) != 'undefined') ){
1641 //Hide the details
1642 master.next('.blc-link-details').hide();
1643 //Flash the main row green to indicate success, then hide it.
1644 var oldColor = master.css('background-color');
1645 master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300, function(){
1646 master.hide();
1647 });
1648
1649 alterLinkCounter(-1);
1650 } else {
1651 $(me).html('<?php echo js_escape(__('Unlink' , 'broken-link-checker')); ?>');
1652 //Show the error message
1653 alert(data.error);
1654 }
1655 }
1656 );
1657 });
1658
1659 //The exclude button - Add this link to the exclusion list
1660 $(".blc-exclude-button").click(function () {
1661 var me = this;
1662 var master = $(me).parents('.blc-row');
1663 var details = master.next('.blc-link-details');
1664 $(me).html('<?php echo js_escape(__('Wait...' , 'broken-link-checker')); ?>');
1665
1666 var link_id = $(me).parents('.blc-row').find('.blc-link-id').html();
1667
1668 $.post(
1669 "<?php echo admin_url('admin-ajax.php'); ?>",
1670 {
1671 'action' : 'blc_exclude_link',
1672 'link_id' : link_id
1673 },
1674 function (data, textStatus){
1675 eval('data = ' + data);
1676
1677 if ( data && ( typeof(data['ok']) != 'undefined' ) ){
1678
1679 if ( 'broken' == blc_current_filter ){
1680 //Flash the row green to indicate success, then hide it.
1681 $(me).replaceWith('<?php echo js_escape(__('Excluded' , 'broken-link-checker')); ?>');
1682 master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: '#E2E2E2' }, 200, function(){
1683 details.hide();
1684 master.hide();
1685 alterLinkCounter(-1);
1686 });
1687 master.addClass('blc-excluded-link');
1688 } else {
1689 //Flash the row green to indicate success and fade to the "excluded link" color
1690 master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: '#E2E2E2' }, 300);
1691 master.addClass('blc-excluded-link');
1692 $(me).replaceWith('<?php echo js_escape(__('Excluded' , 'broken-link-checker')); ?>');
1693 }
1694 } else {
1695 $(me).html('<?php echo js_escape(__('Exclude' , 'broken-link-checker')); ?>');
1696 alert(data.error);
1697 }
1698 }
1699 );
1700 });
1701
1702 //--------------------------------------------
1703 //The search box(es)
1704 //--------------------------------------------
1705
1706 var searchForm = $('#search-links-dialog');
1707
1708 searchForm.dialog({
1709 autoOpen : false,
1710 dialogClass : 'blc-search-container',
1711 resizable: false,
1712 });
1713
1714 $('#blc-open-search-box').click(function(){
1715 if ( searchForm.dialog('isOpen') ){
1716 searchForm.dialog('close');
1717 } else {
1718 var button_position = $('#blc-open-search-box').offset();
1719 var button_height = $('#blc-open-search-box').outerHeight(true);
1720 var button_width = $('#blc-open-search-box').outerWidth(true);
1721
1722 var dialog_width = searchForm.dialog('option', 'width');
1723
1724 searchForm.dialog('option', 'position',
1725 [
1726 button_position.left - dialog_width + button_width/2,
1727 button_position.top + button_height + 1 - $(document).scrollTop()
1728 ]
1729 );
1730 searchForm.dialog('open');
1731 }
1732 });
1733
1734 $('#blc-cancel-search').click(function(){
1735 searchForm.dialog('close');
1736 });
1737
1738 //The "Save This Search Query" button creates a new custom filter based on the current search
1739 $('#blc-create-filter').click(function(){
1740 var filter_name = prompt("<?php echo js_escape(__("Enter a name for the new custom filter", 'broken-link-checker')); ?>", "");
1741 if ( filter_name ){
1742 $('#blc-custom-filter-name').val(filter_name);
1743 $('#custom-filter-form').submit();
1744 }
1745 });
1746
1747 //Display a confirmation dialog when the user clicks the "Delete This Filter" button
1748 $('#blc-delete-filter').click(function(){
1749 if ( confirm('<?php
1750 echo js_escape(
1751 __("You are about to delete the current filter.\n'Cancel' to stop, 'OK' to delete", 'broken-link-checker')
1752 );
1753 ?>') ){
1754 return true;
1755 } else {
1756 return false;
1757 }
1758 });
1759
1760 });
1761
1762 </script>
1763 <?php
1764 }
1765
1766 function links_page_css(){
1767 ?>
1768 <style type='text/css'>
1769 .blc-link-editor {
1770 font-size: 1em;
1771 width: 95%;
1772 }
1773
1774 .blc-excluded-link {
1775 background-color: #E2E2E2;
1776 }
1777
1778 .blc-small-image {
1779 display : block;
1780 float: left;
1781 padding-top: 2px;
1782 margin-right: 3px;
1783 }
1784
1785 .blc-search-container {
1786 background : white !important;
1787 border: 3px solid #EEEEEE;
1788 padding: 12px;
1789 }
1790
1791 .blc-search-container .ui-dialog-titlebar {
1792 display: none;
1793 margin: 0px;
1794 }
1795
1796 #search-links-dialog {
1797 display: none;
1798 }
1799
1800 #search-links-dialog label, #search-links-dialog input.text, #search-links-dialog select { display:block; }
1801 #search-links-dialog input.text { margin-bottom:12px; width:95%; padding: .4em; }
1802 #search-links-dialog select { margin-bottom:12px; padding: .4em; }
1803 #search-links-dialog fieldset { padding:0; border:0; margin-top:25px; }
1804
1805 #blc-search-button-row {
1806 text-align: center;
1807 }
1808
1809 #blc-search-button-row input {
1810 padding: 0.4em;
1811 margin-left: 8px;
1812 margin-right: 8px;
1813 margin-top: 8px;
1814 }
1815
1816 .blc-inline-form {
1817 display: inline;
1818 }
1819
1820 div.search-box{
1821 float: right;
1822 margin-top: -5px;
1823 margin-right: 0pt;
1824 margin-bottom: 0pt;
1825 margin-left: 0pt;
1826 }
1827 </style>
1828 <?php
1829 }
1830
1831 function link_details_row($link){
1832 ?>
1833 <span id='post_date_full' style='display:none;'><?php
1834
1835 print $link['post_date'];
1836
1837 ?></span>
1838 <span id='check_date_full' style='display:none;'><?php
1839 print $link['last_check'];
1840 ?></span>
1841 <ol style='list-style-type: none; width: 50%; float: right;'>
1842 <li><strong><?php _e('Log', 'broken-link-checker'); ?> :</strong>
1843 <span class='blc_log'><?php
1844 print nl2br($link['log']);
1845 ?></span></li>
1846 </ol>
1847
1848 <ol style='list-style-type: none; padding-left: 2px;'>
1849 <?php if ( !empty($link['post_date']) ) { ?>
1850 <li><strong><?php _e('Post published on', 'broken-link-checker'); ?> :</strong>
1851 <span class='post_date'><?php
1852 echo date_i18n(get_option('date_format'),strtotime($link['post_date']));
1853 ?></span></li>
1854 <?php } ?>
1855 <li><strong><?php _e('Link last checked', 'broken-link-checker'); ?> :</strong>
1856 <span class='check_date'><?php
1857 $last_check = strtotime($link['last_check']);
1858 if ( $last_check < strtotime('-10 years') ){
1859 _e('Never', 'broken-link-checker');
1860 } else {
1861 echo date_i18n(get_option('date_format'), $last_check);
1862 }
1863 ?></span></li>
1864
1865 <li><strong><?php _e('HTTP code', 'broken-link-checker'); ?> :</strong>
1866 <span class='http_code'><?php
1867 print $link['http_code'];
1868 ?></span></li>
1869
1870 <li><strong><?php _e('Response time', 'broken-link-checker'); ?> :</strong>
1871 <span class='request_duration'><?php
1872 printf( __('%2.3f seconds', 'broken-link-checker'), $link['request_duration']);
1873 ?></span></li>
1874
1875 <li><strong><?php _e('Final URL', 'broken-link-checker'); ?> :</strong>
1876 <span class='final_url'><?php
1877 print $link['final_url'];
1878 ?></span></li>
1879
1880 <li><strong><?php _e('Redirect count', 'broken-link-checker'); ?> :</strong>
1881 <span class='redirect_count'><?php
1882 print $link['redirect_count'];
1883 ?></span></li>
1884
1885 <li><strong><?php _e('Instance count', 'broken-link-checker'); ?> :</strong>
1886 <span class='instance_count'><?php
1887 print $link['instance_count'];
1888 ?></span></li>
1889
1890 <?php if ( intval( $link['check_count'] ) > 0 ){ ?>
1891 <li><br/>
1892 <?php
1893 printf(
1894 _n('This link has failed %d time.', 'This link has failed %d times.', $link['check_count'], 'broken-link-checker'),
1895 $link['check_count']
1896 );
1897 ?>
1898 </li>
1899 <?php } ?>
1900 </ol>
1901 <?php
1902 }
1903
1904 /**
1905 * ws_broken_link_checker::cleanup_links()
1906 * Remove orphaned links that have no corresponding instances
1907 *
1908 * @param int $link_id (optional) Only check this link
1909 * @return bool
1910 */
1911 function cleanup_links( $link_id = null ){
1912 global $wpdb;
1913
1914 $q = "DELETE FROM {$wpdb->prefix}blc_links
1915 USING {$wpdb->prefix}blc_links LEFT JOIN {$wpdb->prefix}blc_instances
1916 ON {$wpdb->prefix}blc_instances.link_id = {$wpdb->prefix}blc_links.link_id
1917 WHERE
1918 {$wpdb->prefix}blc_instances.link_id IS NULL";
1919
1920 if ( $link_id !==null ) {
1921 $q .= " AND {$wpdb->prefix}blc_links.link_id = " . intval( $link_id );
1922 }
1923
1924 return $wpdb->query( $q );
1925 }
1926
1927 /**
1928 * ws_broken_link_checker::cleanup_instances()
1929 * Remove instances that reference invalid posts or bookmarks
1930 *
1931 * @return bool
1932 */
1933 function cleanup_instances(){
1934 global $wpdb;
1935
1936 //Delete all instances that reference non-existent posts
1937 $q = "DELETE FROM {$wpdb->prefix}blc_instances
1938 USING {$wpdb->prefix}blc_instances LEFT JOIN {$wpdb->posts} ON {$wpdb->prefix}blc_instances.source_id = {$wpdb->posts}.ID
1939 WHERE
1940 {$wpdb->posts}.ID IS NULL
1941 AND ( ( {$wpdb->prefix}blc_instances.source_type = 'post' ) OR ( {$wpdb->prefix}blc_instances.source_type = 'custom_field' ) )";
1942 $rez = $wpdb->query($q);
1943
1944 //Delete all instances that reference non-existent bookmarks
1945 $q = "DELETE FROM {$wpdb->prefix}blc_instances
1946 USING {$wpdb->prefix}blc_instances LEFT JOIN {$wpdb->links} ON {$wpdb->prefix}blc_instances.source_id = {$wpdb->links}.link_id
1947 WHERE
1948 {$wpdb->links}.link_id IS NULL
1949 AND {$wpdb->prefix}blc_instances.source_type = 'blogroll' ";
1950 $rez2 = $wpdb->query($q);
1951
1952 return $rez and $rez2;
1953 }
1954
1955 /**
1956 * ws_broken_link_checker::parse_post()
1957 * Parse a post for links and save them to the DB.
1958 *
1959 * @param string $content Post content
1960 * @param int $post_id Post ID
1961 * @return void
1962 */
1963 function parse_post($content, $post_id){
1964 //remove all <code></code> blocks first
1965 $content = preg_replace('/<code[^>]*>.+?<\/code>/si', ' ', $content);
1966 //Get the post permalink - it's used to resolve relative URLs
1967 $permalink = get_permalink( $post_id );
1968
1969 //Find links
1970 if(preg_match_all(blcUtility::link_pattern(), $content, $matches, PREG_SET_ORDER)){
1971 foreach($matches as $link){
1972 $url = $link[3];
1973 $text = strip_tags( $link[5] );
1974 //FB::log($url, "Found link");
1975
1976 $url = blcUtility::normalize_url($url, $permalink);
1977 //Skip invalid links
1978 if ( !$url || (strlen($url)<6) ) continue;
1979
1980 //Create or load the link
1981 $link_obj = new blcLink($url);
1982 //Add & save a new instance
1983 $link_obj->add_instance($post_id, 'post', $text, 'link');
1984 }
1985 };
1986
1987 //Find images (<img src=...>)
1988 if(preg_match_all(blcUtility::img_pattern(), $content, $matches, PREG_SET_ORDER)){
1989 foreach($matches as $img){
1990 $url = $img[3];
1991 //FB::log($url, "Found image");
1992
1993 $url = blcUtility::normalize_url($url, $permalink);
1994 if ( !$url || (strlen($url)<6) ) continue; //skip invalid URLs
1995
1996 //Create or load the link
1997 $link = new blcLink($url);
1998 //Add & save a new image instance
1999 $link->add_instance($post_id, 'post', '', 'image');
2000 }
2001 };
2002 }
2003
2004 /**
2005 * ws_broken_link_checker::parse_post_meta()
2006 * Parse a post's custom fields for links and save them in the DB.
2007 *
2008 * @param id $post_id
2009 * @return void
2010 */
2011 function parse_post_meta($post_id){
2012 //Get all custom fields of this post
2013 $custom_fields = get_post_custom( $post_id );
2014 //FB::log($custom_fields, "Custom fields loaded");
2015
2016 //Parse the enabled fields
2017 foreach( $this->conf->options['custom_fields'] as $field ){
2018 if ( !isset($custom_fields[$field]) ) continue;
2019
2020 //FB::log($field, "Parsing field");
2021
2022 $values = $custom_fields[$field];
2023 if ( !is_array( $values ) ) $values = array($values);
2024
2025 foreach( $values as $value ){
2026
2027 //If this is a multiline field take the first line (workaround for the enclosure field).
2028 $value = trim( array_shift( explode("\n", $value) ) );
2029
2030 //Attempt to parse the $value as URL
2031 $url = blcUtility::normalize_url($value);
2032 if ( empty($url) ){
2033 //FB::warn($value, "Invalid URL in custom field ".$field);
2034 continue;
2035 }
2036
2037 //FB::log($url, "Found URL");
2038 $link = new blcLink( $url );
2039 //FB::log($link, 'Created/loaded link');
2040 $inst = $link->add_instance( $post_id, 'custom_field', $field, 'link' );
2041 //FB::log($inst, 'Created instance');
2042 }
2043 }
2044
2045 }
2046
2047 function parse_blogroll_link( $the_link ){
2048 //FB::log($the_link, "Parsing blogroll link");
2049
2050 //Attempt to parse the URL
2051 $url = blcUtility::normalize_url( $the_link['link_url'] );
2052 if ( empty($url) ){
2053 //FB::warn( $the_link['link_url'], "Invalid URL in for a blogroll link".$the_link['link_name'] );
2054 return false;
2055 }
2056
2057 //FB::log($url, "Found URL");
2058 $link = new blcLink( $url );
2059 return $link->add_instance( $the_link['link_id'], 'blogroll', $the_link['link_name'], 'link' );
2060 }
2061
2062 function start_timer(){
2063 $this->execution_start_time = microtime_float();
2064 }
2065
2066 function execution_time(){
2067 return microtime_float() - $this->execution_start_time;
2068 }
2069
2070 /**
2071 * ws_broken_link_checker::work()
2072 * The main worker function that does all kinds of things.
2073 *
2074 * @return void
2075 */
2076 function work(){
2077 global $wpdb;
2078
2079 if ( !$this->acquire_lock() ){
2080 //FB::warn("Another instance of BLC is already working. Stop.");
2081 return false;
2082 }
2083
2084 $this->start_timer();
2085
2086 $max_execution_time = $this->conf->options['max_execution_time'];
2087
2088 /*****************************************
2089 Preparation
2090 ******************************************/
2091 // Check for safe mode
2092 if( blcUtility::is_safe_mode() ){
2093 // Do it the safe mode way - obey the existing max_execution_time setting
2094 $t = ini_get('max_execution_time');
2095 if ($t && ($t < $max_execution_time))
2096 $max_execution_time = $t-1;
2097 } else {
2098 // Do it the regular way
2099 @set_time_limit( $max_execution_time * 2 ); //x2 should be plenty, running any longer would mean a glitch.
2100 }
2101
2102 //Don't stop the script when the connection is closed
2103 ignore_user_abort( true );
2104
2105 //Close the connection as per http://www.php.net/manual/en/features.connection-handling.php#71172
2106 //This reduces resource usage and may solve the mysterious slowdowns certain users have
2107 //encountered when activating the plugin.
2108 //(Comment out when debugging or you won't get the FirePHP output)
2109 ob_end_clean();
2110 header("Connection: close");
2111 ob_start();
2112 echo ('Connection closed'); //This could be anything
2113 $size = ob_get_length();
2114 header("Content-Length: $size");
2115 ob_end_flush(); // Strange behaviour, will not work
2116 flush(); // Unless both are called !
2117
2118 $check_threshold = date('Y-m-d H:i:s', strtotime('-'.$this->conf->options['check_threshold'].' hours'));
2119 $recheck_threshold = date('Y-m-d H:i:s', strtotime('-20 minutes'));
2120
2121 $orphans_possible = false;
2122
2123 $still_need_resynch = $this->conf->options['need_resynch'];
2124
2125 /*****************************************
2126 Parse posts and bookmarks
2127 ******************************************/
2128
2129 if ( $this->conf->options['need_resynch'] ) {
2130
2131 //FB::log("Looking for posts and bookmarks that need parsing...");
2132
2133 $tsynch = $wpdb->prefix.'blc_synch';
2134 $tposts = $wpdb->posts;
2135 $tlinks = $wpdb->links;
2136
2137 $synch_q = "SELECT $tsynch.source_id, $tsynch.source_type, $tposts.post_content, $tlinks.link_url, $tlinks.link_id, $tlinks.link_name
2138
2139 FROM
2140 $tsynch LEFT JOIN $tposts
2141 ON ($tposts.id = $tsynch.source_id AND $tsynch.source_type='post')
2142 LEFT JOIN $tlinks
2143 ON ($tlinks.link_id = $tsynch.source_id AND $tsynch.source_type='blogroll')
2144
2145 WHERE
2146 $tsynch.synched = 0
2147
2148 LIMIT 50";
2149
2150 while ( $rows = $wpdb->get_results($synch_q, ARRAY_A) ) {
2151
2152 //FB::log("Found ".count($rows)." items to analyze.");
2153
2154 foreach ($rows as $row) {
2155
2156 if ( $row['source_type'] == 'post' ){
2157
2158 //FB::log("Parsing post ".$row['source_id']);
2159
2160 //Remove instances associated with this post
2161 $q = "DELETE FROM {$wpdb->prefix}blc_instances
2162 WHERE source_id = %d AND (source_type = 'post' OR source_type='custom_field')";
2163 $q = $wpdb->prepare($q, intval($row['source_id']));
2164
2165 //FB::log($q, "Executing query");
2166
2167 if ( $wpdb->query( $q ) === false ){
2168 //FB::error($wpdb->last_error, "Database error");
2169 }
2170
2171 //Gather links and images from the post
2172 $this->parse_post( $row['post_content'], $row['source_id'] );
2173 //Gather links from custom fields
2174 $this->parse_post_meta( $row['source_id'] );
2175
2176 //Some link records might be orhpaned now
2177 $orphans_possible = true;
2178
2179 } else {
2180
2181 //FB::log("Parsing bookmark ".$row['source_id']);
2182
2183 //Remove instances associated with this bookmark
2184 $q = "DELETE FROM {$wpdb->prefix}blc_instances
2185 WHERE source_id = %d AND source_type = 'blogroll'";
2186 $q = $wpdb->prepare($q, intval($row['source_id']));
2187 //FB::log($q, "Executing query");
2188
2189 if ( $wpdb->query( $q ) === false ){
2190 //FB::error($wpdb->last_error, "Database error");
2191 }
2192
2193 //(Re)add the instance and link
2194 $this->parse_blogroll_link( $row );
2195
2196 //Some link records might be orhpaned now
2197 $orphans_possible = true;
2198
2199 }
2200
2201 //Update the table to indicate the item has been parsed
2202 $this->mark_synched( $row['source_id'], $row['source_type'] );
2203
2204 //Check if we still have some execution time left
2205 if( $this->execution_time() > $max_execution_time ){
2206 //FB::log('The alloted execution time has run out');
2207 $this->cleanup_links();
2208 $this->release_lock();
2209 return;
2210 }
2211
2212 }
2213
2214 }
2215
2216 //FB::log('No unparsed items found.');
2217 $still_need_resynch = false;
2218
2219 if ( $wpdb->last_error ){
2220 //FB::error($wpdb->last_error, "Database error");
2221 }
2222
2223 } else {
2224 //FB::log('Resynch not required.');
2225 }
2226
2227 /******************************************
2228 Resynch done?
2229 *******************************************/
2230 if ( $this->conf->options['need_resynch'] && !$still_need_resynch ){
2231 $this->conf->options['need_resynch'] = $still_need_resynch;
2232 $this->conf->save_options();
2233 }
2234
2235 /******************************************
2236 Remove orphaned links
2237 *******************************************/
2238
2239 if ( $orphans_possible ) {
2240 //FB::log('Cleaning up the link table.');
2241 $this->cleanup_links();
2242 }
2243
2244 //Check if we still have some execution time left
2245 if( $this->execution_time() > $max_execution_time ){
2246 //FB::log('The alloted execution time has run out');
2247 $this->release_lock();
2248 return;
2249 }
2250
2251 /*****************************************
2252 Check links
2253 ******************************************/
2254 //FB::log('Looking for links to check (threshold : '.$check_threshold.')...');
2255
2256 //Select some links that haven't been checked for a long time or
2257 //that are broken and need to be re-checked again.
2258
2259 //Note : This is a slow query, but AFAIK there is no way to speed it up.
2260 //I could put an index on last_check, but that value is almost certainly unique
2261 //for each row so it wouldn't be much better than a full table scan.
2262 $q = "SELECT *, ( last_check < %s ) AS meets_check_threshold
2263 FROM {$wpdb->prefix}blc_links
2264 WHERE
2265 ( last_check < %s )
2266 OR
2267 (
2268 ( http_code >= 400 OR http_code < 200 OR timeout = 1)
2269 AND check_count < %d
2270 AND check_count > 0
2271 AND last_check < %s
2272 )
2273 ORDER BY last_check ASC
2274 LIMIT 50";
2275 $link_q = $wpdb->prepare($q, $check_threshold, $check_threshold, $this->conf->options['recheck_count'], $recheck_threshold);
2276 //FB::log($link_q);
2277
2278 while ( $links = $wpdb->get_results($link_q, ARRAY_A) ){
2279
2280 //some unchecked links found
2281 //FB::log("Checking ".count($links)." link(s)");
2282
2283 foreach ($links as $link) {
2284 $link_obj = new blcLink($link);
2285
2286 //Does this link need to be checked?
2287 if ( !$this->is_excluded( $link['url'] ) ) {
2288 //Yes, do it
2289 //FB::log("Checking link {$link[link_id]}");
2290 $link_obj->check( $this->conf->options['timeout'] );
2291 $link_obj->save();
2292 } else {
2293 //Nope, mark it as already checked.
2294 //FB::info("The URL {$link_obj->url} is excluded, marking link {$link_obj->link_id} as already checked.");
2295 $link_obj->last_check = date('Y-m-d H:i:s');
2296 $link_obj->http_code = 200; //Use a fake code so that the link doesn't show up in queries looking for broken links.
2297 $link_obj->timeout = false;
2298 $link_obj->request_duration = 0;
2299 $link_obj->log = __("This link wasn't checked because a matching keyword was found on your exclusion list.", 'broken-link-checker');
2300 $link_obj->save();
2301 }
2302
2303 //Check if we still have some execution time left
2304 if( $this->execution_time() > $max_execution_time ){
2305 //FB::log('The alloted execution time has run out');
2306 $this->release_lock();
2307 return;
2308 }
2309 }
2310 }
2311 //FB::log('No links need to be checked right now.');
2312
2313 $this->release_lock();
2314 //FB::log('All done.');
2315 }
2316
2317 function ajax_full_status( ){
2318 $status = $this->get_status();
2319 $text = $this->status_text( $status );
2320
2321 echo json_encode( array(
2322 'text' => $text,
2323 'status' => $status,
2324 ) );
2325
2326 die();
2327 }
2328
2329 /**
2330 * ws_broken_link_checker::status_text()
2331 * Generates a status message based on the status info in $status
2332 *
2333 * @param array $status
2334 * @return string
2335 */
2336 function status_text( $status ){
2337 $text = '';
2338
2339 if( $status['broken_links'] > 0 ){
2340 $text .= sprintf(
2341 "<a href='%s' title='" . __('View broken links', 'broken-link-checker') . "'><strong>".
2342 _n('Found %d broken link', 'Found %d broken links', $status['broken_links'], 'broken-link-checker') .
2343 "</strong></a>",
2344 admin_url('tools.php?page=view-broken-links'),
2345 $status['broken_links']
2346 );
2347 } else {
2348 $text .= __("No broken links found.", 'broken-link-checker');
2349 }
2350
2351 $text .= "<br/>";
2352
2353 if( $status['unchecked_links'] > 0) {
2354 $text .= sprintf(
2355 _n('%d URL in the work queue', '%d URLs in the work queue', $status['unchecked_links'], 'broken-link-checker'),
2356 $status['unchecked_links'] );
2357 } else {
2358 $text .= __("No URLs in the work queue.", 'broken-link-checker');
2359 }
2360
2361 $text .= "<br/>";
2362 if ( $status['known_links'] > 0 ){
2363 $text .= sprintf(
2364 _n('Detected %d unique URL', 'Detected %d unique URLs', $status['known_links'], 'broken-link-checker') .
2365 ' ' . _n('in %d link', 'in %d links', $status['known_instances'], 'broken-link-checker'),
2366 $status['known_links'],
2367 $status['known_instances']
2368 );
2369 if ($this->conf->options['need_resynch']){
2370 $text .= ' ' . __('and still searching...', 'broken-link-checker');
2371 } else {
2372 $text .= '.';
2373 }
2374 } else {
2375 if ($this->conf->options['need_resynch']){
2376 $text .= __('Searching your blog for links...', 'broken-link-checker');
2377 } else {
2378 $text .= __('No links detected.', 'broken-link-checker');
2379 }
2380 }
2381
2382 return $text;
2383 }
2384
2385 function ajax_dashboard_status(){
2386 //Just display the full status.
2387 $this->ajax_full_status( );
2388 }
2389
2390 /**
2391 * ws_broken_link_checker::get_status()
2392 * Returns an array with various status information about the plugin. Array key reference:
2393 * check_threshold - date/time; links checked before this threshold should be checked again.
2394 * recheck_threshold - date/time; broken links checked before this threshold should be re-checked.
2395 * known_links - the number of detected unique URLs (a misleading name, yes).
2396 * known_instances - the number of detected link instances, i.e. actual link elements in posts and other places.
2397 * broken_links - the number of detected broken links.
2398 * unchecked_links - the number of URLs that need to be checked ASAP; based on check_threshold and recheck_threshold.
2399 *
2400 * @return array
2401 */
2402 function get_status(){
2403 global $wpdb;
2404
2405 $check_threshold=date('Y-m-d H:i:s', strtotime('-'.$this->conf->options['check_threshold'].' hours'));
2406 $recheck_threshold=date('Y-m-d H:i:s', strtotime('-20 minutes'));
2407
2408 $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links WHERE 1";
2409 $known_links = $wpdb->get_var($q);
2410
2411 $q = "SELECT count(*) FROM {$wpdb->prefix}blc_instances WHERE 1";
2412 $known_instances = $wpdb->get_var($q);
2413
2414 /*
2415 $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links
2416 WHERE check_count > 0 AND ( http_code < 200 OR http_code >= 400 OR timeout = 1 ) AND ( http_code <> ".BLC_CHECKING." )";
2417 $broken_links = $wpdb->get_var($q);
2418 */
2419 $broken_links = $this->get_links( $this->native_filters['broken'], 0, 0, true );
2420
2421 $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links
2422 WHERE
2423 ( ( last_check < '$check_threshold' ) OR
2424 (
2425 ( http_code >= 400 OR http_code < 200 )
2426 AND check_count < 3
2427 AND last_check < '$recheck_threshold' )
2428 )";
2429 $unchecked_links = $wpdb->get_var($q);
2430
2431 return array(
2432 'check_threshold' => $check_threshold,
2433 'recheck_threshold' => $recheck_threshold,
2434 'known_links' => $known_links,
2435 'known_instances' => $known_instances,
2436 'broken_links' => $broken_links,
2437 'unchecked_links' => $unchecked_links,
2438 );
2439 }
2440
2441 function ajax_work(){
2442 //Run the worker function
2443 $this->work();
2444 die();
2445 }
2446
2447 function ajax_discard(){
2448 //TODO:Rewrite to use JSON instead of plaintext
2449 if (!current_user_can('edit_others_posts')){
2450 die( __("You're not allowed to do that!", 'broken-link-checker') );
2451 }
2452
2453 if ( isset($_POST['link_id']) ){
2454 //Load the link
2455 $link = new blcLink( intval($_POST['link_id']) );
2456
2457 if ( !$link->valid() ){
2458 printf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) );
2459 die();
2460 }
2461 //Make it appear "not broken"
2462 $link->last_check = date('Y-m-d H:i:s');
2463 $link->http_code = 200;
2464 $link->timeout = 0;
2465 $link->check_count = 0;
2466 $link->log = __("This link was manually marked as working by the user.", 'broken-link-checker');
2467
2468 //Save the changes
2469 if ( $link->save() ){
2470 die( "OK" );
2471 } else {
2472 die( __("Oops, couldn't modify the link!", 'broken-link-checker') ) ;
2473 }
2474 } else {
2475 die( __("Error : link_id not specified", 'broken-link-checker') );
2476 }
2477 }
2478
2479 function ajax_edit(){
2480 if (!current_user_can('edit_others_posts')){
2481 die( json_encode( array(
2482 'error' => __("You're not allowed to do that!", 'broken-link-checker')
2483 )));
2484 }
2485
2486 if ( isset($_GET['link_id']) && !empty($_GET['new_url']) ){
2487 //Load the link
2488 $link = new blcLink( intval($_GET['link_id']) );
2489
2490 if ( !$link->valid() ){
2491 die( json_encode( array(
2492 'error' => sprintf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_GET['link_id']) )
2493 )));
2494 }
2495
2496 $new_url = blcUtility::normalize_url($_GET['new_url']);
2497 if ( !$new_url ){
2498 die( json_encode( array(
2499 'error' => __("Oops, the new URL is invalid!", 'broken-link-checker')
2500 )));
2501 }
2502
2503 //Try and edit the link
2504 $rez = $link->edit($new_url);
2505
2506 if ( $rez == false ){
2507 die( json_encode( array(
2508 'error' => __("An unexpected error occured!", 'broken-link-checker')
2509 )));
2510 } else {
2511 $rez['ok'] = __('OK', 'broken-link-checker');
2512 die( json_encode($rez) );
2513 }
2514
2515 } else {
2516 die( json_encode( array(
2517 'error' => __("Error : link_id or new_url not specified", 'broken-link-checker')
2518 )));
2519 }
2520 }
2521
2522 function ajax_unlink(){
2523 if (!current_user_can('edit_others_posts')){
2524 die( json_encode( array(
2525 'error' => __("You're not allowed to do that!", 'broken-link-checker')
2526 )));
2527 }
2528
2529 if ( isset($_POST['link_id']) ){
2530 //Load the link
2531 $link = new blcLink( intval($_POST['link_id']) );
2532
2533 if ( !$link->valid() ){
2534 die( json_encode( array(
2535 'error' => sprintf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) )
2536 )));
2537 }
2538
2539 //Try and unlink it
2540 if ( $link->unlink() ){
2541 die( json_encode( array(
2542 'ok' => sprintf( __("URL %s was removed.", 'broken-link-checker'), $link->url )
2543 )));
2544 } else {
2545 die( json_encode( array(
2546 'error' => __("The plugin failed to remove the link.", 'broken-link-checker')
2547 )));
2548 }
2549
2550 } else {
2551 die( json_encode( array(
2552 'error' => __("Error : link_id not specified", 'broken-link-checker')
2553 )));
2554 }
2555 }
2556
2557 function ajax_link_details(){
2558 global $wpdb;
2559
2560 if (!current_user_can('edit_others_posts')){
2561 die( __("You don't have sufficient privileges to access this information!", 'broken-link-checker') );
2562 }
2563
2564 //FB::log("Loading link details via AJAX");
2565
2566 if ( isset($_GET['link_id']) ){
2567 //FB::info("Link ID found in GET");
2568 $link_id = intval($_GET['link_id']);
2569 } else if ( isset($_POST['link_id']) ){
2570 //FB::info("Link ID found in POST");
2571 $link_id = intval($_POST['link_id']);
2572 } else {
2573 //FB::error('Link ID not specified, you hacking bastard.');
2574 die( __('Error : link ID not specified', 'broken-link-checker') );
2575 }
2576
2577 //Load the link. link_details_row needs it as an array, so
2578 //we'll have to do this the long way.
2579 $q = "SELECT
2580 links.*,
2581 COUNT(*) as instance_count
2582
2583 FROM
2584 {$wpdb->prefix}blc_links AS links,
2585 {$wpdb->prefix}blc_instances as instances
2586
2587 WHERE
2588 links.link_id = %d
2589
2590 GROUP BY links.link_id";
2591
2592 $link = $wpdb->get_row( $wpdb->prepare($q, $link_id), ARRAY_A );
2593 if ( is_array($link) ){
2594 //FB::info($link, 'Link loaded');
2595 $this->link_details_row($link);
2596 die();
2597 } else {
2598 printf( __('Failed to load link details (%s)', 'broken-link-checker'), $wpdb->last_error );
2599 die ();
2600 }
2601 }
2602
2603 function ajax_exclude_link(){
2604 if ( !current_user_can('manage_options') ){
2605 die( json_encode( array(
2606 'error' => __("You're not allowed to do that!", 'broken-link-checker')
2607 )));
2608 }
2609
2610 if ( isset($_POST['link_id']) ){
2611 //Load the link
2612 $link = new blcLink( intval($_POST['link_id']) );
2613
2614 if ( !$link->valid() ){
2615 die( json_encode( array(
2616 'error' => sprintf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) )
2617 )));
2618 }
2619
2620 //Add the URL to the exclusion list
2621 if ( !in_array( $link->url, $this->conf->options['exclusion_list'] ) ){
2622 $this->conf->options['exclusion_list'][] = $link->url;
2623 //Also mark it as already checked so that it doesn't show up with other broken links.
2624 //FB::info("The URL {$link->url} is excluded, marking link {$link->link_id} as already checked.");
2625 $link->last_check = date('Y-m-d H:i:s');
2626 $link->http_code = 200; //Use a fake code so that the link doesn't show up in queries looking for broken links.
2627 $link->timeout = false;
2628 $link->request_duration = 0;
2629 $link->log = __("This link wasn't checked because a matching keyword was found on your exclusion list.", 'broken-link-checker');
2630 $link->save();
2631 }
2632
2633 $this->conf->save_options();
2634
2635 die( json_encode( array(
2636 'ok' => sprintf( __('URL %s added to the exclusion list', 'broken-link-checker'), $link->url )
2637 )));
2638 } else {
2639 die( json_encode( array(
2640 'error' => __("Link ID not specified", 'broken-link-checker')
2641 )));
2642 }
2643 }
2644
2645 /**
2646 * ws_broken_link_checker::acquire_lock()
2647 * Create and lock a temporary file.
2648 *
2649 * @return bool
2650 */
2651 function acquire_lock(){
2652 //Maybe we already have the lock?
2653 if ( $this->lockfile_handle ){
2654 return true;
2655 }
2656
2657 $fn = $this->lockfile_name();
2658 if ( $fn ){
2659 //Open the lockfile
2660 $this->lockfile_handle = fopen($fn, 'w+');
2661 if ( $this->lockfile_handle ){
2662 //Do an exclusive lock
2663 if (flock($this->lockfile_handle, LOCK_EX | LOCK_NB)) {
2664 //File locked successfully
2665 return true;
2666 } else {
2667 //Something went wrong
2668 fclose($this->lockfile_handle);
2669 $this->lockfile_handle = null;
2670 return false;
2671 }
2672 } else {
2673 //Can't open the file, fail.
2674 return false;
2675 }
2676 } else {
2677 //Uh oh, can't generate a lockfile name. This is bad.
2678 //FB::error("Can't find a writable directory to use for my lock file!");
2679 return false;
2680 };
2681 }
2682
2683 /**
2684 * ws_broken_link_checker::release_lock()
2685 * Unlock and delete the temporary file
2686 *
2687 * @return bool
2688 */
2689 function release_lock(){
2690 if ( $this->lockfile_handle ){
2691 //Close the file (implicitly releasing the lock)
2692 fclose( $this->lockfile_handle );
2693 //Delete the file
2694 $fn = $this->lockfile_name();
2695 if ( file_exists( $fn ) ) {
2696 unlink( $fn );
2697 }
2698 $this->lockfile_handle = null;
2699 return true;
2700 } else {
2701 //We didn't have the lock anyway...
2702 return false;
2703 }
2704 }
2705
2706 /**
2707 * ws_broken_link_checker::lockfile_name()
2708 * Generate system-specific lockfile filename
2709 *
2710 * @return string A filename or FALSE on error
2711 */
2712 function lockfile_name(){
2713 //Try the user-specified temp. directory first, if any
2714 if ( !empty( $this->conf->options['custom_tmp_dir'] ) ) {
2715 $custom_dir = trailingslashit($this->conf->options['custom_tmp_dir']);
2716 if ( blcUtility::is_writable( $custom_dir ) && @is_dir( $custom_dir ) ) {
2717 return $custom_dir . 'wp_blc_lock';
2718 } else {
2719 return false;
2720 }
2721 }
2722
2723 //Try the plugin's own directory.
2724 if ( blcUtility::is_writable( trailingslashit(dirname(__FILE__)) ) ){
2725 return dirname(__FILE__) . '/wp_blc_lock';
2726 } else {
2727
2728 //Try the system-wide temp directory
2729 $path = trailingslashit( sys_get_temp_dir() );
2730 if ( $path && blcUtility::is_writable($path)){
2731 return $path . 'wp_blc_lock';
2732 }
2733
2734 //Try the upload directory.
2735 $path = trailingslashit( ini_get('upload_tmp_dir') );
2736 if ( $path && blcUtility::is_writable($path)){
2737 return $path . 'wp_blc_lock';
2738 }
2739
2740 //Fail
2741 return false;
2742 }
2743 }
2744
2745 function hook_add_link( $link_id ){
2746 $this->mark_unsynched( $link_id, 'blogroll' );
2747 }
2748
2749 function hook_edit_link( $link_id ){
2750 $this->mark_unsynched( $link_id, 'blogroll' );
2751 }
2752
2753 function hook_delete_link( $link_id ){
2754 global $wpdb;
2755 //Delete the synch record
2756 $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}blc_synch WHERE source_id = %d AND source_type='blogroll'", $link_id ) );
2757
2758 //Get the matching instance record.
2759 $inst = $wpdb->get_row( $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_instances WHERE source_id = %d AND source_type = 'blogroll'", $link_id), ARRAY_A );
2760
2761 if ( !$inst ) {
2762 //No instance record? No problem.
2763 return;
2764 }
2765
2766 //Remove it
2767 $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_instances WHERE instance_id = %d", $inst['instance_id']) );
2768
2769 //Remove the link that was associated with this instance if it has no more related instances.
2770 $this->cleanup_links( $inst['link_id'] );
2771 }
2772
2773 function hook_wp_dashboard_setup(){
2774 if ( function_exists( 'wp_add_dashboard_widget' ) ) {
2775 wp_add_dashboard_widget(
2776 'blc_dashboard_widget',
2777 'Broken Link Checker',
2778 array( &$this, 'dashboard_widget' ),
2779 array( &$this, 'dashboard_widget_control' )
2780 );
2781 }
2782 }
2783
2784 function lockfile_warning(){
2785 $my_dir = '/plugins/' . basename(dirname(__FILE__)) . '/';
2786 $settings_page = admin_url( 'options-general.php?page=link-checker-settings#lockfile_directory' );
2787
2788 //Make the notice customized to the current settings
2789 if ( !empty($this->conf->options['custom_tmp_dir']) ){
2790 $action_notice = sprintf(
2791 __('The current temporary directory is not accessible; please <a href="%s">set a different one</a>.', 'broken-link-checker'),
2792 $settings_page
2793 );
2794 } else {
2795 $action_notice = sprintf(
2796 __('Please make the directory <code>%1$s</code> writable by plugins or <a href="%2$s">set a custom temporary directory</a>.', 'broken-link-checker'),
2797 $my_dir, $settings_page
2798 );
2799 }
2800
2801 echo sprintf('
2802 <div id="blc-lockfile-warning" class="error"><p>
2803 <strong>' . __("Broken Link Checker can't create a lockfile.", 'broken-link-checker') .
2804 '</strong> %s <a href="javascript:void(0)" onclick="jQuery(\'#blc-lockfile-details\').toggle()">' .
2805 __('Details', 'broken-link-checker') . '</a> </p>
2806
2807 <div id="blc-lockfile-details" style="display:none;"><p>' .
2808 __("The plugin uses a file-based locking mechanism to ensure that only one instance of the resource-heavy link checking algorithm is running at any given time. Unfortunately, BLC can't find a writable directory where it could store the lockfile - it failed to detect the location of your server's temporary directory, and the plugin's own directory isn't writable by PHP. To fix this problem, please make the plugin's directory writable or enter a specify a custom temporary directory in the plugin's settings.", 'broken-link-checker') .
2809 '</p>
2810 </div>
2811 </div>',
2812 $action_notice);
2813 }
2814
2815 /**
2816 * wsBrokenLinkChecker::get_debug_info()
2817 * Collect various debugging information and return it in an associative array
2818 *
2819 * @return array
2820 */
2821 function get_debug_info(){
2822 global $wpdb;
2823
2824 //Collect some information that's useful for debugging
2825 $debug = array();
2826
2827 //PHP version. Any one is fine as long as WP supports it.
2828 $debug[ __('PHP version', 'broken-link-checker') ] = array(
2829 'state' => 'ok',
2830 'value' => phpversion(),
2831 );
2832
2833 //MySQL version
2834 $debug[ __('MySQL version', 'broken-link-checker') ] = array(
2835 'state' => 'ok',
2836 'value' => @mysql_get_server_info( $wpdb->dbh ),
2837 );
2838
2839 //CURL presence and version
2840 if ( function_exists('curl_version') ){
2841 $version = curl_version();
2842
2843 if ( version_compare( $version['version'], '7.16.0', '<=' ) ){
2844 $data = array(
2845 'state' => 'warning',
2846 'value' => $version['version'],
2847 'message' => __('You have an old version of CURL. Redirect detection may not work properly.', 'broken-link-checker'),
2848 );
2849 } else {
2850 $data = array(
2851 'state' => 'ok',
2852 'value' => $version['version'],
2853 );
2854 }
2855
2856 } else {
2857 $data = array(
2858 'state' => 'warning',
2859 'value' => __('Not installed', 'broken-link-checker'),
2860 );
2861 }
2862 $debug[ __('CURL version', 'broken-link-checker') ] = $data;
2863
2864 //Snoopy presence
2865 if ( class_exists('Snoopy') ){
2866 $data = array(
2867 'state' => 'ok',
2868 'value' => __('Installed', 'broken-link-checker'),
2869 );
2870 } else {
2871 //No Snoopy? This should never happen, but if it does we *must* have CURL.
2872 if ( function_exists('curl_init') ){
2873 $data = array(
2874 'state' => 'ok',
2875 'value' => __('Not installed', 'broken-link-checker'),
2876 );
2877 } else {
2878 $data = array(
2879 'state' => 'error',
2880 'value' => __('Not installed', 'broken-link-checker'),
2881 'message' => __('You must have either CURL or Snoopy installed for the plugin to work!', 'broken-link-checker'),
2882 );
2883 }
2884
2885 }
2886 $debug['Snoopy'] = $data;
2887
2888 //Safe_mode status
2889 if ( blcUtility::is_safe_mode() ){
2890 $debug['Safe mode'] = array(
2891 'state' => 'warning',
2892 'value' => __('On', 'broken-link-checker'),
2893 'message' => __('Redirects may be detected as broken links when safe_mode is on.', 'broken-link-checker'),
2894 );
2895 } else {
2896 $debug['Safe mode'] = array(
2897 'state' => 'ok',
2898 'value' => __('Off', 'broken-link-checker'),
2899 );
2900 }
2901
2902 //Open_basedir status
2903 if ( blcUtility::is_open_basedir() ){
2904 $debug['open_basedir'] = array(
2905 'state' => 'warning',
2906 'value' => sprintf( __('On ( %s )', 'broken-link-checker'), ini_get('open_basedir') ),
2907 'message' => __('Redirects may be detected as broken links when open_basedir is on.', 'broken-link-checker'),
2908 );
2909 } else {
2910 $debug['open_basedir'] = array(
2911 'state' => 'ok',
2912 'value' => __('Off', 'broken-link-checker'),
2913 );
2914 }
2915
2916 //Lockfile location
2917 $lockfile = $this->lockfile_name();
2918 if ( $lockfile ){
2919 $debug['Lockfile'] = array(
2920 'state' => 'ok',
2921 'value' => $lockfile,
2922 );
2923 } else {
2924 $debug['Lockfile'] = array(
2925 'state' => 'error',
2926 'message' => __("Can't create a lockfile. Please specify a custom temporary directory.", 'broken-link-checker'),
2927 );
2928 }
2929
2930 return $debug;
2931 }
2932
2933 /**
2934 * wsBrokenLinkChecker::load_language()
2935 * Load the plugin's textdomain
2936 *
2937 * @return void
2938 */
2939 function load_language(){
2940 load_plugin_textdomain( 'broken-link-checker', false, basename(dirname($this->loader)) . '/languages' );
2941 }
2942
2943 /**
2944 * wsBrokenLinkChecker::get_links()
2945 * Get the list of links that match a given filter.
2946 *
2947 * @param array|null $filter The filter to apply. Set this to null to return all links (default).
2948 * @param integer $offset Skip this many links from the beginning. If this parameter is nonzero you must also set the next one.
2949 * @param integer $max_results The maximum number of links to return.
2950 * @param bool $count_only Only return the total number of matching links, not the links themselves
2951 * @return array|int Either an array of links, or the number of matching links. Null on error.
2952 */
2953 function get_links( $filter = null, $offset = 0, $max_results = 0, $count_only = false){
2954 global $wpdb;
2955
2956 //Figure out the WHERE expression for this filter
2957 $where_expr = '1'; //default = select all links
2958
2959 if ( !empty($filter) ){
2960
2961 //Is this a custom search filter?
2962 if ( empty($filter['is_search']) ){
2963 //It's a native filter, so it should have the WHERE epression already set
2964 $where_expr = $filter['where_expr'];
2965 } else {
2966 //It's a search filter, so we must build the WHERE expr for the specific query
2967 //from the query parameters.
2968
2969 $params = $this->get_search_params($filter);
2970
2971 //Generate the individual clauses of the WHERE expression
2972 $pieces = array();
2973
2974 //Anchor text - use fulltext search
2975 if ( !empty($params['s_link_text']) ){
2976 $pieces[] = 'MATCH(instances.link_text) AGAINST("' . $wpdb->escape($params['s_link_text']) . '")';
2977 }
2978
2979 //URL - try to match both the initial URL and the final URL.
2980 //There is limited wildcard support, e.g. "google.*/search" will match both
2981 //"google.com/search" and "google.lv/search"
2982 if ( !empty($params['s_link_url']) ){
2983 $s_link_url = like_escape($wpdb->escape($params['s_link_url']));
2984 $s_link_url = str_replace('*', '%', $s_link_url);
2985
2986 $pieces[] = '(links.url LIKE "%'. $s_link_url .'%") OR '.
2987 '(links.final_url LIKE "%'. $s_link_url .'%")';
2988 }
2989
2990 //Link type should match either the instance_type or the source_type
2991 if ( !empty($params['s_link_type']) ){
2992 $s_link_type = $wpdb->escape($params['s_link_type']);
2993 $pieces[] = "instances.instance_type = '$s_link_type' OR instances.source_type='$s_link_type'";
2994 }
2995
2996 //HTTP code - the user can provide a list of HTTP response codes and code ranges.
2997 //Example : 201,400-410,500
2998 if ( !empty($params['s_http_code']) ){
2999 //Strip spaces.
3000 $params['s_http_code'] = str_replace(' ', '', $params['s_http_code']);
3001 //Split by comma
3002 $codes = explode(',', $params['s_http_code']);
3003
3004 $individual_codes = array();
3005 $ranges = array();
3006
3007 //Try to parse each response code or range. Invalid ones are simply ignored.
3008 foreach($codes as $code){
3009 if ( is_numeric($code) ){
3010 //It's a single number
3011 $individual_codes[] = abs(intval($code));
3012 } elseif ( strpos($code, '-') !== false ) {
3013 //Try to parse it as a range
3014 $range = explode( '-', $code, 2 );
3015 if ( (count($range) == 2) && is_numeric($range[0]) && is_numeric($range[0]) ){
3016 //Make sure the smaller code comes first
3017 $range = array( intval($range[0]), intval($range[1]) );
3018 $ranges[] = array( min($range), max($range) );
3019 }
3020 }
3021 }
3022
3023 $piece = array();
3024
3025 //All individual response codes get one "http_code IN (...)" clause
3026 if ( !empty($individual_codes) ){
3027 $piece[] = '(links.http_code IN ('. implode(', ', $individual_codes) .'))';
3028 }
3029
3030 //Ranges get a "http_code BETWEEN min AND max" clause each
3031 if ( !empty($ranges) ){
3032 $range_strings = array();
3033 foreach($ranges as $range){
3034 $range_strings[] = "(links.http_code BETWEEN $range[0] AND $range[1])";
3035 }
3036 $piece[] = '( ' . implode(' OR ', $range_strings) . ' )';
3037 }
3038
3039 //Finally, generate a composite WHERE clause for both types of response code queries
3040 if ( !empty($piece) ){
3041 $pieces[] = implode(' OR ', $piece);
3042 }
3043
3044 }
3045
3046 //Custom filters can optionally call one of the native filters
3047 //to narrow down the result set.
3048 if ( !empty($params['s_filter']) && isset($this->native_filters[$params['s_filter']]) ){
3049 $pieces[] = $this->native_filters[$params['s_filter']]['where_expr'];
3050 }
3051
3052 if ( !empty($pieces) ){
3053 $where_expr = "\t( " . implode(" ) AND\n\t( ", $pieces) . ' ) ';
3054 }
3055 }
3056
3057 }
3058
3059 if ( $count_only ){
3060 //Only get the number of matching links. This lets us use a simplified query with less joins.
3061 $q = "
3062 SELECT COUNT(*)
3063 FROM (
3064 SELECT 0
3065
3066 FROM
3067 {$wpdb->prefix}blc_links AS links,
3068 {$wpdb->prefix}blc_instances as instances
3069
3070 WHERE
3071 links.link_id = instances.link_id
3072 AND ". $where_expr ."
3073
3074 GROUP BY links.link_id) AS foo";
3075 return $wpdb->get_var($q);
3076 } else {
3077 //Select the required links + 1 instance per link + 1 post for instances contained in posts.
3078 $q = "SELECT
3079 links.*,
3080 instances.instance_id, instances.source_id, instances.source_type,
3081 instances.link_text, instances.instance_type,
3082 COUNT(*) as instance_count,
3083 posts.post_title,
3084 posts.post_date
3085
3086 FROM
3087 {$wpdb->prefix}blc_links AS links,
3088 {$wpdb->prefix}blc_instances as instances LEFT JOIN {$wpdb->posts} as posts ON instances.source_id = posts.ID
3089
3090 WHERE
3091 links.link_id = instances.link_id
3092 AND ". $where_expr ."
3093
3094 GROUP BY links.link_id";
3095 if ( $max_results || $offset ){
3096 $q .= "\nLIMIT $offset, $max_results";
3097 }
3098
3099 return $wpdb->get_results($q, ARRAY_A);
3100 }
3101 }
3102
3103 }//class ends here
3104
3105 } // if class_exists...
3106
3107 ?>