Broken Link Checker - Version 0.5.10

Version Description

Download this release

Release Info

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

Code changes from version 0.5.9 to 0.5.10

Files changed (7) hide show
  1. broken-link-checker.php +62 -2177
  2. config-manager.php +85 -0
  3. core.php +2137 -0
  4. highlighter-class.php +76 -0
  5. link-classes.php +12 -10
  6. readme.txt +17 -2
  7. utility-class.php +31 -15
broken-link-checker.php CHANGED
@@ -1,9 +1,10 @@
1
  <?php
 
2
  /*
3
  Plugin Name: Broken Link Checker
4
  Plugin URI: http://w-shadow.com/blog/2007/08/05/broken-link-checker-for-wordpress/
5
  Description: Checks your posts for broken links and missing images and notifies you on the dashboard if any are found.
6
- Version: 0.5.9
7
  Author: Janis Elsts
8
  Author URI: http://w-shadow.com/blog/
9
  */
@@ -13,33 +14,6 @@ Created by Janis Elsts (email : whiteshadow@w-shadow.com)
13
  MySQL 4.0 compatibility by Jeroen (www.yukka.eu)
14
  */
15
 
16
- //The plugin will use Snoopy in case CURL is not available
17
- if (!class_exists('Snoopy')) require_once(ABSPATH.'/wp-includes/class-snoopy.php');
18
-
19
- /**
20
- * Simple function to replicate PHP 5 behaviour
21
- */
22
- if ( !function_exists('microtime_float') ) {
23
- function microtime_float()
24
- {
25
- list($usec, $sec) = explode(" ", microtime());
26
- return ((float)$usec + (float)$sec);
27
- }
28
- }
29
-
30
- //Make sure some useful constants are defined
31
- if ( ! defined( 'WP_CONTENT_URL' ) )
32
- define( 'WP_CONTENT_URL', get_option( 'siteurl' ) . '/wp-content' );
33
- if ( ! defined( 'WP_CONTENT_DIR' ) )
34
- define( 'WP_CONTENT_DIR', ABSPATH . 'wp-content' );
35
- if ( ! defined( 'WP_PLUGIN_URL' ) )
36
- define( 'WP_PLUGIN_URL', WP_CONTENT_URL. '/plugins' );
37
- if ( ! defined( 'WP_PLUGIN_DIR' ) )
38
- define( 'WP_PLUGIN_DIR', WP_CONTENT_DIR . '/plugins' );
39
-
40
- define('BLC_CHECKING', 1);
41
- define('BLC_TIMEOUT', 2);
42
-
43
  /*
44
  //FirePHP for debugging
45
  if ( !class_exists('FB') ) {
@@ -51,2156 +25,67 @@ if ( !class_exists('FB') ) {
51
  //to uncomment : \/\/(\s*FB::) -> $1
52
  //*/
53
 
54
- require 'utility-class.php';
55
- require 'instance-classes.php';
56
- require 'link-classes.php';
57
-
58
- if (!class_exists('ws_broken_link_checker')) {
59
-
60
- class ws_broken_link_checker {
61
- var $options;
62
- var $options_name='wsblc_options';
63
- var $myfile=''; //should be removed
64
- var $myfolder=''; //should be removed
65
- var $mybasename=''; //should be removed
66
- var $siteurl; //should be removed
67
- var $defaults;
68
- var $db_version = 1;
69
-
70
- var $execution_start_time; //Used for a simple internal execution timer in start_timer()/execution_time()
71
- var $lockfile_handle = null;
72
-
73
- function ws_broken_link_checker() {
74
- global $wpdb;
75
-
76
- //set default options
77
- $this->defaults = array(
78
- 'max_execution_time' => 5*60, //How long the worker instance may run, at most.
79
- 'check_threshold' => 72, //Check each link every 72 hours.
80
- 'mark_broken_links' => true, //Whether to add the broken_link class to broken links in posts.
81
- 'broken_link_css' => ".broken_link, a.broken_link {\n\ttext-decoration: line-through;\n}",
82
- 'exclusion_list' => array(), //Links that contain a substring listed in this array won't be checked.
83
- 'recheck_count' => 3, //[Internal] How many times a broken link should be re-checked (slightly buggy)
84
-
85
- //These three are currently ignored. Everything is checked by default.
86
- 'check_posts' => true,
87
- 'check_custom_fields' => true,
88
- 'check_blogroll' => true,
89
-
90
- 'custom_fields' => array(), //List of custom fields that can contain URLs and should be checked.
91
-
92
- 'autoexpand_widget' => true, //Autoexpand the Dashboard widget if broken links are detected
93
-
94
- 'need_resynch' => false, //[Internal flag]
95
-
96
- 'current_db_version' => 0, //The current version of the plugin's tables
97
- );
98
-
99
- $this->load_options();
100
-
101
- $this->siteurl = get_option('siteurl');
102
-
103
- $my_file = str_replace('\\', '/',__FILE__);
104
- $my_file = preg_replace('/^.*wp-content[\\\\\/]plugins[\\\\\/]/', '', $my_file);
105
- add_action('activate_' . plugin_basename(__FILE__), array(&$this,'activation'));
106
- $this->myfile=$my_file;
107
- $this->myfolder=basename(dirname(__FILE__));
108
- $this->mybasename=plugin_basename(__FILE__);
109
-
110
- add_action('admin_menu', array(&$this,'admin_menu'));
111
-
112
- //These hooks update the plugin's internal records when posts are added, deleted or modified.
113
- add_action('delete_post', array(&$this,'post_deleted'));
114
- add_action('save_post', array(&$this,'post_saved'));
115
-
116
- //These do the same for (blogroll) links.
117
- add_action('add_link', array(&$this,'hook_add_link'));
118
- add_action('edit_link', array(&$this,'hook_edit_link'));
119
- add_action('delete_link', array(&$this,'hook_delete_link'));
120
-
121
- add_action('admin_footer', array(&$this,'admin_footer'));
122
- add_action('admin_print_scripts', array(&$this,'admin_print_scripts'));
123
- //The dashboard widget
124
- add_action('wp_dashboard_setup', array(&$this, 'hook_wp_dashboard_setup'));
125
-
126
- if ( $this->options['mark_broken_links'] ){
127
- add_filter( 'the_content', array(&$this,'the_content') );
128
- if ( !empty($this->options['broken_link_css']) ){
129
- add_action( 'wp_head', array(&$this,'header_css') );
130
- }
131
- }
132
-
133
- //AJAXy hooks
134
- add_action( 'wp_ajax_blc_full_status', array(&$this,'ajax_full_status') );
135
- add_action( 'wp_ajax_blc_dashboard_status', array(&$this,'ajax_dashboard_status') );
136
- add_action( 'wp_ajax_blc_work', array(&$this,'ajax_work') );
137
- add_action( 'wp_ajax_blc_discard', array(&$this,'ajax_discard') );
138
- add_action( 'wp_ajax_blc_edit', array(&$this,'ajax_edit') );
139
- add_action( 'wp_ajax_blc_link_details', array(&$this,'ajax_link_details') );
140
- add_action( 'wp_ajax_blc_exclude_link', array(&$this,'ajax_exclude_link') );
141
- add_action( 'wp_ajax_blc_unlink', array(&$this,'ajax_unlink') );
142
- }
143
-
144
- function admin_footer(){
145
- ?>
146
- <!-- wsblc admin footer -->
147
- <div id='wsblc_updater_div'></div>
148
- <script type='text/javascript'>
149
- (function($){
150
-
151
- function blcDoWork(){
152
- $.post(
153
- "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
154
- {
155
- 'action' : 'blc_work'
156
- },
157
- function (data, textStatus){
158
-
159
- }
160
- );
161
- }
162
- //Call it the first time
163
- blcDoWork();
164
-
165
- //Then call it periodically every X seconds
166
- setInterval(blcDoWork, <?php echo (intval($this->options['max_execution_time']) + 1 )*1000; ?>);
167
-
168
- })(jQuery);
169
- </script>
170
- <!-- /wsblc admin footer -->
171
- <?php
172
- }
173
-
174
- function header_css(){
175
- echo '<style type="text/css">',$this->options['broken_link_css'],'</style>';
176
- }
177
-
178
- function the_content($content){
179
- global $post, $wpdb;
180
- if ( empty($post) ) return $content;
181
-
182
- $q = "
183
- SELECT instances.link_text, links.*
184
-
185
- FROM {$wpdb->prefix}blc_instances AS instances, {$wpdb->prefix}blc_links AS links
186
-
187
- WHERE
188
- instances.source_id = %d
189
- AND instances.source_type = 'post'
190
- AND instances.instance_type = 'link'
191
-
192
- AND instances.link_id = links.link_id
193
- AND links.check_count > 0
194
- AND ( links.http_code < 200 OR links.http_code >= 400 OR links.timeout = 1 )";
195
-
196
- $rows = $wpdb->get_results( $wpdb->prepare( $q, $post->ID ), ARRAY_A );
197
- if( $rows ){
198
- $this->links_to_remove = array();
199
- foreach($rows as $row){
200
- $this->links_to_remove[$row['url']] = $row;
201
- }
202
- $content = preg_replace_callback( blcUtility::link_pattern(), array(&$this,'mark_broken_links'), $content );
203
- };
204
-
205
- return $content;
206
- }
207
-
208
- function mark_broken_links($matches){
209
- //TODO:Tooltip-style popups with more info
210
- $url = blcUtility::normalize_url( html_entity_decode( $matches[3] ) );
211
- if( isset( $this->links_to_remove[$url] ) ){
212
- return $matches[1].$matches[2].$matches[3].$matches[2].' class="broken_link" '.$matches[4].
213
- $matches[5].$matches[6];
214
- } else {
215
- return $matches[0];
216
- }
217
- }
218
-
219
-
220
- function is_excluded($url){
221
- if (!is_array($this->options['exclusion_list'])) return false;
222
- foreach($this->options['exclusion_list'] as $excluded_word){
223
- if (stristr($url, $excluded_word)){
224
- return true;
225
- }
226
- }
227
- return false;
228
- }
229
-
230
- function dashboard_widget(){
231
- ?>
232
- <p id='wsblc_activity_box'>Loading...</p>
233
- <script type='text/javascript'>
234
- jQuery(function($){
235
- var blc_was_autoexpanded = false;
236
-
237
- function blcDashboardStatus(){
238
- $.getJSON(
239
- "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
240
- {
241
- 'action' : 'blc_dashboard_status'
242
- },
243
- function (data, textStatus){
244
- if ( data && ( typeof(data.text) != 'undefined' ) ) {
245
- $('#wsblc_activity_box').html(data.text);
246
- <?php if ( $this->options['autoexpand_widget'] ) { ?>
247
- //Expand the widget if there are broken links.
248
- //Do this only once per pageload so as not to annoy the user.
249
- if ( !blc_was_autoexpanded && ( data.status.broken_links > 0 ) ){
250
- $('#blc_dashboard_widget.postbox').removeClass('closed');
251
- blc_was_autoexpanded = true;
252
- };
253
- <?php } ?>
254
- } else {
255
- $('#wsblc_activity_box').html('[ Network error ]');
256
- }
257
-
258
- setTimeout( blcDashboardStatus, 120*1000 ); //...update every two minutes
259
- }
260
- );
261
- }
262
-
263
- blcDashboardStatus();//Call it the first time
264
-
265
- });
266
- </script>
267
- <?php
268
- }
269
-
270
- function dashboard_widget_control( $widget_id, $form_inputs = array() ){
271
- if ( 'POST' == $_SERVER['REQUEST_METHOD'] && 'blc_dashboard_widget' == $_POST['widget_id'] ) {
272
- //It appears $form_inputs isn't used in the current WP version, so lets just use $_POST
273
- $this->options['autoexpand_widget'] = !empty($_POST['blc-autoexpand']);
274
- $this->save_options();
275
- }
276
-
277
- ?>
278
- <p><label for="blc-autoexpand">
279
- <input id="blc-autoexpand" name="blc-autoexpand" type="checkbox" value="1" <?php if ( $this->options['autoexpand_widget'] ) echo 'checked="checked"'; ?> />
280
- Automatically expand the widget if broken links have been detected
281
- </label></p>
282
- <?php
283
- }
284
-
285
- function admin_print_scripts(){
286
- //jQuery is used for AJAX and effects
287
- wp_enqueue_script('jquery');
288
- wp_enqueue_script('jquery-ui-core');
289
- }
290
-
291
- /**
292
- * ws_broken_link_checker::post_deleted()
293
- * A hook for post_deleted. Remove link instances associated with that post.
294
- *
295
- * @param int $post_id
296
- * @return void
297
- */
298
- function post_deleted($post_id){
299
- global $wpdb;
300
-
301
- //FB::log($post_id, "Post deleted");
302
- //Remove this post's instances
303
- $q = "DELETE FROM {$wpdb->prefix}blc_instances
304
- WHERE source_id = %d AND (source_type = 'post' OR source_type='custom_field')";
305
- $q = $wpdb->prepare($q, intval($post_id) );
306
-
307
- //FB::log($q, 'Executing query');
308
-
309
- if ( $wpdb->query( $q ) === false ){
310
- //FB::error($wpdb->last_error, "Database error");
311
- }
312
-
313
- //Remove the synch record
314
- $q = "DELETE FROM {$wpdb->prefix}blc_synch
315
- WHERE source_id = %d AND source_type = 'post'";
316
- $wpdb->query( $wpdb->prepare($q, intval($post_id)) );
317
-
318
- //Remove any dangling link records
319
- $this->cleanup_links();
320
- }
321
-
322
- function post_saved($post_id){
323
- global $wpdb;
324
-
325
- $post = get_post($post_id);
326
- //Only check links in posts, not revisions and attachments
327
- if ( ($post->post_type != 'post') && ($post->post_type != 'page') ) return null;
328
- //Only check published posts
329
- if ( $post->post_status != 'publish' ) return null;
330
-
331
- $this->mark_unsynched( $post_id, 'post' );
332
- }
333
-
334
- function initiate_recheck(){
335
- global $wpdb;
336
-
337
- //Delete all discovered instances
338
- $wpdb->query("TRUNCATE {$wpdb->prefix}blc_instances");
339
-
340
- //Delete all discovered links
341
- $wpdb->query("TRUNCATE {$wpdb->prefix}blc_links");
342
-
343
- //Mark all posts, custom fields and bookmarks for processing.
344
- $this->resynch();
345
- }
346
-
347
- function resynch(){
348
- global $wpdb;
349
-
350
- //Drop all synchronization records
351
- $wpdb->query("TRUNCATE {$wpdb->prefix}blc_synch");
352
-
353
-
354
- //Create new synchronization records for posts
355
- $q = "INSERT INTO {$wpdb->prefix}blc_synch(source_id, source_type, synched)
356
- SELECT id, 'post', 0
357
- FROM {$wpdb->posts}
358
- WHERE
359
- {$wpdb->posts}.post_status = 'publish'
360
- AND {$wpdb->posts}.post_type IN ('post', 'page')";
361
- $wpdb->query( $q );
362
-
363
- //Create new synchronization records for bookmarks (the blogroll)
364
- $q = "INSERT INTO {$wpdb->prefix}blc_synch(source_id, source_type, synched)
365
- SELECT link_id, 'blogroll', 0
366
- FROM {$wpdb->links}
367
- WHERE 1";
368
- $wpdb->query( $q );
369
-
370
- //Delete invalid instances
371
- $this->cleanup_instances();
372
- //Delete orphaned links
373
- $this->cleanup_links();
374
-
375
- $this->options['need_resynch'] = true;
376
- $this->save_options();
377
- }
378
-
379
- function mark_unsynched( $source_id, $source_type ){
380
- global $wpdb;
381
-
382
- $q = "REPLACE INTO {$wpdb->prefix}blc_synch( source_id, source_type, synched, last_synch)
383
- VALUES( %d, %s, %d, NOW() )";
384
- $rez = $wpdb->query( $wpdb->prepare( $q, $source_id, $source_type, 0 ) );
385
-
386
- if ( !$this->options['need_resynch'] ){
387
- $this->options['need_resynch'] = true;
388
- $this->save_options();
389
- }
390
-
391
- return $rez;
392
- }
393
-
394
- function mark_synched( $source_id, $source_type ){
395
- global $wpdb;
396
- //FB::log("Marking $source_type $source_id as synched.");
397
- $q = "REPLACE INTO {$wpdb->prefix}blc_synch( source_id, source_type, synched, last_synch)
398
- VALUES( %d, %s, %d, NOW() )";
399
- return $wpdb->query( $wpdb->prepare( $q, $source_id, $source_type, 1 ) );
400
- }
401
-
402
- function activation(){
403
- //Prepare the database.
404
- $this->upgrade_database();
405
-
406
- //Clear the instance table and mark all posts and other parse-able objects as unsynchronized.
407
- $this->resynch();
408
-
409
- //Save the default options.
410
- $this->save_options();
411
- }
412
-
413
- /**
414
- * ws_broken_link_checker::upgrade_database()
415
- * Create and/or upgrade database tables
416
- *
417
- * @return void
418
- */
419
- function upgrade_database(){
420
- global $wpdb;
421
-
422
- //Do we need to upgrade?
423
- if ( $this->db_version == $this->options['current_db_version'] ) return;
424
-
425
- //Delete tables used by older versions of the plugin
426
- $rez = $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}blc_linkdata, {$wpdb->prefix}blc_postdata" );
427
- if ( $rez === false ){
428
- //FB::error($wpdb->last_error, "Database error");
429
- return false;
430
- }
431
-
432
- require_once (ABSPATH . 'wp-admin/includes/upgrade.php');
433
-
434
- //Create the link table if it doesn't exist yet.
435
- $q = "CREATE TABLE {$wpdb->prefix}blc_links (
436
- link_id int(20) unsigned NOT NULL auto_increment,
437
- url text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
438
- last_check datetime NOT NULL default '0000-00-00 00:00:00',
439
- check_count int(2) unsigned NOT NULL default '0',
440
- final_url text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
441
- redirect_count smallint(5) unsigned NOT NULL,
442
- log text NOT NULL,
443
- http_code smallint(6) NOT NULL,
444
- request_duration float NOT NULL default '0',
445
- timeout tinyint(1) unsigned NOT NULL default '0',
446
-
447
- PRIMARY KEY (link_id),
448
- KEY url (url(150)),
449
- KEY final_url (final_url(150)),
450
- KEY http_code (http_code),
451
- KEY timeout (timeout)
452
- )";
453
- dbDelta( $q );
454
-
455
- //Fix URL fields so that they are collated as case-sensitive (this can't be done via dbDelta)
456
- $wpdb->query(
457
- "ALTER TABLE {$wpdb->prefix}blc_links
458
- MODIFY url text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
459
- MODIFY final_url text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL"
460
- );
461
-
462
-
463
- //Create the instance table if it doesn't exist yet.
464
- $q = "CREATE TABLE {$wpdb->prefix}blc_instances (
465
- instance_id int(10) unsigned NOT NULL auto_increment,
466
- link_id int(10) unsigned NOT NULL,
467
- source_id int(10) unsigned NOT NULL,
468
- source_type enum('post','blogroll','custom_field') NOT NULL default 'post',
469
- link_text varchar(250) NOT NULL,
470
- instance_type enum('link','image') NOT NULL default 'link',
471
-
472
- PRIMARY KEY (instance_id),
473
- KEY link_id (link_id),
474
- KEY source_id (source_id,source_type)
475
- )";
476
- $rez = dbDelta($q);
477
-
478
- //....
479
- $q = "CREATE TABLE {$wpdb->prefix}blc_synch (
480
- source_id int(20) unsigned NOT NULL,
481
- source_type enum('post','blogroll') NOT NULL,
482
- synched tinyint(3) unsigned NOT NULL,
483
- last_synch datetime NOT NULL,
484
- PRIMARY KEY (source_id, source_type),
485
- KEY synched (synched)
486
- )";
487
- dbDelta($q);
488
-
489
- $this->options['current_db_version'] = $this->db_version;
490
- $this->save_options();
491
- }
492
-
493
- function admin_menu(){
494
- add_options_page('Link Checker Settings', 'Link Checker', 'manage_options',
495
- 'link-checker-settings',array(&$this, 'options_page'));
496
- if (current_user_can('manage_options'))
497
- add_filter('plugin_action_links', array(&$this, 'plugin_action_links'), 10, 2);
498
-
499
- add_management_page('View Broken Links', 'Broken Links', 'edit_others_posts',
500
- 'view-broken-links',array(&$this, 'links_page'));
501
- }
502
-
503
- /**
504
- * plugin_action_links()
505
- * Handler for the 'plugin_action_links' hook. Adds a "Settings" link to this plugin's entry
506
- * on the plugin list.
507
- *
508
- * @param array $links
509
- * @param string $file
510
- * @return array
511
- */
512
- function plugin_action_links($links, $file) {
513
- if ($file == $this->mybasename)
514
- $links[] = "<a href='options-general.php?page=link-checker-settings'>" . __('Settings') . "</a>";
515
- return $links;
516
- }
517
-
518
- function mytruncate($str, $max_length=50){
519
- if(strlen($str)<=$max_length) return $str;
520
- return (substr($str, 0, $max_length-3).'...');
521
- }
522
-
523
- function options_page(){
524
-
525
- if (isset($_GET['recheck']) && ($_GET['recheck'] == 'true')) {
526
- $this->initiate_recheck();
527
- }
528
- if (isset($_GET['updated']) && ($_GET['updated'] == 'true')) {
529
- if(isset($_POST['submit'])) {
530
-
531
- $new_execution_time = intval($_POST['max_execution_time']);
532
- if( $new_execution_time > 0 ){
533
- $this->options['max_execution_time'] = $new_execution_time;
534
- }
535
-
536
- $new_check_threshold=intval($_POST['check_threshold']);
537
- if( $new_check_threshold > 0 ){
538
- $this->options['check_threshold'] = $new_check_threshold;
539
- }
540
-
541
- $this->options['mark_broken_links'] = !empty($_POST['mark_broken_links']);
542
- $new_broken_link_css = trim($_POST['broken_link_css']);
543
- $this->options['broken_link_css'] = $new_broken_link_css;
544
-
545
- $this->options['exclusion_list']=array_filter( preg_split( '/[\s\r\n]+/',
546
- $_POST['exclusion_list'], -1, PREG_SPLIT_NO_EMPTY ) );
547
- //TODO: Maybe update affected links when exclusion list changes (expensive).
548
-
549
-
550
- $new_custom_fields = array_filter( preg_split( '/[\s\r\n]+/',
551
- $_POST['blc_custom_fields'], -1, PREG_SPLIT_NO_EMPTY ) );
552
- $diff1 = array_diff( $new_custom_fields, $this->options['custom_fields'] );
553
- $diff2 = array_diff( $this->options['custom_fields'], $new_custom_fields );
554
- $this->options['custom_fields'] = $new_custom_fields;
555
-
556
- $this->save_options();
557
-
558
- /*
559
- If the list of custom fields was modified then we MUST resynchronize or
560
- custom fields linked with existing posts may not be detected. This is somewhat
561
- inefficient.
562
- */
563
- if ( ( count($diff1) > 0 ) || ( count($diff2) > 0 ) ){
564
- $this->resynch();
565
- }
566
- }
567
-
568
- }
569
-
570
- ?>
571
- <div class="wrap"><h2>Broken Link Checker Options</h2>
572
-
573
- <form name="link_checker_options" method="post" action="<?php echo basename($_SERVER['PHP_SELF']); ?>?page=link-checker-settings&amp;updated=true">
574
-
575
- <table class="form-table">
576
-
577
- <tr valign="top">
578
- <th scope="row">Status</th>
579
- <td>
580
-
581
-
582
- <div id='wsblc_full_status'>
583
- <br/><br/><br/>
584
- </div>
585
- <script type='text/javascript'>
586
- (function($){
587
-
588
- function blcUpdateStatus(){
589
- $.getJSON(
590
- "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
591
- {
592
- 'action' : 'blc_full_status'
593
- },
594
- function (data, textStatus){
595
- if ( data && ( typeof(data['text']) != 'undefined' ) ){
596
- $('#wsblc_full_status').html(data.text);
597
- } else {
598
- $('#wsblc_full_status').html('[ Network error ]');
599
- }
600
-
601
- setTimeout(blcUpdateStatus, 10000); //...update every 10 seconds
602
- }
603
- );
604
- }
605
- blcUpdateStatus();//Call it the first time
606
-
607
- })(jQuery);
608
- </script>
609
- <?php //JHS: Recheck all posts link: ?>
610
- <p><input class="button" type="button" name="recheckbutton" value="Re-check all pages" onclick="location.replace('<?php echo basename($_SERVER['PHP_SELF']); ?>?page=link-checker-settings&amp;recheck=true')" /></p>
611
- </td>
612
- </tr>
613
-
614
- <tr valign="top">
615
- <th scope="row">Check each link</th>
616
- <td>
617
-
618
- Every <input type="text" name="check_threshold" id="check_threshold"
619
- value="<?php echo $this->options['check_threshold']; ?>" size='5' maxlength='3'/>
620
- hours
621
- <br/>
622
- <span class="description">
623
- Existing links will be checked this often. New links will usually be checked ASAP.
624
- </span>
625
-
626
- </td>
627
- </tr>
628
-
629
- <tr valign="top">
630
- <th scope="row">Broken link CSS</th>
631
- <td>
632
- <input type="checkbox" name="mark_broken_links" id="mark_broken_links"
633
- <?php if ($this->options['mark_broken_links']) echo ' checked="checked"'; ?>/>
634
- <label for='mark_broken_links'>Apply <em>class="broken_link"</em> to broken links</label><br/>
635
- <textarea name="broken_link_css" id="broken_link_css" cols='45' rows='4'/><?php
636
- if( isset($this->options['broken_link_css']) )
637
- echo $this->options['broken_link_css'];
638
- ?></textarea>
639
-
640
- </td>
641
- </tr>
642
-
643
- <tr valign="top">
644
- <th scope="row">Exclusion list</th>
645
- <td>Don't check links where the URL contains any of these words (one per line) :<br/>
646
- <textarea name="exclusion_list" id="exclusion_list" cols='45' rows='4' wrap='off'/><?php
647
- if( isset($this->options['exclusion_list']) )
648
- echo implode("\n", $this->options['exclusion_list']);
649
- ?></textarea>
650
-
651
- </td>
652
- </tr>
653
-
654
- <tr valign="top">
655
- <th scope="row">Custom fields</th>
656
- <td>Check URLs entered in these custom fields (one per line) : <br/>
657
- <textarea name="blc_custom_fields" id="blc_custom_fields" cols='45' rows='4' /><?php
658
- if( isset($this->options['custom_fields']) )
659
- echo implode("\n", $this->options['custom_fields']);
660
- ?></textarea>
661
-
662
- </td>
663
- </tr>
664
-
665
- <tr valign="top">
666
- <th scope="row">Max. execution time (advanced)</th>
667
- <td>
668
-
669
- <input type="text" name="max_execution_time" id="max_execution_time"
670
- value="<?php echo $this->options['max_execution_time']; ?>" size='5' maxlength='3'/>
671
- seconds
672
- <br/><span class="description">
673
- The plugin works by periodically creating a background worker instance that parses your posts looking for links,
674
- checks the discovered URLs, and performs other time-consuming tasks. Here you can set for how long, at most,
675
- the background instance may run each time before stopping.
676
- </span>
677
-
678
- </td>
679
- </tr>
680
-
681
- </table>
682
-
683
- <p class="submit"><input type="submit" name="submit" class='button-primary' value="<?php _e('Save Changes') ?>" /></p>
684
- </form>
685
- </div>
686
- <?php
687
- }
688
-
689
- function links_page(){
690
- global $wpdb;
691
-
692
- //Available filters by link type + the appropriate WHERE expressions
693
- $filters = array(
694
- 'broken' => array(
695
- 'where_expr' => '( http_code < 200 OR http_code >= 400 OR timeout = 1 ) AND ( check_count > 0 ) AND ( http_code <> ' . BLC_CHECKING . ')',
696
- 'name' => 'Broken',
697
- 'heading' => 'Broken Links',
698
- 'heading_zero' => 'No broken links found'
699
- ),
700
- 'redirects' => array(
701
- 'where_expr' => '( redirect_count > 0 )',
702
- 'name' => 'Redirects',
703
- 'heading' => 'Redirected Links',
704
- 'heading_zero' => 'No redirects found'
705
- ),
706
-
707
- 'all' => array(
708
- 'where_expr' => '1',
709
- 'name' => 'All',
710
- 'heading' => 'Detected Links',
711
- 'heading_zero' => 'No links found (yet)'
712
- ),
713
- );
714
-
715
- $link_type = isset($_GET['link_type'])?$_GET['link_type']:'broken';
716
- if ( !isset($filters[$link_type]) ){
717
- $link_type = 'broken';
718
- }
719
-
720
- //Get the desired page number (must be > 0)
721
- $page = isset($_GET['paged'])?intval($_GET['paged']):'1';
722
- if ($page < 1) $page = 1;
723
-
724
- //Links per page [1 - 200]
725
- $per_page = isset($_GET['per_page'])?intval($_GET['per_page']):'30';
726
- if ($per_page < 1){
727
- $per_page = 30;
728
- } else if ($per_page > 200){
729
- $per_page = 200;
730
- }
731
-
732
- //calculate the number of various links
733
- foreach ($filters as $filter => $data){
734
- $filters[$filter]['count'] = $wpdb->get_var(
735
- "SELECT COUNT(*) FROM {$wpdb->prefix}blc_links WHERE ".$data['where_expr'] );
736
- }
737
- $current_filter = $filters[$link_type];
738
- $max_pages = ceil($current_filter['count'] / $per_page);
739
-
740
-
741
- //Select the required links + 1 instance per link.
742
- //Note : The query might be somewhat inefficient, but I can't think of any better way to do this.
743
- $q = "SELECT
744
- links.*,
745
- instances.instance_id, instances.source_id, instances.source_type,
746
- instances.link_text, instances.instance_type,
747
- COUNT(*) as instance_count,
748
- posts.post_title,
749
- posts.post_date
750
-
751
- FROM
752
- {$wpdb->prefix}blc_links AS links,
753
- {$wpdb->prefix}blc_instances as instances LEFT JOIN {$wpdb->posts} as posts ON instances.source_id = posts.ID
754
-
755
- WHERE
756
- links.link_id = instances.link_id
757
- AND ". $current_filter['where_expr'] ."
758
-
759
- GROUP BY links.link_id
760
- LIMIT ".( ($page-1) * $per_page ).", $per_page";
761
- //echo "<pre>$q</pre>";
762
-
763
- $links = $wpdb->get_results($q, ARRAY_A);
764
- if ($links){
765
- /*
766
- echo '<pre>';
767
- print_r($links);
768
- echo '</pre>';
769
- //*/
770
- } else {
771
- echo $wpdb->last_error;
772
- }
773
- ?>
774
-
775
- <script type='text/javascript'>
776
- var blc_current_filter = '<?php echo $link_type; ?>';
777
- </script>
778
-
779
- <style type='text/css'>
780
- .blc-link-editor {
781
- font-size: 1em;
782
- width: 95%;
783
- }
784
-
785
- .blc-excluded-link {
786
- background-color: #E2E2E2;
787
- }
788
-
789
- .blc-small-image {
790
- display : block;
791
- float: left;
792
- padding-top: 2px;
793
- margin-right: 3px;
794
- }
795
- </style>
796
-
797
- <div class="wrap">
798
- <h2><?php
799
- //Output a header matching the current filter
800
- if ( $current_filter['count'] > 0 ){
801
- echo "<span class='current-link-count'>{$current_filter[count]}</span> " . $current_filter['heading'];
802
- } else {
803
- echo "<span class='current-link-count'></span>" . $current_filter['heading_zero'];
804
- }
805
- ?></h2>
806
-
807
- <div class='tablenav'>
808
- <ul class="subsubsub">
809
- <?php
810
- //Construct a submenu of filter types
811
- $items = array();
812
- foreach ($filters as $filter => $data){
813
- $class = $number_class = '';
814
-
815
- if ( $link_type == $filter ) $class = 'class="current"';
816
- if ( $link_type == $filter ) $number_class = 'current-link-count';
817
-
818
- $items[] = "<li><a href='tools.php?page=view-broken-links&link_type=$filter' $class>
819
- {$data[name]}</a> <span class='count'>(<span class='$number_class'>{$data[count]}</span>)</span>";
820
- }
821
- echo implode(' |</li>', $items);
822
- unset($items);
823
- ?>
824
- </ul>
825
- <?php
826
- //Display pagination links
827
- $page_links = paginate_links( array(
828
- 'base' => add_query_arg( 'paged', '%#%' ),
829
- 'format' => '',
830
- 'prev_text' => __('&laquo;'),
831
- 'next_text' => __('&raquo;'),
832
- 'total' => $max_pages,
833
- 'current' => $page
834
- ));
835
-
836
- if ( $page_links ) {
837
- echo '<div class="tablenav-pages">';
838
- $page_links_text = sprintf( '<span class="displaying-num">' . __( 'Displaying %s&#8211;%s of <span class="current-link-count">%s</span>' ) . '</span>%s',
839
- number_format_i18n( ( $page - 1 ) * $per_page + 1 ),
840
- number_format_i18n( min( $page * $per_page, count($links) ) ),
841
- number_format_i18n( $current_filter['count'] ),
842
- $page_links
843
- );
844
- echo $page_links_text;
845
- echo '</div>';
846
- }
847
- ?>
848
-
849
- </div>
850
-
851
-
852
-
853
- <?php
854
- if($links && (count($links)>0)){
855
- ?>
856
- <table class="widefat">
857
- <thead>
858
- <tr>
859
-
860
- <th scope="col">Source
861
- </th>
862
- <th scope="col">Link Text</th>
863
- <th scope="col">URL</th>
864
-
865
- <?php if ( 'broken' == $link_type ) { ?>
866
- <th scope="col"> </th>
867
- <?php } ?>
868
-
869
- </tr>
870
- </thead>
871
- <tbody id="the-list">
872
- <?php
873
- $rowclass = ''; $rownum = 0;
874
- foreach ($links as $link) {
875
- $rownum++;
876
-
877
- $rowclass = 'alternate' == $rowclass ? '' : 'alternate';
878
- $excluded = $this->is_excluded( $link['url'] );
879
- if ( $excluded ) $rowclass .= ' blc-excluded-link';
880
-
881
- ?>
882
- <tr id='<?php echo "blc-row-$rownum"; ?>' class='blc-row <?php echo $rowclass; ?>'>
883
- <td class='post-title column-title'>
884
- <span class='blc-link-id' style='display:none;'><?php echo $link['link_id']; ?></span>
885
- <?php
886
- if ( ('post' == $link['source_type']) || ('custom_field' == $link['source_type']) ){
887
-
888
- echo "<a class='row-title' href='post.php?action=edit&amp;post=$link[source_id]' title='Edit this post'>{$link[post_title]}</a>";
889
-
890
- //Output inline action links (copied from edit-post-rows.php)
891
- $actions = array();
892
- if ( current_user_can('edit_post', $link['source_id']) ) {
893
- $actions['edit'] = '<span class="edit"><a href="' . get_edit_post_link($link['source_id'], true) . '" title="' . attribute_escape(__('Edit this post')) . '">' . __('Edit') . '</a>';
894
- $actions['delete'] = "<span class='delete'><a class='submitdelete' title='" . attribute_escape(__('Delete this post')) . "' href='" . wp_nonce_url("post.php?action=delete&amp;post=".$link['source_id'], 'delete-post_' . $link['source_id']) . "' onclick=\"if ( confirm('" . js_escape(sprintf( __("You are about to delete the post '%s'\n 'Cancel' to stop, 'OK' to delete."), $link['post_title'] )) . "') ) { return true;}return false;\">" . __('Delete') . "</a>";
895
- }
896
- $actions['view'] = '<span class="view"><a href="' . get_permalink($link['source_id']) . '" title="' . attribute_escape(sprintf(__('View "%s"'), $link['post_title'])) . '" rel="permalink">' . __('View') . '</a>';
897
- echo '<div class="row-actions">';
898
- echo implode(' | </span>', $actions);
899
- echo '</div>';
900
-
901
- } elseif ( 'blogroll' == $link['source_type'] ) {
902
-
903
- echo "<a class='row-title' href='link.php?action=edit&amp;link_id=$link[source_id]' title='Edit this bookmark'>{$link[link_text]}</a>";
904
-
905
- //Output inline action links
906
- $actions = array();
907
- if ( current_user_can('manage_links') ) {
908
- $actions['edit'] = '<span class="edit"><a href="link.php?action=edit&amp;link_id=' . $link['source_id'] . '" title="' . attribute_escape(__('Edit this bookmark')) . '">' . __('Edit') . '</a>';
909
- $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>";
910
- }
911
-
912
- echo '<div class="row-actions">';
913
- echo implode(' | </span>', $actions);
914
- echo '</div>';
915
-
916
- } elseif ( empty($link['source_type']) ){
917
-
918
- echo "[An orphaned link! This is a bug.]";
919
-
920
- }
921
- ?>
922
- </td>
923
- <td class='blc-link-text'><?php
924
- if ( 'post' == $link['source_type'] ){
925
-
926
- if ( 'link' == $link['instance_type'] ) {
927
- print strip_tags($link['link_text']);
928
- } elseif ( 'image' == $link['instance_type'] ){
929
- echo "<img src='" . WP_PLUGIN_URL . "/broken-link-checker/images/image.png' class='blc-small-image' alt='Image' title='Image'> Image";
930
- } else {
931
- echo '[ ??? ]';
932
- }
933
-
934
- } elseif ( 'custom_field' == $link['source_type'] ){
935
-
936
- echo "<img src='" . WP_PLUGIN_URL . "/broken-link-checker/images/script_code.png' class='blc-small-image' title='Custom field' alt='Custom field'> ";
937
- echo "<code>".$link['link_text']."</code>";
938
-
939
- } elseif ( 'blogroll' == $link['source_type'] ){
940
- //echo $link['link_text'];
941
- echo "<img src='" . WP_PLUGIN_URL . "/broken-link-checker/images/link.png' class='blc-small-image' title='Bookmark' alt='Bookmark'> Bookmark";
942
- }
943
- ?>
944
- </td>
945
- <td class='column-url'>
946
- <a href='<?php print $link['url']; ?>' target='_blank' class='blc-link-url'>
947
- <?php print $this->mytruncate($link['url']); ?></a>
948
- <input type='text' id='link-editor-<?php print $rownum; ?>'
949
- value='<?php print attribute_escape($link['url']); ?>'
950
- class='blc-link-editor' style='display:none' />
951
- <?php
952
- //Output inline action links for the link/URL
953
- $actions = array();
954
-
955
- $actions['details'] = "<span class='view'><a class='blc-details-button' href='javascript:void(0)' title='Show more info about this link'>Details</a>";
956
-
957
- $actions['delete'] = "<span class='delete'><a class='submitdelete blc-unlink-button' title='Remove this link from all posts' ".
958
- "id='unlink-button-$rownum' href='javascript:void(0);'>Unlink</a>";
959
-
960
- if ( $excluded ){
961
- $actions['exclude'] = "<span class='delete'>Excluded";
962
- } else {
963
- $actions['exclude'] = "<span class='delete'><a class='submitdelete blc-exclude-button' title='Add this URL to the exclusion list' ".
964
- "id='exclude-button-$rownum' href='javascript:void(0);'>Exclude</a>";
965
- }
966
-
967
- $actions['edit'] = "<span class='edit'><a href='javascript:void(0)' class='blc-edit-button' title='Edit link URL'>Edit URL</a>";
968
-
969
- echo '<div class="row-actions">';
970
- echo implode(' | </span>', $actions);
971
-
972
- echo "<span style='display:none' class='blc-cancel-button-container'> ",
973
- "| <a href='javascript:void(0)' class='blc-cancel-button' title='Cancel URL editing'>Cancel</a></span>";
974
-
975
- echo '</div>';
976
- ?>
977
- </td>
978
- <?php if ( 'broken' == $link_type ) { ?>
979
- <td><a href='javascript:void(0);'
980
- id='discard_button-<?php print $rownum; ?>'
981
- class='blc-discard-button'
982
- title='Remove this message and mark the link as valid'>Discard</a>
983
- </td>
984
- <?php } ?>
985
- </tr>
986
- <!-- Link details -->
987
- <tr id='<?php print "link-details-$rownum"; ?>' style='display:none;' class='blc-link-details'>
988
- <td colspan='4'><?php $this->link_details_row($link); ?></td>
989
- </tr><?php
990
- }
991
- ?></tbody></table><?php
992
-
993
- //Also display pagination links at the bottom
994
- if ( $page_links ) {
995
- echo '<div class="tablenav"><div class="tablenav-pages">';
996
- $page_links_text = sprintf( '<span class="displaying-num">' . __( 'Displaying %s&#8211;%s of <span class="current-link-count">%s</span>' ) . '</span>%s',
997
- number_format_i18n( ( $page - 1 ) * $per_page + 1 ),
998
- number_format_i18n( min( $page * $per_page, count($links) ) ),
999
- number_format_i18n( $current_filter['count'] ),
1000
- $page_links
1001
- );
1002
- echo $page_links_text;
1003
- echo '</div></div>';
1004
- }
1005
- };
1006
- ?>
1007
- <?php $this->links_page_js(); ?>
1008
- </div>
1009
- <?php
1010
- }
1011
-
1012
- function links_page_js(){
1013
- ?>
1014
- <script type='text/javascript'>
1015
 
1016
- function alterLinkCounter(factor){
1017
- cnt = parseInt(jQuery('.current-link-count').eq(0).html());
1018
- cnt = cnt + factor;
1019
- jQuery('.current-link-count').html(cnt);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1020
  }
1021
 
1022
- jQuery(function($){
1023
-
1024
- //The discard button - manually mark the link as valid. The link will be checked again later.
1025
- $(".blc-discard-button").click(function () {
1026
- var me = this;
1027
- $(me).html('Wait...');
1028
-
1029
- var link_id = $(me).parents('.blc-row').find('.blc-link-id').html();
1030
-
1031
- $.post(
1032
- "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
1033
- {
1034
- 'action' : 'blc_discard',
1035
- 'link_id' : link_id
1036
- },
1037
- function (data, textStatus){
1038
- if (data == 'OK'){
1039
- var master = $(me).parents('.blc-row');
1040
- var details = master.next('.blc-link-details');
1041
-
1042
- details.hide();
1043
- //Flash the main row green to indicate success, then hide it.
1044
- var oldColor = master.css('background-color');
1045
- master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300, function(){
1046
- master.hide();
1047
- });
1048
-
1049
- alterLinkCounter(-1);
1050
- } else {
1051
- $(me).html('Discard');
1052
- alert(data);
1053
- }
1054
- }
1055
- );
1056
- });
1057
-
1058
- //The details button - display/hide detailed info about a link
1059
- $(".blc-details-button, .blc-link-text").click(function () {
1060
- $(this).parents('.blc-row').next('.blc-link-details').toggle();
1061
- });
1062
-
1063
- //The edit button - edit/save the link's URL
1064
- $(".blc-edit-button").click(function () {
1065
- var edit_button = $(this);
1066
- var master = $(edit_button).parents('.blc-row');
1067
- var editor = $(master).find('.blc-link-editor');
1068
- var url_el = $(master).find('.blc-link-url');
1069
- var cancel_button_container = $(master).find('.blc-cancel-button-container');
1070
-
1071
- //Find the current/original URL
1072
- var orig_url = url_el.attr('href');
1073
- //Find the link ID
1074
- var link_id = $(master).find('.blc-link-id').html();
1075
-
1076
- if ( !$(editor).is(':visible') ){
1077
- //Begin editing
1078
- url_el.hide();
1079
- editor.show();
1080
- cancel_button_container.show();
1081
- editor.focus();
1082
- editor.select();
1083
- edit_button.html('Save URL');
1084
- } else {
1085
- editor.hide();
1086
- cancel_button_container.hide();
1087
- url_el.show();
1088
-
1089
- new_url = editor.val();
1090
-
1091
- if (new_url != orig_url){
1092
- //Save the changed link
1093
- url_el.html('Saving changes...');
1094
-
1095
- $.getJSON(
1096
- "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
1097
- {
1098
- 'action' : 'blc_edit',
1099
- 'link_id' : link_id,
1100
- 'new_url' : new_url
1101
- },
1102
- function (data, textStatus){
1103
- var display_url = '';
1104
-
1105
- if ( data && (typeof(data['error']) != 'undefined') ){
1106
- //data.error is an error message
1107
- alert(data.error);
1108
- display_url = orig_url;
1109
- } else {
1110
- //data contains info about the performed edit
1111
- if ( data.cnt_okay > 0 ){
1112
- display_url = new_url;
1113
-
1114
- url_el.attr('href', new_url);
1115
-
1116
- if ( data.cnt_error > 0 ){
1117
- var msg = "The link was successfully modifed.";
1118
- msg = msg + "\nHowever, "+data.cnt_error+" instances couldn't be edited and still point to the old URL."
1119
- alert(msg);
1120
- } else {
1121
- //Flash the row green to indicate success
1122
- var oldColor = master.css('background-color');
1123
- master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300);
1124
-
1125
- //Save the new ID
1126
- master.find('.blc-link-id').html(data.new_link_id);
1127
- //Load up the new link info (so sue me)
1128
- master.next('.blc-link-details').find('td').html('<center>Loading...</center>').load(
1129
- "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
1130
- {
1131
- 'action' : 'blc_link_details',
1132
- 'link_id' : data.new_link_id
1133
- }
1134
- );
1135
- }
1136
- } else {
1137
- alert("Something went wrong. The plugin failed to edit "+
1138
- data.cnt_error + ' instance(s) of this link.');
1139
-
1140
- display_url = orig_url;
1141
- }
1142
- };
1143
-
1144
- //Shorten the displayed URL if it's > 50 characters
1145
- if ( display_url.length > 50 ){
1146
- display_url = display_url.substr(0, 47) + '...';
1147
- }
1148
- url_el.html(display_url);
1149
- }
1150
- );
1151
-
1152
- } else {
1153
- //It's the same URL, so do nothing.
1154
- }
1155
- edit_button.html('Edit URL');
1156
- }
1157
- });
1158
-
1159
- $(".blc-cancel-button").click(function () {
1160
- var master = $(this).parents('.blc-row');
1161
- var url_el = $(master).find('.blc-link-url');
1162
-
1163
- //Hide the cancel button
1164
- $(this).parent().hide();
1165
- //Show the un-editable URL again
1166
- url_el.show();
1167
- //reset and hide the editor
1168
- master.find('.blc-link-editor').hide().val(url_el.attr('href'));
1169
- //Set the edit button to say "Edit URL"
1170
- master.find('.blc-edit-button').html('Edit URL');
1171
- });
1172
-
1173
- //The unlink button - remove the link/image from all posts, custom fields, etc.
1174
- $(".blc-unlink-button").click(function () {
1175
- var me = this;
1176
- var master = $(me).parents('.blc-row');
1177
- $(me).html('Wait...');
1178
-
1179
- var link_id = $(me).parents('.blc-row').find('.blc-link-id').html();
1180
-
1181
- $.post(
1182
- "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
1183
- {
1184
- 'action' : 'blc_unlink',
1185
- 'link_id' : link_id
1186
- },
1187
- function (data, textStatus){
1188
- eval('data = ' + data);
1189
-
1190
- if ( data && ( typeof(data['ok']) != 'undefined') ){
1191
- //Hide the details
1192
- master.next('.blc-link-details').hide();
1193
- //Flash the main row green to indicate success, then hide it.
1194
- var oldColor = master.css('background-color');
1195
- master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300, function(){
1196
- master.hide();
1197
- });
1198
-
1199
- alterLinkCounter(-1);
1200
- } else {
1201
- $(me).html('Unlink');
1202
- //Show the error message
1203
- alert(data.error);
1204
- }
1205
- }
1206
- );
1207
- });
1208
-
1209
- //The exclude button - Add this link to the exclusion list
1210
- $(".blc-exclude-button").click(function () {
1211
- var me = this;
1212
- var master = $(me).parents('.blc-row');
1213
- var details = master.next('.blc-link-details');
1214
- $(me).html('Wait...');
1215
-
1216
- var link_id = $(me).parents('.blc-row').find('.blc-link-id').html();
1217
-
1218
- $.post(
1219
- "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
1220
- {
1221
- 'action' : 'blc_exclude_link',
1222
- 'link_id' : link_id
1223
- },
1224
- function (data, textStatus){
1225
- eval('data = ' + data);
1226
-
1227
- if ( data && ( typeof(data['ok']) != 'undefined' ) ){
1228
-
1229
- if ( 'broken' == blc_current_filter ){
1230
- //Flash the row green to indicate success, then hide it.
1231
- $(me).replaceWith('Excluded');
1232
- master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: '#E2E2E2' }, 200, function(){
1233
- details.hide();
1234
- master.hide();
1235
- alterLinkCounter(-1);
1236
- });
1237
- master.addClass('blc-excluded-link');
1238
- } else {
1239
- //Flash the row green to indicate success and fade to the "excluded link" color
1240
- master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: '#E2E2E2' }, 300);
1241
- master.addClass('blc-excluded-link');
1242
- $(me).replaceWith('Excluded');
1243
- }
1244
- } else {
1245
- $(me).html('Exclude');
1246
- alert(data.error);
1247
- }
1248
- }
1249
- );
1250
- });
1251
-
1252
- });
1253
-
1254
- </script>
1255
- <?php
1256
- }
1257
-
1258
- function link_details_row($link){
1259
- ?>
1260
- <span id='post_date_full' style='display:none;'><?php
1261
- print $link['post_date'];
1262
- ?></span>
1263
- <span id='check_date_full' style='display:none;'><?php
1264
- print $link['last_check'];
1265
- ?></span>
1266
- <ol style='list-style-type: none; width: 50%; float: right;'>
1267
- <li><strong>Log :</strong>
1268
- <span class='blc_log'><?php
1269
- print nl2br($link['log']);
1270
- ?></span></li>
1271
- </ol>
1272
-
1273
- <ol style='list-style-type: none; padding-left: 2px;'>
1274
- <?php if ( !empty($link['post_date']) ) { ?>
1275
- <li><strong>Post published on :</strong>
1276
- <span class='post_date'><?php
1277
- print strftime("%B %d, %Y",strtotime($link['post_date']));
1278
- ?></span></li>
1279
- <?php } ?>
1280
- <li><strong>Link last checked :</strong>
1281
- <span class='check_date'><?php
1282
- $last_check = strtotime($link['last_check']);
1283
- if ( $last_check < strtotime('-10 years') ){
1284
- echo 'Never';
1285
- } else {
1286
- echo strftime( "%B %d, %Y", $last_check );
1287
- }
1288
- ?></span></li>
1289
-
1290
- <li><strong>HTTP code :</strong>
1291
- <span class='http_code'><?php
1292
- print $link['http_code'];
1293
- ?></span></li>
1294
-
1295
- <li><strong>Response time :</strong>
1296
- <span class='request_duration'><?php
1297
- printf('%2.3f seconds', $link['request_duration']);
1298
- ?></span></li>
1299
-
1300
- <li><strong>Final URL :</strong>
1301
- <span class='final_url'><?php
1302
- print $link['final_url'];
1303
- ?></span></li>
1304
-
1305
- <li><strong>Redirect count :</strong>
1306
- <span class='redirect_count'><?php
1307
- print $link['redirect_count'];
1308
- ?></span></li>
1309
-
1310
- <li><strong>Instance count :</strong>
1311
- <span class='instance_count'><?php
1312
- print $link['instance_count'];
1313
- ?></span></li>
1314
-
1315
- <?php if ( intval( $link['check_count'] ) > 0 ){ ?>
1316
- <li><br/>This link has failed
1317
- <span class='check_count'><?php
1318
- echo $link['check_count'];
1319
- if ( intval($link['check_count'])==1 ){
1320
- echo ' time';
1321
- } else {
1322
- echo ' times';
1323
- }
1324
- ?></span>.</li>
1325
- <?php } ?>
1326
- </ol>
1327
- <?php
1328
- }
1329
-
1330
- /**
1331
- * ws_broken_link_checker::cleanup_links()
1332
- * Remove orphaned links that have no corresponding instances
1333
- *
1334
- * @param int $link_id (optional) Only check this link
1335
- * @return bool
1336
- */
1337
- function cleanup_links( $link_id = null ){
1338
- global $wpdb;
1339
-
1340
- $q = "DELETE FROM {$wpdb->prefix}blc_links
1341
- USING {$wpdb->prefix}blc_links LEFT JOIN {$wpdb->prefix}blc_instances
1342
- ON {$wpdb->prefix}blc_instances.link_id = {$wpdb->prefix}blc_links.link_id
1343
- WHERE
1344
- {$wpdb->prefix}blc_instances.link_id IS NULL";
1345
-
1346
- if ( $link_id !==null ) {
1347
- $q .= " AND {$wpdb->prefix}blc_links.link_id = " . intval( $link_id );
1348
- }
1349
-
1350
- return $wpdb->query( $q );
1351
- }
1352
-
1353
- /**
1354
- * ws_broken_link_checker::cleanup_instances()
1355
- * Remove instances that reference invalid posts or bookmarks
1356
- *
1357
- * @return bool
1358
- */
1359
- function cleanup_instances(){
1360
- global $wpdb;
1361
-
1362
- //Delete all instances that reference non-existent posts
1363
- $q = "DELETE FROM {$wpdb->prefix}blc_instances
1364
- USING {$wpdb->prefix}blc_instances LEFT JOIN {$wpdb->posts} ON {$wpdb->prefix}blc_instances.source_id = {$wpdb->posts}.ID
1365
- WHERE
1366
- {$wpdb->posts}.ID IS NULL
1367
- AND ( ( {$wpdb->prefix}blc_instances.source_type = 'post' ) OR ( {$wpdb->prefix}blc_instances.source_type = 'custom_field' ) )";
1368
- $rez = $wpdb->query($q);
1369
-
1370
- //Delete all instances that reference non-existant bookmarks
1371
- $q = "DELETE FROM {$wpdb->prefix}blc_instances
1372
- USING {$wpdb->prefix}blc_instances LEFT JOIN {$wpdb->links} ON {$wpdb->prefix}blc_instances.source_id = {$wpdb->links}.link_id
1373
- WHERE
1374
- {$wpdb->links}.link_id IS NULL
1375
- AND {$wpdb->prefix}blc_instances.source_type = 'blogroll' ";
1376
- $rez2 = $wpdb->query($q);
1377
-
1378
- return $rez and $rez2;
1379
- }
1380
-
1381
- function load_options(){
1382
- $this->options = get_option($this->options_name);
1383
- if( !is_array( $this->options ) ){
1384
- $this->options = $this->defaults;
1385
- } else {
1386
- $this->options = array_merge( $this->defaults, $this->options );
1387
- }
1388
- }
1389
-
1390
- function save_options(){
1391
- update_option($this->options_name,$this->options);
1392
- }
1393
-
1394
- /**
1395
- * ws_broken_link_checker::parse_post()
1396
- * Parse a post for links and save them to the DB.
1397
- *
1398
- * @param string $content Post content
1399
- * @param int $post_id Post ID
1400
- * @return void
1401
- */
1402
- function parse_post($content, $post_id){
1403
- //remove all <code></code> blocks first
1404
- $content = preg_replace('/<code>.+?<\/code>/i', ' ', $content);
1405
- //remove all <pre></pre> blocks as well
1406
- $content = preg_replace('/<pre[^>]*>.+?<\/pre>/i', ' ', $content);
1407
- //Get the post permalink - it's used to resolve relative URLs
1408
- $permalink = get_permalink( $post_id );
1409
-
1410
- //Find links
1411
- if(preg_match_all(blcUtility::link_pattern(), $content, $matches, PREG_SET_ORDER)){
1412
- foreach($matches as $link){
1413
- $url = $link[3];
1414
- $text = strip_tags( $link[5] );
1415
- //FB::log($url, "Found link");
1416
-
1417
- $url = blcUtility::normalize_url($url, $permalink);
1418
- //Skip invalid links
1419
- if ( !$url || (strlen($url)<6) ) continue;
1420
-
1421
- //Create or load the link
1422
- $link_obj = new blcLink($url);
1423
- //Add & save a new instance
1424
- $link_obj->add_instance($post_id, 'post', $text, 'link');
1425
- }
1426
- };
1427
-
1428
- //Find images (<img src=...>)
1429
- if(preg_match_all(blcUtility::img_pattern(), $content, $matches, PREG_SET_ORDER)){
1430
- foreach($matches as $img){
1431
- $url = $img[3];
1432
- //FB::log($url, "Found image");
1433
-
1434
- $url = blcUtility::normalize_url($url, $permalink);
1435
- if ( !$url || (strlen($url)<6) ) continue; //skip invalid URLs
1436
-
1437
- //Create or load the link
1438
- $link = new blcLink($url);
1439
- //Add & save a new image instance
1440
- $link->add_instance($post_id, 'post', '', 'image');
1441
- }
1442
- };
1443
- }
1444
-
1445
- /**
1446
- * ws_broken_link_checker::parse_post_meta()
1447
- * Parse a post's custom fields for links and save them in the DB.
1448
- *
1449
- * @param id $post_id
1450
- * @return void
1451
- */
1452
- function parse_post_meta($post_id){
1453
- //Get all custom fields of this post
1454
- $custom_fields = get_post_custom( $post_id );
1455
- //FB::log($custom_fields, "Custom fields loaded");
1456
-
1457
- //Parse the enabled fields
1458
- foreach( $this->options['custom_fields'] as $field ){
1459
- if ( !isset($custom_fields[$field]) ) continue;
1460
-
1461
- //FB::log($field, "Parsing field");
1462
-
1463
- $values = $custom_fields[$field];
1464
- if ( !is_array( $values ) ) $values = array($values);
1465
-
1466
- foreach( $values as $value ){
1467
-
1468
- //Attempt to parse the $value as URL
1469
- $url = blcUtility::normalize_url($value);
1470
- if ( empty($url) ){
1471
- //FB::warn($value, "Invalid URL in custom field ".$field);
1472
- continue;
1473
- }
1474
-
1475
- //FB::log($url, "Found URL");
1476
- $link = new blcLink( $url );
1477
- //FB::log($link, 'Created/loaded link');
1478
- $inst = $link->add_instance( $post_id, 'custom_field', $field, 'link' );
1479
- //FB::log($inst, 'Created instance');
1480
- }
1481
- }
1482
-
1483
- }
1484
-
1485
- function parse_blogroll_link( $the_link ){
1486
- //FB::log($the_link, "Parsing blogroll link");
1487
-
1488
- //Attempt to parse the URL
1489
- $url = blcUtility::normalize_url( $the_link['link_url'] );
1490
- if ( empty($url) ){
1491
- //FB::warn( $the_link['link_url'], "Invalid URL in for a blogroll link".$the_link['link_name'] );
1492
- return false;
1493
- }
1494
-
1495
- //FB::log($url, "Found URL");
1496
- $link = new blcLink( $url );
1497
- return $link->add_instance( $the_link['link_id'], 'blogroll', $the_link['link_name'], 'link' );
1498
- }
1499
-
1500
- function start_timer(){
1501
- $this->execution_start_time = microtime_float();
1502
- }
1503
-
1504
- function execution_time(){
1505
- return microtime_float() - $this->execution_start_time;
1506
- }
1507
-
1508
- /**
1509
- * ws_broken_link_checker::work()
1510
- * The main worker function that does all kinds of things.
1511
- *
1512
- * @return void
1513
- */
1514
- function work(){
1515
- global $wpdb;
1516
-
1517
- if ( !$this->acquire_lock() ){
1518
- //FB::warn("Another instance of BLC is already working. Stop.");
1519
- return false;
1520
- }
1521
-
1522
- $this->start_timer();
1523
-
1524
- $max_execution_time = $this->options['max_execution_time'];
1525
-
1526
- /*****************************************
1527
- Preparation
1528
- ******************************************/
1529
- // Check for safe mode
1530
- if( ini_get('safe_mode') ){
1531
- // Do it the safe mode way
1532
- $t=ini_get('max_execution_time');
1533
- if ($t && ($t < $max_execution_time))
1534
- $max_execution_time = $t-1;
1535
- } else {
1536
- // Do it the regular way
1537
- @set_time_limit( $max_execution_time * 2 ); //x2 should be plenty, running any longer would mean a glitch.
1538
- }
1539
- @ignore_user_abort(true);
1540
-
1541
- $check_threshold = date('Y-m-d H:i:s', strtotime('-'.$this->options['check_threshold'].' hours'));
1542
- $recheck_threshold = date('Y-m-d H:i:s', strtotime('-20 minutes'));
1543
-
1544
- $orphans_possible = false;
1545
-
1546
- $still_need_resynch = $this->options['need_resynch'];
1547
-
1548
- /*****************************************
1549
- Parse posts and bookmarks
1550
- ******************************************/
1551
-
1552
- if ( $this->options['need_resynch'] ) {
1553
-
1554
- //FB::log("Looking for posts and bookmarks that need parsing...");
1555
-
1556
- $tsynch = $wpdb->prefix.'blc_synch';
1557
- $tposts = $wpdb->posts;
1558
- $tlinks = $wpdb->links;
1559
-
1560
- $synch_q = "SELECT $tsynch.source_id, $tsynch.source_type, $tposts.post_content, $tlinks.link_url, $tlinks.link_id, $tlinks.link_name
1561
-
1562
- FROM
1563
- $tsynch LEFT JOIN $tposts
1564
- ON ($tposts.id = $tsynch.source_id AND $tsynch.source_type='post')
1565
- LEFT JOIN $tlinks
1566
- ON ($tlinks.link_id = $tsynch.source_id AND $tsynch.source_type='blogroll')
1567
-
1568
- WHERE
1569
- $tsynch.synched = 0
1570
-
1571
- LIMIT 50";
1572
-
1573
- while ( $rows = $wpdb->get_results($synch_q, ARRAY_A) ) {
1574
-
1575
- //FB::log("Found ".count($rows)." items to analyze.");
1576
-
1577
- foreach ($rows as $row) {
1578
-
1579
- if ( $row['source_type'] == 'post' ){
1580
-
1581
- //FB::log("Parsing post ".$row['source_id']);
1582
-
1583
- //Remove instances associated with this post
1584
- $q = "DELETE FROM {$wpdb->prefix}blc_instances
1585
- WHERE source_id = %d AND (source_type = 'post' OR source_type='custom_field')";
1586
- $q = $wpdb->prepare($q, intval($row['source_id']));
1587
-
1588
- //FB::log($q, "Executing query");
1589
-
1590
- if ( $wpdb->query( $q ) === false ){
1591
- //FB::error($wpdb->last_error, "Database error");
1592
- }
1593
-
1594
- //Gather links and images from the post
1595
- $this->parse_post( $row['post_content'], $row['source_id'] );
1596
- //Gather links from custom fields
1597
- $this->parse_post_meta( $row['source_id'] );
1598
-
1599
- //Some link records might be orhpaned now
1600
- $orphans_possible = true;
1601
-
1602
- } else {
1603
-
1604
- //FB::log("Parsing bookmark ".$row['source_id']);
1605
-
1606
- //Remove instances associated with this bookmark
1607
- $q = "DELETE FROM {$wpdb->prefix}blc_instances
1608
- WHERE source_id = %d AND source_type = 'blogroll'";
1609
- $q = $wpdb->prepare($q, intval($row['source_id']));
1610
- //FB::log($q, "Executing query");
1611
-
1612
- if ( $wpdb->query( $q ) === false ){
1613
- //FB::error($wpdb->last_error, "Database error");
1614
- }
1615
-
1616
- //(Re)add the instance and link
1617
- $this->parse_blogroll_link( $row );
1618
-
1619
- //Some link records might be orhpaned now
1620
- $orphans_possible = true;
1621
-
1622
- }
1623
-
1624
- //Update the table to indicate the item has been parsed
1625
- $this->mark_synched( $row['source_id'], $row['source_type'] );
1626
-
1627
- //Check if we still have some execution time left
1628
- if( $this->execution_time() > $max_execution_time ){
1629
- //FB::log('The alloted execution time has run out');
1630
- $this->cleanup_links();
1631
- $this->release_lock();
1632
- return;
1633
- }
1634
-
1635
- }
1636
-
1637
- }
1638
-
1639
- //FB::log('No unparsed items found.');
1640
- $still_need_resynch = false;
1641
-
1642
- if ( $wpdb->last_error ){
1643
- //FB::error($wpdb->last_error, "Database error");
1644
- }
1645
-
1646
- } else {
1647
- //FB::log('Resynch not required.');
1648
- }
1649
-
1650
- /******************************************
1651
- Resynch done?
1652
- *******************************************/
1653
- if ( $this->options['need_resynch'] && !$still_need_resynch ){
1654
- $this->options['need_resynch'] = $still_need_resynch;
1655
- $this->save_options();
1656
- }
1657
-
1658
- /******************************************
1659
- Remove orphaned links
1660
- *******************************************/
1661
-
1662
- if ( $orphans_possible ) {
1663
- //FB::log('Cleaning up the link table.');
1664
- $this->cleanup_links();
1665
- }
1666
-
1667
- //Check if we still have some execution time left
1668
- if( $this->execution_time() > $max_execution_time ){
1669
- //FB::log('The alloted execution time has run out');
1670
- $this->release_lock();
1671
- return;
1672
- }
1673
-
1674
- /*****************************************
1675
- Check links
1676
- ******************************************/
1677
- //FB::log('Looking for links to check (threshold : '.$check_threshold.')...');
1678
-
1679
- //Select some links that haven't been checked for a long time or
1680
- //that are broken and need to be re-checked again.
1681
-
1682
- //Note : This is a slow query, but AFAIK there is no way to speed it up.
1683
- //I could put an index on last_check, but that value is almost certainly unique
1684
- //for each row so it wouldn't be much better than a full table scan.
1685
- $q = "SELECT *, ( last_check < %s ) AS meets_check_threshold
1686
- FROM {$wpdb->prefix}blc_links
1687
- WHERE
1688
- ( last_check < %s )
1689
- OR
1690
- (
1691
- ( http_code >= 400 OR http_code < 200 OR timeout = 1)
1692
- AND check_count < %d
1693
- AND check_count > 0
1694
- AND last_check < %s
1695
- )
1696
- ORDER BY last_check ASC
1697
- LIMIT 50";
1698
- $link_q = $wpdb->prepare($q, $check_threshold, $check_threshold, $this->options['recheck_count'], $recheck_threshold);
1699
- //FB::log($link_q);
1700
-
1701
- while ( $links = $wpdb->get_results($link_q, ARRAY_A) ){
1702
-
1703
- //some unchecked links found
1704
- //FB::log("Checking ".count($links)." link(s)");
1705
-
1706
- foreach ($links as $link) {
1707
- $link_obj = new blcLink($link);
1708
-
1709
- //Does this link need to be checked?
1710
- if ( !$this->is_excluded( $link['url'] ) ) {
1711
- //Yes, do it
1712
- //FB::log("Checking link {$link[link_id]}");
1713
- $link_obj->check();
1714
- $link_obj->save();
1715
- } else {
1716
- //Nope, mark it as already checked.
1717
- //FB::info("The URL {$link_obj->url} is excluded, marking link {$link_obj->link_id} as already checked.");
1718
- $link_obj->last_check = date('Y-m-d H:i:s');
1719
- $link_obj->http_code = 200; //Use a fake code so that the link doesn't show up in queries looking for broken links.
1720
- $link_obj->timeout = false;
1721
- $link_obj->request_duration = 0;
1722
- $link_obj->log = "This link wasn't checked because a matching keyword was found on your exclusion list.";
1723
- $link_obj->save();
1724
- }
1725
-
1726
- //Check if we still have some execution time left
1727
- if( $this->execution_time() > $max_execution_time ){
1728
- //FB::log('The alloted execution time has run out');
1729
- $this->release_lock();
1730
- return;
1731
- }
1732
- }
1733
- }
1734
- //FB::log('No links need to be checked right now.');
1735
-
1736
- $this->release_lock();
1737
- //FB::log('All done.');
1738
- }
1739
-
1740
- function ajax_full_status( ){
1741
- $status = $this->get_status();
1742
- $text = $this->status_text( $status );
1743
-
1744
- echo json_encode( array(
1745
- 'text' => $text,
1746
- 'status' => $status,
1747
- ) );
1748
-
1749
- die();
1750
- }
1751
-
1752
- /**
1753
- * ws_broken_link_checker::status_text()
1754
- * Generates a status message based on the status info in $status
1755
- *
1756
- * @param array $status
1757
- * @return string
1758
- */
1759
- function status_text( $status ){
1760
- $text = '';
1761
-
1762
- if( $status['broken_links'] > 0 ){
1763
- $text .= sprintf( "<a href='%stools.php?page=view-broken-links' title='View broken links'><strong>Found %d broken link%s</strong></a>",
1764
- get_option('wpurl'), $status['broken_links'], ( $status['broken_links'] == 1 )?'':'s' );
1765
- } else {
1766
- $text .= "No broken links found.";
1767
- }
1768
-
1769
- $text .= "<br/>";
1770
-
1771
- if( $status['unchecked_links'] > 0) {
1772
- $text .= sprintf( '%d URL%s in the work queue', $status['unchecked_links'], ($status['unchecked_links'] == 1)?'':'s' );
1773
- } else {
1774
- $text .= "No URLs in the work queue.";
1775
- }
1776
-
1777
- $text .= "<br/>";
1778
- if ( $status['known_links'] > 0 ){
1779
- $text .= sprintf( "Detected %d unique URL%s in %d link%s",
1780
- $status['known_links'], $status['known_links'] == 1 ? '' : 's',
1781
- $status['known_instances'], $status['known_instances'] == 1 ? '' : 's'
1782
- );
1783
- if ($this->options['need_resynch']){
1784
- $text .= ' and still searching...';
1785
- } else {
1786
- $text .= '.';
1787
- }
1788
- } else {
1789
- if ($this->options['need_resynch']){
1790
- $text .= 'Searching your blog for links...';
1791
- } else {
1792
- $text .= 'No links detected.';
1793
- }
1794
- }
1795
-
1796
- return $text;
1797
- }
1798
-
1799
- function ajax_dashboard_status(){
1800
- //Just display the full status.
1801
- $this->ajax_full_status( false );
1802
- /*
1803
- global $wpdb;
1804
-
1805
- //displays a notification if broken links have been found
1806
- $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links
1807
- WHERE check_count > 0 AND ( http_code < 200 OR http_code >= 400 OR timeout = 1 )";
1808
- $broken_links = $wpdb->get_var($q);
1809
-
1810
-
1811
- if($broken_links>0){
1812
- printf( "<a href='%stools.php?page=view-broken-links' title='View broken links'><strong>Found %d broken link%s</strong></a>",
1813
- get_option('wpurl'), $broken_links, ($broken_links==1)?'':'s' );
1814
- } else {
1815
- echo "No broken links found.";
1816
- }
1817
- die();
1818
- */
1819
- }
1820
-
1821
- /**
1822
- * ws_broken_link_checker::get_status()
1823
- * Returns an array with various status information about the plugin. Array key reference:
1824
- * check_threshold - date/time; links checked before this threshold should be checked again.
1825
- * recheck_threshold - date/time; broken links checked before this threshold should be re-checked.
1826
- * known_links - the number of detected unique URLs (a misleading name, yes).
1827
- * known_instances - the number of detected link instances, i.e. actual link elements in posts and other places.
1828
- * broken_links - the number of detected broken links.
1829
- * unchecked_links - the number of URLs that need to be checked ASAP; based on check_threshold and recheck_threshold.
1830
- *
1831
- * @return array
1832
- */
1833
- function get_status(){
1834
- global $wpdb;
1835
-
1836
- $check_threshold=date('Y-m-d H:i:s', strtotime('-'.$this->options['check_threshold'].' hours'));
1837
- $recheck_threshold=date('Y-m-d H:i:s', strtotime('-20 minutes'));
1838
-
1839
- $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links WHERE 1";
1840
- $known_links = $wpdb->get_var($q);
1841
-
1842
- $q = "SELECT count(*) FROM {$wpdb->prefix}blc_instances WHERE 1";
1843
- $known_instances = $wpdb->get_var($q);
1844
-
1845
- $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links
1846
- WHERE check_count > 0 AND ( http_code < 200 OR http_code >= 400 OR timeout = 1 ) AND ( http_code <> ".BLC_CHECKING." )";
1847
- $broken_links = $wpdb->get_var($q);
1848
-
1849
- $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links
1850
- WHERE
1851
- ( ( last_check < '$check_threshold' ) OR
1852
- (
1853
- ( http_code >= 400 OR http_code < 200 )
1854
- AND check_count < 3
1855
- AND last_check < '$recheck_threshold' )
1856
- )";
1857
- $unchecked_links = $wpdb->get_var($q);
1858
-
1859
- return array(
1860
- 'check_threshold' => $check_threshold,
1861
- 'recheck_threshold' => $recheck_threshold,
1862
- 'known_links' => $known_links,
1863
- 'known_instances' => $known_instances,
1864
- 'broken_links' => $broken_links,
1865
- 'unchecked_links' => $unchecked_links,
1866
- );
1867
- }
1868
-
1869
- function ajax_work(){
1870
- //Run the worker function
1871
- $this->work();
1872
- die();
1873
- }
1874
-
1875
- function ajax_discard(){
1876
- //TODO:Rewrite to use JSON instead of plaintext
1877
- if (!current_user_can('edit_others_posts')){
1878
- die( "You're not allowed to do that!" );
1879
- }
1880
-
1881
- if ( isset($_POST['link_id']) ){
1882
- //Load the link
1883
- $link = new blcLink( intval($_POST['link_id']) );
1884
-
1885
- if ( !$link->valid() ){
1886
- die("Oops, I can't find the link ".intval($_POST['link_id']) );
1887
- }
1888
- //Make it appear "not broken"
1889
- $link->last_check = date('Y-m-d H:i:s');
1890
- $link->http_code = 200;
1891
- $link->timeout = 0;
1892
- $link->check_count = 0;
1893
- $link->log = "This link was manually marked as working by the user.";
1894
-
1895
- //Save the changes
1896
- if ( $link->save() ){
1897
- die("OK");
1898
- } else {
1899
- die("Oops, couldn't modify the link!");
1900
- }
1901
- } else {
1902
- die("Error : link_id not specified");
1903
- }
1904
- }
1905
-
1906
- function ajax_edit(){
1907
- if (!current_user_can('edit_others_posts')){
1908
- die( json_encode( array(
1909
- 'error' => "You're not allowed to do that!"
1910
- )));
1911
- }
1912
-
1913
- if ( isset($_GET['link_id']) && !empty($_GET['new_url']) ){
1914
- //Load the link
1915
- $link = new blcLink( intval($_GET['link_id']) );
1916
-
1917
- if ( !$link->valid() ){
1918
- die( json_encode( array(
1919
- 'error' => "Oops, I can't find the link ".intval($_GET['link_id'])
1920
- )));
1921
- }
1922
-
1923
- $new_url = blcUtility::normalize_url($_GET['new_url']);
1924
- if ( !$new_url ){
1925
- die( json_encode( array(
1926
- 'error' => "Oops, the new URL is invalid!"
1927
- )));
1928
- }
1929
-
1930
- //Try and edit the link
1931
- $rez = $link->edit($new_url);
1932
-
1933
- if ( $rez == false ){
1934
- die( json_encode( array(
1935
- 'error' => "An unexpected error occured!"
1936
- )));
1937
- } else {
1938
- $rez['ok'] = 'OK';
1939
- die( json_encode($rez) );
1940
- }
1941
-
1942
- } else {
1943
- die( json_encode( array(
1944
- 'error' => "Error : link_id or new_url not specified"
1945
- )));
1946
- }
1947
- }
1948
-
1949
- function ajax_unlink(){
1950
- if (!current_user_can('edit_others_posts')){
1951
- die( json_encode( array(
1952
- 'error' => "You're not allowed to do that!"
1953
- )));
1954
- }
1955
-
1956
- if ( isset($_POST['link_id']) ){
1957
- //Load the link
1958
- $link = new blcLink( intval($_POST['link_id']) );
1959
-
1960
- if ( !$link->valid() ){
1961
- die( json_encode( array(
1962
- 'error' => "Oops, I can't find the link ".intval($_POST['link_id'])
1963
- )));
1964
- }
1965
-
1966
- //Try and unlink it
1967
- if ( $link->unlink() ){
1968
- die( json_encode( array(
1969
- 'ok' => "URL {$link->url} was removed."
1970
- )));
1971
- } else {
1972
- die( json_encode( array(
1973
- 'error' => "The plugin failed to remove the link."
1974
- )));
1975
- }
1976
-
1977
- } else {
1978
- die( json_encode( array(
1979
- 'error' => "Error : link_id not specified"
1980
- )));
1981
- }
1982
- }
1983
-
1984
- function ajax_link_details(){
1985
- global $wpdb;
1986
-
1987
- if (!current_user_can('edit_others_posts')){
1988
- die("You don't have sufficient privileges to access this information!");
1989
- }
1990
-
1991
- //FB::log("Loading link details via AJAX");
1992
-
1993
- if ( isset($_GET['link_id']) ){
1994
- //FB::info("Link ID found in GET");
1995
- $link_id = intval($_GET['link_id']);
1996
- } else if ( isset($_POST['link_id']) ){
1997
- //FB::info("Link ID found in POST");
1998
- $link_id = intval($_POST['link_id']);
1999
- } else {
2000
- //FB::error('Link ID not specified, you hacking bastard.');
2001
- die('Error : link ID not specified');
2002
- }
2003
-
2004
- //Load the link. link_details_row needs it as an array, so
2005
- //we'll have to do this the long way.
2006
- $q = "SELECT
2007
- links.*,
2008
- COUNT(*) as instance_count
2009
-
2010
- FROM
2011
- {$wpdb->prefix}blc_links AS links,
2012
- {$wpdb->prefix}blc_instances as instances
2013
-
2014
- WHERE
2015
- links.link_id = %d
2016
-
2017
- GROUP BY links.link_id";
2018
-
2019
- $link = $wpdb->get_row( $wpdb->prepare($q, $link_id), ARRAY_A );
2020
- if ( is_array($link) ){
2021
- //FB::info($link, 'Link loaded');
2022
- $this->link_details_row($link);
2023
- die();
2024
- } else {
2025
- die ("Failed to load link details (" . $wpdb->last_error . ")");
2026
- }
2027
- }
2028
-
2029
- function ajax_exclude_link(){
2030
- if ( !current_user_can('manage_options') ){
2031
- die( json_encode( array(
2032
- 'error' => "You're not allowed to do that!"
2033
- )));
2034
- }
2035
-
2036
- if ( isset($_POST['link_id']) ){
2037
- //Load the link
2038
- $link = new blcLink( intval($_POST['link_id']) );
2039
-
2040
- if ( !$link->valid() ){
2041
- die( json_encode( array(
2042
- 'error' => "Oops, I can't find the link ".intval($_POST['link_id'])
2043
- )));
2044
- }
2045
-
2046
- //Add the URL to the exclusion list
2047
- if ( !in_array( $link->url, $this->options['exclusion_list'] ) ){
2048
- $this->options['exclusion_list'][] = $link->url;
2049
- //Also mark it as already checked so that it doesn't show up with other broken links.
2050
- //FB::info("The URL {$link->url} is excluded, marking link {$link->link_id} as already checked.");
2051
- $link->last_check = date('Y-m-d H:i:s');
2052
- $link->http_code = 200; //Use a fake code so that the link doesn't show up in queries looking for broken links.
2053
- $link->timeout = false;
2054
- $link->request_duration = 0;
2055
- $link->log = "This link wasn't checked because a matching keyword was found on your exclusion list.";
2056
- $link->save();
2057
- }
2058
-
2059
- $this->save_options();
2060
-
2061
- die( json_encode( array(
2062
- 'ok' => "URL {$link->url} added to the exclusion list"
2063
- )));
2064
- } else {
2065
- die( json_encode( array(
2066
- 'error' => "Link ID not specified"
2067
- )));
2068
- }
2069
- }
2070
-
2071
- /**
2072
- * ws_broken_link_checker::acquire_lock()
2073
- * Create and lock a temporary file.
2074
- *
2075
- * @return bool
2076
- */
2077
- function acquire_lock(){
2078
- //Maybe we already have the lock?
2079
- if ( $this->lockfile_handle ){
2080
- return true;
2081
- }
2082
-
2083
- $fn = $this->lockfile_name();
2084
- if ( $fn ){
2085
- //Open the lockfile
2086
- $this->lockfile_handle = fopen($fn, 'w+');
2087
- if ( $this->lockfile_handle ){
2088
- //Do an exclusive lock
2089
- if (flock($this->lockfile_handle, LOCK_EX | LOCK_NB)) {
2090
- //File locked successfully
2091
- return true;
2092
- } else {
2093
- //Something went wrong
2094
- fclose($this->lockfile_handle);
2095
- $this->lockfile_handle = null;
2096
- return false;
2097
- }
2098
- } else {
2099
- //Can't open the file, fail.
2100
- return false;
2101
- }
2102
- } else {
2103
- //Uh oh, can't generate a lockfile name. Locking will be disabled.
2104
- return true;
2105
- };
2106
- }
2107
-
2108
- /**
2109
- * ws_broken_link_checker::release_lock()
2110
- * Unlock and delete the temporary file
2111
- *
2112
- * @return bool
2113
- */
2114
- function release_lock(){
2115
- if ( $this->lockfile_handle ){
2116
- //Close the file (implicitly releasing the lock)
2117
- fclose( $this->lockfile_handle );
2118
- //Delete the file
2119
- $fn = $this->lockfile_name();
2120
- if ( file_exists( $fn ) ) {
2121
- unlink( $fn );
2122
- }
2123
- $this->lockfile_handle = null;
2124
- return true;
2125
- } else {
2126
- //We didn't have the lock anyway...
2127
- return false;
2128
- }
2129
- }
2130
-
2131
- /**
2132
- * ws_broken_link_checker::lockfile_name()
2133
- * Generate system-specific lockfile filename
2134
- *
2135
- * @return string A filename or FALSE on error
2136
- */
2137
- function lockfile_name(){
2138
- //Try the plugin's directory first.
2139
- if ( is_writable( dirname(__FILE__) ) ){
2140
- return dirname(__FILE__) . '/wp_blc_lock';
2141
- } else {
2142
- //Try to find the temp directory.
2143
- $dummy = tempnam('asdf', 'blc');
2144
- if ( $dummy ) {
2145
- $path = dirname($dummy);
2146
- //Kill the dummy file
2147
- if ( file_exists( $dummy ) ) unlink($dummy);
2148
- return $path . '/wp_blc_lock';
2149
- } else {
2150
- //Try /tmp as a last resort
2151
- if ( is_writable('/tmp') ){
2152
- return '/tmp/wp_blc_lock';
2153
- } else {
2154
- return false;
2155
- }
2156
- }
2157
- }
2158
- }
2159
-
2160
- function hook_add_link( $link_id ){
2161
- $this->mark_unsynched( $link_id, 'blogroll' );
2162
- }
2163
-
2164
- function hook_edit_link( $link_id ){
2165
- $this->mark_unsynched( $link_id, 'blogroll' );
2166
- }
2167
-
2168
- function hook_delete_link( $link_id ){
2169
- global $wpdb;
2170
- //Delete the synch record
2171
- $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}blc_synch WHERE source_id = %d AND source_type='blogroll'", $link_id ) );
2172
-
2173
- //Get the matching instance record.
2174
- $inst = $wpdb->get_row( $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_instances WHERE source_id = %d AND source_type = 'blogroll'", $link_id), ARRAY_A );
2175
-
2176
- if ( !$inst ) {
2177
- //No instance record? No problem.
2178
- return;
2179
- }
2180
-
2181
- //Remove it
2182
- $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_instances WHERE instance_id = %d", $inst['instance_id']) );
2183
-
2184
- //Remove the link that was associated with this instance if it has no more related instances.
2185
- $this->cleanup_links( $inst['link_id'] );
2186
- }
2187
-
2188
- function hook_wp_dashboard_setup(){
2189
- if ( function_exists( 'wp_add_dashboard_widget' ) ) {
2190
- wp_add_dashboard_widget(
2191
- 'blc_dashboard_widget',
2192
- 'Broken Link Checker',
2193
- array( &$this, 'dashboard_widget' ),
2194
- array( &$this, 'dashboard_widget_control' )
2195
- );
2196
- }
2197
- }
2198
-
2199
- }//class ends here
2200
-
2201
- } // if class_exists...
2202
-
2203
- if ( !isset($ws_link_checker) )
2204
- $ws_link_checker = new ws_broken_link_checker();
2205
-
2206
  ?>
1
  <?php
2
+
3
  /*
4
  Plugin Name: Broken Link Checker
5
  Plugin URI: http://w-shadow.com/blog/2007/08/05/broken-link-checker-for-wordpress/
6
  Description: Checks your posts for broken links and missing images and notifies you on the dashboard if any are found.
7
+ Version: 0.5.10
8
  Author: Janis Elsts
9
  Author URI: http://w-shadow.com/blog/
10
  */
14
  MySQL 4.0 compatibility by Jeroen (www.yukka.eu)
15
  */
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  /*
18
  //FirePHP for debugging
19
  if ( !class_exists('FB') ) {
25
  //to uncomment : \/\/(\s*FB::) -> $1
26
  //*/
27
 
28
+ //Make sure some useful constants are defined
29
+ if ( ! defined( 'WP_CONTENT_URL' ) )
30
+ define( 'WP_CONTENT_URL', get_option( 'siteurl' ) . '/wp-content' );
31
+ if ( ! defined( 'WP_CONTENT_DIR' ) )
32
+ define( 'WP_CONTENT_DIR', ABSPATH . 'wp-content' );
33
+ if ( ! defined( 'WP_PLUGIN_URL' ) )
34
+ define( 'WP_PLUGIN_URL', WP_CONTENT_URL. '/plugins' );
35
+ if ( ! defined( 'WP_PLUGIN_DIR' ) )
36
+ define( 'WP_PLUGIN_DIR', WP_CONTENT_DIR . '/plugins' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
+ //The HTTP code of a link record can be set to one of these in some special circumstances
39
+ if ( ! defined('BLC_CHECKING') )
40
+ define('BLC_CHECKING', 1); //The link is currently being checked. If this state persists, suspect a glitch.
41
+ if ( ! defined('BLC_TIMEOUT') )
42
+ define('BLC_TIMEOUT', 0); //The code used for links that timed out and didn't return an actual response.
43
+
44
+ //Load and initialize the plugin's configuration
45
+ require 'config-manager.php';
46
+ $blc_config_manager = new blcConfigurationManager(
47
+ //Save the plugin's configuration into this DB option
48
+ 'wsblc_options',
49
+ //Initialize default settings
50
+ array(
51
+ 'max_execution_time' => 5*60, //How long the worker instance may run, at most.
52
+ 'check_threshold' => 72, //Check each link every 72 hours.
53
+ 'mark_broken_links' => true, //Whether to add the broken_link class to broken links in posts.
54
+ 'broken_link_css' => ".broken_link, a.broken_link {\n\ttext-decoration: line-through;\n}",
55
+ 'exclusion_list' => array(), //Links that contain a substring listed in this array won't be checked.
56
+ 'recheck_count' => 3, //[Internal] How many times a broken link should be re-checked (slightly buggy)
57
+
58
+ //These three are currently ignored. Everything is checked by default.
59
+ 'check_posts' => true,
60
+ 'check_custom_fields' => true,
61
+ 'check_blogroll' => true,
62
+
63
+ 'custom_fields' => array(), //List of custom fields that can contain URLs and should be checked.
64
+
65
+ 'autoexpand_widget' => true, //Autoexpand the Dashboard widget if broken links are detected
66
+
67
+ 'need_resynch' => false, //[Internal flag]
68
+ 'current_db_version' => 0, //The current version of the plugin's tables
69
+ )
70
+ );
71
+
72
+
73
+ if ( !is_admin() ){
74
+ //This is user-side request, so the only thing we may need to do is run the broken link highlighter.
75
+ if ( $blc_config_manager->options['mark_broken_links'] ){
76
+ //Load some utilities (used by the higlighter) and the highlighter itself
77
+ require 'utility-class.php';
78
+ require 'highlighter-class.php';
79
+ $blc_link_highlighter = new blcLinkHighlighter( $blc_config_manager->options['broken_link_css'] );
80
+ }
81
+ } else {
82
+ //Load everything
83
+ require 'utility-class.php';
84
+ require 'instance-classes.php';
85
+ require 'link-classes.php';
86
+ require 'core.php';
87
+
88
+ $ws_link_checker = new wsBrokenLinkChecker( __FILE__ , $blc_config_manager );
89
  }
90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  ?>
config-manager.php ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @author W-Shadow
5
+ * @copyright 2009
6
+ */
7
+
8
+ class blcConfigurationManager {
9
+
10
+ var $option_name;
11
+
12
+ var $options;
13
+ var $defaults;
14
+ var $loaded_values;
15
+
16
+ function blcConfigurationManager( $option_name = '', $default_settings = null ){
17
+ $this->option_name = $option_name;
18
+
19
+ if ( is_array($default_settings) ){
20
+ $this->defaults = $default_settings;
21
+ } else {
22
+ $this->defaults = array();
23
+ }
24
+ $this->loaded_values = array();
25
+
26
+ $this->options = $this->defaults;
27
+
28
+ if ( !empty( $this->option_name ) )
29
+ $this->load_options();
30
+ }
31
+
32
+ function set_defaults( $default_settings = null ){
33
+ if ( is_array($default_settings) ){
34
+ $this->defaults = array();
35
+ } else {
36
+ $this->defaults = $default_settings;
37
+ }
38
+ $this->options = array_merge($this->defaults, $this->loaded_values);
39
+ }
40
+
41
+ /**
42
+ * blcOptionManager::load_options()
43
+ * Load plugin options from the database. The current $options values are not affected
44
+ * if this function fails.
45
+ *
46
+ * @param string $option_name
47
+ * @return bool True if options were loaded, false otherwise.
48
+ */
49
+ function load_options( $option_name = '' ){
50
+ if ( !empty($option_name) ){
51
+ $this->option_name = $option_name;
52
+ }
53
+
54
+ if ( empty($this->option_name) ) return false;
55
+
56
+ $new_options = get_option($this->option_name);
57
+ if( !is_array( $new_options ) ){
58
+ return false;
59
+ } else {
60
+ $this->loaded_values = $new_options;
61
+ $this->options = array_merge( $this->defaults, $this->loaded_values );
62
+ return true;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * blcOptionManager::save_options()
68
+ * Save plugin options to the databse.
69
+ *
70
+ * @param string $option_name (Optional) Save the options under this name
71
+ * @return bool True on success, false on failure
72
+ */
73
+ function save_options( $option_name = '' ){
74
+ if ( !empty($option_name) ){
75
+ $this->option_name = $option_name;
76
+ }
77
+
78
+ if ( empty($this->option_name) ) return false;
79
+
80
+ update_option( $this->option_name, $this->options );
81
+ return true;
82
+ }
83
+ }
84
+
85
+ ?>
core.php ADDED
@@ -0,0 +1,2137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ //The plugin will use Snoopy in case CURL is not available
4
+ if (!class_exists('Snoopy')) require_once(ABSPATH.'/wp-includes/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('ws_broken_link_checker')) {
18
+
19
+ class wsBrokenLinkChecker {
20
+ var $conf;
21
+
22
+ var $loader;
23
+ var $my_basename = '';
24
+
25
+ var $db_version = 2;
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
+ /**
31
+ * wsBrokenLinkChecker::wsBrokenLinkChecker()
32
+ * Class constructor
33
+ *
34
+ * @param string $loader The fully qualified filename of the loader script that WP identifies as the "main" plugin file.
35
+ * @param blcConfigurationManager $conf An instance of the configuration manager
36
+ * @return void
37
+ */
38
+ function wsBrokenLinkChecker ( $loader, $conf ) {
39
+ global $wpdb;
40
+
41
+ $this->loader = $loader;
42
+ $this->conf = $conf;
43
+
44
+ add_action('activate_' . plugin_basename( $this->loader ), array(&$this,'activation'));
45
+ $this->my_basename = plugin_basename( $this->loader );
46
+
47
+ add_action('admin_menu', array(&$this,'admin_menu'));
48
+
49
+ //These hooks update the plugin's internal records when posts are added, deleted or modified.
50
+ add_action('delete_post', array(&$this,'post_deleted'));
51
+ add_action('save_post', array(&$this,'post_saved'));
52
+
53
+ //These do the same for (blogroll) links.
54
+ add_action('add_link', array(&$this,'hook_add_link'));
55
+ add_action('edit_link', array(&$this,'hook_edit_link'));
56
+ add_action('delete_link', array(&$this,'hook_delete_link'));
57
+
58
+ //Load jQuery on Dashboard pages (possibly redundant as WP already does that)
59
+ add_action('admin_print_scripts', array(&$this,'admin_print_scripts'));
60
+
61
+ //The dashboard widget
62
+ add_action('wp_dashboard_setup', array(&$this, 'hook_wp_dashboard_setup'));
63
+
64
+ //AJAXy hooks
65
+ //TODO: Check nonces in AJAX hooks
66
+ add_action( 'wp_ajax_blc_full_status', array(&$this,'ajax_full_status') );
67
+ add_action( 'wp_ajax_blc_dashboard_status', array(&$this,'ajax_dashboard_status') );
68
+ add_action( 'wp_ajax_blc_work', array(&$this,'ajax_work') );
69
+ add_action( 'wp_ajax_blc_discard', array(&$this,'ajax_discard') );
70
+ add_action( 'wp_ajax_blc_edit', array(&$this,'ajax_edit') );
71
+ add_action( 'wp_ajax_blc_link_details', array(&$this,'ajax_link_details') );
72
+ add_action( 'wp_ajax_blc_exclude_link', array(&$this,'ajax_exclude_link') );
73
+ add_action( 'wp_ajax_blc_unlink', array(&$this,'ajax_unlink') );
74
+
75
+ //Check if it's possible to create a lockfile and nag the user about it if not.
76
+ if ( $this->lockfile_name() ){
77
+ //Lockfiles work, so it's safe to enable the footer hook that will call the worker
78
+ //function via AJAX.
79
+ add_action('admin_footer', array(&$this,'admin_footer'));
80
+ } else {
81
+ //No lockfiles, nag nag nag!
82
+ add_action( 'admin_notices', array( &$this, 'lockfile_warning' ) );
83
+ }
84
+
85
+ }
86
+
87
+ function admin_footer(){
88
+ ?>
89
+ <!-- wsblc admin footer -->
90
+ <div id='wsblc_updater_div'></div>
91
+ <script type='text/javascript'>
92
+ (function($){
93
+
94
+ function blcDoWork(){
95
+ $.post(
96
+ "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
97
+ {
98
+ 'action' : 'blc_work'
99
+ },
100
+ function (data, textStatus){
101
+
102
+ }
103
+ );
104
+ }
105
+ //Call it the first time
106
+ blcDoWork();
107
+
108
+ //Then call it periodically every X seconds
109
+ setInterval(blcDoWork, <?php echo (intval($this->conf->options['max_execution_time']) + 1 )*1000; ?>);
110
+
111
+ })(jQuery);
112
+ </script>
113
+ <!-- /wsblc admin footer -->
114
+ <?php
115
+ }
116
+
117
+ function is_excluded($url){
118
+ if (!is_array($this->conf->options['exclusion_list'])) return false;
119
+ foreach($this->conf->options['exclusion_list'] as $excluded_word){
120
+ if (stristr($url, $excluded_word)){
121
+ return true;
122
+ }
123
+ }
124
+ return false;
125
+ }
126
+
127
+ function dashboard_widget(){
128
+ ?>
129
+ <p id='wsblc_activity_box'>Loading...</p>
130
+ <script type='text/javascript'>
131
+ jQuery(function($){
132
+ var blc_was_autoexpanded = false;
133
+
134
+ function blcDashboardStatus(){
135
+ $.getJSON(
136
+ "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
137
+ {
138
+ 'action' : 'blc_dashboard_status'
139
+ },
140
+ function (data, textStatus){
141
+ if ( data && ( typeof(data.text) != 'undefined' ) ) {
142
+ $('#wsblc_activity_box').html(data.text);
143
+ <?php if ( $this->conf->options['autoexpand_widget'] ) { ?>
144
+ //Expand the widget if there are broken links.
145
+ //Do this only once per pageload so as not to annoy the user.
146
+ if ( !blc_was_autoexpanded && ( data.status.broken_links > 0 ) ){
147
+ $('#blc_dashboard_widget.postbox').removeClass('closed');
148
+ blc_was_autoexpanded = true;
149
+ };
150
+ <?php } ?>
151
+ } else {
152
+ $('#wsblc_activity_box').html('[ Network error ]');
153
+ }
154
+
155
+ setTimeout( blcDashboardStatus, 120*1000 ); //...update every two minutes
156
+ }
157
+ );
158
+ }
159
+
160
+ blcDashboardStatus();//Call it the first time
161
+
162
+ });
163
+ </script>
164
+ <?php
165
+ }
166
+
167
+ function dashboard_widget_control( $widget_id, $form_inputs = array() ){
168
+ if ( 'POST' == $_SERVER['REQUEST_METHOD'] && 'blc_dashboard_widget' == $_POST['widget_id'] ) {
169
+ //It appears $form_inputs isn't used in the current WP version, so lets just use $_POST
170
+ $this->conf->options['autoexpand_widget'] = !empty($_POST['blc-autoexpand']);
171
+ $this->conf->save_options();
172
+ }
173
+
174
+ ?>
175
+ <p><label for="blc-autoexpand">
176
+ <input id="blc-autoexpand" name="blc-autoexpand" type="checkbox" value="1" <?php if ( $this->conf->options['autoexpand_widget'] ) echo 'checked="checked"'; ?> />
177
+ Automatically expand the widget if broken links have been detected
178
+ </label></p>
179
+ <?php
180
+ }
181
+
182
+ function admin_print_scripts(){
183
+ //jQuery is used for AJAX and effects
184
+ wp_enqueue_script('jquery');
185
+ wp_enqueue_script('jquery-ui-core');
186
+ }
187
+
188
+ /**
189
+ * ws_broken_link_checker::post_deleted()
190
+ * A hook for post_deleted. Remove link instances associated with that post.
191
+ *
192
+ * @param int $post_id
193
+ * @return void
194
+ */
195
+ function post_deleted($post_id){
196
+ global $wpdb;
197
+
198
+ //FB::log($post_id, "Post deleted");
199
+ //Remove this post's instances
200
+ $q = "DELETE FROM {$wpdb->prefix}blc_instances
201
+ WHERE source_id = %d AND (source_type = 'post' OR source_type='custom_field')";
202
+ $q = $wpdb->prepare($q, intval($post_id) );
203
+
204
+ //FB::log($q, 'Executing query');
205
+
206
+ if ( $wpdb->query( $q ) === false ){
207
+ //FB::error($wpdb->last_error, "Database error");
208
+ }
209
+
210
+ //Remove the synch record
211
+ $q = "DELETE FROM {$wpdb->prefix}blc_synch
212
+ WHERE source_id = %d AND source_type = 'post'";
213
+ $wpdb->query( $wpdb->prepare($q, intval($post_id)) );
214
+
215
+ //Remove any dangling link records
216
+ $this->cleanup_links();
217
+ }
218
+
219
+ function post_saved($post_id){
220
+ global $wpdb;
221
+
222
+ $post = get_post($post_id);
223
+ //Only check links in posts, not revisions and attachments
224
+ if ( ($post->post_type != 'post') && ($post->post_type != 'page') ) return null;
225
+ //Only check published posts
226
+ if ( $post->post_status != 'publish' ) return null;
227
+
228
+ $this->mark_unsynched( $post_id, 'post' );
229
+ }
230
+
231
+ function initiate_recheck(){
232
+ global $wpdb;
233
+
234
+ //Delete all discovered instances
235
+ $wpdb->query("TRUNCATE {$wpdb->prefix}blc_instances");
236
+
237
+ //Delete all discovered links
238
+ $wpdb->query("TRUNCATE {$wpdb->prefix}blc_links");
239
+
240
+ //Mark all posts, custom fields and bookmarks for processing.
241
+ $this->resynch();
242
+ }
243
+
244
+ function resynch(){
245
+ global $wpdb;
246
+
247
+ //Drop all synchronization records
248
+ $wpdb->query("TRUNCATE {$wpdb->prefix}blc_synch");
249
+
250
+
251
+ //Create new synchronization records for posts
252
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch(source_id, source_type, synched)
253
+ SELECT id, 'post', 0
254
+ FROM {$wpdb->posts}
255
+ WHERE
256
+ {$wpdb->posts}.post_status = 'publish'
257
+ AND {$wpdb->posts}.post_type IN ('post', 'page')";
258
+ $wpdb->query( $q );
259
+
260
+ //Create new synchronization records for bookmarks (the blogroll)
261
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch(source_id, source_type, synched)
262
+ SELECT link_id, 'blogroll', 0
263
+ FROM {$wpdb->links}
264
+ WHERE 1";
265
+ $wpdb->query( $q );
266
+
267
+ //Delete invalid instances
268
+ $this->cleanup_instances();
269
+ //Delete orphaned links
270
+ $this->cleanup_links();
271
+
272
+ $this->conf->options['need_resynch'] = true;
273
+ $this->conf->save_options();
274
+ }
275
+
276
+ function mark_unsynched( $source_id, $source_type ){
277
+ global $wpdb;
278
+
279
+ $q = "REPLACE INTO {$wpdb->prefix}blc_synch( source_id, source_type, synched, last_synch)
280
+ VALUES( %d, %s, %d, NOW() )";
281
+ $rez = $wpdb->query( $wpdb->prepare( $q, $source_id, $source_type, 0 ) );
282
+
283
+ if ( !$this->conf->options['need_resynch'] ){
284
+ $this->conf->options['need_resynch'] = true;
285
+ $this->conf->save_options();
286
+ }
287
+
288
+ return $rez;
289
+ }
290
+
291
+ function mark_synched( $source_id, $source_type ){
292
+ global $wpdb;
293
+ //FB::log("Marking $source_type $source_id as synched.");
294
+ $q = "REPLACE INTO {$wpdb->prefix}blc_synch( source_id, source_type, synched, last_synch)
295
+ VALUES( %d, %s, %d, NOW() )";
296
+ return $wpdb->query( $wpdb->prepare( $q, $source_id, $source_type, 1 ) );
297
+ }
298
+
299
+ function activation(){
300
+ //Prepare the database.
301
+ $this->upgrade_database();
302
+
303
+ //Clear the instance table and mark all posts and other parse-able objects as unsynchronized.
304
+ $this->resynch();
305
+
306
+ //Save the default options.
307
+ $this->conf->save_options();
308
+
309
+ //And optimize my DB tables, too (for good measure)
310
+ $this->optimize_database();
311
+ }
312
+
313
+ /**
314
+ * ws_broken_link_checker::upgrade_database()
315
+ * Create and/or upgrade database tables
316
+ *
317
+ * @return void
318
+ */
319
+ function upgrade_database(){
320
+ global $wpdb;
321
+
322
+ //Do we need to upgrade?
323
+ //[ Disabled for now, was causing issues when the user manually deletes the plugin ]
324
+ //if ( $this->db_version == $this->conf->options['current_db_version'] ) return;
325
+
326
+ //Delete tables used by older versions of the plugin
327
+ $rez = $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}blc_linkdata, {$wpdb->prefix}blc_postdata" );
328
+ if ( $rez === false ){
329
+ //FB::error($wpdb->last_error, "Database error");
330
+ return false;
331
+ }
332
+
333
+ require_once (ABSPATH . 'wp-admin/includes/upgrade.php');
334
+
335
+ //Create the link table if it doesn't exist yet.
336
+ $q = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}blc_links (
337
+ link_id int(20) unsigned NOT NULL auto_increment,
338
+ url text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
339
+ last_check datetime NOT NULL default '0000-00-00 00:00:00',
340
+ check_count int(2) unsigned NOT NULL default '0',
341
+ final_url text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
342
+ redirect_count smallint(5) unsigned NOT NULL,
343
+ log text NOT NULL,
344
+ http_code smallint(6) NOT NULL,
345
+ request_duration float NOT NULL default '0',
346
+ timeout tinyint(1) unsigned NOT NULL default '0',
347
+
348
+ PRIMARY KEY (link_id),
349
+ KEY url (url(150)),
350
+ KEY final_url (final_url(150)),
351
+ KEY http_code (http_code),
352
+ KEY timeout (timeout)
353
+ )";
354
+ if ( $wpdb->query( $q ) === false ){
355
+ //FB::error($wpdb->last_error, "Database error");
356
+ return false;
357
+ };
358
+
359
+ //Fix URL fields so that they are collated as case-sensitive (this can't be done via dbDelta)
360
+ $q = "ALTER TABLE {$wpdb->prefix}blc_links
361
+ MODIFY url text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
362
+ MODIFY final_url text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL";
363
+ if ( $wpdb->query( $q ) === false ){
364
+ //FB::error($wpdb->last_error, "Database error");
365
+ return false;
366
+ };
367
+
368
+ //Create the instance table if it doesn't exist yet.
369
+ $q = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}blc_instances (
370
+ instance_id int(10) unsigned NOT NULL auto_increment,
371
+ link_id int(10) unsigned NOT NULL,
372
+ source_id int(10) unsigned NOT NULL,
373
+ source_type enum('post','blogroll','custom_field') NOT NULL default 'post',
374
+ link_text varchar(250) NOT NULL,
375
+ instance_type enum('link','image') NOT NULL default 'link',
376
+
377
+ PRIMARY KEY (instance_id),
378
+ KEY link_id (link_id),
379
+ KEY source_id (source_id,source_type)
380
+ )";
381
+ if ( $wpdb->query( $q ) === false ){
382
+ //FB::error($wpdb->last_error, "Database error");
383
+ return false;
384
+ };
385
+
386
+ //....
387
+ $q = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}blc_synch (
388
+ source_id int(20) unsigned NOT NULL,
389
+ source_type enum('post','blogroll') NOT NULL,
390
+ synched tinyint(3) unsigned NOT NULL,
391
+ last_synch datetime NOT NULL,
392
+ PRIMARY KEY (source_id, source_type),
393
+ KEY synched (synched)
394
+ )";
395
+ if ( $wpdb->query( $q ) === false ){
396
+ //FB::error($wpdb->last_error, "Database error");
397
+ return false;
398
+ };
399
+
400
+ $this->conf->options['current_db_version'] = $this->db_version;
401
+ $this->conf->save_options();
402
+
403
+ return true;
404
+ }
405
+
406
+ /**
407
+ * wsBrokenLinkChecker::optimize_database()
408
+ * Optimize the plugin's tables
409
+ *
410
+ * @return void
411
+ */
412
+ function optimize_database(){
413
+ global $wpdb;
414
+
415
+ $wpdb->query("OPTIMIZE TABLE {$wpdb->prefix}blc_links, {$wpdb->prefix}blc_instances, {$wpdb->prefix}blc_synch");
416
+ }
417
+
418
+ function admin_menu(){
419
+ add_options_page('Link Checker Settings', 'Link Checker', 'manage_options',
420
+ 'link-checker-settings',array(&$this, 'options_page'));
421
+ if (current_user_can('manage_options'))
422
+ add_filter('plugin_action_links', array(&$this, 'plugin_action_links'), 10, 2);
423
+
424
+ add_management_page('View Broken Links', 'Broken Links', 'edit_others_posts',
425
+ 'view-broken-links',array(&$this, 'links_page'));
426
+ }
427
+
428
+ /**
429
+ * plugin_action_links()
430
+ * Handler for the 'plugin_action_links' hook. Adds a "Settings" link to this plugin's entry
431
+ * on the plugin list.
432
+ *
433
+ * @param array $links
434
+ * @param string $file
435
+ * @return array
436
+ */
437
+ function plugin_action_links($links, $file) {
438
+ if ($file == $this->my_basename)
439
+ $links[] = "<a href='options-general.php?page=link-checker-settings'>" . __('Settings') . "</a>";
440
+ return $links;
441
+ }
442
+
443
+ function mytruncate($str, $max_length=50){
444
+ if(strlen($str)<=$max_length) return $str;
445
+ return (substr($str, 0, $max_length-3).'...');
446
+ }
447
+
448
+ function options_page(){
449
+ if (isset($_GET['recheck']) && ($_GET['recheck'] == 'true')) {
450
+ $this->initiate_recheck();
451
+ }
452
+ if (isset($_GET['updated']) && ($_GET['updated'] == 'true')) {
453
+ if(isset($_POST['submit'])) {
454
+ check_admin_referer('link-checker-options');
455
+
456
+ $new_execution_time = intval($_POST['max_execution_time']);
457
+ if( $new_execution_time > 0 ){
458
+ $this->conf->options['max_execution_time'] = $new_execution_time;
459
+ }
460
+
461
+ $new_check_threshold=intval($_POST['check_threshold']);
462
+ if( $new_check_threshold > 0 ){
463
+ $this->conf->options['check_threshold'] = $new_check_threshold;
464
+ }
465
+
466
+ $this->conf->options['mark_broken_links'] = !empty($_POST['mark_broken_links']);
467
+ $new_broken_link_css = trim($_POST['broken_link_css']);
468
+ $this->conf->options['broken_link_css'] = $new_broken_link_css;
469
+
470
+ $this->conf->options['exclusion_list']=array_filter( preg_split( '/[\s\r\n]+/',
471
+ $_POST['exclusion_list'], -1, PREG_SPLIT_NO_EMPTY ) );
472
+ //TODO: Maybe update affected links when exclusion list changes (expensive).
473
+
474
+
475
+ $new_custom_fields = array_filter( preg_split( '/[\s\r\n]+/',
476
+ $_POST['blc_custom_fields'], -1, PREG_SPLIT_NO_EMPTY ) );
477
+ $diff1 = array_diff( $new_custom_fields, $this->conf->options['custom_fields'] );
478
+ $diff2 = array_diff( $this->conf->options['custom_fields'], $new_custom_fields );
479
+ $this->conf->options['custom_fields'] = $new_custom_fields;
480
+
481
+ $this->conf->save_options();
482
+
483
+ /*
484
+ If the list of custom fields was modified then we MUST resynchronize or
485
+ custom fields linked with existing posts may not be detected. This is somewhat
486
+ inefficient.
487
+ */
488
+ if ( ( count($diff1) > 0 ) || ( count($diff2) > 0 ) ){
489
+ $this->resynch();
490
+ }
491
+ }
492
+
493
+ }
494
+
495
+ ?>
496
+ <div class="wrap"><h2>Broken Link Checker Options</h2>
497
+
498
+ <form name="link_checker_options" method="post" action="<?php echo basename($_SERVER['PHP_SELF']); ?>?page=link-checker-settings&amp;updated=true">
499
+ <?php
500
+ wp_nonce_field('link-checker-options');
501
+ ?>
502
+
503
+ <table class="form-table">
504
+
505
+ <tr valign="top">
506
+ <th scope="row">Status</th>
507
+ <td>
508
+
509
+
510
+ <div id='wsblc_full_status'>
511
+ <br/><br/><br/>
512
+ </div>
513
+ <script type='text/javascript'>
514
+ (function($){
515
+
516
+ function blcUpdateStatus(){
517
+ $.getJSON(
518
+ "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
519
+ {
520
+ 'action' : 'blc_full_status'
521
+ },
522
+ function (data, textStatus){
523
+ if ( data && ( typeof(data['text']) != 'undefined' ) ){
524
+ $('#wsblc_full_status').html(data.text);
525
+ } else {
526
+ $('#wsblc_full_status').html('[ Network error ]');
527
+ }
528
+
529
+ setTimeout(blcUpdateStatus, 10000); //...update every 10 seconds
530
+ }
531
+ );
532
+ }
533
+ blcUpdateStatus();//Call it the first time
534
+
535
+ })(jQuery);
536
+ </script>
537
+ <?php //JHS: Recheck all posts link: ?>
538
+ <p><input class="button" type="button" name="recheckbutton" value="Re-check all pages" onclick="location.replace('<?php echo basename($_SERVER['PHP_SELF']); ?>?page=link-checker-settings&amp;recheck=true')" /></p>
539
+ </td>
540
+ </tr>
541
+
542
+ <tr valign="top">
543
+ <th scope="row">Check each link</th>
544
+ <td>
545
+
546
+ Every <input type="text" name="check_threshold" id="check_threshold"
547
+ value="<?php echo $this->conf->options['check_threshold']; ?>" size='5' maxlength='3'/>
548
+ hours
549
+ <br/>
550
+ <span class="description">
551
+ Existing links will be checked this often. New links will usually be checked ASAP.
552
+ </span>
553
+
554
+ </td>
555
+ </tr>
556
+
557
+ <tr valign="top">
558
+ <th scope="row">Broken link CSS</th>
559
+ <td>
560
+ <input type="checkbox" name="mark_broken_links" id="mark_broken_links"
561
+ <?php if ($this->conf->options['mark_broken_links']) echo ' checked="checked"'; ?>/>
562
+ <label for='mark_broken_links'>Apply <em>class="broken_link"</em> to broken links</label><br/>
563
+ <textarea name="broken_link_css" id="broken_link_css" cols='45' rows='4'/><?php
564
+ if( isset($this->conf->options['broken_link_css']) )
565
+ echo $this->conf->options['broken_link_css'];
566
+ ?></textarea>
567
+
568
+ </td>
569
+ </tr>
570
+
571
+ <tr valign="top">
572
+ <th scope="row">Exclusion list</th>
573
+ <td>Don't check links where the URL contains any of these words (one per line) :<br/>
574
+ <textarea name="exclusion_list" id="exclusion_list" cols='45' rows='4' wrap='off'/><?php
575
+ if( isset($this->conf->options['exclusion_list']) )
576
+ echo implode("\n", $this->conf->options['exclusion_list']);
577
+ ?></textarea>
578
+
579
+ </td>
580
+ </tr>
581
+
582
+ <tr valign="top">
583
+ <th scope="row">Custom fields</th>
584
+ <td>Check URLs entered in these custom fields (one per line) : <br/>
585
+ <textarea name="blc_custom_fields" id="blc_custom_fields" cols='45' rows='4' /><?php
586
+ if( isset($this->conf->options['custom_fields']) )
587
+ echo implode("\n", $this->conf->options['custom_fields']);
588
+ ?></textarea>
589
+
590
+ </td>
591
+ </tr>
592
+
593
+ <tr valign="top">
594
+ <th scope="row">Max. execution time (advanced)</th>
595
+ <td>
596
+
597
+ <input type="text" name="max_execution_time" id="max_execution_time"
598
+ value="<?php echo $this->conf->options['max_execution_time']; ?>" size='5' maxlength='3'/>
599
+ seconds
600
+ <br/><span class="description">
601
+ The plugin works by periodically creating a background worker instance that parses your posts looking for links,
602
+ checks the discovered URLs, and performs other time-consuming tasks. Here you can set for how long, at most,
603
+ the background instance may run each time before stopping.
604
+ </span>
605
+
606
+ </td>
607
+ </tr>
608
+
609
+ </table>
610
+
611
+ <p class="submit"><input type="submit" name="submit" class='button-primary' value="<?php _e('Save Changes') ?>" /></p>
612
+ </form>
613
+ </div>
614
+ <?php
615
+ }
616
+
617
+ function links_page(){
618
+ global $wpdb;
619
+
620
+ //Available filters by link type + the appropriate WHERE expressions
621
+ $filters = array(
622
+ 'broken' => array(
623
+ 'where_expr' => '( http_code < 200 OR http_code >= 400 OR timeout = 1 ) AND ( check_count > 0 ) AND ( http_code <> ' . BLC_CHECKING . ')',
624
+ 'name' => 'Broken',
625
+ 'heading' => 'Broken Links',
626
+ 'heading_zero' => 'No broken links found'
627
+ ),
628
+ 'redirects' => array(
629
+ 'where_expr' => '( redirect_count > 0 )',
630
+ 'name' => 'Redirects',
631
+ 'heading' => 'Redirected Links',
632
+ 'heading_zero' => 'No redirects found'
633
+ ),
634
+
635
+ 'all' => array(
636
+ 'where_expr' => '1',
637
+ 'name' => 'All',
638
+ 'heading' => 'Detected Links',
639
+ 'heading_zero' => 'No links found (yet)'
640
+ ),
641
+ );
642
+
643
+ $link_type = isset($_GET['link_type'])?$_GET['link_type']:'broken';
644
+ if ( !isset($filters[$link_type]) ){
645
+ $link_type = 'broken';
646
+ }
647
+
648
+ //Get the desired page number (must be > 0)
649
+ $page = isset($_GET['paged'])?intval($_GET['paged']):'1';
650
+ if ($page < 1) $page = 1;
651
+
652
+ //Links per page [1 - 200]
653
+ $per_page = isset($_GET['per_page'])?intval($_GET['per_page']):'30';
654
+ if ($per_page < 1){
655
+ $per_page = 30;
656
+ } else if ($per_page > 200){
657
+ $per_page = 200;
658
+ }
659
+
660
+ //calculate the number of various links
661
+ foreach ($filters as $filter => $data){
662
+ $filters[$filter]['count'] = $wpdb->get_var(
663
+ "SELECT COUNT(*) FROM {$wpdb->prefix}blc_links WHERE ".$data['where_expr'] );
664
+ }
665
+ $current_filter = $filters[$link_type];
666
+ $max_pages = ceil($current_filter['count'] / $per_page);
667
+
668
+
669
+ //Select the required links + 1 instance per link.
670
+ //Note : The query might be somewhat inefficient, but I can't think of any better way to do this.
671
+ $q = "SELECT
672
+ links.*,
673
+ instances.instance_id, instances.source_id, instances.source_type,
674
+ instances.link_text, instances.instance_type,
675
+ COUNT(*) as instance_count,
676
+ posts.post_title,
677
+ posts.post_date
678
+
679
+ FROM
680
+ {$wpdb->prefix}blc_links AS links,
681
+ {$wpdb->prefix}blc_instances as instances LEFT JOIN {$wpdb->posts} as posts ON instances.source_id = posts.ID
682
+
683
+ WHERE
684
+ links.link_id = instances.link_id
685
+ AND ". $current_filter['where_expr'] ."
686
+
687
+ GROUP BY links.link_id
688
+ LIMIT ".( ($page-1) * $per_page ).", $per_page";
689
+ //echo "<pre>$q</pre>";
690
+
691
+ $links = $wpdb->get_results($q, ARRAY_A);
692
+ if ($links){
693
+ /*
694
+ echo '<pre>';
695
+ print_r($links);
696
+ echo '</pre>';
697
+ //*/
698
+ } else {
699
+ echo $wpdb->last_error;
700
+ }
701
+ ?>
702
+
703
+ <script type='text/javascript'>
704
+ var blc_current_filter = '<?php echo $link_type; ?>';
705
+ </script>
706
+
707
+ <style type='text/css'>
708
+ .blc-link-editor {
709
+ font-size: 1em;
710
+ width: 95%;
711
+ }
712
+
713
+ .blc-excluded-link {
714
+ background-color: #E2E2E2;
715
+ }
716
+
717
+ .blc-small-image {
718
+ display : block;
719
+ float: left;
720
+ padding-top: 2px;
721
+ margin-right: 3px;
722
+ }
723
+ </style>
724
+
725
+ <div class="wrap">
726
+ <h2><?php
727
+ //Output a header matching the current filter
728
+ if ( $current_filter['count'] > 0 ){
729
+ echo "<span class='current-link-count'>{$current_filter[count]}</span> " . $current_filter['heading'];
730
+ } else {
731
+ echo "<span class='current-link-count'></span>" . $current_filter['heading_zero'];
732
+ }
733
+ ?></h2>
734
+
735
+ <div class='tablenav'>
736
+ <ul class="subsubsub">
737
+ <?php
738
+ //Construct a submenu of filter types
739
+ $items = array();
740
+ foreach ($filters as $filter => $data){
741
+ $class = $number_class = '';
742
+
743
+ if ( $link_type == $filter ) $class = 'class="current"';
744
+ if ( $link_type == $filter ) $number_class = 'current-link-count';
745
+
746
+ $items[] = "<li><a href='tools.php?page=view-broken-links&link_type=$filter' $class>
747
+ {$data[name]}</a> <span class='count'>(<span class='$number_class'>{$data[count]}</span>)</span>";
748
+ }
749
+ echo implode(' |</li>', $items);
750
+ unset($items);
751
+ ?>
752
+ </ul>
753
+ <?php
754
+ //Display pagination links
755
+ $page_links = paginate_links( array(
756
+ 'base' => add_query_arg( 'paged', '%#%' ),
757
+ 'format' => '',
758
+ 'prev_text' => __('&laquo;'),
759
+ 'next_text' => __('&raquo;'),
760
+ 'total' => $max_pages,
761
+ 'current' => $page
762
+ ));
763
+
764
+ if ( $page_links ) {
765
+ echo '<div class="tablenav-pages">';
766
+ $page_links_text = sprintf( '<span class="displaying-num">' . __( 'Displaying %s&#8211;%s of <span class="current-link-count">%s</span>' ) . '</span>%s',
767
+ number_format_i18n( ( $page - 1 ) * $per_page + 1 ),
768
+ number_format_i18n( min( $page * $per_page, count($links) ) ),
769
+ number_format_i18n( $current_filter['count'] ),
770
+ $page_links
771
+ );
772
+ echo $page_links_text;
773
+ echo '</div>';
774
+ }
775
+ ?>
776
+
777
+ </div>
778
+
779
+
780
+
781
+ <?php
782
+ if($links && (count($links)>0)){
783
+ ?>
784
+ <table class="widefat">
785
+ <thead>
786
+ <tr>
787
+
788
+ <th scope="col">Source
789
+ </th>
790
+ <th scope="col">Link Text</th>
791
+ <th scope="col">URL</th>
792
+
793
+ <?php if ( 'broken' == $link_type ) { ?>
794
+ <th scope="col"> </th>
795
+ <?php } ?>
796
+
797
+ </tr>
798
+ </thead>
799
+ <tbody id="the-list">
800
+ <?php
801
+ $rowclass = ''; $rownum = 0;
802
+ foreach ($links as $link) {
803
+ $rownum++;
804
+
805
+ $rowclass = 'alternate' == $rowclass ? '' : 'alternate';
806
+ $excluded = $this->is_excluded( $link['url'] );
807
+ if ( $excluded ) $rowclass .= ' blc-excluded-link';
808
+
809
+ ?>
810
+ <tr id='<?php echo "blc-row-$rownum"; ?>' class='blc-row <?php echo $rowclass; ?>'>
811
+ <td class='post-title column-title'>
812
+ <span class='blc-link-id' style='display:none;'><?php echo $link['link_id']; ?></span>
813
+ <?php
814
+ if ( ('post' == $link['source_type']) || ('custom_field' == $link['source_type']) ){
815
+
816
+ echo "<a class='row-title' href='post.php?action=edit&amp;post=$link[source_id]' title='Edit this post'>{$link[post_title]}</a>";
817
+
818
+ //Output inline action links (copied from edit-post-rows.php)
819
+ $actions = array();
820
+ if ( current_user_can('edit_post', $link['source_id']) ) {
821
+ $actions['edit'] = '<span class="edit"><a href="' . get_edit_post_link($link['source_id'], true) . '" title="' . attribute_escape(__('Edit this post')) . '">' . __('Edit') . '</a>';
822
+ $actions['delete'] = "<span class='delete'><a class='submitdelete' title='" . attribute_escape(__('Delete this post')) . "' href='" . wp_nonce_url("post.php?action=delete&amp;post=".$link['source_id'], 'delete-post_' . $link['source_id']) . "' onclick=\"if ( confirm('" . js_escape(sprintf( __("You are about to delete the post '%s'\n 'Cancel' to stop, 'OK' to delete."), $link['post_title'] )) . "') ) { return true;}return false;\">" . __('Delete') . "</a>";
823
+ }
824
+ $actions['view'] = '<span class="view"><a href="' . get_permalink($link['source_id']) . '" title="' . attribute_escape(sprintf(__('View "%s"'), $link['post_title'])) . '" rel="permalink">' . __('View') . '</a>';
825
+ echo '<div class="row-actions">';
826
+ echo implode(' | </span>', $actions);
827
+ echo '</div>';
828
+
829
+ } elseif ( 'blogroll' == $link['source_type'] ) {
830
+
831
+ echo "<a class='row-title' href='link.php?action=edit&amp;link_id=$link[source_id]' title='Edit this bookmark'>{$link[link_text]}</a>";
832
+
833
+ //Output inline action links
834
+ $actions = array();
835
+ if ( current_user_can('manage_links') ) {
836
+ $actions['edit'] = '<span class="edit"><a href="link.php?action=edit&amp;link_id=' . $link['source_id'] . '" title="' . attribute_escape(__('Edit this bookmark')) . '">' . __('Edit') . '</a>';
837
+ $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>";
838
+ }
839
+
840
+ echo '<div class="row-actions">';
841
+ echo implode(' | </span>', $actions);
842
+ echo '</div>';
843
+
844
+ } elseif ( empty($link['source_type']) ){
845
+
846
+ echo "[An orphaned link! This is a bug.]";
847
+
848
+ }
849
+ ?>
850
+ </td>
851
+ <td class='blc-link-text'><?php
852
+ if ( 'post' == $link['source_type'] ){
853
+
854
+ if ( 'link' == $link['instance_type'] ) {
855
+ print strip_tags($link['link_text']);
856
+ } elseif ( 'image' == $link['instance_type'] ){
857
+ echo "<img src='" . WP_PLUGIN_URL . "/broken-link-checker/images/image.png' class='blc-small-image' alt='Image' title='Image'> Image";
858
+ } else {
859
+ echo '[ ??? ]';
860
+ }
861
+
862
+ } elseif ( 'custom_field' == $link['source_type'] ){
863
+
864
+ echo "<img src='" . WP_PLUGIN_URL . "/broken-link-checker/images/script_code.png' class='blc-small-image' title='Custom field' alt='Custom field'> ";
865
+ echo "<code>".$link['link_text']."</code>";
866
+
867
+ } elseif ( 'blogroll' == $link['source_type'] ){
868
+ //echo $link['link_text'];
869
+ echo "<img src='" . WP_PLUGIN_URL . "/broken-link-checker/images/link.png' class='blc-small-image' title='Bookmark' alt='Bookmark'> Bookmark";
870
+ }
871
+ ?>
872
+ </td>
873
+ <td class='column-url'>
874
+ <a href='<?php print $link['url']; ?>' target='_blank' class='blc-link-url'>
875
+ <?php print $this->mytruncate($link['url']); ?></a>
876
+ <input type='text' id='link-editor-<?php print $rownum; ?>'
877
+ value='<?php print attribute_escape($link['url']); ?>'
878
+ class='blc-link-editor' style='display:none' />
879
+ <?php
880
+ //Output inline action links for the link/URL
881
+ $actions = array();
882
+
883
+ $actions['details'] = "<span class='view'><a class='blc-details-button' href='javascript:void(0)' title='Show more info about this link'>Details</a>";
884
+
885
+ $actions['delete'] = "<span class='delete'><a class='submitdelete blc-unlink-button' title='Remove this link from all posts' ".
886
+ "id='unlink-button-$rownum' href='javascript:void(0);'>Unlink</a>";
887
+
888
+ if ( $excluded ){
889
+ $actions['exclude'] = "<span class='delete'>Excluded";
890
+ } else {
891
+ $actions['exclude'] = "<span class='delete'><a class='submitdelete blc-exclude-button' title='Add this URL to the exclusion list' ".
892
+ "id='exclude-button-$rownum' href='javascript:void(0);'>Exclude</a>";
893
+ }
894
+
895
+ $actions['edit'] = "<span class='edit'><a href='javascript:void(0)' class='blc-edit-button' title='Edit link URL'>Edit URL</a>";
896
+
897
+ echo '<div class="row-actions">';
898
+ echo implode(' | </span>', $actions);
899
+
900
+ echo "<span style='display:none' class='blc-cancel-button-container'> ",
901
+ "| <a href='javascript:void(0)' class='blc-cancel-button' title='Cancel URL editing'>Cancel</a></span>";
902
+
903
+ echo '</div>';
904
+ ?>
905
+ </td>
906
+ <?php if ( 'broken' == $link_type ) { ?>
907
+ <td><a href='javascript:void(0);'
908
+ id='discard_button-<?php print $rownum; ?>'
909
+ class='blc-discard-button'
910
+ title='Remove this message and mark the link as valid'>Discard</a>
911
+ </td>
912
+ <?php } ?>
913
+ </tr>
914
+ <!-- Link details -->
915
+ <tr id='<?php print "link-details-$rownum"; ?>' style='display:none;' class='blc-link-details'>
916
+ <td colspan='4'><?php $this->link_details_row($link); ?></td>
917
+ </tr><?php
918
+ }
919
+ ?></tbody></table><?php
920
+
921
+ //Also display pagination links at the bottom
922
+ if ( $page_links ) {
923
+ echo '<div class="tablenav"><div class="tablenav-pages">';
924
+ $page_links_text = sprintf( '<span class="displaying-num">' . __( 'Displaying %s&#8211;%s of <span class="current-link-count">%s</span>' ) . '</span>%s',
925
+ number_format_i18n( ( $page - 1 ) * $per_page + 1 ),
926
+ number_format_i18n( min( $page * $per_page, count($links) ) ),
927
+ number_format_i18n( $current_filter['count'] ),
928
+ $page_links
929
+ );
930
+ echo $page_links_text;
931
+ echo '</div></div>';
932
+ }
933
+ };
934
+ ?>
935
+ <?php $this->links_page_js(); ?>
936
+ </div>
937
+ <?php
938
+ }
939
+
940
+ function links_page_js(){
941
+ ?>
942
+ <script type='text/javascript'>
943
+
944
+ function alterLinkCounter(factor){
945
+ cnt = parseInt(jQuery('.current-link-count').eq(0).html());
946
+ cnt = cnt + factor;
947
+ jQuery('.current-link-count').html(cnt);
948
+ }
949
+
950
+ jQuery(function($){
951
+
952
+ //The discard button - manually mark the link as valid. The link will be checked again later.
953
+ $(".blc-discard-button").click(function () {
954
+ var me = this;
955
+ $(me).html('Wait...');
956
+
957
+ var link_id = $(me).parents('.blc-row').find('.blc-link-id').html();
958
+
959
+ $.post(
960
+ "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
961
+ {
962
+ 'action' : 'blc_discard',
963
+ 'link_id' : link_id
964
+ },
965
+ function (data, textStatus){
966
+ if (data == 'OK'){
967
+ var master = $(me).parents('.blc-row');
968
+ var details = master.next('.blc-link-details');
969
+
970
+ details.hide();
971
+ //Flash the main row green to indicate success, then hide it.
972
+ var oldColor = master.css('background-color');
973
+ master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300, function(){
974
+ master.hide();
975
+ });
976
+
977
+ alterLinkCounter(-1);
978
+ } else {
979
+ $(me).html('Discard');
980
+ alert(data);
981
+ }
982
+ }
983
+ );
984
+ });
985
+
986
+ //The details button - display/hide detailed info about a link
987
+ $(".blc-details-button, .blc-link-text").click(function () {
988
+ $(this).parents('.blc-row').next('.blc-link-details').toggle();
989
+ });
990
+
991
+ //The edit button - edit/save the link's URL
992
+ $(".blc-edit-button").click(function () {
993
+ var edit_button = $(this);
994
+ var master = $(edit_button).parents('.blc-row');
995
+ var editor = $(master).find('.blc-link-editor');
996
+ var url_el = $(master).find('.blc-link-url');
997
+ var cancel_button_container = $(master).find('.blc-cancel-button-container');
998
+
999
+ //Find the current/original URL
1000
+ var orig_url = url_el.attr('href');
1001
+ //Find the link ID
1002
+ var link_id = $(master).find('.blc-link-id').html();
1003
+
1004
+ if ( !$(editor).is(':visible') ){
1005
+ //Begin editing
1006
+ url_el.hide();
1007
+ editor.show();
1008
+ cancel_button_container.show();
1009
+ editor.focus();
1010
+ editor.select();
1011
+ edit_button.html('Save URL');
1012
+ } else {
1013
+ editor.hide();
1014
+ cancel_button_container.hide();
1015
+ url_el.show();
1016
+
1017
+ new_url = editor.val();
1018
+
1019
+ if (new_url != orig_url){
1020
+ //Save the changed link
1021
+ url_el.html('Saving changes...');
1022
+
1023
+ $.getJSON(
1024
+ "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
1025
+ {
1026
+ 'action' : 'blc_edit',
1027
+ 'link_id' : link_id,
1028
+ 'new_url' : new_url
1029
+ },
1030
+ function (data, textStatus){
1031
+ var display_url = '';
1032
+
1033
+ if ( data && (typeof(data['error']) != 'undefined') ){
1034
+ //data.error is an error message
1035
+ alert(data.error);
1036
+ display_url = orig_url;
1037
+ } else {
1038
+ //data contains info about the performed edit
1039
+ if ( data.cnt_okay > 0 ){
1040
+ display_url = new_url;
1041
+
1042
+ url_el.attr('href', new_url);
1043
+
1044
+ if ( data.cnt_error > 0 ){
1045
+ var msg = "The link was successfully modifed.";
1046
+ msg = msg + "\nHowever, "+data.cnt_error+" instances couldn't be edited and still point to the old URL."
1047
+ alert(msg);
1048
+ } else {
1049
+ //Flash the row green to indicate success
1050
+ var oldColor = master.css('background-color');
1051
+ master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300);
1052
+
1053
+ //Save the new ID
1054
+ master.find('.blc-link-id').html(data.new_link_id);
1055
+ //Load up the new link info (so sue me)
1056
+ master.next('.blc-link-details').find('td').html('<center>Loading...</center>').load(
1057
+ "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
1058
+ {
1059
+ 'action' : 'blc_link_details',
1060
+ 'link_id' : data.new_link_id
1061
+ }
1062
+ );
1063
+ }
1064
+ } else {
1065
+ alert("Something went wrong. The plugin failed to edit "+
1066
+ data.cnt_error + ' instance(s) of this link.');
1067
+
1068
+ display_url = orig_url;
1069
+ }
1070
+ };
1071
+
1072
+ //Shorten the displayed URL if it's > 50 characters
1073
+ if ( display_url.length > 50 ){
1074
+ display_url = display_url.substr(0, 47) + '...';
1075
+ }
1076
+ url_el.html(display_url);
1077
+ }
1078
+ );
1079
+
1080
+ } else {
1081
+ //It's the same URL, so do nothing.
1082
+ }
1083
+ edit_button.html('Edit URL');
1084
+ }
1085
+ });
1086
+
1087
+ $(".blc-cancel-button").click(function () {
1088
+ var master = $(this).parents('.blc-row');
1089
+ var url_el = $(master).find('.blc-link-url');
1090
+
1091
+ //Hide the cancel button
1092
+ $(this).parent().hide();
1093
+ //Show the un-editable URL again
1094
+ url_el.show();
1095
+ //reset and hide the editor
1096
+ master.find('.blc-link-editor').hide().val(url_el.attr('href'));
1097
+ //Set the edit button to say "Edit URL"
1098
+ master.find('.blc-edit-button').html('Edit URL');
1099
+ });
1100
+
1101
+ //The unlink button - remove the link/image from all posts, custom fields, etc.
1102
+ $(".blc-unlink-button").click(function () {
1103
+ var me = this;
1104
+ var master = $(me).parents('.blc-row');
1105
+ $(me).html('Wait...');
1106
+
1107
+ var link_id = $(me).parents('.blc-row').find('.blc-link-id').html();
1108
+
1109
+ $.post(
1110
+ "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
1111
+ {
1112
+ 'action' : 'blc_unlink',
1113
+ 'link_id' : link_id
1114
+ },
1115
+ function (data, textStatus){
1116
+ eval('data = ' + data);
1117
+
1118
+ if ( data && ( typeof(data['ok']) != 'undefined') ){
1119
+ //Hide the details
1120
+ master.next('.blc-link-details').hide();
1121
+ //Flash the main row green to indicate success, then hide it.
1122
+ var oldColor = master.css('background-color');
1123
+ master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300, function(){
1124
+ master.hide();
1125
+ });
1126
+
1127
+ alterLinkCounter(-1);
1128
+ } else {
1129
+ $(me).html('Unlink');
1130
+ //Show the error message
1131
+ alert(data.error);
1132
+ }
1133
+ }
1134
+ );
1135
+ });
1136
+
1137
+ //The exclude button - Add this link to the exclusion list
1138
+ $(".blc-exclude-button").click(function () {
1139
+ var me = this;
1140
+ var master = $(me).parents('.blc-row');
1141
+ var details = master.next('.blc-link-details');
1142
+ $(me).html('Wait...');
1143
+
1144
+ var link_id = $(me).parents('.blc-row').find('.blc-link-id').html();
1145
+
1146
+ $.post(
1147
+ "<?php bloginfo( 'wpurl' ); ?>/wp-admin/admin-ajax.php",
1148
+ {
1149
+ 'action' : 'blc_exclude_link',
1150
+ 'link_id' : link_id
1151
+ },
1152
+ function (data, textStatus){
1153
+ eval('data = ' + data);
1154
+
1155
+ if ( data && ( typeof(data['ok']) != 'undefined' ) ){
1156
+
1157
+ if ( 'broken' == blc_current_filter ){
1158
+ //Flash the row green to indicate success, then hide it.
1159
+ $(me).replaceWith('Excluded');
1160
+ master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: '#E2E2E2' }, 200, function(){
1161
+ details.hide();
1162
+ master.hide();
1163
+ alterLinkCounter(-1);
1164
+ });
1165
+ master.addClass('blc-excluded-link');
1166
+ } else {
1167
+ //Flash the row green to indicate success and fade to the "excluded link" color
1168
+ master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: '#E2E2E2' }, 300);
1169
+ master.addClass('blc-excluded-link');
1170
+ $(me).replaceWith('Excluded');
1171
+ }
1172
+ } else {
1173
+ $(me).html('Exclude');
1174
+ alert(data.error);
1175
+ }
1176
+ }
1177
+ );
1178
+ });
1179
+
1180
+ });
1181
+
1182
+ </script>
1183
+ <?php
1184
+ }
1185
+
1186
+ function link_details_row($link){
1187
+ ?>
1188
+ <span id='post_date_full' style='display:none;'><?php
1189
+ print $link['post_date'];
1190
+ ?></span>
1191
+ <span id='check_date_full' style='display:none;'><?php
1192
+ print $link['last_check'];
1193
+ ?></span>
1194
+ <ol style='list-style-type: none; width: 50%; float: right;'>
1195
+ <li><strong>Log :</strong>
1196
+ <span class='blc_log'><?php
1197
+ print nl2br($link['log']);
1198
+ ?></span></li>
1199
+ </ol>
1200
+
1201
+ <ol style='list-style-type: none; padding-left: 2px;'>
1202
+ <?php if ( !empty($link['post_date']) ) { ?>
1203
+ <li><strong>Post published on :</strong>
1204
+ <span class='post_date'><?php
1205
+ print strftime("%B %d, %Y",strtotime($link['post_date']));
1206
+ ?></span></li>
1207
+ <?php } ?>
1208
+ <li><strong>Link last checked :</strong>
1209
+ <span class='check_date'><?php
1210
+ $last_check = strtotime($link['last_check']);
1211
+ if ( $last_check < strtotime('-10 years') ){
1212
+ echo 'Never';
1213
+ } else {
1214
+ echo strftime( "%B %d, %Y", $last_check );
1215
+ }
1216
+ ?></span></li>
1217
+
1218
+ <li><strong>HTTP code :</strong>
1219
+ <span class='http_code'><?php
1220
+ print $link['http_code'];
1221
+ ?></span></li>
1222
+
1223
+ <li><strong>Response time :</strong>
1224
+ <span class='request_duration'><?php
1225
+ printf('%2.3f seconds', $link['request_duration']);
1226
+ ?></span></li>
1227
+
1228
+ <li><strong>Final URL :</strong>
1229
+ <span class='final_url'><?php
1230
+ print $link['final_url'];
1231
+ ?></span></li>
1232
+
1233
+ <li><strong>Redirect count :</strong>
1234
+ <span class='redirect_count'><?php
1235
+ print $link['redirect_count'];
1236
+ ?></span></li>
1237
+
1238
+ <li><strong>Instance count :</strong>
1239
+ <span class='instance_count'><?php
1240
+ print $link['instance_count'];
1241
+ ?></span></li>
1242
+
1243
+ <?php if ( intval( $link['check_count'] ) > 0 ){ ?>
1244
+ <li><br/>This link has failed
1245
+ <span class='check_count'><?php
1246
+ echo $link['check_count'];
1247
+ if ( intval($link['check_count'])==1 ){
1248
+ echo ' time';
1249
+ } else {
1250
+ echo ' times';
1251
+ }
1252
+ ?></span>.</li>
1253
+ <?php } ?>
1254
+ </ol>
1255
+ <?php
1256
+ }
1257
+
1258
+ /**
1259
+ * ws_broken_link_checker::cleanup_links()
1260
+ * Remove orphaned links that have no corresponding instances
1261
+ *
1262
+ * @param int $link_id (optional) Only check this link
1263
+ * @return bool
1264
+ */
1265
+ function cleanup_links( $link_id = null ){
1266
+ global $wpdb;
1267
+
1268
+ $q = "DELETE FROM {$wpdb->prefix}blc_links
1269
+ USING {$wpdb->prefix}blc_links LEFT JOIN {$wpdb->prefix}blc_instances
1270
+ ON {$wpdb->prefix}blc_instances.link_id = {$wpdb->prefix}blc_links.link_id
1271
+ WHERE
1272
+ {$wpdb->prefix}blc_instances.link_id IS NULL";
1273
+
1274
+ if ( $link_id !==null ) {
1275
+ $q .= " AND {$wpdb->prefix}blc_links.link_id = " . intval( $link_id );
1276
+ }
1277
+
1278
+ return $wpdb->query( $q );
1279
+ }
1280
+
1281
+ /**
1282
+ * ws_broken_link_checker::cleanup_instances()
1283
+ * Remove instances that reference invalid posts or bookmarks
1284
+ *
1285
+ * @return bool
1286
+ */
1287
+ function cleanup_instances(){
1288
+ global $wpdb;
1289
+
1290
+ //Delete all instances that reference non-existent posts
1291
+ $q = "DELETE FROM {$wpdb->prefix}blc_instances
1292
+ USING {$wpdb->prefix}blc_instances LEFT JOIN {$wpdb->posts} ON {$wpdb->prefix}blc_instances.source_id = {$wpdb->posts}.ID
1293
+ WHERE
1294
+ {$wpdb->posts}.ID IS NULL
1295
+ AND ( ( {$wpdb->prefix}blc_instances.source_type = 'post' ) OR ( {$wpdb->prefix}blc_instances.source_type = 'custom_field' ) )";
1296
+ $rez = $wpdb->query($q);
1297
+
1298
+ //Delete all instances that reference non-existant bookmarks
1299
+ $q = "DELETE FROM {$wpdb->prefix}blc_instances
1300
+ USING {$wpdb->prefix}blc_instances LEFT JOIN {$wpdb->links} ON {$wpdb->prefix}blc_instances.source_id = {$wpdb->links}.link_id
1301
+ WHERE
1302
+ {$wpdb->links}.link_id IS NULL
1303
+ AND {$wpdb->prefix}blc_instances.source_type = 'blogroll' ";
1304
+ $rez2 = $wpdb->query($q);
1305
+
1306
+ return $rez and $rez2;
1307
+ }
1308
+
1309
+ /**
1310
+ * ws_broken_link_checker::parse_post()
1311
+ * Parse a post for links and save them to the DB.
1312
+ *
1313
+ * @param string $content Post content
1314
+ * @param int $post_id Post ID
1315
+ * @return void
1316
+ */
1317
+ function parse_post($content, $post_id){
1318
+ //remove all <code></code> blocks first
1319
+ $content = preg_replace('/<code[^>]*>.+?<\/code>/si', ' ', $content);
1320
+ //Get the post permalink - it's used to resolve relative URLs
1321
+ $permalink = get_permalink( $post_id );
1322
+
1323
+ //Find links
1324
+ if(preg_match_all(blcUtility::link_pattern(), $content, $matches, PREG_SET_ORDER)){
1325
+ foreach($matches as $link){
1326
+ $url = $link[3];
1327
+ $text = strip_tags( $link[5] );
1328
+ //FB::log($url, "Found link");
1329
+
1330
+ $url = blcUtility::normalize_url($url, $permalink);
1331
+ //Skip invalid links
1332
+ if ( !$url || (strlen($url)<6) ) continue;
1333
+
1334
+ //Create or load the link
1335
+ $link_obj = new blcLink($url);
1336
+ //Add & save a new instance
1337
+ $link_obj->add_instance($post_id, 'post', $text, 'link');
1338
+ }
1339
+ };
1340
+
1341
+ //Find images (<img src=...>)
1342
+ if(preg_match_all(blcUtility::img_pattern(), $content, $matches, PREG_SET_ORDER)){
1343
+ foreach($matches as $img){
1344
+ $url = $img[3];
1345
+ //FB::log($url, "Found image");
1346
+
1347
+ $url = blcUtility::normalize_url($url, $permalink);
1348
+ if ( !$url || (strlen($url)<6) ) continue; //skip invalid URLs
1349
+
1350
+ //Create or load the link
1351
+ $link = new blcLink($url);
1352
+ //Add & save a new image instance
1353
+ $link->add_instance($post_id, 'post', '', 'image');
1354
+ }
1355
+ };
1356
+ }
1357
+
1358
+ /**
1359
+ * ws_broken_link_checker::parse_post_meta()
1360
+ * Parse a post's custom fields for links and save them in the DB.
1361
+ *
1362
+ * @param id $post_id
1363
+ * @return void
1364
+ */
1365
+ function parse_post_meta($post_id){
1366
+ //Get all custom fields of this post
1367
+ $custom_fields = get_post_custom( $post_id );
1368
+ //FB::log($custom_fields, "Custom fields loaded");
1369
+
1370
+ //Parse the enabled fields
1371
+ foreach( $this->conf->options['custom_fields'] as $field ){
1372
+ if ( !isset($custom_fields[$field]) ) continue;
1373
+
1374
+ //FB::log($field, "Parsing field");
1375
+
1376
+ $values = $custom_fields[$field];
1377
+ if ( !is_array( $values ) ) $values = array($values);
1378
+
1379
+ foreach( $values as $value ){
1380
+
1381
+ //Attempt to parse the $value as URL
1382
+ $url = blcUtility::normalize_url($value);
1383
+ if ( empty($url) ){
1384
+ //FB::warn($value, "Invalid URL in custom field ".$field);
1385
+ continue;
1386
+ }
1387
+
1388
+ //FB::log($url, "Found URL");
1389
+ $link = new blcLink( $url );
1390
+ //FB::log($link, 'Created/loaded link');
1391
+ $inst = $link->add_instance( $post_id, 'custom_field', $field, 'link' );
1392
+ //FB::log($inst, 'Created instance');
1393
+ }
1394
+ }
1395
+
1396
+ }
1397
+
1398
+ function parse_blogroll_link( $the_link ){
1399
+ //FB::log($the_link, "Parsing blogroll link");
1400
+
1401
+ //Attempt to parse the URL
1402
+ $url = blcUtility::normalize_url( $the_link['link_url'] );
1403
+ if ( empty($url) ){
1404
+ //FB::warn( $the_link['link_url'], "Invalid URL in for a blogroll link".$the_link['link_name'] );
1405
+ return false;
1406
+ }
1407
+
1408
+ //FB::log($url, "Found URL");
1409
+ $link = new blcLink( $url );
1410
+ return $link->add_instance( $the_link['link_id'], 'blogroll', $the_link['link_name'], 'link' );
1411
+ }
1412
+
1413
+ function start_timer(){
1414
+ $this->execution_start_time = microtime_float();
1415
+ }
1416
+
1417
+ function execution_time(){
1418
+ return microtime_float() - $this->execution_start_time;
1419
+ }
1420
+
1421
+ /**
1422
+ * ws_broken_link_checker::work()
1423
+ * The main worker function that does all kinds of things.
1424
+ *
1425
+ * @return void
1426
+ */
1427
+ function work(){
1428
+ global $wpdb;
1429
+
1430
+ if ( !$this->acquire_lock() ){
1431
+ //FB::warn("Another instance of BLC is already working. Stop.");
1432
+ return false;
1433
+ }
1434
+
1435
+ $this->start_timer();
1436
+
1437
+ $max_execution_time = $this->conf->options['max_execution_time'];
1438
+
1439
+ /*****************************************
1440
+ Preparation
1441
+ ******************************************/
1442
+ // Check for safe mode
1443
+ if( ini_get('safe_mode') ){
1444
+ // Do it the safe mode way
1445
+ $t=ini_get('max_execution_time');
1446
+ if ($t && ($t < $max_execution_time))
1447
+ $max_execution_time = $t-1;
1448
+ } else {
1449
+ // Do it the regular way
1450
+ @set_time_limit( $max_execution_time * 2 ); //x2 should be plenty, running any longer would mean a glitch.
1451
+ }
1452
+ @ignore_user_abort(true);
1453
+
1454
+ $check_threshold = date('Y-m-d H:i:s', strtotime('-'.$this->conf->options['check_threshold'].' hours'));
1455
+ $recheck_threshold = date('Y-m-d H:i:s', strtotime('-20 minutes'));
1456
+
1457
+ $orphans_possible = false;
1458
+
1459
+ $still_need_resynch = $this->conf->options['need_resynch'];
1460
+
1461
+ /*****************************************
1462
+ Parse posts and bookmarks
1463
+ ******************************************/
1464
+
1465
+ if ( $this->conf->options['need_resynch'] ) {
1466
+
1467
+ //FB::log("Looking for posts and bookmarks that need parsing...");
1468
+
1469
+ $tsynch = $wpdb->prefix.'blc_synch';
1470
+ $tposts = $wpdb->posts;
1471
+ $tlinks = $wpdb->links;
1472
+
1473
+ $synch_q = "SELECT $tsynch.source_id, $tsynch.source_type, $tposts.post_content, $tlinks.link_url, $tlinks.link_id, $tlinks.link_name
1474
+
1475
+ FROM
1476
+ $tsynch LEFT JOIN $tposts
1477
+ ON ($tposts.id = $tsynch.source_id AND $tsynch.source_type='post')
1478
+ LEFT JOIN $tlinks
1479
+ ON ($tlinks.link_id = $tsynch.source_id AND $tsynch.source_type='blogroll')
1480
+
1481
+ WHERE
1482
+ $tsynch.synched = 0
1483
+
1484
+ LIMIT 50";
1485
+
1486
+ while ( $rows = $wpdb->get_results($synch_q, ARRAY_A) ) {
1487
+
1488
+ //FB::log("Found ".count($rows)." items to analyze.");
1489
+
1490
+ foreach ($rows as $row) {
1491
+
1492
+ if ( $row['source_type'] == 'post' ){
1493
+
1494
+ //FB::log("Parsing post ".$row['source_id']);
1495
+
1496
+ //Remove instances associated with this post
1497
+ $q = "DELETE FROM {$wpdb->prefix}blc_instances
1498
+ WHERE source_id = %d AND (source_type = 'post' OR source_type='custom_field')";
1499
+ $q = $wpdb->prepare($q, intval($row['source_id']));
1500
+
1501
+ //FB::log($q, "Executing query");
1502
+
1503
+ if ( $wpdb->query( $q ) === false ){
1504
+ //FB::error($wpdb->last_error, "Database error");
1505
+ }
1506
+
1507
+ //Gather links and images from the post
1508
+ $this->parse_post( $row['post_content'], $row['source_id'] );
1509
+ //Gather links from custom fields
1510
+ $this->parse_post_meta( $row['source_id'] );
1511
+
1512
+ //Some link records might be orhpaned now
1513
+ $orphans_possible = true;
1514
+
1515
+ } else {
1516
+
1517
+ //FB::log("Parsing bookmark ".$row['source_id']);
1518
+
1519
+ //Remove instances associated with this bookmark
1520
+ $q = "DELETE FROM {$wpdb->prefix}blc_instances
1521
+ WHERE source_id = %d AND source_type = 'blogroll'";
1522
+ $q = $wpdb->prepare($q, intval($row['source_id']));
1523
+ //FB::log($q, "Executing query");
1524
+
1525
+ if ( $wpdb->query( $q ) === false ){
1526
+ //FB::error($wpdb->last_error, "Database error");
1527
+ }
1528
+
1529
+ //(Re)add the instance and link
1530
+ $this->parse_blogroll_link( $row );
1531
+
1532
+ //Some link records might be orhpaned now
1533
+ $orphans_possible = true;
1534
+
1535
+ }
1536
+
1537
+ //Update the table to indicate the item has been parsed
1538
+ $this->mark_synched( $row['source_id'], $row['source_type'] );
1539
+
1540
+ //Check if we still have some execution time left
1541
+ if( $this->execution_time() > $max_execution_time ){
1542
+ //FB::log('The alloted execution time has run out');
1543
+ $this->cleanup_links();
1544
+ $this->release_lock();
1545
+ return;
1546
+ }
1547
+
1548
+ }
1549
+
1550
+ }
1551
+
1552
+ //FB::log('No unparsed items found.');
1553
+ $still_need_resynch = false;
1554
+
1555
+ if ( $wpdb->last_error ){
1556
+ //FB::error($wpdb->last_error, "Database error");
1557
+ }
1558
+
1559
+ } else {
1560
+ //FB::log('Resynch not required.');
1561
+ }
1562
+
1563
+ /******************************************
1564
+ Resynch done?
1565
+ *******************************************/
1566
+ if ( $this->conf->options['need_resynch'] && !$still_need_resynch ){
1567
+ $this->conf->options['need_resynch'] = $still_need_resynch;
1568
+ $this->conf->save_options();
1569
+ }
1570
+
1571
+ /******************************************
1572
+ Remove orphaned links
1573
+ *******************************************/
1574
+
1575
+ if ( $orphans_possible ) {
1576
+ //FB::log('Cleaning up the link table.');
1577
+ $this->cleanup_links();
1578
+ }
1579
+
1580
+ //Check if we still have some execution time left
1581
+ if( $this->execution_time() > $max_execution_time ){
1582
+ //FB::log('The alloted execution time has run out');
1583
+ $this->release_lock();
1584
+ return;
1585
+ }
1586
+
1587
+ /*****************************************
1588
+ Check links
1589
+ ******************************************/
1590
+ //FB::log('Looking for links to check (threshold : '.$check_threshold.')...');
1591
+
1592
+ //Select some links that haven't been checked for a long time or
1593
+ //that are broken and need to be re-checked again.
1594
+
1595
+ //Note : This is a slow query, but AFAIK there is no way to speed it up.
1596
+ //I could put an index on last_check, but that value is almost certainly unique
1597
+ //for each row so it wouldn't be much better than a full table scan.
1598
+ $q = "SELECT *, ( last_check < %s ) AS meets_check_threshold
1599
+ FROM {$wpdb->prefix}blc_links
1600
+ WHERE
1601
+ ( last_check < %s )
1602
+ OR
1603
+ (
1604
+ ( http_code >= 400 OR http_code < 200 OR timeout = 1)
1605
+ AND check_count < %d
1606
+ AND check_count > 0
1607
+ AND last_check < %s
1608
+ )
1609
+ ORDER BY last_check ASC
1610
+ LIMIT 50";
1611
+ $link_q = $wpdb->prepare($q, $check_threshold, $check_threshold, $this->conf->options['recheck_count'], $recheck_threshold);
1612
+ //FB::log($link_q);
1613
+
1614
+ while ( $links = $wpdb->get_results($link_q, ARRAY_A) ){
1615
+
1616
+ //some unchecked links found
1617
+ //FB::log("Checking ".count($links)." link(s)");
1618
+
1619
+ foreach ($links as $link) {
1620
+ $link_obj = new blcLink($link);
1621
+
1622
+ //Does this link need to be checked?
1623
+ if ( !$this->is_excluded( $link['url'] ) ) {
1624
+ //Yes, do it
1625
+ //FB::log("Checking link {$link[link_id]}");
1626
+ $link_obj->check();
1627
+ $link_obj->save();
1628
+ } else {
1629
+ //Nope, mark it as already checked.
1630
+ //FB::info("The URL {$link_obj->url} is excluded, marking link {$link_obj->link_id} as already checked.");
1631
+ $link_obj->last_check = date('Y-m-d H:i:s');
1632
+ $link_obj->http_code = 200; //Use a fake code so that the link doesn't show up in queries looking for broken links.
1633
+ $link_obj->timeout = false;
1634
+ $link_obj->request_duration = 0;
1635
+ $link_obj->log = "This link wasn't checked because a matching keyword was found on your exclusion list.";
1636
+ $link_obj->save();
1637
+ }
1638
+
1639
+ //Check if we still have some execution time left
1640
+ if( $this->execution_time() > $max_execution_time ){
1641
+ //FB::log('The alloted execution time has run out');
1642
+ $this->release_lock();
1643
+ return;
1644
+ }
1645
+ }
1646
+ }
1647
+ //FB::log('No links need to be checked right now.');
1648
+
1649
+ $this->release_lock();
1650
+ //FB::log('All done.');
1651
+ }
1652
+
1653
+ function ajax_full_status( ){
1654
+ $status = $this->get_status();
1655
+ $text = $this->status_text( $status );
1656
+
1657
+ echo json_encode( array(
1658
+ 'text' => $text,
1659
+ 'status' => $status,
1660
+ ) );
1661
+
1662
+ die();
1663
+ }
1664
+
1665
+ /**
1666
+ * ws_broken_link_checker::status_text()
1667
+ * Generates a status message based on the status info in $status
1668
+ *
1669
+ * @param array $status
1670
+ * @return string
1671
+ */
1672
+ function status_text( $status ){
1673
+ $text = '';
1674
+
1675
+ if( $status['broken_links'] > 0 ){
1676
+ $text .= sprintf( "<a href='%stools.php?page=view-broken-links' title='View broken links'><strong>Found %d broken link%s</strong></a>",
1677
+ get_option('wpurl'), $status['broken_links'], ( $status['broken_links'] == 1 )?'':'s' );
1678
+ } else {
1679
+ $text .= "No broken links found.";
1680
+ }
1681
+
1682
+ $text .= "<br/>";
1683
+
1684
+ if( $status['unchecked_links'] > 0) {
1685
+ $text .= sprintf( '%d URL%s in the work queue', $status['unchecked_links'], ($status['unchecked_links'] == 1)?'':'s' );
1686
+ } else {
1687
+ $text .= "No URLs in the work queue.";
1688
+ }
1689
+
1690
+ $text .= "<br/>";
1691
+ if ( $status['known_links'] > 0 ){
1692
+ $text .= sprintf( "Detected %d unique URL%s in %d link%s",
1693
+ $status['known_links'], $status['known_links'] == 1 ? '' : 's',
1694
+ $status['known_instances'], $status['known_instances'] == 1 ? '' : 's'
1695
+ );
1696
+ if ($this->conf->options['need_resynch']){
1697
+ $text .= ' and still searching...';
1698
+ } else {
1699
+ $text .= '.';
1700
+ }
1701
+ } else {
1702
+ if ($this->conf->options['need_resynch']){
1703
+ $text .= 'Searching your blog for links...';
1704
+ } else {
1705
+ $text .= 'No links detected.';
1706
+ }
1707
+ }
1708
+
1709
+ return $text;
1710
+ }
1711
+
1712
+ function ajax_dashboard_status(){
1713
+ //Just display the full status.
1714
+ $this->ajax_full_status( false );
1715
+ /*
1716
+ global $wpdb;
1717
+
1718
+ //displays a notification if broken links have been found
1719
+ $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links
1720
+ WHERE check_count > 0 AND ( http_code < 200 OR http_code >= 400 OR timeout = 1 )";
1721
+ $broken_links = $wpdb->get_var($q);
1722
+
1723
+
1724
+ if($broken_links>0){
1725
+ printf( "<a href='%stools.php?page=view-broken-links' title='View broken links'><strong>Found %d broken link%s</strong></a>",
1726
+ get_option('wpurl'), $broken_links, ($broken_links==1)?'':'s' );
1727
+ } else {
1728
+ echo "No broken links found.";
1729
+ }
1730
+ die();
1731
+ */
1732
+ }
1733
+
1734
+ /**
1735
+ * ws_broken_link_checker::get_status()
1736
+ * Returns an array with various status information about the plugin. Array key reference:
1737
+ * check_threshold - date/time; links checked before this threshold should be checked again.
1738
+ * recheck_threshold - date/time; broken links checked before this threshold should be re-checked.
1739
+ * known_links - the number of detected unique URLs (a misleading name, yes).
1740
+ * known_instances - the number of detected link instances, i.e. actual link elements in posts and other places.
1741
+ * broken_links - the number of detected broken links.
1742
+ * unchecked_links - the number of URLs that need to be checked ASAP; based on check_threshold and recheck_threshold.
1743
+ *
1744
+ * @return array
1745
+ */
1746
+ function get_status(){
1747
+ global $wpdb;
1748
+
1749
+ $check_threshold=date('Y-m-d H:i:s', strtotime('-'.$this->conf->options['check_threshold'].' hours'));
1750
+ $recheck_threshold=date('Y-m-d H:i:s', strtotime('-20 minutes'));
1751
+
1752
+ $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links WHERE 1";
1753
+ $known_links = $wpdb->get_var($q);
1754
+
1755
+ $q = "SELECT count(*) FROM {$wpdb->prefix}blc_instances WHERE 1";
1756
+ $known_instances = $wpdb->get_var($q);
1757
+
1758
+ $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links
1759
+ WHERE check_count > 0 AND ( http_code < 200 OR http_code >= 400 OR timeout = 1 ) AND ( http_code <> ".BLC_CHECKING." )";
1760
+ $broken_links = $wpdb->get_var($q);
1761
+
1762
+ $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links
1763
+ WHERE
1764
+ ( ( last_check < '$check_threshold' ) OR
1765
+ (
1766
+ ( http_code >= 400 OR http_code < 200 )
1767
+ AND check_count < 3
1768
+ AND last_check < '$recheck_threshold' )
1769
+ )";
1770
+ $unchecked_links = $wpdb->get_var($q);
1771
+
1772
+ return array(
1773
+ 'check_threshold' => $check_threshold,
1774
+ 'recheck_threshold' => $recheck_threshold,
1775
+ 'known_links' => $known_links,
1776
+ 'known_instances' => $known_instances,
1777
+ 'broken_links' => $broken_links,
1778
+ 'unchecked_links' => $unchecked_links,
1779
+ );
1780
+ }
1781
+
1782
+ function ajax_work(){
1783
+ //Run the worker function
1784
+ $this->work();
1785
+ die();
1786
+ }
1787
+
1788
+ function ajax_discard(){
1789
+ //TODO:Rewrite to use JSON instead of plaintext
1790
+ if (!current_user_can('edit_others_posts')){
1791
+ die( "You're not allowed to do that!" );
1792
+ }
1793
+
1794
+ if ( isset($_POST['link_id']) ){
1795
+ //Load the link
1796
+ $link = new blcLink( intval($_POST['link_id']) );
1797
+
1798
+ if ( !$link->valid() ){
1799
+ die("Oops, I can't find the link ".intval($_POST['link_id']) );
1800
+ }
1801
+ //Make it appear "not broken"
1802
+ $link->last_check = date('Y-m-d H:i:s');
1803
+ $link->http_code = 200;
1804
+ $link->timeout = 0;
1805
+ $link->check_count = 0;
1806
+ $link->log = "This link was manually marked as working by the user.";
1807
+
1808
+ //Save the changes
1809
+ if ( $link->save() ){
1810
+ die("OK");
1811
+ } else {
1812
+ die("Oops, couldn't modify the link!");
1813
+ }
1814
+ } else {
1815
+ die("Error : link_id not specified");
1816
+ }
1817
+ }
1818
+
1819
+ function ajax_edit(){
1820
+ if (!current_user_can('edit_others_posts')){
1821
+ die( json_encode( array(
1822
+ 'error' => "You're not allowed to do that!"
1823
+ )));
1824
+ }
1825
+
1826
+ if ( isset($_GET['link_id']) && !empty($_GET['new_url']) ){
1827
+ //Load the link
1828
+ $link = new blcLink( intval($_GET['link_id']) );
1829
+
1830
+ if ( !$link->valid() ){
1831
+ die( json_encode( array(
1832
+ 'error' => "Oops, I can't find the link ".intval($_GET['link_id'])
1833
+ )));
1834
+ }
1835
+
1836
+ $new_url = blcUtility::normalize_url($_GET['new_url']);
1837
+ if ( !$new_url ){
1838
+ die( json_encode( array(
1839
+ 'error' => "Oops, the new URL is invalid!"
1840
+ )));
1841
+ }
1842
+
1843
+ //Try and edit the link
1844
+ $rez = $link->edit($new_url);
1845
+
1846
+ if ( $rez == false ){
1847
+ die( json_encode( array(
1848
+ 'error' => "An unexpected error occured!"
1849
+ )));
1850
+ } else {
1851
+ $rez['ok'] = 'OK';
1852
+ die( json_encode($rez) );
1853
+ }
1854
+
1855
+ } else {
1856
+ die( json_encode( array(
1857
+ 'error' => "Error : link_id or new_url not specified"
1858
+ )));
1859
+ }
1860
+ }
1861
+
1862
+ function ajax_unlink(){
1863
+ if (!current_user_can('edit_others_posts')){
1864
+ die( json_encode( array(
1865
+ 'error' => "You're not allowed to do that!"
1866
+ )));
1867
+ }
1868
+
1869
+ if ( isset($_POST['link_id']) ){
1870
+ //Load the link
1871
+ $link = new blcLink( intval($_POST['link_id']) );
1872
+
1873
+ if ( !$link->valid() ){
1874
+ die( json_encode( array(
1875
+ 'error' => "Oops, I can't find the link ".intval($_POST['link_id'])
1876
+ )));
1877
+ }
1878
+
1879
+ //Try and unlink it
1880
+ if ( $link->unlink() ){
1881
+ die( json_encode( array(
1882
+ 'ok' => "URL {$link->url} was removed."
1883
+ )));
1884
+ } else {
1885
+ die( json_encode( array(
1886
+ 'error' => "The plugin failed to remove the link."
1887
+ )));
1888
+ }
1889
+
1890
+ } else {
1891
+ die( json_encode( array(
1892
+ 'error' => "Error : link_id not specified"
1893
+ )));
1894
+ }
1895
+ }
1896
+
1897
+ function ajax_link_details(){
1898
+ global $wpdb;
1899
+
1900
+ if (!current_user_can('edit_others_posts')){
1901
+ die("You don't have sufficient privileges to access this information!");
1902
+ }
1903
+
1904
+ //FB::log("Loading link details via AJAX");
1905
+
1906
+ if ( isset($_GET['link_id']) ){
1907
+ //FB::info("Link ID found in GET");
1908
+ $link_id = intval($_GET['link_id']);
1909
+ } else if ( isset($_POST['link_id']) ){
1910
+ //FB::info("Link ID found in POST");
1911
+ $link_id = intval($_POST['link_id']);
1912
+ } else {
1913
+ //FB::error('Link ID not specified, you hacking bastard.');
1914
+ die('Error : link ID not specified');
1915
+ }
1916
+
1917
+ //Load the link. link_details_row needs it as an array, so
1918
+ //we'll have to do this the long way.
1919
+ $q = "SELECT
1920
+ links.*,
1921
+ COUNT(*) as instance_count
1922
+
1923
+ FROM
1924
+ {$wpdb->prefix}blc_links AS links,
1925
+ {$wpdb->prefix}blc_instances as instances
1926
+
1927
+ WHERE
1928
+ links.link_id = %d
1929
+
1930
+ GROUP BY links.link_id";
1931
+
1932
+ $link = $wpdb->get_row( $wpdb->prepare($q, $link_id), ARRAY_A );
1933
+ if ( is_array($link) ){
1934
+ //FB::info($link, 'Link loaded');
1935
+ $this->link_details_row($link);
1936
+ die();
1937
+ } else {
1938
+ die ("Failed to load link details (" . $wpdb->last_error . ")");
1939
+ }
1940
+ }
1941
+
1942
+ function ajax_exclude_link(){
1943
+ if ( !current_user_can('manage_options') ){
1944
+ die( json_encode( array(
1945
+ 'error' => "You're not allowed to do that!"
1946
+ )));
1947
+ }
1948
+
1949
+ if ( isset($_POST['link_id']) ){
1950
+ //Load the link
1951
+ $link = new blcLink( intval($_POST['link_id']) );
1952
+
1953
+ if ( !$link->valid() ){
1954
+ die( json_encode( array(
1955
+ 'error' => "Oops, I can't find the link ".intval($_POST['link_id'])
1956
+ )));
1957
+ }
1958
+
1959
+ //Add the URL to the exclusion list
1960
+ if ( !in_array( $link->url, $this->conf->options['exclusion_list'] ) ){
1961
+ $this->conf->options['exclusion_list'][] = $link->url;
1962
+ //Also mark it as already checked so that it doesn't show up with other broken links.
1963
+ //FB::info("The URL {$link->url} is excluded, marking link {$link->link_id} as already checked.");
1964
+ $link->last_check = date('Y-m-d H:i:s');
1965
+ $link->http_code = 200; //Use a fake code so that the link doesn't show up in queries looking for broken links.
1966
+ $link->timeout = false;
1967
+ $link->request_duration = 0;
1968
+ $link->log = "This link wasn't checked because a matching keyword was found on your exclusion list.";
1969
+ $link->save();
1970
+ }
1971
+
1972
+ $this->conf->save_options();
1973
+
1974
+ die( json_encode( array(
1975
+ 'ok' => "URL {$link->url} added to the exclusion list"
1976
+ )));
1977
+ } else {
1978
+ die( json_encode( array(
1979
+ 'error' => "Link ID not specified"
1980
+ )));
1981
+ }
1982
+ }
1983
+
1984
+ /**
1985
+ * ws_broken_link_checker::acquire_lock()
1986
+ * Create and lock a temporary file.
1987
+ *
1988
+ * @return bool
1989
+ */
1990
+ function acquire_lock(){
1991
+ //Maybe we already have the lock?
1992
+ if ( $this->lockfile_handle ){
1993
+ return true;
1994
+ }
1995
+
1996
+ $fn = $this->lockfile_name();
1997
+ if ( $fn ){
1998
+ //Open the lockfile
1999
+ $this->lockfile_handle = fopen($fn, 'w+');
2000
+ if ( $this->lockfile_handle ){
2001
+ //Do an exclusive lock
2002
+ if (flock($this->lockfile_handle, LOCK_EX | LOCK_NB)) {
2003
+ //File locked successfully
2004
+ return true;
2005
+ } else {
2006
+ //Something went wrong
2007
+ fclose($this->lockfile_handle);
2008
+ $this->lockfile_handle = null;
2009
+ return false;
2010
+ }
2011
+ } else {
2012
+ //Can't open the file, fail.
2013
+ return false;
2014
+ }
2015
+ } else {
2016
+ //Uh oh, can't generate a lockfile name. This is bad.
2017
+ //FB::error("Can't find a writable directory to use for my lock file!");
2018
+ return false;
2019
+ };
2020
+ }
2021
+
2022
+ /**
2023
+ * ws_broken_link_checker::release_lock()
2024
+ * Unlock and delete the temporary file
2025
+ *
2026
+ * @return bool
2027
+ */
2028
+ function release_lock(){
2029
+ if ( $this->lockfile_handle ){
2030
+ //Close the file (implicitly releasing the lock)
2031
+ fclose( $this->lockfile_handle );
2032
+ //Delete the file
2033
+ $fn = $this->lockfile_name();
2034
+ if ( file_exists( $fn ) ) {
2035
+ unlink( $fn );
2036
+ }
2037
+ $this->lockfile_handle = null;
2038
+ return true;
2039
+ } else {
2040
+ //We didn't have the lock anyway...
2041
+ return false;
2042
+ }
2043
+ }
2044
+
2045
+ /**
2046
+ * ws_broken_link_checker::lockfile_name()
2047
+ * Generate system-specific lockfile filename
2048
+ *
2049
+ * @return string A filename or FALSE on error
2050
+ */
2051
+ function lockfile_name(){
2052
+ //Try to find the temp directory.
2053
+ $path = sys_get_temp_dir();
2054
+ if ( $path && is_writable($path)){
2055
+ return trailingslashit($path) . '/wp_blc_lock';
2056
+ } else {
2057
+ //Try the plugin's directory.
2058
+ if ( is_writable( dirname(__FILE__) ) ){
2059
+ return dirname(__FILE__) . '/wp_blc_lock';
2060
+ //Try the wp-content directory
2061
+ } else if ( is_writable( WP_CONTENT_DIR ) ){
2062
+ return WP_CONTENT_DIR . '/wp_blc_lock';
2063
+ } else {
2064
+ //Fail.
2065
+ return false;
2066
+ }
2067
+ }
2068
+ }
2069
+
2070
+ function hook_add_link( $link_id ){
2071
+ $this->mark_unsynched( $link_id, 'blogroll' );
2072
+ }
2073
+
2074
+ function hook_edit_link( $link_id ){
2075
+ $this->mark_unsynched( $link_id, 'blogroll' );
2076
+ }
2077
+
2078
+ function hook_delete_link( $link_id ){
2079
+ global $wpdb;
2080
+ //Delete the synch record
2081
+ $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}blc_synch WHERE source_id = %d AND source_type='blogroll'", $link_id ) );
2082
+
2083
+ //Get the matching instance record.
2084
+ $inst = $wpdb->get_row( $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_instances WHERE source_id = %d AND source_type = 'blogroll'", $link_id), ARRAY_A );
2085
+
2086
+ if ( !$inst ) {
2087
+ //No instance record? No problem.
2088
+ return;
2089
+ }
2090
+
2091
+ //Remove it
2092
+ $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_instances WHERE instance_id = %d", $inst['instance_id']) );
2093
+
2094
+ //Remove the link that was associated with this instance if it has no more related instances.
2095
+ $this->cleanup_links( $inst['link_id'] );
2096
+ }
2097
+
2098
+ function hook_wp_dashboard_setup(){
2099
+ if ( function_exists( 'wp_add_dashboard_widget' ) ) {
2100
+ wp_add_dashboard_widget(
2101
+ 'blc_dashboard_widget',
2102
+ 'Broken Link Checker',
2103
+ array( &$this, 'dashboard_widget' ),
2104
+ array( &$this, 'dashboard_widget_control' )
2105
+ );
2106
+ }
2107
+ }
2108
+
2109
+ function lockfile_warning(){
2110
+ $my_dir = '/plugins/' . basename(dirname(__FILE__)) . '/';
2111
+ $settings_page = admin_url( 'options-general.php?page=link-checker-settings#lockfile_directory' );
2112
+
2113
+ echo sprintf('
2114
+ <div id="blc-lockfile-warning" class="error"><p>
2115
+ <strong>Broken Link Checker can\'t create a lockfile.</strong>
2116
+ Please make the directory <code>%s</code> writable by plugins.
2117
+
2118
+ <a href="javascript:void(0)" onclick="jQuery(\'#blc-lockfile-details\').toggle()">Details</a>
2119
+ </p>
2120
+
2121
+ <div id="blc-lockfile-details" style="display:none;"><p>
2122
+ The plugin uses a file-based locking mechanism to ensure that only one instance of the
2123
+ resource-heavy link checking algorithm is running at any given time. Unfortunately,
2124
+ BLC can\'t find a writable directory where it could store the lockfile - it failed to
2125
+ detect the location of your server\'s temporary directory, and the plugin\'s own directory
2126
+ isn\'t writable by PHP. To fix this problem, please make the plugin\'s directory writable.
2127
+ </p>
2128
+ </div>
2129
+ </div>',
2130
+ $my_dir, $settings_page);
2131
+ }
2132
+
2133
+ }//class ends here
2134
+
2135
+ } // if class_exists...
2136
+
2137
+ ?>
highlighter-class.php ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @author W-Shadow
5
+ * @copyright 2009
6
+ *
7
+ * @requires blcUtility
8
+ */
9
+
10
+ class blcLinkHighlighter {
11
+
12
+ var $links_to_remove;
13
+ var $broken_link_css;
14
+ var $current_permalink;
15
+
16
+ function blcLinkHighlighter( $broken_link_css = '' ) {
17
+ if ( !empty( $broken_link_css ) ){
18
+ $this->broken_link_css = $broken_link_css;
19
+ add_action( 'wp_head', array(&$this,'hook_wp_head') );
20
+ }
21
+
22
+ add_filter( 'the_content', array(&$this,'hook_the_content') );
23
+ $this->current_permalink = '';
24
+ }
25
+
26
+ function hook_the_content($content){
27
+ global $post, $wpdb;
28
+ if ( empty($post) ) return $content;
29
+
30
+ //Get the post permalink - it's used to resolve relative URLs
31
+ $this->current_permalink = get_permalink( $post->ID );
32
+
33
+ $q = "
34
+ SELECT instances.link_text, links.*
35
+
36
+ FROM {$wpdb->prefix}blc_instances AS instances, {$wpdb->prefix}blc_links AS links
37
+
38
+ WHERE
39
+ instances.source_id = %d
40
+ AND instances.source_type = 'post'
41
+ AND instances.instance_type = 'link'
42
+
43
+ AND instances.link_id = links.link_id
44
+ AND links.check_count > 0
45
+ AND ( links.http_code < 200 OR links.http_code >= 400 OR links.timeout = 1 )
46
+ AND links.http_code <> " . BLC_CHECKING;
47
+
48
+ $rows = $wpdb->get_results( $wpdb->prepare( $q, $post->ID ), ARRAY_A );
49
+ if( $rows ){
50
+ $this->links_to_remove = array();
51
+ foreach($rows as $row){
52
+ $this->links_to_remove[$row['url']] = $row;
53
+ }
54
+ $content = preg_replace_callback( blcUtility::link_pattern(), array(&$this,'mark_broken_links'), $content );
55
+ };
56
+
57
+ return $content;
58
+ }
59
+
60
+ function mark_broken_links($matches){
61
+ //TODO: Tooltip-style popups with more info
62
+ $url = blcUtility::normalize_url( html_entity_decode( $matches[3], $this->current_permalink ) );
63
+ if( isset( $this->links_to_remove[$url] ) ){
64
+ return $matches[1].$matches[2].$matches[3].$matches[2].' class="broken_link" '.$matches[4].
65
+ $matches[5].$matches[6];
66
+ } else {
67
+ return $matches[0];
68
+ }
69
+ }
70
+
71
+ function hook_wp_head(){
72
+ echo '<style type="text/css">',$this->broken_link_css,'</style>';
73
+ }
74
+ }
75
+
76
+ ?>
link-classes.php CHANGED
@@ -96,17 +96,22 @@ class blcLink {
96
  */
97
  function check(){
98
  if ( !$this->valid() ) return false;
 
 
 
 
 
99
  /*
100
  Check for problematic (though not necessarily "broken") links.
101
  If a link has been checked multiple times and still hasn't been marked as
102
  timed-out or broken then probably the checking algorithm is having problems with
103
  that link. Mark it as timed-out and hope the user sorts it out.
104
  */
105
- if ( ($this->check_count >= 3) && ( !$this->timeout ) && ( !$this->http_code ) ) {
106
- $this->timeout = 1;
 
107
  $this->last_check = date('Y-m-d H:i:s');
108
  $this->log .= "\r\n[A weird error was detected. This should never happen.]";
109
- $this->save();
110
  return false;
111
  }
112
 
@@ -132,7 +137,8 @@ class blcLink {
132
  $parts = parse_url($url);
133
  //Only HTTP links are checked. All others are automatically considered okay.
134
  if ( ($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') ) {
135
- $this->log .= "URL protocol ($parts[scheme]) is not HTTP. This link won't be checked.\n";
 
136
  return true;
137
  }
138
 
@@ -174,12 +180,11 @@ class blcLink {
174
  }
175
  curl_setopt($ch, CURLOPT_PROXYUSERPWD, $auth);
176
  }
177
-
178
 
179
  curl_setopt($ch, CURLOPT_FAILONERROR, false);
180
 
181
  $nobody=false;
182
- if($parts['scheme']=='https'){
183
  //TODO: Redirects don't work with HTTPS
184
  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
185
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
@@ -189,11 +194,8 @@ class blcLink {
189
  //curl_setopt($ch, CURLOPT_RANGE, '0-1023');
190
  }
191
 
192
- //We definitely want headers.
193
  curl_setopt($ch, CURLOPT_HEADER, true);
194
  //register a callback function which will process the headers
195
- //this assumes your code is into a class method, and uses $this->readHeader
196
- //as the callback function.
197
  curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$this,'read_header'));
198
 
199
  //Execute the request
@@ -221,7 +223,7 @@ class blcLink {
221
  $this->log .= $this->last_headers."\n";
222
  }
223
 
224
- $this->http_code = $code!=0 ? $code : BLC_TIMEOUT;
225
  $this->final_url = $info['url'];
226
  $this->request_duration = $info['total_time'];
227
  $this->redirect_count = $info['redirect_count'];
96
  */
97
  function check(){
98
  if ( !$this->valid() ) return false;
99
+
100
+ //General note : there is usually no need to save() the result of the check
101
+ //in this method because it will be typically called from wsBrokenLinkChecker::work()
102
+ //that will call the save() method for us.
103
+
104
  /*
105
  Check for problematic (though not necessarily "broken") links.
106
  If a link has been checked multiple times and still hasn't been marked as
107
  timed-out or broken then probably the checking algorithm is having problems with
108
  that link. Mark it as timed-out and hope the user sorts it out.
109
  */
110
+ if ( ($this->check_count >= 3) && ( !$this->timeout ) && ( $this->http_code == BLC_CHECKING ) ) {
111
+ $this->timeout = true;
112
+ $this->http_code = BLC_TIMEOUT;
113
  $this->last_check = date('Y-m-d H:i:s');
114
  $this->log .= "\r\n[A weird error was detected. This should never happen.]";
 
115
  return false;
116
  }
117
 
137
  $parts = parse_url($url);
138
  //Only HTTP links are checked. All others are automatically considered okay.
139
  if ( ($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') ) {
140
+ $this->log .= "URL protocol ($parts[scheme]) is not HTTP(S). This link won't be checked.\n";
141
+ $this->http_code = 200;
142
  return true;
143
  }
144
 
180
  }
181
  curl_setopt($ch, CURLOPT_PROXYUSERPWD, $auth);
182
  }
 
183
 
184
  curl_setopt($ch, CURLOPT_FAILONERROR, false);
185
 
186
  $nobody=false;
187
+ if( $parts['scheme'] == 'https' ){
188
  //TODO: Redirects don't work with HTTPS
189
  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
190
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
194
  //curl_setopt($ch, CURLOPT_RANGE, '0-1023');
195
  }
196
 
 
197
  curl_setopt($ch, CURLOPT_HEADER, true);
198
  //register a callback function which will process the headers
 
 
199
  curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$this,'read_header'));
200
 
201
  //Execute the request
223
  $this->log .= $this->last_headers."\n";
224
  }
225
 
226
+ $this->http_code = $code != 0 ? $code : BLC_TIMEOUT;
227
  $this->final_url = $info['url'];
228
  $this->request_duration = $info['total_time'];
229
  $this->redirect_count = $info['redirect_count'];
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: whiteshadow
3
  Tags: links, broken, maintenance, blogroll, custom fields, admin
4
  Requires at least: 2.7.0
5
  Tested up to: 2.9
6
- Stable tag: 0.5.9
7
 
8
  This plugin will check your posts, custom fields and the blogroll for broken links and missing images and notify you if any are found.
9
 
@@ -58,6 +58,15 @@ To upgrade your installation
58
 
59
  == Changelog ==
60
 
 
 
 
 
 
 
 
 
 
61
  = 0.5.8.1 =
62
  * Added partial proxy support when CURL is available. Proxies will be fully supported in a later version.
63
 
@@ -72,6 +81,7 @@ To upgrade your installation
72
  * Improved relative URL parsing. The plugin now uses the permalink as the base URL when processing posts.
73
 
74
  = 0.5.5 =
 
75
  * URLs with spaces (and some other special characters) are now handled better and won't get marked as "broken" all the time.
76
  * Links that contain quote characters are parsed properly.
77
 
@@ -92,7 +102,8 @@ To upgrade your installation
92
  * Fix a bug when the plugin creates a DB table with the wrong prefix.
93
 
94
  = 0.5 =
95
- * This is a near-complete rewrite with a lot of new features. See �http://w-shadow.com/blog/2009/05/22/broken-link-checker-05/ for details.
 
96
 
97
  = 0.4.14 =
98
  * Fix false positives when the URL contains an #anchor
@@ -125,8 +136,10 @@ To upgrade your installation
125
  * New and improved (TM) regexps for finding links and images.
126
  * A "Settings" link added to plugin's action links.
127
  * And probably other stuff I forgot!
 
128
 
129
  = 0.4.7 =
 
130
  * Autoselect link URL after the user clicks "Edit".
131
  * Make sure only HTTP and HTTPS links are checked.
132
  * More substantive improvements will hopefully follow next week.
@@ -135,6 +148,7 @@ To upgrade your installation
135
  * Minor compatibility enhancement in wsblc\_ajax.php - don't load wpdb if it's already loaded.
136
 
137
  = 0.4.5 =
 
138
  * Revisions don't get added to the work queue anymore.
139
  * Workaround for rare cURL timeout bug.
140
  * Improved WP 2.6 compatibility.
@@ -214,3 +228,4 @@ To upgrade your installation
214
 
215
  = 0.1 =
216
  * *There are no release notes for this version*
 
3
  Tags: links, broken, maintenance, blogroll, custom fields, admin
4
  Requires at least: 2.7.0
5
  Tested up to: 2.9
6
+ Stable tag: 0.5.10
7
 
8
  This plugin will check your posts, custom fields and the blogroll for broken links and missing images and notify you if any are found.
9
 
58
 
59
  == Changelog ==
60
 
61
+ *This is an automatically generated changelog*
62
+
63
+ = 0.5.9 =
64
+ * Added an autogenerated changelog.
65
+ * Added a workaround to make this plugin compatible with the SimplePress forum.
66
+ * Fixed <pre> block parsing, again.
67
+ * Fixed a bug where URLs that only differ in character case would be treated as equivalent.
68
+ * Improved the database upgrade routine.
69
+
70
  = 0.5.8.1 =
71
  * Added partial proxy support when CURL is available. Proxies will be fully supported in a later version.
72
 
81
  * Improved relative URL parsing. The plugin now uses the permalink as the base URL when processing posts.
82
 
83
  = 0.5.5 =
84
+ * Minor bugfixes
85
  * URLs with spaces (and some other special characters) are now handled better and won't get marked as "broken" all the time.
86
  * Links that contain quote characters are parsed properly.
87
 
102
  * Fix a bug when the plugin creates a DB table with the wrong prefix.
103
 
104
  = 0.5 =
105
+ * This is a near-complete rewrite with a lot of new features.
106
+ * See �http://w-shadow.com/blog/2009/05/22/broken-link-checker-05/ for details.
107
 
108
  = 0.4.14 =
109
  * Fix false positives when the URL contains an #anchor
136
  * New and improved (TM) regexps for finding links and images.
137
  * A "Settings" link added to plugin's action links.
138
  * And probably other stuff I forgot!
139
+ * Grr :P
140
 
141
  = 0.4.7 =
142
+ * Minor enhancements :
143
  * Autoselect link URL after the user clicks "Edit".
144
  * Make sure only HTTP and HTTPS links are checked.
145
  * More substantive improvements will hopefully follow next week.
148
  * Minor compatibility enhancement in wsblc\_ajax.php - don't load wpdb if it's already loaded.
149
 
150
  = 0.4.5 =
151
+ * Bugfixes. Nothing more, nothing less.
152
  * Revisions don't get added to the work queue anymore.
153
  * Workaround for rare cURL timeout bug.
154
  * Improved WP 2.6 compatibility.
228
 
229
  = 0.1 =
230
  * *There are no release notes for this version*
231
+
utility-class.php CHANGED
@@ -6,7 +6,7 @@
6
  */
7
 
8
 
9
- if (is_admin() && !function_exists('json_encode')){
10
  //Load JSON functions for PHP < 5.2
11
  if (!class_exists('Services_JSON')){
12
  require 'JSON.php';
@@ -19,20 +19,33 @@ if (is_admin() && !function_exists('json_encode')){
19
  }
20
  }
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  if ( !class_exists('blcUtility') ){
23
 
24
  class blcUtility {
25
 
26
  //A regxp for images
27
  function img_pattern(){
28
- // \1 \2 \3 URL \4
29
- return '/(<img[\s]+[^>]*src\s*=\s*)([\"\'])([^\2>]+?)\2([^<>]*>)/i';
30
  }
31
 
32
  //A regexp for links
33
  function link_pattern(){
34
- // \1 \2 \3 URL \4 \5 Text \6
35
- return '/(<a[\s]+[^>]*href\s*=\s*)([\"\'])([^\2>]+?)\2([^<>]*>)((?sU).*)(<\/a>)/i';
36
  }
37
 
38
  /**
@@ -43,8 +56,11 @@ class blcUtility {
43
  * @return string A normalized URL or FALSE if the URL is invalid
44
  */
45
  function normalize_url($url, $base_url = ''){
46
- $parts=@parse_url($url);
47
- if(!$parts) return false;
 
 
 
48
 
49
  if(isset($parts['scheme'])) {
50
  //Only HTTP(S) links are checked. Other protocols are not supported.
@@ -54,17 +70,17 @@ class blcUtility {
54
 
55
  $url = html_entity_decode($url);
56
  $url = preg_replace(
57
- array('/([\?&]PHPSESSID=\w+)$/i',
58
- '/(#[^\/]*)$/',
59
- '/&amp;/',
60
- '/^(javascript:.*)/i',
61
- '/([\?&]sid=\w+)$/i'
62
  ),
63
  array('','','&','',''),
64
  $url);
65
- $url=trim($url);
66
 
67
- if($url=='') return false;
68
 
69
  // turn relative URLs into absolute URLs
70
  if ( empty($base_url) ) $base_url = get_option('siteurl');
@@ -86,7 +102,7 @@ class blcUtility {
86
  //WTF? $relative is a seriously malformed URL
87
  return false;
88
  }
89
- if(isset($p["scheme"])) return $relative;
90
 
91
  $parts=(parse_url($absolute));
92
 
6
  */
7
 
8
 
9
+ if ( is_admin() && !function_exists('json_encode') ){
10
  //Load JSON functions for PHP < 5.2
11
  if (!class_exists('Services_JSON')){
12
  require 'JSON.php';
19
  }
20
  }
21
 
22
+ if ( !function_exists('sys_get_temp_dir')) {
23
+ function sys_get_temp_dir() {
24
+ if (!empty($_ENV['TMP'])) { return realpath($_ENV['TMP']); }
25
+ if (!empty($_ENV['TMPDIR'])) { return realpath( $_ENV['TMPDIR']); }
26
+ if (!empty($_ENV['TEMP'])) { return realpath( $_ENV['TEMP']); }
27
+ $tempfile=tempnam(uniqid(rand(),TRUE),'');
28
+ if (file_exists($tempfile)) {
29
+ unlink($tempfile);
30
+ return realpath(dirname($tempfile));
31
+ }
32
+ }
33
+ }
34
+
35
  if ( !class_exists('blcUtility') ){
36
 
37
  class blcUtility {
38
 
39
  //A regxp for images
40
  function img_pattern(){
41
+ // \1 \2 \3 URL \4
42
+ return '/(<img[\s]+[^>]*src\s*=\s*)([\"\'])([^>]+?)\2([^<>]*>)/i';
43
  }
44
 
45
  //A regexp for links
46
  function link_pattern(){
47
+ // \1 \2 \3 URL \4 \5 Text \6
48
+ return '/(<a[\s]+[^>]*href\s*=\s*)([\"\'])([^>]+?)\2([^<>]*>)((?sU).*)(<\/a>)/i';
49
  }
50
 
51
  /**
56
  * @return string A normalized URL or FALSE if the URL is invalid
57
  */
58
  function normalize_url($url, $base_url = ''){
59
+ //Sometimes links may contain shortcodes. Parse them.
60
+ $url = do_shortcode($url);
61
+
62
+ $parts = @parse_url($url);
63
+ if(!$parts) return false; //Invalid URL
64
 
65
  if(isset($parts['scheme'])) {
66
  //Only HTTP(S) links are checked. Other protocols are not supported.
70
 
71
  $url = html_entity_decode($url);
72
  $url = preg_replace(
73
+ array('/([\?&]PHPSESSID=\w+)$/i', //remove session ID
74
+ '/(#[^\/]*)$/', //and anchors/fragments
75
+ '/&amp;/', //convert improper HTML entities
76
+ '/^(javascript:.*)/i', //treat links that contain JS as links with an empty URL
77
+ '/([\?&]sid=\w+)$/i' //remove another flavour of session ID
78
  ),
79
  array('','','&','',''),
80
  $url);
81
+ $url = trim($url);
82
 
83
+ if ( $url=='' ) return false;
84
 
85
  // turn relative URLs into absolute URLs
86
  if ( empty($base_url) ) $base_url = get_option('siteurl');
102
  //WTF? $relative is a seriously malformed URL
103
  return false;
104
  }
105
+ if( isset($p["scheme"]) ) return $relative;
106
 
107
  $parts=(parse_url($absolute));
108