Broken Link Checker - Version 1.11.4

Version Description

  • Fixed a few more PHP 7.x/5.6 compatibility issues
Download this release

Release Info

Developer freediver
Plugin Icon 128x128 Broken Link Checker
Version 1.11.4
Comparing to
See all releases

Code changes from version 1.11.3 to 1.11.4

broken-link-checker.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: Broken Link Checker
4
  Plugin URI: https://wordpress.org/plugins/broken-link-checker/
5
  Description: Checks your blog for broken links and missing images and notifies you on the dashboard if any are found.
6
- Version: 1.11.3
7
  Author: Janis Elsts, Vladimir Prelovac
8
  Text Domain: broken-link-checker
9
  */
3
  Plugin Name: Broken Link Checker
4
  Plugin URI: https://wordpress.org/plugins/broken-link-checker/
5
  Description: Checks your blog for broken links and missing images and notifies you on the dashboard if any are found.
6
+ Version: 1.11.4
7
  Author: Janis Elsts, Vladimir Prelovac
8
  Text Domain: broken-link-checker
9
  */
core/core.php CHANGED
@@ -1,13 +1,11 @@
1
  <?php
2
-
3
  /**
4
  * Simple function to replicate PHP 5 behaviour
5
  */
6
- if ( !function_exists( 'microtime_float' ) ) {
7
- function microtime_float()
8
- {
9
- list($usec, $sec) = explode(" ", microtime());
10
- return ((float)$usec + (float)$sec);
11
  }
12
  }
13
 
@@ -19,17 +17,16 @@ require BLC_DIRECTORY . '/includes/transactions-manager.php';
19
  if (!class_exists('wsBrokenLinkChecker')) {
20
 
21
  class wsBrokenLinkChecker {
22
- var $conf;
23
-
24
  var $loader;
25
- var $my_basename = '';
26
-
27
- var $db_version; //The required version of the plugin's DB schema.
28
-
29
- var $execution_start_time; //Used for a simple internal execution timer in start_timer()/execution_time()
30
 
31
  private $is_textdomain_loaded = false;
32
-
33
  /**
34
  * wsBrokenLinkChecker::wsBrokenLinkChecker()
35
  * Class constructor
@@ -40,26 +37,26 @@ class wsBrokenLinkChecker {
40
  */
41
  function __construct ( $loader, $conf ) {
42
  $this->db_version = BLC_DATABASE_VERSION;
43
-
44
  $this->conf = $conf;
45
  $this->loader = $loader;
46
  $this->my_basename = plugin_basename( $this->loader );
47
 
48
  $this->load_language();
49
-
50
  //Unlike the activation hook, the deactivation callback *can* be registered in this file
51
- //because deactivation happens after this class has already been instantiated (durinng the
52
- //'init' action).
53
  register_deactivation_hook($loader, array($this, 'deactivation'));
54
-
55
  add_action('admin_menu', array($this,'admin_menu'));
56
 
57
  //Load jQuery on Dashboard pages (probably redundant as WP already does that)
58
  add_action('admin_print_scripts', array($this,'admin_print_scripts'));
59
-
60
  //The dashboard widget
61
  add_action('wp_dashboard_setup', array($this, 'hook_wp_dashboard_setup'));
62
-
63
  //AJAXy hooks
64
  add_action( 'wp_ajax_blc_full_status', array($this,'ajax_full_status') );
65
  add_action( 'wp_ajax_blc_dashboard_status', array($this,'ajax_dashboard_status') );
@@ -74,18 +71,17 @@ class wsBrokenLinkChecker {
74
 
75
  add_action( 'wp_ajax_blc_dismiss', array($this, 'ajax_dismiss') );
76
  add_action( 'wp_ajax_blc_undismiss', array($this, 'ajax_undismiss') );
77
-
78
  //Add/remove Cron events
79
  $this->setup_cron_events();
80
-
81
  //Set hooks that listen for our Cron actions
82
- add_action('blc_cron_email_notifications', array( $this, 'maybe_send_email_notifications' ));
83
- add_action('blc_cron_check_links', array($this, 'cron_check_links'));
84
- add_action('blc_cron_database_maintenance', array($this, 'database_maintenance'));
85
 
86
  //Set the footer hook that will call the worker function via AJAX.
87
- add_action('admin_footer', array($this,'admin_footer'));
88
-
89
  //Add a "Screen Options" panel to the "Broken Links" page
90
  add_screen_options_panel(
91
  'blc-screen-options',
@@ -97,7 +93,25 @@ class wsBrokenLinkChecker {
97
  );
98
 
99
  //Display an explanatory note on the "Tools -> Broken Links -> Warnings" page.
100
- add_action('admin_notices', array($this, 'show_warnings_section_notice'));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  }
102
 
103
  /**
@@ -114,8 +128,8 @@ class wsBrokenLinkChecker {
114
  <!-- wsblc admin footer -->
115
  <script type='text/javascript'>
116
  (function($){
117
-
118
- //(Re)starts the background worker thread
119
  function blcDoWork(){
120
  $.post(
121
  "<?php echo admin_url('admin-ajax.php'); ?>",
@@ -127,16 +141,16 @@ class wsBrokenLinkChecker {
127
  }
128
  //Call it the first time
129
  blcDoWork();
130
-
131
- //Then call it periodically every X seconds
132
  setInterval(blcDoWork, <?php echo (intval($this->conf->options['max_execution_time']) + 1 )*1000; ?>);
133
-
134
  })(jQuery);
135
  </script>
136
  <!-- /wsblc admin footer -->
137
  <?php
138
  }
139
-
140
  /**
141
  * Check if an URL matches the exclusion list.
142
  *
@@ -155,11 +169,11 @@ class wsBrokenLinkChecker {
155
 
156
  function dashboard_widget(){
157
  ?>
158
- <p id='wsblc_activity_box'><?php _e('Loading...', 'broken-link-checker'); ?></p>
159
  <script type='text/javascript'>
160
  jQuery( function($){
161
  var blc_was_autoexpanded = false;
162
-
163
  function blcDashboardStatus(){
164
  $.getJSON(
165
  "<?php echo admin_url('admin-ajax.php'); ?>",
@@ -169,7 +183,7 @@ class wsBrokenLinkChecker {
169
  },
170
  function (data){
171
  if ( data && ( typeof(data.text) != 'undefined' ) ) {
172
- $('#wsblc_activity_box').html(data.text);
173
  <?php if ( $this->conf->options['autoexpand_widget'] ) { ?>
174
  //Expand the widget if there are broken links.
175
  //Do this only once per pageload so as not to annoy the user.
@@ -181,14 +195,14 @@ class wsBrokenLinkChecker {
181
  } else {
182
  $('#wsblc_activity_box').html('<?php _e('[ Network error ]', 'broken-link-checker'); ?>');
183
  }
184
-
185
  setTimeout( blcDashboardStatus, 120*1000 ); //...update every two minutes
186
  }
187
  );
188
  }
189
-
190
  blcDashboardStatus();//Call it the first time
191
-
192
  } );
193
  </script>
194
  <?php
@@ -202,7 +216,7 @@ class wsBrokenLinkChecker {
202
  $this->conf->options['autoexpand_widget'] = !empty($_POST['blc-autoexpand']);
203
  $this->conf->save_options();
204
  }
205
-
206
  ?>
207
  <p><label for="blc-autoexpand">
208
  <input id="blc-autoexpand" name="blc-autoexpand" type="checkbox" value="1" <?php if ( $this->conf->options['autoexpand_widget'] ) echo 'checked="checked"'; ?> />
@@ -215,7 +229,7 @@ class wsBrokenLinkChecker {
215
  //jQuery is used for triggering the link monitor via AJAX when any admin page is open.
216
  wp_enqueue_script('jquery');
217
  }
218
-
219
  function enqueue_settings_scripts(){
220
  //jQuery UI is used on the settings page
221
  wp_enqueue_script('jquery-ui-core'); //Used for background color animation
@@ -223,16 +237,16 @@ class wsBrokenLinkChecker {
223
  wp_enqueue_script('jquery-ui-tabs');
224
  wp_enqueue_script('jquery-cookie', plugins_url('js/jquery.cookie.js', BLC_PLUGIN_FILE)); //Used for storing last widget states, etc
225
  }
226
-
227
  function enqueue_link_page_scripts(){
228
  wp_enqueue_script('jquery-ui-core');
229
  wp_enqueue_script('jquery-ui-dialog'); //Used for the search form
230
  wp_enqueue_script('jquery-color'); //Used for background color animation
231
  wp_enqueue_script('sprintf', plugins_url('js/sprintf.js', BLC_PLUGIN_FILE)); //Used in error messages
232
  }
233
-
234
  /**
235
- * Initiate a full recheck - reparse everything and check all links anew.
236
  *
237
  * @return void
238
  */
@@ -241,10 +255,10 @@ class wsBrokenLinkChecker {
241
 
242
  //Delete all discovered instances
243
  $wpdb->query("TRUNCATE {$wpdb->prefix}blc_instances");
244
-
245
  //Delete all discovered links
246
  $wpdb->query("TRUNCATE {$wpdb->prefix}blc_links");
247
-
248
  //Mark all posts, custom fields and bookmarks for processing.
249
  blc_resynch(true);
250
  }
@@ -260,7 +274,7 @@ class wsBrokenLinkChecker {
260
  wp_clear_scheduled_hook('blc_cron_email_notifications');
261
  wp_clear_scheduled_hook('blc_cron_database_maintenance');
262
  wp_clear_scheduled_hook('blc_cron_check_news'); //Unused event.
263
- //Note the deactivation time for each module. This will help them
264
  //synch up propely if/when the plugin is reactivated.
265
  $moduleManager = blcModuleManager::getInstance();
266
  $the_time = current_time('timestamp');
@@ -269,68 +283,68 @@ class wsBrokenLinkChecker {
269
  }
270
  $this->conf->save_options();
271
  }
272
-
273
  /**
274
  * Perform various database maintenance tasks on the plugin's tables.
275
- *
276
  * Removes records that reference disabled containers and parsers,
277
  * deletes invalid instances and links, optimizes tables, etc.
278
- *
279
  * @return void
280
  */
281
  function database_maintenance(){
282
  blcContainerHelper::cleanup_containers();
283
  blc_cleanup_instances();
284
  blc_cleanup_links();
285
-
286
  blcUtility::optimize_database();
287
  }
288
 
289
  /**
290
  * Create the plugin's menu items and enqueue their scripts and CSS.
291
- * Callback for the 'admin_menu' action.
292
- *
293
  * @return void
294
  */
295
  function admin_menu(){
296
  if (current_user_can('manage_options'))
297
  add_filter('plugin_action_links', array($this, 'plugin_action_links'), 10, 2);
298
-
299
- $options_page_hook = add_options_page(
300
- __('Link Checker Settings', 'broken-link-checker'),
301
- __('Link Checker', 'broken-link-checker'),
302
  'manage_options',
303
  'link-checker-settings',array($this, 'options_page')
304
  );
305
-
306
  $menu_title = __('Broken Links', 'broken-link-checker');
307
  if ( $this->conf->options['show_link_count_bubble'] ){
308
- //To make it easier to notice when broken links appear, display the current number of
309
- //broken links in a little bubble notification in the "Broken Links" menu.
310
  //(Similar to how the number of plugin updates and unmoderated comments is displayed).
311
  $blc_link_query = blcLinkQuery::getInstance();
312
  $broken_links = $blc_link_query->get_filter_links('broken', array('count_only' => true));
313
  if ( $broken_links > 0 ){
314
- //TODO: Appropriating existing CSS classes for my own purposes is hacky. Fix eventually.
315
  $menu_title .= sprintf(
316
- ' <span class="update-plugins"><span class="update-count blc-menu-bubble">%d</span></span>',
317
  $broken_links
318
  );
319
  }
320
- }
321
  $links_page_hook = add_management_page(
322
- __('View Broken Links', 'broken-link-checker'),
323
- $menu_title,
324
  'edit_others_posts',
325
  'view-broken-links',array($this, 'links_page')
326
  );
327
-
328
  //Add plugin-specific scripts and CSS only to the it's own pages
329
  add_action( 'admin_print_styles-' . $options_page_hook, array($this, 'options_page_css') );
330
  add_action( 'admin_print_styles-' . $links_page_hook, array($this, 'links_page_css') );
331
  add_action( 'admin_print_scripts-' . $options_page_hook, array($this, 'enqueue_settings_scripts') );
332
  add_action( 'admin_print_scripts-' . $links_page_hook, array($this, 'enqueue_link_page_scripts') );
333
-
334
  //Make the Settings page link to the link list
335
  add_screen_meta_link(
336
  'blc-links-page-link',
@@ -340,7 +354,7 @@ class wsBrokenLinkChecker {
340
  array('style' => 'font-weight: bold;')
341
  );
342
  }
343
-
344
  /**
345
  * plugin_action_links()
346
  * Handler for the 'plugin_action_links' hook. Adds a "Settings" link to this plugin's entry
@@ -373,7 +387,7 @@ class wsBrokenLinkChecker {
373
 
374
  if (isset($_POST['recheck']) && !empty($_POST['recheck']) ){
375
  $this->initiate_recheck();
376
-
377
  //Redirect back to the settings page
378
  $base_url = remove_query_arg( array('_wpnonce', 'noheader', 'updated', 'error', 'action', 'message') );
379
  wp_redirect( add_query_arg( array( 'recheck-initiated' => true), $base_url ) );
@@ -388,7 +402,7 @@ class wsBrokenLinkChecker {
388
  'blc-recheck-action' => __('Recheck', 'broken-link-checker'),
389
  'blc-deredirect-action' => _x('Fix redirect', 'link action; replace one redirect with a direct link', 'broken-link-checker')
390
  );
391
-
392
  if(isset($_POST['submit'])) {
393
  check_admin_referer('link-checker-options');
394
 
@@ -396,17 +410,17 @@ class wsBrokenLinkChecker {
396
  if ( function_exists('wp_magic_quotes') ){
397
  $cleanPost = stripslashes_deep($cleanPost); //Ceterum censeo, WP shouldn't mangle superglobals.
398
  }
399
-
400
  //Activate/deactivate modules
401
  if ( !empty($_POST['module']) ){
402
  $active = array_keys($_POST['module']);
403
  $moduleManager->set_active_modules($active);
404
  }
405
-
406
  //Only post statuses that actually exist can be selected
407
  if ( isset($_POST['enabled_post_statuses']) && is_array($_POST['enabled_post_statuses']) ){
408
  $available_statuses = get_post_stati();
409
- $enabled_post_statuses = array_intersect($_POST['enabled_post_statuses'], $available_statuses);
410
  } else {
411
  $enabled_post_statuses = array();
412
  }
@@ -433,28 +447,28 @@ class wsBrokenLinkChecker {
433
  if( $new_check_threshold > 0 ){
434
  $this->conf->options['check_threshold'] = $new_check_threshold;
435
  }
436
-
437
  $this->conf->options['mark_broken_links'] = !empty($_POST['mark_broken_links']);
438
  $new_broken_link_css = trim($cleanPost['broken_link_css']);
439
  $this->conf->options['broken_link_css'] = $new_broken_link_css;
440
-
441
  $this->conf->options['mark_removed_links'] = !empty($_POST['mark_removed_links']);
442
  $new_removed_link_css = trim($cleanPost['removed_link_css']);
443
  $this->conf->options['removed_link_css'] = $new_removed_link_css;
444
-
445
  $this->conf->options['nofollow_broken_links'] = !empty($_POST['nofollow_broken_links']);
446
-
447
  $this->conf->options['suggestions_enabled'] = !empty($_POST['suggestions_enabled']);
448
 
449
  $this->conf->options['exclusion_list'] = array_filter(
450
- preg_split(
451
- '/[\s\r\n]+/', //split on newlines and whitespace
452
  $cleanPost['exclusion_list'],
453
  -1,
454
  PREG_SPLIT_NO_EMPTY //skip empty values
455
- )
456
  );
457
-
458
  //Parse the custom field list
459
  $new_custom_fields = array_filter(
460
  preg_split( '/[\r\n]+/', $cleanPost['blc_custom_fields'], -1, PREG_SPLIT_NO_EMPTY )
@@ -465,7 +479,15 @@ class wsBrokenLinkChecker {
465
  $diff2 = array_diff( $this->conf->options['custom_fields'], $new_custom_fields );
466
  $this->conf->options['custom_fields'] = $new_custom_fields;
467
 
468
- //Turning off warnings turns existing warnings into "broken" links.
 
 
 
 
 
 
 
 
469
  $warnings_enabled = !empty($_POST['warnings_enabled']);
470
  if ( $this->conf->get('warnings_enabled') && !$warnings_enabled ) {
471
  $this->promote_warnings_to_broken();
@@ -477,14 +499,14 @@ class wsBrokenLinkChecker {
477
  if( $new_timeout > 0 ){
478
  $this->conf->options['timeout'] = $new_timeout ;
479
  }
480
-
481
- //Server load limit
482
  if ( isset($_POST['server_load_limit']) ){
483
  $this->conf->options['server_load_limit'] = floatval($_POST['server_load_limit']);
484
  if ( $this->conf->options['server_load_limit'] < 0 ){
485
  $this->conf->options['server_load_limit'] = 0;
486
  }
487
-
488
  $this->conf->options['enable_load_limit'] = $this->conf->options['server_load_limit'] > 0;
489
  }
490
 
@@ -494,11 +516,11 @@ class wsBrokenLinkChecker {
494
  $usage = max(min($usage / 100, 1), 0.01);
495
  $this->conf->options['target_resource_usage'] = $usage;
496
  }
497
-
498
  //When to run the checker
499
  $this->conf->options['run_in_dashboard'] = !empty($_POST['run_in_dashboard']);
500
  $this->conf->options['run_via_cron'] = !empty($_POST['run_via_cron']);
501
-
502
  //Email notifications on/off
503
  $email_notifications = !empty($_POST['send_email_notifications']);
504
  $send_authors_email_notifications = !empty($_POST['send_authors_email_notifications']);
@@ -572,13 +594,13 @@ class wsBrokenLinkChecker {
572
 
573
  //Make settings that affect our Cron events take effect immediately
574
  $this->setup_cron_events();
575
-
576
  $this->conf->save_options();
577
-
578
  /*
579
  If the list of custom fields was modified then we MUST resynchronize or
580
  custom fields linked with existing posts may not be detected. This is somewhat
581
- inefficient.
582
  */
583
  if ( ( count($diff1) > 0 ) || ( count($diff2) > 0 ) ){
584
  $manager = blcContainerHelper::get_manager('custom_field');
@@ -588,6 +610,19 @@ class wsBrokenLinkChecker {
588
  }
589
  }
590
 
 
 
 
 
 
 
 
 
 
 
 
 
 
591
  //Resynchronize posts when the user enables or disables post statuses.
592
  if ( $post_statuses_changed ) {
593
  $overlord = blcPostTypeOverlord::getInstance();
@@ -598,49 +633,49 @@ class wsBrokenLinkChecker {
598
  blc_cleanup_instances();
599
  blc_cleanup_links();
600
  }
601
-
602
  //Redirect back to the settings page
603
  $base_url = remove_query_arg( array('_wpnonce', 'noheader', 'updated', 'error', 'action', 'message') );
604
  wp_redirect( add_query_arg( array( 'settings-updated' => true), $base_url ) );
605
  }
606
-
607
- //Show a confirmation message when settings are saved.
608
  if ( !empty($_GET['settings-updated']) ){
609
  echo '<div id="message" class="updated fade"><p><strong>',__('Settings saved.', 'broken-link-checker'), '</strong></p></div>';
610
-
611
  }
612
-
613
  //Show a thank-you message when a donation is made.
614
  if ( !empty($_GET['donated']) ){
615
  echo '<div id="message" class="updated fade"><p><strong>',__('Thank you for your donation!', 'broken-link-checker'), '</strong></p></div>';
616
  $this->conf->set('user_has_donated', true);
617
  $this->conf->save_options();
618
  }
619
-
620
- //Show one when recheck is started, too.
621
  if ( !empty($_GET['recheck-initiated']) ){
622
  echo '<div id="message" class="updated fade"><p><strong>',
623
- __('Complete site recheck started.', 'broken-link-checker'), // -- Yoda
624
  '</strong></p></div>';
625
  }
626
-
627
  //Cull invalid and missing modules
628
  $moduleManager->validate_active_modules();
629
-
630
  $debug = $this->get_debug_info();
631
-
632
  add_filter('blc-module-settings-custom_field', array($this, 'make_custom_field_input'), 10, 2);
633
-
634
  //Translate and markup-ify module headers for display
635
  $modules = $moduleManager->get_modules_by_category('', true, true);
636
-
637
  //Output the custom broken link/removed link styles for example links
638
  printf(
639
- '<style type="text/css">%s %s</style>',
640
  $this->conf->options['broken_link_css'],
641
  $this->conf->options['removed_link_css']
642
  );
643
-
644
  $section_names = array(
645
  'general' => __('General', 'broken-link-checker'),
646
  'where' => __('Look For Links In', 'broken-link-checker'),
@@ -649,56 +684,56 @@ class wsBrokenLinkChecker {
649
  'advanced' => __('Advanced', 'broken-link-checker'),
650
  );
651
  ?>
652
-
653
  <!--[if lte IE 7]>
654
  <style type="text/css">
655
  /* Simulate inline-block in IE7 */
656
  ul.ui-tabs-nav li {
657
- display: inline;
658
  zoom: 1;
659
  }
660
  </style>
661
  <![endif]-->
662
-
663
  <div class="wrap" id="blc-settings-wrap">
664
  <h2><?php _e('Broken Link Checker Options', 'broken-link-checker'); ?></h2>
665
-
666
-
667
  <div id="blc-sidebar">
668
  <div class="metabox-holder">
669
  <?php include BLC_DIRECTORY . '/includes/admin/sidebar.php'; ?>
670
  </div>
671
  </div>
672
-
673
-
674
  <div id="blc-admin-content">
675
-
676
- <form name="link_checker_options" id="link_checker_options" method="post" action="<?php
677
- echo admin_url('options-general.php?page=link-checker-settings&noheader=1');
678
  ?>">
679
- <?php
680
  wp_nonce_field('link-checker-options');
681
  ?>
682
-
683
  <div id="blc-tabs">
684
-
685
  <ul class="hide-if-no-js">
686
  <?php
687
  foreach($section_names as $section_id => $section_name){
688
  printf(
689
  '<li id="tab-button-%s"><a href="#section-%s" title="%s">%s</a></li>',
690
- esc_attr($section_id),
691
  esc_attr($section_id),
692
- esc_attr($section_name),
 
693
  $section_name
694
- );
695
  }
696
  ?>
697
  </ul>
698
 
699
  <div id="section-general" class="blc-section">
700
  <h3 class="hide-if-js"><?php echo $section_names['general']; ?></h3>
701
-
702
  <table class="form-table">
703
 
704
  <tr valign="top">
@@ -712,23 +747,23 @@ class wsBrokenLinkChecker {
712
  <div id='wsblc_full_status'>
713
  <br/><br/><br/>
714
  </div>
715
-
716
  <table id="blc-debug-info">
717
  <?php
718
-
719
  //Output the debug info in a table
720
  foreach( $debug as $key => $value ){
721
  printf (
722
  '<tr valign="top" class="blc-debug-item-%s"><th scope="row">%s</th><td>%s<div class="blc-debug-message">%s</div></td></tr>',
723
  $value['state'],
724
  $key,
725
- $value['value'],
726
  ( array_key_exists('message', $value)?$value['message']:'')
727
  );
728
  }
729
  ?>
730
  </table>
731
-
732
  </td>
733
  </tr>
734
 
@@ -737,13 +772,13 @@ class wsBrokenLinkChecker {
737
  <td>
738
 
739
  <?php
740
- printf(
741
  __('Every %s hours','broken-link-checker'),
742
  sprintf(
743
  '<input type="text" name="check_threshold" id="check_threshold" value="%d" size="5" maxlength="5" />',
744
  $this->conf->options['check_threshold']
745
  )
746
- );
747
  ?>
748
  <br/>
749
  <span class="description">
@@ -752,7 +787,7 @@ class wsBrokenLinkChecker {
752
 
753
  </td>
754
  </tr>
755
-
756
  <tr valign="top">
757
  <th scope="row"><?php _e('E-mail notifications', 'broken-link-checker'); ?></th>
758
  <td>
@@ -807,11 +842,11 @@ class wsBrokenLinkChecker {
807
  _e('Edit CSS', 'broken-link-checker');
808
  ?></a>
809
  </p>
810
-
811
- <div id="broken-link-css-wrap"<?php
812
  if ( !blcUtility::get_cookie('broken-link-css-wrap', false) ){
813
  echo ' class="hidden"';
814
- }
815
  ?>>
816
  <textarea name="broken_link_css" id="broken_link_css" cols='45' rows='4'><?php
817
  if( isset($this->conf->options['broken_link_css']) ) {
@@ -826,7 +861,7 @@ class wsBrokenLinkChecker {
826
  echo ' ', __('Click "Save Changes" to update example output.', 'broken-link-checker');
827
  ?></p>
828
  </div>
829
-
830
  <p style="margin-bottom: 0.5em;">
831
  <label for='mark_removed_links'>
832
  <input type="checkbox" name="mark_removed_links" id="mark_removed_links"
@@ -838,17 +873,17 @@ class wsBrokenLinkChecker {
838
  _e('Edit CSS', 'broken-link-checker');
839
  ?></a>
840
  </p>
841
-
842
- <div id="removed-link-css-wrap" <?php
843
  if ( !blcUtility::get_cookie('removed-link-css-wrap', false) ){
844
  echo ' class="hidden"';
845
- }
846
  ?>>
847
  <textarea name="removed_link_css" id="removed_link_css" cols='45' rows='4'><?php
848
  if( isset($this->conf->options['removed_link_css']) )
849
  echo $this->conf->options['removed_link_css'];
850
  ?></textarea>
851
-
852
  <p class="description"><?php
853
  printf(
854
  __('Example : Lorem ipsum <span %s>removed link</span>, dolor sit amet.', 'broken-link-checker'),
@@ -859,7 +894,7 @@ class wsBrokenLinkChecker {
859
 
860
  </p>
861
  </div>
862
-
863
  <p>
864
  <label for='nofollow_broken_links'>
865
  <input type="checkbox" name="nofollow_broken_links" id="nofollow_broken_links"
@@ -906,14 +941,14 @@ class wsBrokenLinkChecker {
906
  </tr>
907
 
908
  </table>
909
-
910
  </div>
911
-
912
  <div id="section-where" class="blc-section">
913
  <h3 class="hide-if-js"><?php echo $section_names['where']; ?></h3>
914
-
915
  <table class="form-table">
916
-
917
  <tr valign="top">
918
  <th scope="row"><?php _e('Look for links in', 'broken-link-checker'); ?></th>
919
  <td>
@@ -921,22 +956,22 @@ class wsBrokenLinkChecker {
921
  if ( !empty($modules['container']) ){
922
  uasort($modules['container'], create_function('$a, $b', 'return strcasecmp($a["Name"], $b["Name"]);'));
923
  $this->print_module_list($modules['container'], $this->conf->options);
924
- }
925
  ?>
926
  </td></tr>
927
-
928
  <tr valign="top">
929
  <th scope="row"><?php _e('Post statuses', 'broken-link-checker'); ?></th>
930
  <td>
931
  <?php
932
  $available_statuses = get_post_stati(array('internal' => false), 'objects');
933
-
934
  if ( isset($this->conf->options['enabled_post_statuses']) ){
935
  $enabled_post_statuses = $this->conf->options['enabled_post_statuses'];
936
  } else {
937
  $enabled_post_statuses = array();
938
  }
939
-
940
  foreach($available_statuses as $status => $status_object){
941
  printf(
942
  '<p><label><input type="checkbox" name="enabled_post_statuses[]" value="%s"%s> %s</label></p>',
@@ -947,17 +982,17 @@ class wsBrokenLinkChecker {
947
  }
948
  ?>
949
  </td></tr>
950
-
951
  </table>
952
-
953
  </div>
954
-
955
-
956
  <div id="section-which" class="blc-section">
957
  <h3 class="hide-if-js"><?php echo $section_names['which']; ?></h3>
958
-
959
  <table class="form-table">
960
-
961
  <tr valign="top">
962
  <th scope="row"><?php _e('Link types', 'broken-link-checker'); ?></th>
963
  <td>
@@ -970,7 +1005,7 @@ class wsBrokenLinkChecker {
970
  ?>
971
  </td>
972
  </tr>
973
-
974
  <tr valign="top">
975
  <th scope="row"><?php _e('Exclusion list', 'broken-link-checker'); ?></th>
976
  <td><?php _e("Don't check links where the URL contains any of these words (one per line) :", 'broken-link-checker'); ?><br/>
@@ -981,15 +1016,15 @@ class wsBrokenLinkChecker {
981
 
982
  </td>
983
  </tr>
984
-
985
  </table>
986
  </div>
987
-
988
  <div id="section-how" class="blc-section">
989
  <h3 class="hide-if-js"><?php echo $section_names['how']; ?></h3>
990
-
991
  <table class="form-table">
992
-
993
  <tr valign="top">
994
  <th scope="row"><?php _e('Check links using', 'broken-link-checker'); ?></th>
995
  <td>
@@ -1000,57 +1035,57 @@ class wsBrokenLinkChecker {
1000
  }
1001
  ?>
1002
  </td></tr>
1003
-
1004
  </table>
1005
  </div>
1006
-
1007
  <div id="section-advanced" class="blc-section">
1008
  <h3 class="hide-if-js"><?php echo $section_names['advanced']; ?></h3>
1009
-
1010
  <table class="form-table">
1011
-
1012
  <tr valign="top">
1013
  <th scope="row"><?php _e('Timeout', 'broken-link-checker'); ?></th>
1014
  <td>
1015
 
1016
  <?php
1017
-
1018
  printf(
1019
  __('%s seconds', 'broken-link-checker'),
1020
  sprintf(
1021
- '<input type="text" name="timeout" id="blc_timeout" value="%d" size="5" maxlength="3" />',
1022
  $this->conf->options['timeout']
1023
  )
1024
  );
1025
-
1026
  ?>
1027
  <br/><span class="description">
1028
- <?php _e('Links that take longer than this to load will be marked as broken.','broken-link-checker'); ?>
1029
  </span>
1030
 
1031
  </td>
1032
  </tr>
1033
-
1034
  <tr valign="top">
1035
  <th scope="row"><?php _e('Link monitor', 'broken-link-checker'); ?></th>
1036
  <td>
1037
-
1038
  <p>
1039
  <label for='run_in_dashboard'>
1040
-
1041
  <input type="checkbox" name="run_in_dashboard" id="run_in_dashboard"
1042
  <?php if ($this->conf->options['run_in_dashboard']) echo ' checked="checked"'; ?>/>
1043
  <?php _e('Run continuously while the Dashboard is open', 'broken-link-checker'); ?>
1044
  </label>
1045
  </p>
1046
-
1047
  <p>
1048
  <label for='run_via_cron'>
1049
  <input type="checkbox" name="run_via_cron" id="run_via_cron"
1050
  <?php if ($this->conf->options['run_via_cron']) echo ' checked="checked"'; ?>/>
1051
  <?php _e('Run hourly in the background', 'broken-link-checker'); ?>
1052
  </label>
1053
- </p>
1054
 
1055
  </td>
1056
  </tr>
@@ -1095,38 +1130,38 @@ class wsBrokenLinkChecker {
1095
  ?>
1096
  </td>
1097
  </tr>
1098
-
1099
  <tr valign="top">
1100
  <th scope="row"><?php _e('Max. execution time', 'broken-link-checker'); ?></th>
1101
  <td>
1102
 
1103
  <?php
1104
-
1105
  printf(
1106
  __('%s seconds', 'broken-link-checker'),
1107
  sprintf(
1108
- '<input type="text" name="max_execution_time" id="max_execution_time" value="%d" size="5" maxlength="5" />',
1109
  $this->conf->options['max_execution_time']
1110
  )
1111
  );
1112
-
1113
- ?>
1114
  <br/><span class="description">
1115
  <?php
1116
-
1117
  _e('The plugin works by periodically launching a background job that parses your posts for links, checks the discovered URLs, and performs other time-consuming tasks. Here you can set for how long, at most, the link monitor may run each time before stopping.', 'broken-link-checker');
1118
-
1119
- ?>
1120
  </span>
1121
 
1122
  </td>
1123
  </tr>
1124
-
1125
  <tr valign="top">
1126
  <th scope="row"><?php _e('Server load limit', 'broken-link-checker'); ?></th>
1127
  <td>
1128
  <?php
1129
-
1130
  $load = blcUtility::get_server_load();
1131
  $available = !empty($load);
1132
 
@@ -1136,7 +1171,7 @@ class wsBrokenLinkChecker {
1136
  '<input type="text" name="server_load_limit" id="server_load_limit" value="%s" size="5" maxlength="5"/> ',
1137
  $value
1138
  );
1139
-
1140
  printf(
1141
  __('Current load : %s', 'broken-link-checker'),
1142
  '<span id="wsblc_current_load">...</span>'
@@ -1144,20 +1179,20 @@ class wsBrokenLinkChecker {
1144
  echo '<br/><span class="description">';
1145
  printf(
1146
  __(
1147
- 'Link checking will be suspended if the average <a href="%s">server load</a> rises above this number. Leave this field blank to disable load limiting.',
1148
  'broken-link-checker'
1149
  ),
1150
  'http://en.wikipedia.org/wiki/Load_(computing)'
1151
  );
1152
  echo '</span>';
1153
-
1154
  } else {
1155
  echo '<input type="text" disabled="disabled" value="', esc_attr(__('Not available', 'broken-link-checker')), '" size="13"/><br>';
1156
  echo '<span class="description">';
1157
  _e('Load limiting only works on Linux-like systems where <code>/proc/loadavg</code> is present and accessible.', 'broken-link-checker');
1158
  echo '</span>';
1159
  }
1160
- ?>
1161
  </td>
1162
  </tr>
1163
 
@@ -1226,87 +1261,87 @@ class wsBrokenLinkChecker {
1226
  </td>
1227
  </tr>
1228
 
1229
-
1230
  <tr valign="top">
1231
  <th scope="row"><?php _e('Forced recheck', 'broken-link-checker'); ?></th>
1232
  <td>
1233
- <input class="button" type="button" name="start-recheck" id="start-recheck"
1234
  value="<?php _e('Re-check all pages', 'broken-link-checker'); ?>" />
1235
  <input type="hidden" name="recheck" value="" id="recheck" />
1236
  <br />
1237
  <span class="description"><?php
1238
  _e('The "Nuclear Option". Click this button to make the plugin empty its link database and recheck the entire site from scratch.', 'broken-link-checker');
1239
-
1240
  ?></span>
1241
  </td>
1242
  </tr>
1243
-
1244
  </table>
1245
  </div>
1246
-
1247
  </div>
1248
-
1249
  <p class="submit"><input type="submit" name="submit" class='button-primary' value="<?php _e('Save Changes') ?>" /></p>
1250
  </form>
1251
-
1252
  </div> <!-- First postbox-container -->
1253
-
1254
-
1255
  </div>
1256
-
1257
-
1258
-
1259
  <?php
1260
  //The various JS for this page is stored in a separate file for the purposes readability.
1261
  include dirname($this->loader) . '/includes/admin/options-page-js.php';
1262
  }
1263
-
1264
  /**
1265
  * Output a list of modules and their settings.
1266
- *
1267
- * Each list entry will contain a checkbox that is checked if the module is
1268
- * currently active.
1269
- *
1270
  * @param array $modules Array of modules to display
1271
  * @param array $current_settings
1272
  * @return void
1273
  */
1274
  function print_module_list($modules, $current_settings){
1275
  $moduleManager = blcModuleManager::getInstance();
1276
-
1277
  foreach($modules as $module_id => $module_data){
1278
  $module_id = $module_data['ModuleID'];
1279
-
1280
  $style = $module_data['ModuleHidden']?' style="display:none;"':'';
1281
-
1282
  printf(
1283
  '<div class="module-container" id="module-container-%s"%s>',
1284
  $module_id,
1285
  $style
1286
  );
1287
  $this->print_module_checkbox($module_id, $module_data, $moduleManager->is_active($module_id));
1288
-
1289
  $extra_settings = apply_filters(
1290
  'blc-module-settings-'.$module_id,
1291
  '',
1292
  $current_settings
1293
  );
1294
-
1295
  if ( !empty($extra_settings) ){
1296
-
1297
  printf(
1298
  ' | <a class="blc-toggle-link toggle-module-settings" id="toggle-module-settings-%s" href="#">%s</a>',
1299
  esc_attr($module_id),
1300
  __('Configure', 'broken-link-checker')
1301
  );
1302
-
1303
  //The plugin remembers the last open/closed state of module configuration boxes
1304
- $box_id = 'module-extra-settings-' . $module_id;
1305
  $show = blcUtility::get_cookie(
1306
  $box_id,
1307
  $moduleManager->is_active($module_id)
1308
  );
1309
-
1310
  printf(
1311
  '<div class="module-extra-settings%s" id="%s">%s</div>',
1312
  $show?'':' hidden',
@@ -1314,19 +1349,19 @@ class wsBrokenLinkChecker {
1314
  $extra_settings
1315
  );
1316
  }
1317
-
1318
  echo '</div>';
1319
  }
1320
  }
1321
-
1322
  /**
1323
  * Output a checkbox for a module.
1324
- *
1325
  * Generates a simple checkbox that can be used to mark a module as active/inactive.
1326
  * If the specified module can't be deactivated (ModuleAlwaysActive = true), the checkbox
1327
  * will be displayed in a disabled state and a hidden field will be created to make
1328
  * form submissions work correctly.
1329
- *
1330
  * @param string $module_id Module ID.
1331
  * @param array $module_data Associative array of module data.
1332
  * @param bool $active If true, the newly created checkbox will start out checked.
@@ -1337,17 +1372,17 @@ class wsBrokenLinkChecker {
1337
  $name_prefix = 'module';
1338
  $label_class = '';
1339
  $active = $active || $module_data['ModuleAlwaysActive'];
1340
-
1341
  if ( $module_data['ModuleAlwaysActive'] ){
1342
  $disabled = true;
1343
  $name_prefix = 'module-always-active';
1344
  }
1345
-
1346
  $checked = $active ? ' checked="checked"':'';
1347
  if ( $disabled ){
1348
  $checked .= ' disabled="disabled"';
1349
  }
1350
-
1351
  printf(
1352
  '<label class="%s">
1353
  <input type="checkbox" name="%s[%s]" id="module-checkbox-%s"%s /> %s
@@ -1359,7 +1394,7 @@ class wsBrokenLinkChecker {
1359
  $checked,
1360
  $module_data['Name']
1361
  );
1362
-
1363
  if ( $module_data['ModuleAlwaysActive'] ){
1364
  printf(
1365
  '<input type="hidden" name="module[%s]" value="on">',
@@ -1367,12 +1402,12 @@ class wsBrokenLinkChecker {
1367
  );
1368
  }
1369
  }
1370
-
1371
  /**
1372
  * Add extra settings to the "Custom fields" entry on the plugin's config. page.
1373
- *
1374
- * Callback for the 'blc-module-settings-custom_field' filter.
1375
- *
1376
  * @param string $html Current extra HTML
1377
  * @param array $current_settings The current plugin configuration.
1378
  * @return string New extra HTML.
@@ -1385,51 +1420,61 @@ class wsBrokenLinkChecker {
1385
  ) .
1386
  '</span>';
1387
  $html .= '<br><textarea name="blc_custom_fields" id="blc_custom_fields" cols="45" rows="4">';
1388
- if( isset($current_settings['custom_fields']) )
1389
  $html .= esc_textarea(implode("\n", $current_settings['custom_fields']));
 
1390
  $html .= '</textarea>';
1391
-
 
 
 
 
 
 
 
 
 
 
1392
  return $html;
1393
  }
1394
-
1395
  /**
1396
  * Enqueue CSS file for the plugin's Settings page.
1397
- *
1398
  * @return void
1399
  */
1400
  function options_page_css(){
1401
  wp_enqueue_style('blc-options-page', plugins_url('css/options-page.css', BLC_PLUGIN_FILE), array(), '20141113');
1402
  wp_enqueue_style('dashboard');
1403
  }
1404
-
1405
 
1406
  /**
1407
  * Display the "Broken Links" page, listing links detected by the plugin and their status.
1408
- *
1409
  * @return void
1410
  */
1411
  function links_page(){
1412
  global $wpdb; /* @var wpdb $wpdb */
1413
-
1414
  $blc_link_query = blcLinkQuery::getInstance();
1415
 
1416
  //Cull invalid and missing modules so that we don't get dummy links/instances showing up.
1417
  $moduleManager = blcModuleManager::getInstance();
1418
  $moduleManager->validate_active_modules();
1419
-
1420
  if ( defined('BLC_DEBUG') && constant('BLC_DEBUG') ){
1421
- //Make module headers translatable. They need to be formatted corrrectly and
1422
  //placed in a .php file to be visible to the script(s) that generate .pot files.
1423
  $code = $moduleManager->_build_header_translation_code();
1424
  file_put_contents( dirname($this->loader) . '/includes/extra-strings.php', $code );
1425
  }
1426
-
1427
  $action = !empty($_POST['action'])?$_POST['action']:'';
1428
  if ( intval($action) == -1 ){
1429
  //Try the second bulk actions box
1430
  $action = !empty($_POST['action2'])?$_POST['action2']:'';
1431
  }
1432
-
1433
  //Get the list of link IDs selected via checkboxes
1434
  $selected_links = array();
1435
  if ( isset($_POST['selected_links']) && is_array($_POST['selected_links']) ){
@@ -1438,17 +1483,17 @@ class wsBrokenLinkChecker {
1438
  //Remove all zeroes
1439
  $selected_links = array_filter($selected_links);
1440
  }
1441
-
1442
  $message = '';
1443
  $msg_class = 'updated';
1444
-
1445
  //Run the selected bulk action, if any
1446
  $force_delete = false;
1447
  switch ( $action ){
1448
  case 'create-custom-filter':
1449
  list($message, $msg_class) = $this->do_create_custom_filter();
1450
  break;
1451
-
1452
  case 'delete-custom-filter':
1453
  list($message, $msg_class) = $this->do_delete_custom_filter();
1454
  break;
@@ -1459,19 +1504,19 @@ class wsBrokenLinkChecker {
1459
  case 'bulk-trash-sources':
1460
  list($message, $msg_class) = $this->do_bulk_delete_sources($selected_links, $force_delete);
1461
  break;
1462
-
1463
  case 'bulk-unlink':
1464
  list($message, $msg_class) = $this->do_bulk_unlink($selected_links);
1465
  break;
1466
-
1467
  case 'bulk-deredirect':
1468
  list($message, $msg_class) = $this->do_bulk_deredirect($selected_links);
1469
  break;
1470
-
1471
  case 'bulk-recheck':
1472
  list($message, $msg_class) = $this->do_bulk_recheck($selected_links);
1473
  break;
1474
-
1475
  case 'bulk-not-broken':
1476
  list($message, $msg_class) = $this->do_bulk_discard($selected_links);
1477
  break;
@@ -1484,26 +1529,26 @@ class wsBrokenLinkChecker {
1484
  list($message, $msg_class) = $this->do_bulk_edit($selected_links);
1485
  break;
1486
  }
1487
-
1488
-
1489
  if ( !empty($message) ){
1490
  echo '<div id="message" class="'.$msg_class.' fade"><p>'.$message.'</p></div>';
1491
  }
1492
-
1493
  $start_time = microtime_float();
1494
-
1495
  //Load custom filters, if any
1496
  $blc_link_query->load_custom_filters();
1497
-
1498
  //Calculate the number of links matching each filter
1499
  $blc_link_query->count_filter_results();
1500
-
1501
  //Run the selected filter (defaults to displaying broken links)
1502
  $selected_filter_id = isset($_GET['filter_id'])?$_GET['filter_id']:'broken';
1503
  $current_filter = $blc_link_query->exec_filter(
1504
  $selected_filter_id,
1505
  isset($_GET['paged']) ? intval($_GET['paged']) : 1,
1506
- $this->conf->options['table_links_per_page'],
1507
  'broken',
1508
  isset($_GET['orderby']) ? $_GET['orderby'] : '',
1509
  isset($_GET['order']) ? $_GET['order'] : ''
@@ -1512,29 +1557,29 @@ class wsBrokenLinkChecker {
1512
  //exec_filter() returns an array with filter data, including the actual filter ID that was used.
1513
  $filter_id = $current_filter['filter_id'];
1514
 
1515
- //Error?
1516
  if ( empty($current_filter['links']) && !empty($wpdb->last_error) ){
1517
  printf( __('Database error : %s', 'broken-link-checker'), $wpdb->last_error);
1518
  }
1519
  ?>
1520
-
1521
  <script type='text/javascript'>
1522
  var blc_current_filter = '<?php echo $filter_id; ?>';
1523
  var blc_is_broken_filter = <?php echo $current_filter['is_broken_filter'] ? 'true' : 'false'; ?>;
1524
  var blc_current_base_filter = '<?php echo esc_js($current_filter['base_filter']); ?>';
1525
  var blc_suggestions_enabled = <?php echo $this->conf->options['suggestions_enabled'] ? 'true' : 'false'; ?>;
1526
  </script>
1527
-
1528
  <div class="wrap">
1529
  <?php
1530
  $blc_link_query->print_filter_heading($current_filter);
1531
  $blc_link_query->print_filter_menu($filter_id);
1532
-
1533
  //Display the "Search" form and associated buttons.
1534
  //The form requires the $filter_id and $current_filter variables to be set.
1535
  include dirname($this->loader) . '/includes/admin/search-form.php';
1536
-
1537
- //If the user has decided to switch the table to a different mode (compact/full),
1538
  //save the new setting.
1539
  if ( isset($_GET['compact']) ){
1540
  $this->conf->options['table_compact'] = (bool)$_GET['compact'];
@@ -1543,32 +1588,32 @@ class wsBrokenLinkChecker {
1543
 
1544
  //Display the links, if any
1545
  if( $current_filter['links'] && ( count($current_filter['links']) > 0 ) ) {
1546
-
1547
  include dirname($this->loader) . '/includes/admin/table-printer.php';
1548
  $table = new blcTablePrinter($this);
1549
  $table->print_table(
1550
  $current_filter,
1551
- $this->conf->options['table_layout'],
1552
  $this->conf->options['table_visible_columns'],
1553
  $this->conf->options['table_compact']
1554
  );
1555
 
1556
  };
1557
- printf('<!-- Total elapsed : %.4f seconds -->', microtime_float() - $start_time);
1558
-
1559
  //Load assorted JS event handlers and other shinies
1560
  include dirname($this->loader) . '/includes/admin/links-page-js.php';
1561
-
1562
  ?></div><?php
1563
  }
1564
-
1565
  /**
1566
  * Create a custom link filter using params passed in $_POST.
1567
  *
1568
  * @uses $_POST
1569
- * @uses $_GET to replace the current filter ID (if any) with that of the newly created filter.
1570
  *
1571
- * @return array Message and the CSS class to apply to the message.
1572
  */
1573
  function do_create_custom_filter(){
1574
  global $wpdb;
@@ -1590,34 +1635,34 @@ class wsBrokenLinkChecker {
1590
  $name = strip_tags(strval($_POST['name']));
1591
  $blc_link_query = blcLinkQuery::getInstance();
1592
  $filter_id = $blc_link_query->create_custom_filter($name, $_POST['params']);
1593
-
1594
  if ( $filter_id ){
1595
  //Saved
1596
  $message = sprintf( __('Filter "%s" created', 'broken-link-checker'), $name);
1597
  //A little hack to make the filter active immediately
1598
- $_GET['filter_id'] = $filter_id;
1599
  } else {
1600
  //Error
1601
  $message = sprintf( __("Database error : %s", 'broken-link-checker'), $wpdb->last_error);
1602
  $msg_class = 'error';
1603
  }
1604
  }
1605
-
1606
  return array($message, $msg_class);
1607
  }
1608
-
1609
  /**
1610
  * Delete a custom link filter.
1611
  *
1612
  * @uses $_POST
1613
  *
1614
- * @return array Message and a CSS class to apply to the message.
1615
  */
1616
  function do_delete_custom_filter(){
1617
  //Delete an existing custom filter!
1618
  check_admin_referer( 'delete-custom-filter' );
1619
  $msg_class = 'updated';
1620
-
1621
  //Filter ID must be set
1622
  if ( empty($_POST['filter_id']) ){
1623
  $message = __("Filter ID not specified.", 'broken-link-checker');
@@ -1634,10 +1679,10 @@ class wsBrokenLinkChecker {
1634
  $msg_class = 'error';
1635
  }
1636
  }
1637
-
1638
  return array($message, $msg_class);
1639
  }
1640
-
1641
  /**
1642
  * Modify multiple links to point to their target URLs.
1643
  *
@@ -1646,23 +1691,23 @@ class wsBrokenLinkChecker {
1646
  */
1647
  function do_bulk_deredirect($selected_links){
1648
  //For all selected links, replace the URL with the final URL that it redirects to.
1649
-
1650
  $message = '';
1651
  $msg_class = 'updated';
1652
-
1653
  check_admin_referer( 'bulk-action' );
1654
-
1655
- if ( count($selected_links) > 0 ) {
1656
  //Fetch all the selected links
1657
  $links = blc_get_links(array(
1658
  'link_ids' => $selected_links,
1659
  'purpose' => BLC_FOR_EDITING,
1660
  ));
1661
-
1662
  if ( count($links) > 0 ) {
1663
  $processed_links = 0;
1664
  $failed_links = 0;
1665
-
1666
  //Deredirect all selected links
1667
  foreach($links as $link){
1668
  $rez = $link->deredirect();
@@ -1671,22 +1716,22 @@ class wsBrokenLinkChecker {
1671
  } else {
1672
  $failed_links++;
1673
  }
1674
- }
1675
-
1676
  $message = sprintf(
1677
  _n(
1678
  'Replaced %d redirect with a direct link',
1679
  'Replaced %d redirects with direct links',
1680
- $processed_links,
1681
  'broken-link-checker'
1682
  ),
1683
  $processed_links
1684
- );
1685
-
1686
  if ( $failed_links > 0 ) {
1687
  $message .= '<br>' . sprintf(
1688
  _n(
1689
- 'Failed to fix %d redirect',
1690
  'Failed to fix %d redirects',
1691
  $failed_links,
1692
  'broken-link-checker'
@@ -1699,10 +1744,10 @@ class wsBrokenLinkChecker {
1699
  $message = __('None of the selected links are redirects!', 'broken-link-checker');
1700
  }
1701
  }
1702
-
1703
  return array($message, $msg_class);
1704
  }
1705
-
1706
  /**
1707
  * Edit multiple links in one go.
1708
  *
@@ -1712,19 +1757,19 @@ class wsBrokenLinkChecker {
1712
  function do_bulk_edit($selected_links){
1713
  $message = '';
1714
  $msg_class = 'updated';
1715
-
1716
  check_admin_referer( 'bulk-action' );
1717
-
1718
  $post = $_POST;
1719
  if ( function_exists('wp_magic_quotes') ){
1720
  $post = stripslashes_deep($post); //Ceterum censeo, WP shouldn't mangle superglobals.
1721
  }
1722
-
1723
  $search = isset($post['search']) ? $post['search'] : '';
1724
- $replace = isset($post['replace']) ? $post['replace'] : '';
1725
  $use_regex = !empty($post['regex']);
1726
  $case_sensitive = !empty($post['case_sensitive']);
1727
-
1728
  $delimiter = '`'; //Pick a char that's uncommon in URLs so that escaping won't usually be a problem
1729
  if ( $use_regex ){
1730
  $search = $delimiter . $this->escape_regex_delimiter($search, $delimiter) . $delimiter;
@@ -1737,21 +1782,21 @@ class wsBrokenLinkChecker {
1737
  $search = $delimiter . preg_quote($search, $delimiter) . $delimiter . 'i';
1738
  $use_regex = true;
1739
  }
1740
-
1741
- if ( count($selected_links) > 0 ) {
1742
  set_time_limit(300); //In case the user decides to edit hundreds of links at once
1743
-
1744
  //Fetch all the selected links
1745
  $links = blc_get_links(array(
1746
  'link_ids' => $selected_links,
1747
  'purpose' => BLC_FOR_EDITING,
1748
  ));
1749
-
1750
  if ( count($links) > 0 ) {
1751
  $processed_links = 0;
1752
  $failed_links = 0;
1753
  $skipped_links = 0;
1754
-
1755
  //Edit the links
1756
  foreach($links as $link){
1757
  if ( $use_regex ){
@@ -1759,34 +1804,34 @@ class wsBrokenLinkChecker {
1759
  } else {
1760
  $new_url = str_replace($search, $replace, $link->url);
1761
  }
1762
-
1763
  if ( $new_url == $link->url ){
1764
  $skipped_links++;
1765
  continue;
1766
  }
1767
-
1768
  $rez = $link->edit($new_url);
1769
  if ( !is_wp_error($rez) && empty($rez['errors'] )){
1770
  $processed_links++;
1771
  } else {
1772
  $failed_links++;
1773
  }
1774
- }
1775
-
1776
  $message .= sprintf(
1777
  _n(
1778
  '%d link updated.',
1779
  '%d links updated.',
1780
- $processed_links,
1781
  'broken-link-checker'
1782
  ),
1783
  $processed_links
1784
  );
1785
-
1786
  if ( $failed_links > 0 ) {
1787
  $message .= '<br>' . sprintf(
1788
  _n(
1789
- 'Failed to update %d link.',
1790
  'Failed to update %d links.',
1791
  $failed_links,
1792
  'broken-link-checker'
@@ -1797,7 +1842,7 @@ class wsBrokenLinkChecker {
1797
  }
1798
  }
1799
  }
1800
-
1801
  return array($message, $msg_class);
1802
  }
1803
 
@@ -1835,7 +1880,7 @@ class wsBrokenLinkChecker {
1835
 
1836
  return $output;
1837
  }
1838
-
1839
  /**
1840
  * Unlink multiple links.
1841
  *
@@ -1846,21 +1891,21 @@ class wsBrokenLinkChecker {
1846
  //Unlink all selected links.
1847
  $message = '';
1848
  $msg_class = 'updated';
1849
-
1850
  check_admin_referer( 'bulk-action' );
1851
-
1852
- if ( count($selected_links) > 0 ) {
1853
-
1854
  //Fetch all the selected links
1855
  $links = blc_get_links(array(
1856
  'link_ids' => $selected_links,
1857
  'purpose' => BLC_FOR_EDITING,
1858
  ));
1859
-
1860
  if ( count($links) > 0 ) {
1861
  $processed_links = 0;
1862
  $failed_links = 0;
1863
-
1864
  //Unlink (delete) each one
1865
  foreach($links as $link){
1866
  $rez = $link->unlink();
@@ -1869,24 +1914,24 @@ class wsBrokenLinkChecker {
1869
  } else {
1870
  $processed_links++;
1871
  }
1872
- }
1873
-
1874
- //This message is slightly misleading - it doesn't account for the fact that
1875
  //a link can be present in more than one post.
1876
  $message = sprintf(
1877
  _n(
1878
  '%d link removed',
1879
  '%d links removed',
1880
- $processed_links,
1881
  'broken-link-checker'
1882
  ),
1883
  $processed_links
1884
- );
1885
-
1886
  if ( $failed_links > 0 ) {
1887
  $message .= '<br>' . sprintf(
1888
  _n(
1889
- 'Failed to remove %d link',
1890
  'Failed to remove %d links',
1891
  $failed_links,
1892
  'broken-link-checker'
@@ -1897,14 +1942,14 @@ class wsBrokenLinkChecker {
1897
  }
1898
  }
1899
  }
1900
-
1901
  return array($message, $msg_class);
1902
  }
1903
-
1904
  /**
1905
  * Delete or trash posts, bookmarks and other items that contain any of the specified links.
1906
- *
1907
- * Will prefer moving stuff to trash to permanent deletion. If it encounters an item that
1908
  * can't be moved to the trash, it will skip that item by default.
1909
  *
1910
  * @param array $selected_links An array of link IDs
@@ -1914,25 +1959,25 @@ class wsBrokenLinkChecker {
1914
  function do_bulk_delete_sources($selected_links, $force_delete = false){
1915
  $message = '';
1916
  $msg_class = 'updated';
1917
-
1918
  //Delete posts, blogroll entries and any other link containers that contain any of the selected links.
1919
  //
1920
  //Note that once all containers containing a particular link have been deleted,
1921
- //there is no need to explicitly delete the link record itself. The hooks attached to
1922
- //the actions that execute when something is deleted (e.g. "post_deleted") will
1923
- //take care of that.
1924
-
1925
  check_admin_referer( 'bulk-action' );
1926
-
1927
- if ( count($selected_links) > 0 ) {
1928
  $messages = array();
1929
-
1930
  //Fetch all the selected links
1931
  $links = blc_get_links(array(
1932
  'link_ids' => $selected_links,
1933
  'load_instances' => true,
1934
  ));
1935
-
1936
  //Make a list of all containers associated with these links, with each container
1937
  //listed only once.
1938
  $containers = array();
@@ -1943,7 +1988,7 @@ class wsBrokenLinkChecker {
1943
  $containers[$key] = array($instance->container_type, $instance->container_id);
1944
  }
1945
  }
1946
-
1947
  //Instantiate the containers
1948
  $containers = blcContainerHelper::get_containers($containers);
1949
 
@@ -1954,18 +1999,18 @@ class wsBrokenLinkChecker {
1954
  if ( !$container->current_user_can_delete() ){
1955
  continue;
1956
  }
1957
-
1958
  if ( $force_delete ){
1959
  $rez = $container->delete_wrapped_object();
1960
  } else {
1961
  if ( $container->can_be_trashed() ){
1962
  $rez = $container->trash_wrapped_object();
1963
  } else {
1964
- $skipped[] = $container;
1965
  continue;
1966
  }
1967
  }
1968
-
1969
  if ( is_wp_error($rez) ){ /* @var WP_Error $rez */
1970
  //Record error messages for later display
1971
  $messages[] = $rez->get_error_message();
@@ -1980,7 +2025,7 @@ class wsBrokenLinkChecker {
1980
  }
1981
  }
1982
  }
1983
-
1984
  //Generate delete confirmation messages
1985
  foreach($deleted as $container_type => $number){
1986
  if ( $force_delete ){
@@ -1988,9 +2033,9 @@ class wsBrokenLinkChecker {
1988
  } else {
1989
  $messages[] = blcContainerHelper::ui_bulk_trash_message($container_type, $number);
1990
  }
1991
-
1992
  }
1993
-
1994
  //If some items couldn't be trashed, let the user know
1995
  if ( count($skipped) > 0 ){
1996
  $message = sprintf(
@@ -2009,10 +2054,10 @@ class wsBrokenLinkChecker {
2009
  );
2010
  }
2011
  $message .= '</ul>';
2012
-
2013
  $messages[] = $message;
2014
  }
2015
-
2016
  if ( count($messages) > 0 ){
2017
  $message = implode('<p>', $messages);
2018
  } else {
@@ -2020,10 +2065,10 @@ class wsBrokenLinkChecker {
2020
  $msg_class = 'error';
2021
  }
2022
  }
2023
-
2024
  return array($message, $msg_class);
2025
  }
2026
-
2027
  /**
2028
  * Mark multiple links as unchecked.
2029
  *
@@ -2033,61 +2078,61 @@ class wsBrokenLinkChecker {
2033
  function do_bulk_recheck($selected_links){
2034
  /** @var wpdb $wpdb */
2035
  global $wpdb;
2036
-
2037
  $message = '';
2038
  $msg_class = 'updated';
2039
 
2040
  check_admin_referer('bulk-action');
2041
-
2042
  if ( count($selected_links) > 0 ){
2043
- $q = "UPDATE {$wpdb->prefix}blc_links
2044
- SET last_check_attempt = '0000-00-00 00:00:00'
2045
  WHERE link_id IN (".implode(', ', $selected_links).")";
2046
  $changes = $wpdb->query($q);
2047
-
2048
  $message = sprintf(
2049
  _n(
2050
  "%d link scheduled for rechecking",
2051
  "%d links scheduled for rechecking",
2052
- $changes,
2053
  'broken-link-checker'
2054
  ),
2055
  $changes
2056
  );
2057
  }
2058
-
2059
  return array($message, $msg_class);
2060
  }
2061
-
2062
-
2063
  /**
2064
  * Mark multiple links as not broken.
2065
- *
2066
  * @param array $selected_links An array of link IDs
2067
  * @return array Confirmation nessage and the CSS class to use with that message.
2068
  */
2069
  function do_bulk_discard($selected_links){
2070
  check_admin_referer( 'bulk-action' );
2071
-
2072
  $messages = array();
2073
  $msg_class = 'updated';
2074
  $processed_links = 0;
2075
-
2076
  if ( count($selected_links) > 0 ){
2077
  foreach($selected_links as $link_id){
2078
  //Load the link
2079
  $link = new blcLink( intval($link_id) );
2080
-
2081
  //Skip links that don't actually exist
2082
  if ( !$link->valid() ){
2083
  continue;
2084
  }
2085
-
2086
  //Skip links that weren't actually detected as broken
2087
  if ( !$link->broken && !$link->warning ){
2088
  continue;
2089
  }
2090
-
2091
  //Make it appear "not broken"
2092
  $link->broken = false;
2093
  $link->warning = false;
@@ -2108,19 +2153,19 @@ class wsBrokenLinkChecker {
2108
  }
2109
  }
2110
  }
2111
-
2112
  if ( $processed_links > 0 ){
2113
  $messages[] = sprintf(
2114
  _n(
2115
  '%d link marked as not broken',
2116
  '%d links marked as not broken',
2117
- $processed_links,
2118
  'broken-link-checker'
2119
  ),
2120
  $processed_links
2121
  );
2122
  }
2123
-
2124
  return array(implode('<br>', $messages), $msg_class);
2125
  }
2126
 
@@ -2182,11 +2227,11 @@ class wsBrokenLinkChecker {
2182
 
2183
  return array(implode('<br>', $messages), $msg_class);
2184
  }
2185
-
2186
-
2187
  /**
2188
  * Enqueue CSS files for the "Broken Links" page
2189
- *
2190
  * @return void
2191
  */
2192
  function links_page_css(){
@@ -2245,10 +2290,10 @@ class wsBrokenLinkChecker {
2245
  )
2246
  );
2247
  }
2248
-
2249
  /**
2250
  * Generate the HTML for the plugin's Screen Options panel.
2251
- *
2252
  * @return string
2253
  */
2254
  function screen_options_html(){
@@ -2261,16 +2306,16 @@ class wsBrokenLinkChecker {
2261
  $this->conf->save_options();
2262
  }
2263
  }
2264
-
2265
  //Let the user show/hide individual table columns
2266
  $html = '<h5>' . __('Table columns', 'broken-link-checker') . '</h5>';
2267
-
2268
  include dirname($this->loader) . '/includes/admin/table-printer.php';
2269
  $table = new blcTablePrinter($this);
2270
  $available_columns = $table->get_layout_columns($this->conf->options['table_layout']);
2271
-
2272
  $html .= '<div id="blc-column-selector" class="metabox-prefs">';
2273
-
2274
  foreach( $available_columns as $column_id => $data ){
2275
  $html .= sprintf(
2276
  '<label><input type="checkbox" name="visible_columns[%s]"%s>%s</label>',
@@ -2278,11 +2323,11 @@ class wsBrokenLinkChecker {
2278
  in_array($column_id, $this->conf->options['table_visible_columns']) ? ' checked="checked"' : '',
2279
  $data['heading']
2280
  );
2281
- }
2282
-
2283
  $html .= '</div>';
2284
-
2285
- $html .= '<h5>' . __('Show on screen') . '</h5>';
2286
  $html .= '<div class="screen-options">';
2287
  $html .= sprintf(
2288
  '<input type="text" name="per_page" maxlength="3" value="%d" class="screen-per-page" id="blc_links_per_page" />
@@ -2293,12 +2338,12 @@ class wsBrokenLinkChecker {
2293
  __('Apply')
2294
  );
2295
  $html .= '</div>';
2296
-
2297
  $html .= '<h5>' . __('Misc', 'broken-link-checker') . '</h5>';
2298
  $html .= '<div class="screen-options">';
2299
  /*
2300
- Display a checkbox in "Screen Options" that lets the user highlight links that
2301
- have been broken for at least X days.
2302
  */
2303
  $html .= sprintf(
2304
  '<label><input type="checkbox" id="highlight_permanent_failures" name="highlight_permanent_failures"%s> ',
@@ -2313,56 +2358,56 @@ class wsBrokenLinkChecker {
2313
  $input_box
2314
  );
2315
  $html .= '</label>';
2316
-
2317
  //Display a checkbox for turning colourful link status messages on/off
2318
  $html .= sprintf(
2319
  '<br/><label><input type="checkbox" id="table_color_code_status" name="table_color_code_status"%s> %s</label>',
2320
  $this->conf->options['table_color_code_status'] ? ' checked="checked"' : '',
2321
  __('Color-code status codes', 'broken-link-checker')
2322
  );
2323
-
2324
  $html .= '</div>';
2325
-
2326
  return $html;
2327
  }
2328
-
2329
  /**
2330
  * AJAX callback for saving the "Screen Options" panel settings
2331
- *
2332
  * @param array $form
2333
  * @return void
2334
  */
2335
  function ajax_save_screen_options($form){
2336
  if ( !current_user_can('edit_others_posts') ){
2337
  die( json_encode( array(
2338
- 'error' => __("You're not allowed to do that!", 'broken-link-checker')
2339
  )));
2340
  }
2341
-
2342
  $this->conf->options['highlight_permanent_failures'] = !empty($form['highlight_permanent_failures']);
2343
  $this->conf->options['table_color_code_status'] = !empty($form['table_color_code_status']);
2344
-
2345
  $failure_duration_threshold = intval($form['failure_duration_threshold']);
2346
  if ( $failure_duration_threshold >=1 ){
2347
  $this->conf->options['failure_duration_threshold'] = $failure_duration_threshold;
2348
  }
2349
-
2350
  if ( isset($form['visible_columns']) && is_array($form['visible_columns']) ){
2351
  $this->conf->options['table_visible_columns'] = array_keys($form['visible_columns']);
2352
  }
2353
-
2354
  $this->conf->save_options();
2355
  die('1');
2356
  }
2357
-
2358
  function start_timer(){
2359
  $this->execution_start_time = microtime_float();
2360
  }
2361
-
2362
  function execution_time(){
2363
  return microtime_float() - $this->execution_start_time;
2364
  }
2365
-
2366
  /**
2367
  * The main worker function that does all kinds of things.
2368
  *
@@ -2379,24 +2424,24 @@ class wsBrokenLinkChecker {
2379
  if ( session_id() != '' ) {
2380
  session_write_close();
2381
  }
2382
-
2383
  if ( !$this->acquire_lock() ){
2384
  //FB::warn("Another instance of BLC is already working. Stop.");
2385
  $blclog->info('Another instance of BLC is already working. Stop.');
2386
  return;
2387
  }
2388
-
2389
  if ( $this->server_too_busy() ){
2390
  //FB::warn("Server is too busy. Stop.");
2391
  $blclog->warn('Server load is too high, stopping.');
2392
  return;
2393
  }
2394
-
2395
  $this->start_timer();
2396
  $blclog->info('work() starts');
2397
 
2398
  $max_execution_time = $this->conf->options['max_execution_time'];
2399
-
2400
  /*****************************************
2401
  Preparation
2402
  ******************************************/
@@ -2404,13 +2449,13 @@ class wsBrokenLinkChecker {
2404
  if( blcUtility::is_safe_mode() ){
2405
  // Do it the safe mode way - obey the existing max_execution_time setting
2406
  $t = ini_get('max_execution_time');
2407
- if ($t && ($t < $max_execution_time))
2408
  $max_execution_time = $t-1;
2409
  } else {
2410
  // Do it the regular way
2411
  @set_time_limit( $max_execution_time * 2 ); //x2 should be plenty, running any longer would mean a glitch.
2412
  }
2413
-
2414
  //Don't stop the script when the connection is closed
2415
  ignore_user_abort( true );
2416
 
@@ -2431,7 +2476,7 @@ class wsBrokenLinkChecker {
2431
  ob_end_flush(); // Strange behaviour, will not work
2432
  flush(); // Unless both are called !
2433
  }
2434
-
2435
  //Load modules for this context
2436
  $moduleManager = blcModuleManager::getInstance();
2437
  $moduleManager->load_modules('work');
@@ -2439,15 +2484,15 @@ class wsBrokenLinkChecker {
2439
  $target_usage_fraction = $this->conf->get('target_resource_usage', 0.25);
2440
  //Target usage must be between 1% and 100%.
2441
  $target_usage_fraction = max(min($target_usage_fraction, 1), 0.01);
2442
-
2443
-
2444
  /*****************************************
2445
  Parse posts and bookmarks
2446
  ******************************************/
2447
-
2448
  $orphans_possible = false;
2449
  $still_need_resynch = $this->conf->options['need_resynch'];
2450
-
2451
  if ( $still_need_resynch ) {
2452
 
2453
  //FB::log("Looking for containers that need parsing...");
@@ -2460,7 +2505,7 @@ class wsBrokenLinkChecker {
2460
  while( !empty($containers) ){
2461
  //FB::log($containers, 'Found containers');
2462
  $this->sleep_to_maintain_ratio($get_containers_time, $target_usage_fraction);
2463
-
2464
  foreach($containers as $container){
2465
  $synch_start_time = microtime(true);
2466
 
@@ -2474,7 +2519,7 @@ class wsBrokenLinkChecker {
2474
  $container->container_id,
2475
  $synch_elapsed_time * 1000
2476
  ));
2477
-
2478
  //Check if we still have some execution time left
2479
  if( $this->execution_time() > $max_execution_time ){
2480
  //FB::log('The allotted execution time has run out');
@@ -2482,7 +2527,7 @@ class wsBrokenLinkChecker {
2482
  $this->release_lock();
2483
  return;
2484
  }
2485
-
2486
  //Check if the server isn't overloaded
2487
  if ( $this->server_too_busy() ){
2488
  //FB::log('Server overloaded, bailing out.');
@@ -2501,14 +2546,14 @@ class wsBrokenLinkChecker {
2501
  $containers = blcContainerHelper::get_unsynched_containers($max_containers_per_query);
2502
  $get_containers_time = microtime(true) - $start;
2503
  }
2504
-
2505
  //FB::log('No unparsed items found.');
2506
  $still_need_resynch = false;
2507
-
2508
  } else {
2509
  //FB::log('Resynch not required.');
2510
  }
2511
-
2512
  /******************************************
2513
  Resynch done?
2514
  *******************************************/
@@ -2516,11 +2561,11 @@ class wsBrokenLinkChecker {
2516
  $this->conf->options['need_resynch'] = $still_need_resynch;
2517
  $this->conf->save_options();
2518
  }
2519
-
2520
  /******************************************
2521
  Remove orphaned links
2522
  *******************************************/
2523
-
2524
  if ( $orphans_possible ) {
2525
  $start = microtime(true);
2526
 
@@ -2530,7 +2575,7 @@ class wsBrokenLinkChecker {
2530
  $get_links_time = microtime(true) - $start;
2531
  $this->sleep_to_maintain_ratio($get_links_time, $target_usage_fraction);
2532
  }
2533
-
2534
  //Check if we still have some execution time left
2535
  if( $this->execution_time() > $max_execution_time ){
2536
  //FB::log('The allotted execution time has run out');
@@ -2538,14 +2583,14 @@ class wsBrokenLinkChecker {
2538
  $this->release_lock();
2539
  return;
2540
  }
2541
-
2542
  if ( $this->server_too_busy() ){
2543
  //FB::log('Server overloaded, bailing out.');
2544
  $blclog->info('Server load too high, stopping.');
2545
  $this->release_lock();
2546
  return;
2547
  }
2548
-
2549
  /*****************************************
2550
  Check links
2551
  ******************************************/
@@ -2557,7 +2602,7 @@ class wsBrokenLinkChecker {
2557
 
2558
  while ( $links ){
2559
  $this->sleep_to_maintain_ratio($get_links_time, $target_usage_fraction);
2560
-
2561
  //Some unchecked links found
2562
  //FB::log("Checking ".count($links)." link(s)");
2563
  $blclog->info("Checking ".count($links)." link(s)");
@@ -2580,7 +2625,7 @@ class wsBrokenLinkChecker {
2580
  $link->last_check_attempt = time();
2581
  $link->save();
2582
  }
2583
-
2584
  //Check if we still have some execution time left
2585
  if( $this->execution_time() > $max_execution_time ){
2586
  //FB::log('The allotted execution time has run out');
@@ -2588,7 +2633,7 @@ class wsBrokenLinkChecker {
2588
  $this->release_lock();
2589
  return;
2590
  }
2591
-
2592
  //Check if the server isn't overloaded
2593
  if ( $this->server_too_busy() ){
2594
  //FB::log('Server overloaded, bailing out.');
@@ -2604,7 +2649,7 @@ class wsBrokenLinkChecker {
2604
  $get_links_time = microtime(true) - $start;
2605
  }
2606
  //FB::log('No links need to be checked right now.');
2607
-
2608
  $this->release_lock();
2609
  $blclog->info('work(): All done.');
2610
  //FB::log('All done.');
@@ -2634,48 +2679,48 @@ class wsBrokenLinkChecker {
2634
  usleep($sleep_time * 1000000);
2635
  }
2636
  }
2637
-
2638
  /**
2639
  * This function is called when the plugin's cron hook executes.
2640
  * Its only purpose is to invoke the worker function.
2641
  *
2642
- * @uses wsBrokenLinkChecker::work()
2643
  *
2644
  * @return void
2645
  */
2646
  function cron_check_links(){
2647
  $this->work();
2648
  }
2649
-
2650
  /**
2651
  * Retrieve links that need to be checked or re-checked.
2652
  *
2653
  * @param integer $max_results The maximum number of links to return. Defaults to 0 = no limit.
2654
- * @param bool $count_only If true, only the number of found links will be returned, not the links themselves.
2655
  * @return int|blcLink[]
2656
  */
2657
  function get_links_to_check($max_results = 0, $count_only = false){
2658
  global $wpdb; /* @var wpdb $wpdb */
2659
-
2660
  $check_threshold = date('Y-m-d H:i:s', strtotime('-'.$this->conf->options['check_threshold'].' hours'));
2661
  $recheck_threshold = date('Y-m-d H:i:s', time() - $this->conf->options['recheck_threshold']);
2662
-
2663
  //FB::log('Looking for links to check (threshold : '.$check_threshold.', recheck_threshold : '.$recheck_threshold.')...');
2664
-
2665
  //Select some links that haven't been checked for a long time or
2666
  //that are broken and need to be re-checked again. Links that are
2667
  //marked as "being checked" and have been that way for several minutes
2668
- //can also be considered broken/buggy, so those will be selected
2669
  //as well.
2670
-
2671
- //Only check links that have at least one valid instance (i.e. an instance exists and
2672
  //it corresponds to one of the currently loaded container/parser types).
2673
  $manager = blcModuleManager::getInstance();
2674
  $loaded_containers = $manager->get_escaped_ids('container');
2675
  $loaded_parsers = $manager->get_escaped_ids('parser');
2676
-
2677
  //Note : This is a slow query, but AFAIK there is no way to speed it up.
2678
- //I could put an index on last_check_attempt, but that value is almost
2679
  //certainly unique for each row so it wouldn't be much better than a full table scan.
2680
  if ( $count_only ){
2681
  $q = "SELECT COUNT(DISTINCT links.link_id)\n";
@@ -2707,36 +2752,36 @@ class wsBrokenLinkChecker {
2707
  $q .= "LIMIT " . intval($max_results);
2708
  }
2709
  }
2710
-
2711
  $link_q = $wpdb->prepare(
2712
- $q,
2713
- $check_threshold,
2714
- $this->conf->options['recheck_count'],
2715
  $recheck_threshold
2716
  );
2717
  //FB::log($link_q, "Find links to check");
2718
  //$blclog->debug("Find links to check: \n" . $link_q);
2719
-
2720
  //If we just need the number of links, retrieve it and return
2721
  if ( $count_only ){
2722
  return $wpdb->get_var($link_q);
2723
  }
2724
-
2725
  //Fetch the link data
2726
  $link_data = $wpdb->get_results($link_q, ARRAY_A);
2727
  if ( empty($link_data) ){
2728
  return array();
2729
  }
2730
-
2731
  //Instantiate blcLink objects for all fetched links
2732
  $links = array();
2733
  foreach($link_data as $data){
2734
  $links[] = new blcLink($data);
2735
  }
2736
-
2737
  return $links;
2738
  }
2739
-
2740
  /**
2741
  * Output the current link checker status in JSON format.
2742
  * Ajax hook for the 'blc_full_status' action.
@@ -2746,15 +2791,15 @@ class wsBrokenLinkChecker {
2746
  function ajax_full_status( ){
2747
  $status = $this->get_status();
2748
  $text = $this->status_text( $status );
2749
-
2750
  echo json_encode( array(
2751
  'text' => $text,
2752
- 'status' => $status,
2753
  ) );
2754
-
2755
  die();
2756
  }
2757
-
2758
  /**
2759
  * Generates a status message based on the status info in $status
2760
  *
@@ -2763,10 +2808,10 @@ class wsBrokenLinkChecker {
2763
  */
2764
  function status_text( $status ){
2765
  $text = '';
2766
-
2767
  if( $status['broken_links'] > 0 ){
2768
- $text .= sprintf(
2769
- "<a href='%s' title='" . __('View broken links', 'broken-link-checker') . "'><strong>".
2770
  _n('Found %d broken link', 'Found %d broken links', $status['broken_links'], 'broken-link-checker') .
2771
  "</strong></a>",
2772
  esc_attr(admin_url('tools.php?page=view-broken-links')),
@@ -2775,17 +2820,17 @@ class wsBrokenLinkChecker {
2775
  } else {
2776
  $text .= __("No broken links found.", 'broken-link-checker');
2777
  }
2778
-
2779
  $text .= "<br/>";
2780
-
2781
  if( $status['unchecked_links'] > 0) {
2782
- $text .= sprintf(
2783
- _n('%d URL in the work queue', '%d URLs in the work queue', $status['unchecked_links'], 'broken-link-checker'),
2784
  $status['unchecked_links'] );
2785
  } else {
2786
  $text .= __("No URLs in the work queue.", 'broken-link-checker');
2787
  }
2788
-
2789
  $text .= "<br/>";
2790
  if ( $status['known_links'] > 0 ){
2791
  $url_count = sprintf(
@@ -2817,12 +2862,12 @@ class wsBrokenLinkChecker {
2817
  $text .= __('No links detected.', 'broken-link-checker');
2818
  }
2819
  }
2820
-
2821
  return $text;
2822
  }
2823
-
2824
  /**
2825
- * @uses wsBrokenLinkChecker::ajax_full_status()
2826
  *
2827
  * @return void
2828
  */
@@ -2830,7 +2875,7 @@ class wsBrokenLinkChecker {
2830
  //Just display the full status.
2831
  $this->ajax_full_status();
2832
  }
2833
-
2834
  /**
2835
  * Output the current average server load (over the last one-minute period).
2836
  * Called via AJAX.
@@ -2842,36 +2887,36 @@ class wsBrokenLinkChecker {
2842
  if ( empty($load) ){
2843
  die( _x('Unknown', 'current load', 'broken-link-checker') );
2844
  }
2845
-
2846
  $one_minute = reset($load);
2847
  printf('%.2f', $one_minute);
2848
  die();
2849
  }
2850
-
2851
  /**
2852
- * Returns an array with various status information about the plugin. Array key reference:
2853
  * check_threshold - date/time; links checked before this threshold should be checked again.
2854
  * recheck_threshold - date/time; broken links checked before this threshold should be re-checked.
2855
  * known_links - the number of detected unique URLs (a misleading name, yes).
2856
  * known_instances - the number of detected link instances, i.e. actual link elements in posts and other places.
2857
- * broken_links - the number of detected broken links.
2858
  * unchecked_links - the number of URLs that need to be checked ASAP; based on check_threshold and recheck_threshold.
2859
  *
2860
  * @return array
2861
  */
2862
  function get_status(){
2863
  $blc_link_query = blcLinkQuery::getInstance();
2864
-
2865
  $check_threshold=date('Y-m-d H:i:s', strtotime('-'.$this->conf->options['check_threshold'].' hours'));
2866
  $recheck_threshold=date('Y-m-d H:i:s', time() - $this->conf->options['recheck_threshold']);
2867
-
2868
  $known_links = blc_get_links(array('count_only' => true));
2869
  $known_instances = blc_get_usable_instance_count();
2870
-
2871
  $broken_links = $blc_link_query->get_filter_links('broken', array('count_only' => true));
2872
-
2873
  $unchecked_links = $this->get_links_to_check(0, true);
2874
-
2875
  return array(
2876
  'check_threshold' => $check_threshold,
2877
  'recheck_threshold' => $recheck_threshold,
@@ -2881,15 +2926,15 @@ class wsBrokenLinkChecker {
2881
  'unchecked_links' => $unchecked_links,
2882
  );
2883
  }
2884
-
2885
  function ajax_work(){
2886
  check_ajax_referer('blc_work');
2887
 
2888
- //Run the worker function
2889
  $this->work();
2890
  die();
2891
  }
2892
-
2893
  /**
2894
  * AJAX hook for the "Not broken" button. Marks a link as broken and as a likely false positive.
2895
  *
@@ -2899,17 +2944,17 @@ class wsBrokenLinkChecker {
2899
  if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_discard', false, false)){
2900
  die( __("You're not allowed to do that!", 'broken-link-checker') );
2901
  }
2902
-
2903
  if ( isset($_POST['link_id']) ){
2904
  //Load the link
2905
  $link = new blcLink( intval($_POST['link_id']) );
2906
-
2907
  if ( !$link->valid() ){
2908
  printf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) );
2909
  die();
2910
  }
2911
  //Make it appear "not broken"
2912
- $link->broken = false;
2913
  $link->warning = false;
2914
  $link->false_positive = true;
2915
  $link->last_check_attempt = time();
@@ -2964,16 +3009,16 @@ class wsBrokenLinkChecker {
2964
  die( __("Error : link_id not specified", 'broken-link-checker') );
2965
  }
2966
  }
2967
-
2968
  /**
2969
- * AJAX hook for the inline link editor on Tools -> Broken Links.
2970
  *
2971
  * @return void
2972
  */
2973
  function ajax_edit(){
2974
  if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_edit', false, false)){
2975
  die( json_encode( array(
2976
- 'error' => __("You're not allowed to do that!", 'broken-link-checker')
2977
  )));
2978
  }
2979
 
@@ -3064,9 +3109,9 @@ class wsBrokenLinkChecker {
3064
  die( json_encode($response) );
3065
  }
3066
  }
3067
-
3068
  /**
3069
- * AJAX hook for the "Unlink" action links in Tools -> Broken Links.
3070
  * Removes the specified link from all posts and other supported items.
3071
  *
3072
  * @return void
@@ -3074,23 +3119,23 @@ class wsBrokenLinkChecker {
3074
  function ajax_unlink(){
3075
  if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_unlink', false, false)){
3076
  die( json_encode( array(
3077
- 'error' => __("You're not allowed to do that!", 'broken-link-checker')
3078
  )));
3079
  }
3080
-
3081
  if ( isset($_POST['link_id']) ){
3082
  //Load the link
3083
  $link = new blcLink( intval($_POST['link_id']) );
3084
-
3085
  if ( !$link->valid() ){
3086
  die( json_encode( array(
3087
- 'error' => sprintf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) )
3088
  )));
3089
  }
3090
-
3091
  //Try and unlink it
3092
  $rez = $link->unlink();
3093
-
3094
  if ( $rez === false ){
3095
  die( json_encode( array(
3096
  'error' => __("An unexpected error occured!", 'broken-link-checker')
@@ -3104,13 +3149,13 @@ class wsBrokenLinkChecker {
3104
  foreach($rez['errors'] as $error){ /** @var WP_Error $error */
3105
  array_push( $response['errors'], implode(', ', $error->get_error_messages()) );
3106
  }
3107
-
3108
  die( json_encode($response) );
3109
  }
3110
-
3111
  } else {
3112
  die( json_encode( array(
3113
- 'error' => __("Error : link_id not specified", 'broken-link-checker')
3114
  )));
3115
  }
3116
  }
@@ -3218,16 +3263,16 @@ class wsBrokenLinkChecker {
3218
 
3219
  die(json_encode($response));
3220
  }
3221
-
3222
  function ajax_link_details(){
3223
  global $wpdb; /* @var wpdb $wpdb */
3224
-
3225
  if (!current_user_can('edit_others_posts')){
3226
  die( __("You don't have sufficient privileges to access this information!", 'broken-link-checker') );
3227
  }
3228
-
3229
  //FB::log("Loading link details via AJAX");
3230
-
3231
  if ( isset($_GET['link_id']) ){
3232
  //FB::info("Link ID found in GET");
3233
  $link_id = intval($_GET['link_id']);
@@ -3238,10 +3283,10 @@ class wsBrokenLinkChecker {
3238
  //FB::error('Link ID not specified, you hacking bastard.');
3239
  die( __('Error : link ID not specified', 'broken-link-checker') );
3240
  }
3241
-
3242
- //Load the link.
3243
  $link = new blcLink($link_id);
3244
-
3245
  if ( !$link->is_new ){
3246
  //FB::info($link, 'Link loaded');
3247
  if ( !class_exists('blcTablePrinter') ){
@@ -3254,7 +3299,7 @@ class wsBrokenLinkChecker {
3254
  die();
3255
  }
3256
  }
3257
-
3258
  /**
3259
  * Acquire an exclusive lock.
3260
  * If we already hold a lock, it will be released and a new one will be acquired.
@@ -3264,9 +3309,9 @@ class wsBrokenLinkChecker {
3264
  function acquire_lock(){
3265
  return WPMutex::acquire('blc_lock');
3266
  }
3267
-
3268
  /**
3269
- * Relese our exclusive lock.
3270
  * Does nothing if the lock has already been released.
3271
  *
3272
  * @return bool
@@ -3274,7 +3319,7 @@ class wsBrokenLinkChecker {
3274
  function release_lock(){
3275
  return WPMutex::release('blc_lock');
3276
  }
3277
-
3278
  /**
3279
  * Check if server is currently too overloaded to run the link checker.
3280
  *
@@ -3284,33 +3329,33 @@ class wsBrokenLinkChecker {
3284
  if ( !$this->conf->options['enable_load_limit'] || !isset($this->conf->options['server_load_limit']) ){
3285
  return false;
3286
  }
3287
-
3288
  $loads = blcUtility::get_server_load();
3289
  if ( empty($loads) ){
3290
  return false;
3291
  }
3292
  $one_minute = floatval(reset($loads));
3293
-
3294
  return $one_minute > $this->conf->options['server_load_limit'];
3295
  }
3296
-
3297
  /**
3298
  * Register BLC's Dashboard widget
3299
- *
3300
  * @return void
3301
  */
3302
  function hook_wp_dashboard_setup(){
3303
  $show_widget = current_user_can($this->conf->get('dashboard_widget_capability', 'edit_others_posts'));
3304
  if ( function_exists( 'wp_add_dashboard_widget' ) && $show_widget ) {
3305
  wp_add_dashboard_widget(
3306
- 'blc_dashboard_widget',
3307
- __('Broken Link Checker', 'broken-link-checker'),
3308
  array( $this, 'dashboard_widget' ),
3309
  array( $this, 'dashboard_widget_control' )
3310
  );
3311
  }
3312
  }
3313
-
3314
  /**
3315
  * Collect various debugging information and return it in an associative array
3316
  *
@@ -3320,46 +3365,46 @@ class wsBrokenLinkChecker {
3320
  /** @var wpdb $wpdb */
3321
  global $wpdb;
3322
 
3323
- //Collect some information that's useful for debugging
3324
  $debug = array();
3325
-
3326
  //PHP version. Any one is fine as long as WP supports it.
3327
  $debug[ __('PHP version', 'broken-link-checker') ] = array(
3328
  'state' => 'ok',
3329
- 'value' => phpversion(),
3330
  );
3331
-
3332
  //MySQL version
3333
  $debug[ __('MySQL version', 'broken-link-checker') ] = array(
3334
  'state' => 'ok',
3335
  'value' => $wpdb->db_version(),
3336
  );
3337
-
3338
  //CURL presence and version
3339
  if ( function_exists('curl_version') ){
3340
  $version = curl_version();
3341
-
3342
  if ( version_compare( $version['version'], '7.16.0', '<=' ) ){
3343
  $data = array(
3344
- 'state' => 'warning',
3345
  'value' => $version['version'],
3346
  'message' => __('You have an old version of CURL. Redirect detection may not work properly.', 'broken-link-checker'),
3347
  );
3348
  } else {
3349
  $data = array(
3350
- 'state' => 'ok',
3351
  'value' => $version['version'],
3352
  );
3353
  }
3354
-
3355
  } else {
3356
  $data = array(
3357
- 'state' => 'warning',
3358
  'value' => __('Not installed', 'broken-link-checker'),
3359
  );
3360
  }
3361
  $debug[ __('CURL version', 'broken-link-checker') ] = $data;
3362
-
3363
  //Snoopy presence
3364
  if ( class_exists('Snoopy') || file_exists(ABSPATH. WPINC . '/class-snoopy.php') ){
3365
  $data = array(
@@ -3367,7 +3412,7 @@ class wsBrokenLinkChecker {
3367
  'value' => __('Installed', 'broken-link-checker'),
3368
  );
3369
  } else {
3370
- //No Snoopy? This should never happen, but if it does we *must* have CURL.
3371
  if ( function_exists('curl_init') ){
3372
  $data = array(
3373
  'state' => 'ok',
@@ -3380,10 +3425,10 @@ class wsBrokenLinkChecker {
3380
  'message' => __('You must have either CURL or Snoopy installed for the plugin to work!', 'broken-link-checker'),
3381
  );
3382
  }
3383
-
3384
  }
3385
  $debug['Snoopy'] = $data;
3386
-
3387
  //Safe_mode status
3388
  if ( blcUtility::is_safe_mode() ){
3389
  $debug['Safe mode'] = array(
@@ -3397,7 +3442,7 @@ class wsBrokenLinkChecker {
3397
  'value' => __('Off', 'broken-link-checker'),
3398
  );
3399
  }
3400
-
3401
  //Open_basedir status
3402
  if ( blcUtility::is_open_basedir() ){
3403
  $debug['open_basedir'] = array(
@@ -3411,7 +3456,7 @@ class wsBrokenLinkChecker {
3411
  'value' => __('Off', 'broken-link-checker'),
3412
  );
3413
  }
3414
-
3415
  //Default PHP execution time limit
3416
  $debug['Default PHP execution time limit'] = array(
3417
  'state' => 'ok',
@@ -3431,7 +3476,7 @@ class wsBrokenLinkChecker {
3431
  'state' => 'ok',
3432
  'value' => sprintf('%d', $this->conf->options['need_resynch'] ? '1 (resynch. required)' : '0 (resynch. not required)'),
3433
  );
3434
-
3435
  //Synch records
3436
  $synch_records = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_synch"));
3437
  $data = array(
@@ -3443,21 +3488,21 @@ class wsBrokenLinkChecker {
3443
  $data['message'] = __('If this value is zero even after several page reloads you have probably encountered a bug.', 'broken-link-checker');
3444
  }
3445
  $debug['Synch. records'] = $data;
3446
-
3447
  //Total links and instances (including invalid ones)
3448
  $all_links = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_links"));
3449
  $all_instances = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_instances"));
3450
-
3451
- //Show the number of unparsed containers. Useful for debugging. For performance,
3452
  //this is only shown when we have no links/instances yet.
3453
  if( ($all_links == 0) && ($all_instances == 0) ){
3454
  $unparsed_items = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_synch WHERE synched=0"));
3455
  $debug['Unparsed items'] = array(
3456
- 'state' => 'warning',
3457
  'value' => $unparsed_items,
3458
  );
3459
- }
3460
-
3461
  //Links & instances
3462
  if ( ($all_links > 0) && ($all_instances > 0) ){
3463
  $debug['Link records'] = array(
@@ -3469,7 +3514,7 @@ class wsBrokenLinkChecker {
3469
  'state' => 'warning',
3470
  'value' => sprintf('%d (%d)', $all_links, $all_instances),
3471
  );
3472
- }
3473
 
3474
  //Email notifications.
3475
  if ( $this->conf->options['last_notification_sent'] ) {
@@ -3513,7 +3558,7 @@ class wsBrokenLinkChecker {
3513
  'value' => 'No installation log found found.',
3514
  );
3515
  }
3516
-
3517
  return $debug;
3518
  }
3519
 
@@ -3707,7 +3752,7 @@ class wsBrokenLinkChecker {
3707
  $this->send_html_email($author->user_email, $subject, $body);
3708
  }
3709
  }
3710
-
3711
  function override_mail_content_type(/** @noinspection PhpUnusedParameterInspection */ $content_type){
3712
  return 'text/html';
3713
  }
@@ -3729,24 +3774,25 @@ class wsBrokenLinkChecker {
3729
  '%d'
3730
  );
3731
  }
3732
-
3733
  /**
3734
  * Install or uninstall the plugin's Cron events based on current settings.
3735
  *
3736
- * @uses wsBrokenLinkChecker::$conf Uses $conf->options to determine if events need to be (un)installed.
3737
  *
3738
  * @return void
3739
  */
3740
  function setup_cron_events(){
3741
- //Link monitor
 
3742
  if ( $this->conf->options['run_via_cron'] ){
3743
  if (!wp_next_scheduled('blc_cron_check_links')) {
3744
- wp_schedule_event( time(), 'hourly', 'blc_cron_check_links' );
3745
  }
3746
  } else {
3747
  wp_clear_scheduled_hook('blc_cron_check_links');
3748
  }
3749
-
3750
  //Email notifications about broken links
3751
  if ( $this->conf->options['send_email_notifications'] || $this->conf->options['send_authors_email_notifications'] ){
3752
  if ( !wp_next_scheduled('blc_cron_email_notifications') ){
@@ -3755,13 +3801,13 @@ class wsBrokenLinkChecker {
3755
  } else {
3756
  wp_clear_scheduled_hook('blc_cron_email_notifications');
3757
  }
3758
-
3759
  //Run database maintenance every two weeks or so
3760
  if ( !wp_next_scheduled('blc_cron_database_maintenance') ){
3761
- wp_schedule_event(time(), 'bimonthly', 'blc_cron_database_maintenance');
3762
  }
3763
  }
3764
-
3765
  /**
3766
  * Load the plugin's textdomain.
3767
  *
@@ -3770,7 +3816,7 @@ class wsBrokenLinkChecker {
3770
  function load_language(){
3771
  $this->is_textdomain_loaded = load_plugin_textdomain( 'broken-link-checker', false, basename(dirname($this->loader)) . '/languages' );
3772
  }
3773
-
3774
  protected static function get_default_log_directory() {
3775
  $uploads = wp_upload_dir();
3776
  return $uploads['basedir'] . '/broken-link-checker';
1
  <?php
 
2
  /**
3
  * Simple function to replicate PHP 5 behaviour
4
  */
5
+ if ( ! function_exists( 'microtime_float' ) ) {
6
+ function microtime_float() {
7
+ list($usec, $sec) = explode( ' ', microtime() );
8
+ return ( (float)$usec + (float)$sec);
 
9
  }
10
  }
11
 
17
  if (!class_exists('wsBrokenLinkChecker')) {
18
 
19
  class wsBrokenLinkChecker {
20
+ var $conf;
 
21
  var $loader;
22
+ var $my_basename = '';
23
+
24
+ var $db_version; //The required version of the plugin's DB schema.
25
+
26
+ var $execution_start_time; //Used for a simple internal execution timer in start_timer()/execution_time()
27
 
28
  private $is_textdomain_loaded = false;
29
+
30
  /**
31
  * wsBrokenLinkChecker::wsBrokenLinkChecker()
32
  * Class constructor
37
  */
38
  function __construct ( $loader, $conf ) {
39
  $this->db_version = BLC_DATABASE_VERSION;
40
+
41
  $this->conf = $conf;
42
  $this->loader = $loader;
43
  $this->my_basename = plugin_basename( $this->loader );
44
 
45
  $this->load_language();
46
+
47
  //Unlike the activation hook, the deactivation callback *can* be registered in this file
48
+ //because deactivation happens after this class has already been instantiated (durinng the
49
+ //'init' action).
50
  register_deactivation_hook($loader, array($this, 'deactivation'));
51
+
52
  add_action('admin_menu', array($this,'admin_menu'));
53
 
54
  //Load jQuery on Dashboard pages (probably redundant as WP already does that)
55
  add_action('admin_print_scripts', array($this,'admin_print_scripts'));
56
+
57
  //The dashboard widget
58
  add_action('wp_dashboard_setup', array($this, 'hook_wp_dashboard_setup'));
59
+
60
  //AJAXy hooks
61
  add_action( 'wp_ajax_blc_full_status', array($this,'ajax_full_status') );
62
  add_action( 'wp_ajax_blc_dashboard_status', array($this,'ajax_dashboard_status') );
71
 
72
  add_action( 'wp_ajax_blc_dismiss', array($this, 'ajax_dismiss') );
73
  add_action( 'wp_ajax_blc_undismiss', array($this, 'ajax_undismiss') );
74
+
75
  //Add/remove Cron events
76
  $this->setup_cron_events();
77
+
78
  //Set hooks that listen for our Cron actions
79
+ add_action( 'blc_cron_email_notifications', array( $this, 'maybe_send_email_notifications' ) );
80
+ add_action( 'blc_cron_check_links', array( $this, 'cron_check_links' ) );
81
+ add_action( 'blc_cron_database_maintenance', array( $this, 'database_maintenance' ) );
82
 
83
  //Set the footer hook that will call the worker function via AJAX.
84
+ add_action( 'admin_footer', array( $this,'admin_footer' ) );
 
85
  //Add a "Screen Options" panel to the "Broken Links" page
86
  add_screen_options_panel(
87
  'blc-screen-options',
93
  );
94
 
95
  //Display an explanatory note on the "Tools -> Broken Links -> Warnings" page.
96
+ add_action( 'admin_notices', array( $this, 'show_warnings_section_notice' ) );
97
+
98
+ add_filter('cron_schedules', array( $this, 'cron_add_every_10_minutes'));
99
+
100
+ }
101
+
102
+ /**
103
+ * @param $schedules
104
+ *
105
+ * @return mixed
106
+ */
107
+ function cron_add_every_10_minutes( $schedules ) {
108
+ // Adds once weekly to the existing schedules.
109
+ $schedules['10min'] = [
110
+ 'interval' => 600,
111
+ 'display' => __('Every 10 minutes')
112
+ ];
113
+
114
+ return $schedules;
115
  }
116
 
117
  /**
128
  <!-- wsblc admin footer -->
129
  <script type='text/javascript'>
130
  (function($){
131
+
132
+ //(Re)starts the background worker thread
133
  function blcDoWork(){
134
  $.post(
135
  "<?php echo admin_url('admin-ajax.php'); ?>",
141
  }
142
  //Call it the first time
143
  blcDoWork();
144
+
145
+ //Then call it periodically every X seconds
146
  setInterval(blcDoWork, <?php echo (intval($this->conf->options['max_execution_time']) + 1 )*1000; ?>);
147
+
148
  })(jQuery);
149
  </script>
150
  <!-- /wsblc admin footer -->
151
  <?php
152
  }
153
+
154
  /**
155
  * Check if an URL matches the exclusion list.
156
  *
169
 
170
  function dashboard_widget(){
171
  ?>
172
+ <p id='wsblc_activity_box'><?php _e( 'Loading...', 'broken-link-checker' ); ?></p>
173
  <script type='text/javascript'>
174
  jQuery( function($){
175
  var blc_was_autoexpanded = false;
176
+
177
  function blcDashboardStatus(){
178
  $.getJSON(
179
  "<?php echo admin_url('admin-ajax.php'); ?>",
183
  },
184
  function (data){
185
  if ( data && ( typeof(data.text) != 'undefined' ) ) {
186
+ $('#wsblc_activity_box').html(data.text);
187
  <?php if ( $this->conf->options['autoexpand_widget'] ) { ?>
188
  //Expand the widget if there are broken links.
189
  //Do this only once per pageload so as not to annoy the user.
195
  } else {
196
  $('#wsblc_activity_box').html('<?php _e('[ Network error ]', 'broken-link-checker'); ?>');
197
  }
198
+
199
  setTimeout( blcDashboardStatus, 120*1000 ); //...update every two minutes
200
  }
201
  );
202
  }
203
+
204
  blcDashboardStatus();//Call it the first time
205
+
206
  } );
207
  </script>
208
  <?php
216
  $this->conf->options['autoexpand_widget'] = !empty($_POST['blc-autoexpand']);
217
  $this->conf->save_options();
218
  }
219
+
220
  ?>
221
  <p><label for="blc-autoexpand">
222
  <input id="blc-autoexpand" name="blc-autoexpand" type="checkbox" value="1" <?php if ( $this->conf->options['autoexpand_widget'] ) echo 'checked="checked"'; ?> />
229
  //jQuery is used for triggering the link monitor via AJAX when any admin page is open.
230
  wp_enqueue_script('jquery');
231
  }
232
+
233
  function enqueue_settings_scripts(){
234
  //jQuery UI is used on the settings page
235
  wp_enqueue_script('jquery-ui-core'); //Used for background color animation
237
  wp_enqueue_script('jquery-ui-tabs');
238
  wp_enqueue_script('jquery-cookie', plugins_url('js/jquery.cookie.js', BLC_PLUGIN_FILE)); //Used for storing last widget states, etc
239
  }
240
+
241
  function enqueue_link_page_scripts(){
242
  wp_enqueue_script('jquery-ui-core');
243
  wp_enqueue_script('jquery-ui-dialog'); //Used for the search form
244
  wp_enqueue_script('jquery-color'); //Used for background color animation
245
  wp_enqueue_script('sprintf', plugins_url('js/sprintf.js', BLC_PLUGIN_FILE)); //Used in error messages
246
  }
247
+
248
  /**
249
+ * Initiate a full recheck - reparse everything and check all links anew.
250
  *
251
  * @return void
252
  */
255
 
256
  //Delete all discovered instances
257
  $wpdb->query("TRUNCATE {$wpdb->prefix}blc_instances");
258
+
259
  //Delete all discovered links
260
  $wpdb->query("TRUNCATE {$wpdb->prefix}blc_links");
261
+
262
  //Mark all posts, custom fields and bookmarks for processing.
263
  blc_resynch(true);
264
  }
274
  wp_clear_scheduled_hook('blc_cron_email_notifications');
275
  wp_clear_scheduled_hook('blc_cron_database_maintenance');
276
  wp_clear_scheduled_hook('blc_cron_check_news'); //Unused event.
277
+ //Note the deactivation time for each module. This will help them
278
  //synch up propely if/when the plugin is reactivated.
279
  $moduleManager = blcModuleManager::getInstance();
280
  $the_time = current_time('timestamp');
283
  }
284
  $this->conf->save_options();
285
  }
286
+
287
  /**
288
  * Perform various database maintenance tasks on the plugin's tables.
289
+ *
290
  * Removes records that reference disabled containers and parsers,
291
  * deletes invalid instances and links, optimizes tables, etc.
292
+ *
293
  * @return void
294
  */
295
  function database_maintenance(){
296
  blcContainerHelper::cleanup_containers();
297
  blc_cleanup_instances();
298
  blc_cleanup_links();
299
+
300
  blcUtility::optimize_database();
301
  }
302
 
303
  /**
304
  * Create the plugin's menu items and enqueue their scripts and CSS.
305
+ * Callback for the 'admin_menu' action.
306
+ *
307
  * @return void
308
  */
309
  function admin_menu(){
310
  if (current_user_can('manage_options'))
311
  add_filter('plugin_action_links', array($this, 'plugin_action_links'), 10, 2);
312
+
313
+ $options_page_hook = add_options_page(
314
+ __('Link Checker Settings', 'broken-link-checker'),
315
+ __('Link Checker', 'broken-link-checker'),
316
  'manage_options',
317
  'link-checker-settings',array($this, 'options_page')
318
  );
319
+
320
  $menu_title = __('Broken Links', 'broken-link-checker');
321
  if ( $this->conf->options['show_link_count_bubble'] ){
322
+ //To make it easier to notice when broken links appear, display the current number of
323
+ //broken links in a little bubble notification in the "Broken Links" menu.
324
  //(Similar to how the number of plugin updates and unmoderated comments is displayed).
325
  $blc_link_query = blcLinkQuery::getInstance();
326
  $broken_links = $blc_link_query->get_filter_links('broken', array('count_only' => true));
327
  if ( $broken_links > 0 ){
328
+ //TODO: Appropriating existing CSS classes for my own purposes is hacky. Fix eventually.
329
  $menu_title .= sprintf(
330
+ ' <span class="update-plugins"><span class="update-count blc-menu-bubble">%d</span></span>',
331
  $broken_links
332
  );
333
  }
334
+ }
335
  $links_page_hook = add_management_page(
336
+ __('View Broken Links', 'broken-link-checker'),
337
+ $menu_title,
338
  'edit_others_posts',
339
  'view-broken-links',array($this, 'links_page')
340
  );
341
+
342
  //Add plugin-specific scripts and CSS only to the it's own pages
343
  add_action( 'admin_print_styles-' . $options_page_hook, array($this, 'options_page_css') );
344
  add_action( 'admin_print_styles-' . $links_page_hook, array($this, 'links_page_css') );
345
  add_action( 'admin_print_scripts-' . $options_page_hook, array($this, 'enqueue_settings_scripts') );
346
  add_action( 'admin_print_scripts-' . $links_page_hook, array($this, 'enqueue_link_page_scripts') );
347
+
348
  //Make the Settings page link to the link list
349
  add_screen_meta_link(
350
  'blc-links-page-link',
354
  array('style' => 'font-weight: bold;')
355
  );
356
  }
357
+
358
  /**
359
  * plugin_action_links()
360
  * Handler for the 'plugin_action_links' hook. Adds a "Settings" link to this plugin's entry
387
 
388
  if (isset($_POST['recheck']) && !empty($_POST['recheck']) ){
389
  $this->initiate_recheck();
390
+
391
  //Redirect back to the settings page
392
  $base_url = remove_query_arg( array('_wpnonce', 'noheader', 'updated', 'error', 'action', 'message') );
393
  wp_redirect( add_query_arg( array( 'recheck-initiated' => true), $base_url ) );
402
  'blc-recheck-action' => __('Recheck', 'broken-link-checker'),
403
  'blc-deredirect-action' => _x('Fix redirect', 'link action; replace one redirect with a direct link', 'broken-link-checker')
404
  );
405
+
406
  if(isset($_POST['submit'])) {
407
  check_admin_referer('link-checker-options');
408
 
410
  if ( function_exists('wp_magic_quotes') ){
411
  $cleanPost = stripslashes_deep($cleanPost); //Ceterum censeo, WP shouldn't mangle superglobals.
412
  }
413
+
414
  //Activate/deactivate modules
415
  if ( !empty($_POST['module']) ){
416
  $active = array_keys($_POST['module']);
417
  $moduleManager->set_active_modules($active);
418
  }
419
+
420
  //Only post statuses that actually exist can be selected
421
  if ( isset($_POST['enabled_post_statuses']) && is_array($_POST['enabled_post_statuses']) ){
422
  $available_statuses = get_post_stati();
423
+ $enabled_post_statuses = array_intersect($_POST['enabled_post_statuses'], $available_statuses);
424
  } else {
425
  $enabled_post_statuses = array();
426
  }
447
  if( $new_check_threshold > 0 ){
448
  $this->conf->options['check_threshold'] = $new_check_threshold;
449
  }
450
+
451
  $this->conf->options['mark_broken_links'] = !empty($_POST['mark_broken_links']);
452
  $new_broken_link_css = trim($cleanPost['broken_link_css']);
453
  $this->conf->options['broken_link_css'] = $new_broken_link_css;
454
+
455
  $this->conf->options['mark_removed_links'] = !empty($_POST['mark_removed_links']);
456
  $new_removed_link_css = trim($cleanPost['removed_link_css']);
457
  $this->conf->options['removed_link_css'] = $new_removed_link_css;
458
+
459
  $this->conf->options['nofollow_broken_links'] = !empty($_POST['nofollow_broken_links']);
460
+
461
  $this->conf->options['suggestions_enabled'] = !empty($_POST['suggestions_enabled']);
462
 
463
  $this->conf->options['exclusion_list'] = array_filter(
464
+ preg_split(
465
+ '/[\s\r\n]+/', //split on newlines and whitespace
466
  $cleanPost['exclusion_list'],
467
  -1,
468
  PREG_SPLIT_NO_EMPTY //skip empty values
469
+ )
470
  );
471
+
472
  //Parse the custom field list
473
  $new_custom_fields = array_filter(
474
  preg_split( '/[\r\n]+/', $cleanPost['blc_custom_fields'], -1, PREG_SPLIT_NO_EMPTY )
479
  $diff2 = array_diff( $this->conf->options['custom_fields'], $new_custom_fields );
480
  $this->conf->options['custom_fields'] = $new_custom_fields;
481
 
482
+ //Parse the custom field list
483
+ $new_acf_fields = array_filter(preg_split('/[\r\n]+/', $cleanPost['blc_acf_fields'], -1, PREG_SPLIT_NO_EMPTY));
484
+
485
+ //Calculate the difference between the old custom field list and the new one (used later)
486
+ $acf_fields_diff1 = array_diff($new_acf_fields, $this->conf->options['acf_fields']);
487
+ $acf_fields_diff2 = array_diff($this->conf->options['acf_fields'], $new_acf_fields);
488
+ $this->conf->options['acf_fields'] = $new_acf_fields;
489
+
490
+ //Turning off warnings turns existing warnings into "broken" links.
491
  $warnings_enabled = !empty($_POST['warnings_enabled']);
492
  if ( $this->conf->get('warnings_enabled') && !$warnings_enabled ) {
493
  $this->promote_warnings_to_broken();
499
  if( $new_timeout > 0 ){
500
  $this->conf->options['timeout'] = $new_timeout ;
501
  }
502
+
503
+ //Server load limit
504
  if ( isset($_POST['server_load_limit']) ){
505
  $this->conf->options['server_load_limit'] = floatval($_POST['server_load_limit']);
506
  if ( $this->conf->options['server_load_limit'] < 0 ){
507
  $this->conf->options['server_load_limit'] = 0;
508
  }
509
+
510
  $this->conf->options['enable_load_limit'] = $this->conf->options['server_load_limit'] > 0;
511
  }
512
 
516
  $usage = max(min($usage / 100, 1), 0.01);
517
  $this->conf->options['target_resource_usage'] = $usage;
518
  }
519
+
520
  //When to run the checker
521
  $this->conf->options['run_in_dashboard'] = !empty($_POST['run_in_dashboard']);
522
  $this->conf->options['run_via_cron'] = !empty($_POST['run_via_cron']);
523
+
524
  //Email notifications on/off
525
  $email_notifications = !empty($_POST['send_email_notifications']);
526
  $send_authors_email_notifications = !empty($_POST['send_authors_email_notifications']);
594
 
595
  //Make settings that affect our Cron events take effect immediately
596
  $this->setup_cron_events();
597
+
598
  $this->conf->save_options();
599
+
600
  /*
601
  If the list of custom fields was modified then we MUST resynchronize or
602
  custom fields linked with existing posts may not be detected. This is somewhat
603
+ inefficient.
604
  */
605
  if ( ( count($diff1) > 0 ) || ( count($diff2) > 0 ) ){
606
  $manager = blcContainerHelper::get_manager('custom_field');
610
  }
611
  }
612
 
613
+ /*
614
+ If the list of acf fields was modified then we MUST resynchronize or
615
+ acf fields linked with existing posts may not be detected. This is somewhat
616
+ inefficient.
617
+ */
618
+ if ( ( count($acf_fields_diff1) > 0 ) || ( count($acf_fields_diff2) > 0 ) ){
619
+ $manager = blcContainerHelper::get_manager('acf_field');
620
+ if ( !is_null($manager) ){
621
+ $manager->resynch();
622
+ blc_got_unsynched_items();
623
+ }
624
+ }
625
+
626
  //Resynchronize posts when the user enables or disables post statuses.
627
  if ( $post_statuses_changed ) {
628
  $overlord = blcPostTypeOverlord::getInstance();
633
  blc_cleanup_instances();
634
  blc_cleanup_links();
635
  }
636
+
637
  //Redirect back to the settings page
638
  $base_url = remove_query_arg( array('_wpnonce', 'noheader', 'updated', 'error', 'action', 'message') );
639
  wp_redirect( add_query_arg( array( 'settings-updated' => true), $base_url ) );
640
  }
641
+
642
+ //Show a confirmation message when settings are saved.
643
  if ( !empty($_GET['settings-updated']) ){
644
  echo '<div id="message" class="updated fade"><p><strong>',__('Settings saved.', 'broken-link-checker'), '</strong></p></div>';
645
+
646
  }
647
+
648
  //Show a thank-you message when a donation is made.
649
  if ( !empty($_GET['donated']) ){
650
  echo '<div id="message" class="updated fade"><p><strong>',__('Thank you for your donation!', 'broken-link-checker'), '</strong></p></div>';
651
  $this->conf->set('user_has_donated', true);
652
  $this->conf->save_options();
653
  }
654
+
655
+ //Show one when recheck is started, too.
656
  if ( !empty($_GET['recheck-initiated']) ){
657
  echo '<div id="message" class="updated fade"><p><strong>',
658
+ __('Complete site recheck started.', 'broken-link-checker'), // -- Yoda
659
  '</strong></p></div>';
660
  }
661
+
662
  //Cull invalid and missing modules
663
  $moduleManager->validate_active_modules();
664
+
665
  $debug = $this->get_debug_info();
666
+
667
  add_filter('blc-module-settings-custom_field', array($this, 'make_custom_field_input'), 10, 2);
668
+ add_filter('blc-module-settings-acf_field', array($this, 'make_acf_field_input'), 10, 2);
669
  //Translate and markup-ify module headers for display
670
  $modules = $moduleManager->get_modules_by_category('', true, true);
671
+
672
  //Output the custom broken link/removed link styles for example links
673
  printf(
674
+ '<style type="text/css">%s %s</style>',
675
  $this->conf->options['broken_link_css'],
676
  $this->conf->options['removed_link_css']
677
  );
678
+
679
  $section_names = array(
680
  'general' => __('General', 'broken-link-checker'),
681
  'where' => __('Look For Links In', 'broken-link-checker'),
684
  'advanced' => __('Advanced', 'broken-link-checker'),
685
  );
686
  ?>
687
+
688
  <!--[if lte IE 7]>
689
  <style type="text/css">
690
  /* Simulate inline-block in IE7 */
691
  ul.ui-tabs-nav li {
692
+ display: inline;
693
  zoom: 1;
694
  }
695
  </style>
696
  <![endif]-->
697
+
698
  <div class="wrap" id="blc-settings-wrap">
699
  <h2><?php _e('Broken Link Checker Options', 'broken-link-checker'); ?></h2>
700
+
701
+
702
  <div id="blc-sidebar">
703
  <div class="metabox-holder">
704
  <?php include BLC_DIRECTORY . '/includes/admin/sidebar.php'; ?>
705
  </div>
706
  </div>
707
+
708
+
709
  <div id="blc-admin-content">
710
+
711
+ <form name="link_checker_options" id="link_checker_options" method="post" action="<?php
712
+ echo admin_url('options-general.php?page=link-checker-settings&noheader=1');
713
  ?>">
714
+ <?php
715
  wp_nonce_field('link-checker-options');
716
  ?>
717
+
718
  <div id="blc-tabs">
719
+
720
  <ul class="hide-if-no-js">
721
  <?php
722
  foreach($section_names as $section_id => $section_name){
723
  printf(
724
  '<li id="tab-button-%s"><a href="#section-%s" title="%s">%s</a></li>',
 
725
  esc_attr($section_id),
726
+ esc_attr($section_id),
727
+ esc_attr($section_name),
728
  $section_name
729
+ );
730
  }
731
  ?>
732
  </ul>
733
 
734
  <div id="section-general" class="blc-section">
735
  <h3 class="hide-if-js"><?php echo $section_names['general']; ?></h3>
736
+
737
  <table class="form-table">
738
 
739
  <tr valign="top">
747
  <div id='wsblc_full_status'>
748
  <br/><br/><br/>
749
  </div>
750
+
751
  <table id="blc-debug-info">
752
  <?php
753
+
754
  //Output the debug info in a table
755
  foreach( $debug as $key => $value ){
756
  printf (
757
  '<tr valign="top" class="blc-debug-item-%s"><th scope="row">%s</th><td>%s<div class="blc-debug-message">%s</div></td></tr>',
758
  $value['state'],
759
  $key,
760
+ $value['value'],
761
  ( array_key_exists('message', $value)?$value['message']:'')
762
  );
763
  }
764
  ?>
765
  </table>
766
+
767
  </td>
768
  </tr>
769
 
772
  <td>
773
 
774
  <?php
775
+ printf(
776
  __('Every %s hours','broken-link-checker'),
777
  sprintf(
778
  '<input type="text" name="check_threshold" id="check_threshold" value="%d" size="5" maxlength="5" />',
779
  $this->conf->options['check_threshold']
780
  )
781
+ );
782
  ?>
783
  <br/>
784
  <span class="description">
787
 
788
  </td>
789
  </tr>
790
+
791
  <tr valign="top">
792
  <th scope="row"><?php _e('E-mail notifications', 'broken-link-checker'); ?></th>
793
  <td>
842
  _e('Edit CSS', 'broken-link-checker');
843
  ?></a>
844
  </p>
845
+
846
+ <div id="broken-link-css-wrap"<?php
847
  if ( !blcUtility::get_cookie('broken-link-css-wrap', false) ){
848
  echo ' class="hidden"';
849
+ }
850
  ?>>
851
  <textarea name="broken_link_css" id="broken_link_css" cols='45' rows='4'><?php
852
  if( isset($this->conf->options['broken_link_css']) ) {
861
  echo ' ', __('Click "Save Changes" to update example output.', 'broken-link-checker');
862
  ?></p>
863
  </div>
864
+
865
  <p style="margin-bottom: 0.5em;">
866
  <label for='mark_removed_links'>
867
  <input type="checkbox" name="mark_removed_links" id="mark_removed_links"
873
  _e('Edit CSS', 'broken-link-checker');
874
  ?></a>
875
  </p>
876
+
877
+ <div id="removed-link-css-wrap" <?php
878
  if ( !blcUtility::get_cookie('removed-link-css-wrap', false) ){
879
  echo ' class="hidden"';
880
+ }
881
  ?>>
882
  <textarea name="removed_link_css" id="removed_link_css" cols='45' rows='4'><?php
883
  if( isset($this->conf->options['removed_link_css']) )
884
  echo $this->conf->options['removed_link_css'];
885
  ?></textarea>
886
+
887
  <p class="description"><?php
888
  printf(
889
  __('Example : Lorem ipsum <span %s>removed link</span>, dolor sit amet.', 'broken-link-checker'),
894
 
895
  </p>
896
  </div>
897
+
898
  <p>
899
  <label for='nofollow_broken_links'>
900
  <input type="checkbox" name="nofollow_broken_links" id="nofollow_broken_links"
941
  </tr>
942
 
943
  </table>
944
+
945
  </div>
946
+
947
  <div id="section-where" class="blc-section">
948
  <h3 class="hide-if-js"><?php echo $section_names['where']; ?></h3>
949
+
950
  <table class="form-table">
951
+
952
  <tr valign="top">
953
  <th scope="row"><?php _e('Look for links in', 'broken-link-checker'); ?></th>
954
  <td>
956
  if ( !empty($modules['container']) ){
957
  uasort($modules['container'], create_function('$a, $b', 'return strcasecmp($a["Name"], $b["Name"]);'));
958
  $this->print_module_list($modules['container'], $this->conf->options);
959
+ }
960
  ?>
961
  </td></tr>
962
+
963
  <tr valign="top">
964
  <th scope="row"><?php _e('Post statuses', 'broken-link-checker'); ?></th>
965
  <td>
966
  <?php
967
  $available_statuses = get_post_stati(array('internal' => false), 'objects');
968
+
969
  if ( isset($this->conf->options['enabled_post_statuses']) ){
970
  $enabled_post_statuses = $this->conf->options['enabled_post_statuses'];
971
  } else {
972
  $enabled_post_statuses = array();
973
  }
974
+
975
  foreach($available_statuses as $status => $status_object){
976
  printf(
977
  '<p><label><input type="checkbox" name="enabled_post_statuses[]" value="%s"%s> %s</label></p>',
982
  }
983
  ?>
984
  </td></tr>
985
+
986
  </table>
987
+
988
  </div>
989
+
990
+
991
  <div id="section-which" class="blc-section">
992
  <h3 class="hide-if-js"><?php echo $section_names['which']; ?></h3>
993
+
994
  <table class="form-table">
995
+
996
  <tr valign="top">
997
  <th scope="row"><?php _e('Link types', 'broken-link-checker'); ?></th>
998
  <td>
1005
  ?>
1006
  </td>
1007
  </tr>
1008
+
1009
  <tr valign="top">
1010
  <th scope="row"><?php _e('Exclusion list', 'broken-link-checker'); ?></th>
1011
  <td><?php _e("Don't check links where the URL contains any of these words (one per line) :", 'broken-link-checker'); ?><br/>
1016
 
1017
  </td>
1018
  </tr>
1019
+
1020
  </table>
1021
  </div>
1022
+
1023
  <div id="section-how" class="blc-section">
1024
  <h3 class="hide-if-js"><?php echo $section_names['how']; ?></h3>
1025
+
1026
  <table class="form-table">
1027
+
1028
  <tr valign="top">
1029
  <th scope="row"><?php _e('Check links using', 'broken-link-checker'); ?></th>
1030
  <td>
1035
  }
1036
  ?>
1037
  </td></tr>
1038
+
1039
  </table>
1040
  </div>
1041
+
1042
  <div id="section-advanced" class="blc-section">
1043
  <h3 class="hide-if-js"><?php echo $section_names['advanced']; ?></h3>
1044
+
1045
  <table class="form-table">
1046
+
1047
  <tr valign="top">
1048
  <th scope="row"><?php _e('Timeout', 'broken-link-checker'); ?></th>
1049
  <td>
1050
 
1051
  <?php
1052
+
1053
  printf(
1054
  __('%s seconds', 'broken-link-checker'),
1055
  sprintf(
1056
+ '<input type="text" name="timeout" id="blc_timeout" value="%d" size="5" maxlength="3" />',
1057
  $this->conf->options['timeout']
1058
  )
1059
  );
1060
+
1061
  ?>
1062
  <br/><span class="description">
1063
+ <?php _e('Links that take longer than this to load will be marked as broken.','broken-link-checker'); ?>
1064
  </span>
1065
 
1066
  </td>
1067
  </tr>
1068
+
1069
  <tr valign="top">
1070
  <th scope="row"><?php _e('Link monitor', 'broken-link-checker'); ?></th>
1071
  <td>
1072
+
1073
  <p>
1074
  <label for='run_in_dashboard'>
1075
+
1076
  <input type="checkbox" name="run_in_dashboard" id="run_in_dashboard"
1077
  <?php if ($this->conf->options['run_in_dashboard']) echo ' checked="checked"'; ?>/>
1078
  <?php _e('Run continuously while the Dashboard is open', 'broken-link-checker'); ?>
1079
  </label>
1080
  </p>
1081
+
1082
  <p>
1083
  <label for='run_via_cron'>
1084
  <input type="checkbox" name="run_via_cron" id="run_via_cron"
1085
  <?php if ($this->conf->options['run_via_cron']) echo ' checked="checked"'; ?>/>
1086
  <?php _e('Run hourly in the background', 'broken-link-checker'); ?>
1087
  </label>
1088
+ </p>
1089
 
1090
  </td>
1091
  </tr>
1130
  ?>
1131
  </td>
1132
  </tr>
1133
+
1134
  <tr valign="top">
1135
  <th scope="row"><?php _e('Max. execution time', 'broken-link-checker'); ?></th>
1136
  <td>
1137
 
1138
  <?php
1139
+
1140
  printf(
1141
  __('%s seconds', 'broken-link-checker'),
1142
  sprintf(
1143
+ '<input type="text" name="max_execution_time" id="max_execution_time" value="%d" size="5" maxlength="5" />',
1144
  $this->conf->options['max_execution_time']
1145
  )
1146
  );
1147
+
1148
+ ?>
1149
  <br/><span class="description">
1150
  <?php
1151
+
1152
  _e('The plugin works by periodically launching a background job that parses your posts for links, checks the discovered URLs, and performs other time-consuming tasks. Here you can set for how long, at most, the link monitor may run each time before stopping.', 'broken-link-checker');
1153
+
1154
+ ?>
1155
  </span>
1156
 
1157
  </td>
1158
  </tr>
1159
+
1160
  <tr valign="top">
1161
  <th scope="row"><?php _e('Server load limit', 'broken-link-checker'); ?></th>
1162
  <td>
1163
  <?php
1164
+
1165
  $load = blcUtility::get_server_load();
1166
  $available = !empty($load);
1167
 
1171
  '<input type="text" name="server_load_limit" id="server_load_limit" value="%s" size="5" maxlength="5"/> ',
1172
  $value
1173
  );
1174
+
1175
  printf(
1176
  __('Current load : %s', 'broken-link-checker'),
1177
  '<span id="wsblc_current_load">...</span>'
1179
  echo '<br/><span class="description">';
1180
  printf(
1181
  __(
1182
+ 'Link checking will be suspended if the average <a href="%s">server load</a> rises above this number. Leave this field blank to disable load limiting.',
1183
  'broken-link-checker'
1184
  ),
1185
  'http://en.wikipedia.org/wiki/Load_(computing)'
1186
  );
1187
  echo '</span>';
1188
+
1189
  } else {
1190
  echo '<input type="text" disabled="disabled" value="', esc_attr(__('Not available', 'broken-link-checker')), '" size="13"/><br>';
1191
  echo '<span class="description">';
1192
  _e('Load limiting only works on Linux-like systems where <code>/proc/loadavg</code> is present and accessible.', 'broken-link-checker');
1193
  echo '</span>';
1194
  }
1195
+ ?>
1196
  </td>
1197
  </tr>
1198
 
1261
  </td>
1262
  </tr>
1263
 
1264
+
1265
  <tr valign="top">
1266
  <th scope="row"><?php _e('Forced recheck', 'broken-link-checker'); ?></th>
1267
  <td>
1268
+ <input class="button" type="button" name="start-recheck" id="start-recheck"
1269
  value="<?php _e('Re-check all pages', 'broken-link-checker'); ?>" />
1270
  <input type="hidden" name="recheck" value="" id="recheck" />
1271
  <br />
1272
  <span class="description"><?php
1273
  _e('The "Nuclear Option". Click this button to make the plugin empty its link database and recheck the entire site from scratch.', 'broken-link-checker');
1274
+
1275
  ?></span>
1276
  </td>
1277
  </tr>
1278
+
1279
  </table>
1280
  </div>
1281
+
1282
  </div>
1283
+
1284
  <p class="submit"><input type="submit" name="submit" class='button-primary' value="<?php _e('Save Changes') ?>" /></p>
1285
  </form>
1286
+
1287
  </div> <!-- First postbox-container -->
1288
+
1289
+
1290
  </div>
1291
+
1292
+
1293
+
1294
  <?php
1295
  //The various JS for this page is stored in a separate file for the purposes readability.
1296
  include dirname($this->loader) . '/includes/admin/options-page-js.php';
1297
  }
1298
+
1299
  /**
1300
  * Output a list of modules and their settings.
1301
+ *
1302
+ * Each list entry will contain a checkbox that is checked if the module is
1303
+ * currently active.
1304
+ *
1305
  * @param array $modules Array of modules to display
1306
  * @param array $current_settings
1307
  * @return void
1308
  */
1309
  function print_module_list($modules, $current_settings){
1310
  $moduleManager = blcModuleManager::getInstance();
1311
+
1312
  foreach($modules as $module_id => $module_data){
1313
  $module_id = $module_data['ModuleID'];
1314
+
1315
  $style = $module_data['ModuleHidden']?' style="display:none;"':'';
1316
+
1317
  printf(
1318
  '<div class="module-container" id="module-container-%s"%s>',
1319
  $module_id,
1320
  $style
1321
  );
1322
  $this->print_module_checkbox($module_id, $module_data, $moduleManager->is_active($module_id));
1323
+
1324
  $extra_settings = apply_filters(
1325
  'blc-module-settings-'.$module_id,
1326
  '',
1327
  $current_settings
1328
  );
1329
+
1330
  if ( !empty($extra_settings) ){
1331
+
1332
  printf(
1333
  ' | <a class="blc-toggle-link toggle-module-settings" id="toggle-module-settings-%s" href="#">%s</a>',
1334
  esc_attr($module_id),
1335
  __('Configure', 'broken-link-checker')
1336
  );
1337
+
1338
  //The plugin remembers the last open/closed state of module configuration boxes
1339
+ $box_id = 'module-extra-settings-' . $module_id;
1340
  $show = blcUtility::get_cookie(
1341
  $box_id,
1342
  $moduleManager->is_active($module_id)
1343
  );
1344
+
1345
  printf(
1346
  '<div class="module-extra-settings%s" id="%s">%s</div>',
1347
  $show?'':' hidden',
1349
  $extra_settings
1350
  );
1351
  }
1352
+
1353
  echo '</div>';
1354
  }
1355
  }
1356
+
1357
  /**
1358
  * Output a checkbox for a module.
1359
+ *
1360
  * Generates a simple checkbox that can be used to mark a module as active/inactive.
1361
  * If the specified module can't be deactivated (ModuleAlwaysActive = true), the checkbox
1362
  * will be displayed in a disabled state and a hidden field will be created to make
1363
  * form submissions work correctly.
1364
+ *
1365
  * @param string $module_id Module ID.
1366
  * @param array $module_data Associative array of module data.
1367
  * @param bool $active If true, the newly created checkbox will start out checked.
1372
  $name_prefix = 'module';
1373
  $label_class = '';
1374
  $active = $active || $module_data['ModuleAlwaysActive'];
1375
+
1376
  if ( $module_data['ModuleAlwaysActive'] ){
1377
  $disabled = true;
1378
  $name_prefix = 'module-always-active';
1379
  }
1380
+
1381
  $checked = $active ? ' checked="checked"':'';
1382
  if ( $disabled ){
1383
  $checked .= ' disabled="disabled"';
1384
  }
1385
+
1386
  printf(
1387
  '<label class="%s">
1388
  <input type="checkbox" name="%s[%s]" id="module-checkbox-%s"%s /> %s
1394
  $checked,
1395
  $module_data['Name']
1396
  );
1397
+
1398
  if ( $module_data['ModuleAlwaysActive'] ){
1399
  printf(
1400
  '<input type="hidden" name="module[%s]" value="on">',
1402
  );
1403
  }
1404
  }
1405
+
1406
  /**
1407
  * Add extra settings to the "Custom fields" entry on the plugin's config. page.
1408
+ *
1409
+ * Callback for the 'blc-module-settings-custom_field' filter.
1410
+ *
1411
  * @param string $html Current extra HTML
1412
  * @param array $current_settings The current plugin configuration.
1413
  * @return string New extra HTML.
1420
  ) .
1421
  '</span>';
1422
  $html .= '<br><textarea name="blc_custom_fields" id="blc_custom_fields" cols="45" rows="4">';
1423
+ if( isset($current_settings['custom_fields']) ){
1424
  $html .= esc_textarea(implode("\n", $current_settings['custom_fields']));
1425
+ }
1426
  $html .= '</textarea>';
1427
+
1428
+ return $html;
1429
+ }
1430
+ function make_acf_field_input($html, $current_settings) {
1431
+ $html .= '<span class="description">' . __('Enter the keys of acf fields you want to check (one per line). If a field contains HTML code, prefix its name with <code>html:</code>. For example, <code>html:field_586a3eaa4091b</code>.', 'broken-link-checker') . '</span>';
1432
+ $html .= '<br><textarea name="blc_acf_fields" id="blc_acf_fields" cols="45" rows="4">';
1433
+ if (isset($current_settings['acf_fields'])) {
1434
+ $html .= esc_textarea(implode("\n", $current_settings['acf_fields']));
1435
+ }
1436
+ $html .= '</textarea>';
1437
+
1438
  return $html;
1439
  }
 
1440
  /**
1441
  * Enqueue CSS file for the plugin's Settings page.
1442
+ *
1443
  * @return void
1444
  */
1445
  function options_page_css(){
1446
  wp_enqueue_style('blc-options-page', plugins_url('css/options-page.css', BLC_PLUGIN_FILE), array(), '20141113');
1447
  wp_enqueue_style('dashboard');
1448
  }
1449
+
1450
 
1451
  /**
1452
  * Display the "Broken Links" page, listing links detected by the plugin and their status.
1453
+ *
1454
  * @return void
1455
  */
1456
  function links_page(){
1457
  global $wpdb; /* @var wpdb $wpdb */
1458
+
1459
  $blc_link_query = blcLinkQuery::getInstance();
1460
 
1461
  //Cull invalid and missing modules so that we don't get dummy links/instances showing up.
1462
  $moduleManager = blcModuleManager::getInstance();
1463
  $moduleManager->validate_active_modules();
1464
+
1465
  if ( defined('BLC_DEBUG') && constant('BLC_DEBUG') ){
1466
+ //Make module headers translatable. They need to be formatted corrrectly and
1467
  //placed in a .php file to be visible to the script(s) that generate .pot files.
1468
  $code = $moduleManager->_build_header_translation_code();
1469
  file_put_contents( dirname($this->loader) . '/includes/extra-strings.php', $code );
1470
  }
1471
+
1472
  $action = !empty($_POST['action'])?$_POST['action']:'';
1473
  if ( intval($action) == -1 ){
1474
  //Try the second bulk actions box
1475
  $action = !empty($_POST['action2'])?$_POST['action2']:'';
1476
  }
1477
+
1478
  //Get the list of link IDs selected via checkboxes
1479
  $selected_links = array();
1480
  if ( isset($_POST['selected_links']) && is_array($_POST['selected_links']) ){
1483
  //Remove all zeroes
1484
  $selected_links = array_filter($selected_links);
1485
  }
1486
+
1487
  $message = '';
1488
  $msg_class = 'updated';
1489
+
1490
  //Run the selected bulk action, if any
1491
  $force_delete = false;
1492
  switch ( $action ){
1493
  case 'create-custom-filter':
1494
  list($message, $msg_class) = $this->do_create_custom_filter();
1495
  break;
1496
+
1497
  case 'delete-custom-filter':
1498
  list($message, $msg_class) = $this->do_delete_custom_filter();
1499
  break;
1504
  case 'bulk-trash-sources':
1505
  list($message, $msg_class) = $this->do_bulk_delete_sources($selected_links, $force_delete);
1506
  break;
1507
+
1508
  case 'bulk-unlink':
1509
  list($message, $msg_class) = $this->do_bulk_unlink($selected_links);
1510
  break;
1511
+
1512
  case 'bulk-deredirect':
1513
  list($message, $msg_class) = $this->do_bulk_deredirect($selected_links);
1514
  break;
1515
+
1516
  case 'bulk-recheck':
1517
  list($message, $msg_class) = $this->do_bulk_recheck($selected_links);
1518
  break;
1519
+
1520
  case 'bulk-not-broken':
1521
  list($message, $msg_class) = $this->do_bulk_discard($selected_links);
1522
  break;
1529
  list($message, $msg_class) = $this->do_bulk_edit($selected_links);
1530
  break;
1531
  }
1532
+
1533
+
1534
  if ( !empty($message) ){
1535
  echo '<div id="message" class="'.$msg_class.' fade"><p>'.$message.'</p></div>';
1536
  }
1537
+
1538
  $start_time = microtime_float();
1539
+
1540
  //Load custom filters, if any
1541
  $blc_link_query->load_custom_filters();
1542
+
1543
  //Calculate the number of links matching each filter
1544
  $blc_link_query->count_filter_results();
1545
+
1546
  //Run the selected filter (defaults to displaying broken links)
1547
  $selected_filter_id = isset($_GET['filter_id'])?$_GET['filter_id']:'broken';
1548
  $current_filter = $blc_link_query->exec_filter(
1549
  $selected_filter_id,
1550
  isset($_GET['paged']) ? intval($_GET['paged']) : 1,
1551
+ $this->conf->options['table_links_per_page'],
1552
  'broken',
1553
  isset($_GET['orderby']) ? $_GET['orderby'] : '',
1554
  isset($_GET['order']) ? $_GET['order'] : ''
1557
  //exec_filter() returns an array with filter data, including the actual filter ID that was used.
1558
  $filter_id = $current_filter['filter_id'];
1559
 
1560
+ //Error?
1561
  if ( empty($current_filter['links']) && !empty($wpdb->last_error) ){
1562
  printf( __('Database error : %s', 'broken-link-checker'), $wpdb->last_error);
1563
  }
1564
  ?>
1565
+
1566
  <script type='text/javascript'>
1567
  var blc_current_filter = '<?php echo $filter_id; ?>';
1568
  var blc_is_broken_filter = <?php echo $current_filter['is_broken_filter'] ? 'true' : 'false'; ?>;
1569
  var blc_current_base_filter = '<?php echo esc_js($current_filter['base_filter']); ?>';
1570
  var blc_suggestions_enabled = <?php echo $this->conf->options['suggestions_enabled'] ? 'true' : 'false'; ?>;
1571
  </script>
1572
+
1573
  <div class="wrap">
1574
  <?php
1575
  $blc_link_query->print_filter_heading($current_filter);
1576
  $blc_link_query->print_filter_menu($filter_id);
1577
+
1578
  //Display the "Search" form and associated buttons.
1579
  //The form requires the $filter_id and $current_filter variables to be set.
1580
  include dirname($this->loader) . '/includes/admin/search-form.php';
1581
+
1582
+ //If the user has decided to switch the table to a different mode (compact/full),
1583
  //save the new setting.
1584
  if ( isset($_GET['compact']) ){
1585
  $this->conf->options['table_compact'] = (bool)$_GET['compact'];
1588
 
1589
  //Display the links, if any
1590
  if( $current_filter['links'] && ( count($current_filter['links']) > 0 ) ) {
1591
+
1592
  include dirname($this->loader) . '/includes/admin/table-printer.php';
1593
  $table = new blcTablePrinter($this);
1594
  $table->print_table(
1595
  $current_filter,
1596
+ $this->conf->options['table_layout'],
1597
  $this->conf->options['table_visible_columns'],
1598
  $this->conf->options['table_compact']
1599
  );
1600
 
1601
  };
1602
+ printf('<!-- Total elapsed : %.4f seconds -->', microtime_float() - $start_time);
1603
+
1604
  //Load assorted JS event handlers and other shinies
1605
  include dirname($this->loader) . '/includes/admin/links-page-js.php';
1606
+
1607
  ?></div><?php
1608
  }
1609
+
1610
  /**
1611
  * Create a custom link filter using params passed in $_POST.
1612
  *
1613
  * @uses $_POST
1614
+ * @uses $_GET to replace the current filter ID (if any) with that of the newly created filter.
1615
  *
1616
+ * @return array Message and the CSS class to apply to the message.
1617
  */
1618
  function do_create_custom_filter(){
1619
  global $wpdb;
1635
  $name = strip_tags(strval($_POST['name']));
1636
  $blc_link_query = blcLinkQuery::getInstance();
1637
  $filter_id = $blc_link_query->create_custom_filter($name, $_POST['params']);
1638
+
1639
  if ( $filter_id ){
1640
  //Saved
1641
  $message = sprintf( __('Filter "%s" created', 'broken-link-checker'), $name);
1642
  //A little hack to make the filter active immediately
1643
+ $_GET['filter_id'] = $filter_id;
1644
  } else {
1645
  //Error
1646
  $message = sprintf( __("Database error : %s", 'broken-link-checker'), $wpdb->last_error);
1647
  $msg_class = 'error';
1648
  }
1649
  }
1650
+
1651
  return array($message, $msg_class);
1652
  }
1653
+
1654
  /**
1655
  * Delete a custom link filter.
1656
  *
1657
  * @uses $_POST
1658
  *
1659
+ * @return array Message and a CSS class to apply to the message.
1660
  */
1661
  function do_delete_custom_filter(){
1662
  //Delete an existing custom filter!
1663
  check_admin_referer( 'delete-custom-filter' );
1664
  $msg_class = 'updated';
1665
+
1666
  //Filter ID must be set
1667
  if ( empty($_POST['filter_id']) ){
1668
  $message = __("Filter ID not specified.", 'broken-link-checker');
1679
  $msg_class = 'error';
1680
  }
1681
  }
1682
+
1683
  return array($message, $msg_class);
1684
  }
1685
+
1686
  /**
1687
  * Modify multiple links to point to their target URLs.
1688
  *
1691
  */
1692
  function do_bulk_deredirect($selected_links){
1693
  //For all selected links, replace the URL with the final URL that it redirects to.
1694
+
1695
  $message = '';
1696
  $msg_class = 'updated';
1697
+
1698
  check_admin_referer( 'bulk-action' );
1699
+
1700
+ if ( count($selected_links) > 0 ) {
1701
  //Fetch all the selected links
1702
  $links = blc_get_links(array(
1703
  'link_ids' => $selected_links,
1704
  'purpose' => BLC_FOR_EDITING,
1705
  ));
1706
+
1707
  if ( count($links) > 0 ) {
1708
  $processed_links = 0;
1709
  $failed_links = 0;
1710
+
1711
  //Deredirect all selected links
1712
  foreach($links as $link){
1713
  $rez = $link->deredirect();
1716
  } else {
1717
  $failed_links++;
1718
  }
1719
+ }
1720
+
1721
  $message = sprintf(
1722
  _n(
1723
  'Replaced %d redirect with a direct link',
1724
  'Replaced %d redirects with direct links',
1725
+ $processed_links,
1726
  'broken-link-checker'
1727
  ),
1728
  $processed_links
1729
+ );
1730
+
1731
  if ( $failed_links > 0 ) {
1732
  $message .= '<br>' . sprintf(
1733
  _n(
1734
+ 'Failed to fix %d redirect',
1735
  'Failed to fix %d redirects',
1736
  $failed_links,
1737
  'broken-link-checker'
1744
  $message = __('None of the selected links are redirects!', 'broken-link-checker');
1745
  }
1746
  }
1747
+
1748
  return array($message, $msg_class);
1749
  }
1750
+
1751
  /**
1752
  * Edit multiple links in one go.
1753
  *
1757
  function do_bulk_edit($selected_links){
1758
  $message = '';
1759
  $msg_class = 'updated';
1760
+
1761
  check_admin_referer( 'bulk-action' );
1762
+
1763
  $post = $_POST;
1764
  if ( function_exists('wp_magic_quotes') ){
1765
  $post = stripslashes_deep($post); //Ceterum censeo, WP shouldn't mangle superglobals.
1766
  }
1767
+
1768
  $search = isset($post['search']) ? $post['search'] : '';
1769
+ $replace = isset($post['replace']) ? $post['replace'] : '';
1770
  $use_regex = !empty($post['regex']);
1771
  $case_sensitive = !empty($post['case_sensitive']);
1772
+
1773
  $delimiter = '`'; //Pick a char that's uncommon in URLs so that escaping won't usually be a problem
1774
  if ( $use_regex ){
1775
  $search = $delimiter . $this->escape_regex_delimiter($search, $delimiter) . $delimiter;
1782
  $search = $delimiter . preg_quote($search, $delimiter) . $delimiter . 'i';
1783
  $use_regex = true;
1784
  }
1785
+
1786
+ if ( count($selected_links) > 0 ) {
1787
  set_time_limit(300); //In case the user decides to edit hundreds of links at once
1788
+
1789
  //Fetch all the selected links
1790
  $links = blc_get_links(array(
1791
  'link_ids' => $selected_links,
1792
  'purpose' => BLC_FOR_EDITING,
1793
  ));
1794
+
1795
  if ( count($links) > 0 ) {
1796
  $processed_links = 0;
1797
  $failed_links = 0;
1798
  $skipped_links = 0;
1799
+
1800
  //Edit the links
1801
  foreach($links as $link){
1802
  if ( $use_regex ){
1804
  } else {
1805
  $new_url = str_replace($search, $replace, $link->url);
1806
  }
1807
+
1808
  if ( $new_url == $link->url ){
1809
  $skipped_links++;
1810
  continue;
1811
  }
1812
+
1813
  $rez = $link->edit($new_url);
1814
  if ( !is_wp_error($rez) && empty($rez['errors'] )){
1815
  $processed_links++;
1816
  } else {
1817
  $failed_links++;
1818
  }
1819
+ }
1820
+
1821
  $message .= sprintf(
1822
  _n(
1823
  '%d link updated.',
1824
  '%d links updated.',
1825
+ $processed_links,
1826
  'broken-link-checker'
1827
  ),
1828
  $processed_links
1829
  );
1830
+
1831
  if ( $failed_links > 0 ) {
1832
  $message .= '<br>' . sprintf(
1833
  _n(
1834
+ 'Failed to update %d link.',
1835
  'Failed to update %d links.',
1836
  $failed_links,
1837
  'broken-link-checker'
1842
  }
1843
  }
1844
  }
1845
+
1846
  return array($message, $msg_class);
1847
  }
1848
 
1880
 
1881
  return $output;
1882
  }
1883
+
1884
  /**
1885
  * Unlink multiple links.
1886
  *
1891
  //Unlink all selected links.
1892
  $message = '';
1893
  $msg_class = 'updated';
1894
+
1895
  check_admin_referer( 'bulk-action' );
1896
+
1897
+ if ( count($selected_links) > 0 ) {
1898
+
1899
  //Fetch all the selected links
1900
  $links = blc_get_links(array(
1901
  'link_ids' => $selected_links,
1902
  'purpose' => BLC_FOR_EDITING,
1903
  ));
1904
+
1905
  if ( count($links) > 0 ) {
1906
  $processed_links = 0;
1907
  $failed_links = 0;
1908
+
1909
  //Unlink (delete) each one
1910
  foreach($links as $link){
1911
  $rez = $link->unlink();
1914
  } else {
1915
  $processed_links++;
1916
  }
1917
+ }
1918
+
1919
+ //This message is slightly misleading - it doesn't account for the fact that
1920
  //a link can be present in more than one post.
1921
  $message = sprintf(
1922
  _n(
1923
  '%d link removed',
1924
  '%d links removed',
1925
+ $processed_links,
1926
  'broken-link-checker'
1927
  ),
1928
  $processed_links
1929
+ );
1930
+
1931
  if ( $failed_links > 0 ) {
1932
  $message .= '<br>' . sprintf(
1933
  _n(
1934
+ 'Failed to remove %d link',
1935
  'Failed to remove %d links',
1936
  $failed_links,
1937
  'broken-link-checker'
1942
  }
1943
  }
1944
  }
1945
+
1946
  return array($message, $msg_class);
1947
  }
1948
+
1949
  /**
1950
  * Delete or trash posts, bookmarks and other items that contain any of the specified links.
1951
+ *
1952
+ * Will prefer moving stuff to trash to permanent deletion. If it encounters an item that
1953
  * can't be moved to the trash, it will skip that item by default.
1954
  *
1955
  * @param array $selected_links An array of link IDs
1959
  function do_bulk_delete_sources($selected_links, $force_delete = false){
1960
  $message = '';
1961
  $msg_class = 'updated';
1962
+
1963
  //Delete posts, blogroll entries and any other link containers that contain any of the selected links.
1964
  //
1965
  //Note that once all containers containing a particular link have been deleted,
1966
+ //there is no need to explicitly delete the link record itself. The hooks attached to
1967
+ //the actions that execute when something is deleted (e.g. "post_deleted") will
1968
+ //take care of that.
1969
+
1970
  check_admin_referer( 'bulk-action' );
1971
+
1972
+ if ( count($selected_links) > 0 ) {
1973
  $messages = array();
1974
+
1975
  //Fetch all the selected links
1976
  $links = blc_get_links(array(
1977
  'link_ids' => $selected_links,
1978
  'load_instances' => true,
1979
  ));
1980
+
1981
  //Make a list of all containers associated with these links, with each container
1982
  //listed only once.
1983
  $containers = array();
1988
  $containers[$key] = array($instance->container_type, $instance->container_id);
1989
  }
1990
  }
1991
+
1992
  //Instantiate the containers
1993
  $containers = blcContainerHelper::get_containers($containers);
1994
 
1999
  if ( !$container->current_user_can_delete() ){
2000
  continue;
2001
  }
2002
+
2003
  if ( $force_delete ){
2004
  $rez = $container->delete_wrapped_object();
2005
  } else {
2006
  if ( $container->can_be_trashed() ){
2007
  $rez = $container->trash_wrapped_object();
2008
  } else {
2009
+ $skipped[] = $container;
2010
  continue;
2011
  }
2012
  }
2013
+
2014
  if ( is_wp_error($rez) ){ /* @var WP_Error $rez */
2015
  //Record error messages for later display
2016
  $messages[] = $rez->get_error_message();
2025
  }
2026
  }
2027
  }
2028
+
2029
  //Generate delete confirmation messages
2030
  foreach($deleted as $container_type => $number){
2031
  if ( $force_delete ){
2033
  } else {
2034
  $messages[] = blcContainerHelper::ui_bulk_trash_message($container_type, $number);
2035
  }
2036
+
2037
  }
2038
+
2039
  //If some items couldn't be trashed, let the user know
2040
  if ( count($skipped) > 0 ){
2041
  $message = sprintf(
2054
  );
2055
  }
2056
  $message .= '</ul>';
2057
+
2058
  $messages[] = $message;
2059
  }
2060
+
2061
  if ( count($messages) > 0 ){
2062
  $message = implode('<p>', $messages);
2063
  } else {
2065
  $msg_class = 'error';
2066
  }
2067
  }
2068
+
2069
  return array($message, $msg_class);
2070
  }
2071
+
2072
  /**
2073
  * Mark multiple links as unchecked.
2074
  *
2078
  function do_bulk_recheck($selected_links){
2079
  /** @var wpdb $wpdb */
2080
  global $wpdb;
2081
+
2082
  $message = '';
2083
  $msg_class = 'updated';
2084
 
2085
  check_admin_referer('bulk-action');
2086
+
2087
  if ( count($selected_links) > 0 ){
2088
+ $q = "UPDATE {$wpdb->prefix}blc_links
2089
+ SET last_check_attempt = '0000-00-00 00:00:00'
2090
  WHERE link_id IN (".implode(', ', $selected_links).")";
2091
  $changes = $wpdb->query($q);
2092
+
2093
  $message = sprintf(
2094
  _n(
2095
  "%d link scheduled for rechecking",
2096
  "%d links scheduled for rechecking",
2097
+ $changes,
2098
  'broken-link-checker'
2099
  ),
2100
  $changes
2101
  );
2102
  }
2103
+
2104
  return array($message, $msg_class);
2105
  }
2106
+
2107
+
2108
  /**
2109
  * Mark multiple links as not broken.
2110
+ *
2111
  * @param array $selected_links An array of link IDs
2112
  * @return array Confirmation nessage and the CSS class to use with that message.
2113
  */
2114
  function do_bulk_discard($selected_links){
2115
  check_admin_referer( 'bulk-action' );
2116
+
2117
  $messages = array();
2118
  $msg_class = 'updated';
2119
  $processed_links = 0;
2120
+
2121
  if ( count($selected_links) > 0 ){
2122
  foreach($selected_links as $link_id){
2123
  //Load the link
2124
  $link = new blcLink( intval($link_id) );
2125
+
2126
  //Skip links that don't actually exist
2127
  if ( !$link->valid() ){
2128
  continue;
2129
  }
2130
+
2131
  //Skip links that weren't actually detected as broken
2132
  if ( !$link->broken && !$link->warning ){
2133
  continue;
2134
  }
2135
+
2136
  //Make it appear "not broken"
2137
  $link->broken = false;
2138
  $link->warning = false;
2153
  }
2154
  }
2155
  }
2156
+
2157
  if ( $processed_links > 0 ){
2158
  $messages[] = sprintf(
2159
  _n(
2160
  '%d link marked as not broken',
2161
  '%d links marked as not broken',
2162
+ $processed_links,
2163
  'broken-link-checker'
2164
  ),
2165
  $processed_links
2166
  );
2167
  }
2168
+
2169
  return array(implode('<br>', $messages), $msg_class);
2170
  }
2171
 
2227
 
2228
  return array(implode('<br>', $messages), $msg_class);
2229
  }
2230
+
2231
+
2232
  /**
2233
  * Enqueue CSS files for the "Broken Links" page
2234
+ *
2235
  * @return void
2236
  */
2237
  function links_page_css(){
2290
  )
2291
  );
2292
  }
2293
+
2294
  /**
2295
  * Generate the HTML for the plugin's Screen Options panel.
2296
+ *
2297
  * @return string
2298
  */
2299
  function screen_options_html(){
2306
  $this->conf->save_options();
2307
  }
2308
  }
2309
+
2310
  //Let the user show/hide individual table columns
2311
  $html = '<h5>' . __('Table columns', 'broken-link-checker') . '</h5>';
2312
+
2313
  include dirname($this->loader) . '/includes/admin/table-printer.php';
2314
  $table = new blcTablePrinter($this);
2315
  $available_columns = $table->get_layout_columns($this->conf->options['table_layout']);
2316
+
2317
  $html .= '<div id="blc-column-selector" class="metabox-prefs">';
2318
+
2319
  foreach( $available_columns as $column_id => $data ){
2320
  $html .= sprintf(
2321
  '<label><input type="checkbox" name="visible_columns[%s]"%s>%s</label>',
2323
  in_array($column_id, $this->conf->options['table_visible_columns']) ? ' checked="checked"' : '',
2324
  $data['heading']
2325
  );
2326
+ }
2327
+
2328
  $html .= '</div>';
2329
+
2330
+ $html .= '<h5>' . __('Show on screen', 'broken-link-checker') . '</h5>';
2331
  $html .= '<div class="screen-options">';
2332
  $html .= sprintf(
2333
  '<input type="text" name="per_page" maxlength="3" value="%d" class="screen-per-page" id="blc_links_per_page" />
2338
  __('Apply')
2339
  );
2340
  $html .= '</div>';
2341
+
2342
  $html .= '<h5>' . __('Misc', 'broken-link-checker') . '</h5>';
2343
  $html .= '<div class="screen-options">';
2344
  /*
2345
+ Display a checkbox in "Screen Options" that lets the user highlight links that
2346
+ have been broken for at least X days.
2347
  */
2348
  $html .= sprintf(
2349
  '<label><input type="checkbox" id="highlight_permanent_failures" name="highlight_permanent_failures"%s> ',
2358
  $input_box
2359
  );
2360
  $html .= '</label>';
2361
+
2362
  //Display a checkbox for turning colourful link status messages on/off
2363
  $html .= sprintf(
2364
  '<br/><label><input type="checkbox" id="table_color_code_status" name="table_color_code_status"%s> %s</label>',
2365
  $this->conf->options['table_color_code_status'] ? ' checked="checked"' : '',
2366
  __('Color-code status codes', 'broken-link-checker')
2367
  );
2368
+
2369
  $html .= '</div>';
2370
+
2371
  return $html;
2372
  }
2373
+
2374
  /**
2375
  * AJAX callback for saving the "Screen Options" panel settings
2376
+ *
2377
  * @param array $form
2378
  * @return void
2379
  */
2380
  function ajax_save_screen_options($form){
2381
  if ( !current_user_can('edit_others_posts') ){
2382
  die( json_encode( array(
2383
+ 'error' => __("You're not allowed to do that!", 'broken-link-checker')
2384
  )));
2385
  }
2386
+
2387
  $this->conf->options['highlight_permanent_failures'] = !empty($form['highlight_permanent_failures']);
2388
  $this->conf->options['table_color_code_status'] = !empty($form['table_color_code_status']);
2389
+
2390
  $failure_duration_threshold = intval($form['failure_duration_threshold']);
2391
  if ( $failure_duration_threshold >=1 ){
2392
  $this->conf->options['failure_duration_threshold'] = $failure_duration_threshold;
2393
  }
2394
+
2395
  if ( isset($form['visible_columns']) && is_array($form['visible_columns']) ){
2396
  $this->conf->options['table_visible_columns'] = array_keys($form['visible_columns']);
2397
  }
2398
+
2399
  $this->conf->save_options();
2400
  die('1');
2401
  }
2402
+
2403
  function start_timer(){
2404
  $this->execution_start_time = microtime_float();
2405
  }
2406
+
2407
  function execution_time(){
2408
  return microtime_float() - $this->execution_start_time;
2409
  }
2410
+
2411
  /**
2412
  * The main worker function that does all kinds of things.
2413
  *
2424
  if ( session_id() != '' ) {
2425
  session_write_close();
2426
  }
2427
+
2428
  if ( !$this->acquire_lock() ){
2429
  //FB::warn("Another instance of BLC is already working. Stop.");
2430
  $blclog->info('Another instance of BLC is already working. Stop.');
2431
  return;
2432
  }
2433
+
2434
  if ( $this->server_too_busy() ){
2435
  //FB::warn("Server is too busy. Stop.");
2436
  $blclog->warn('Server load is too high, stopping.');
2437
  return;
2438
  }
2439
+
2440
  $this->start_timer();
2441
  $blclog->info('work() starts');
2442
 
2443
  $max_execution_time = $this->conf->options['max_execution_time'];
2444
+
2445
  /*****************************************
2446
  Preparation
2447
  ******************************************/
2449
  if( blcUtility::is_safe_mode() ){
2450
  // Do it the safe mode way - obey the existing max_execution_time setting
2451
  $t = ini_get('max_execution_time');
2452
+ if ($t && ($t < $max_execution_time))
2453
  $max_execution_time = $t-1;
2454
  } else {
2455
  // Do it the regular way
2456
  @set_time_limit( $max_execution_time * 2 ); //x2 should be plenty, running any longer would mean a glitch.
2457
  }
2458
+
2459
  //Don't stop the script when the connection is closed
2460
  ignore_user_abort( true );
2461
 
2476
  ob_end_flush(); // Strange behaviour, will not work
2477
  flush(); // Unless both are called !
2478
  }
2479
+
2480
  //Load modules for this context
2481
  $moduleManager = blcModuleManager::getInstance();
2482
  $moduleManager->load_modules('work');
2484
  $target_usage_fraction = $this->conf->get('target_resource_usage', 0.25);
2485
  //Target usage must be between 1% and 100%.
2486
  $target_usage_fraction = max(min($target_usage_fraction, 1), 0.01);
2487
+
2488
+
2489
  /*****************************************
2490
  Parse posts and bookmarks
2491
  ******************************************/
2492
+
2493
  $orphans_possible = false;
2494
  $still_need_resynch = $this->conf->options['need_resynch'];
2495
+
2496
  if ( $still_need_resynch ) {
2497
 
2498
  //FB::log("Looking for containers that need parsing...");
2505
  while( !empty($containers) ){
2506
  //FB::log($containers, 'Found containers');
2507
  $this->sleep_to_maintain_ratio($get_containers_time, $target_usage_fraction);
2508
+
2509
  foreach($containers as $container){
2510
  $synch_start_time = microtime(true);
2511
 
2519
  $container->container_id,
2520
  $synch_elapsed_time * 1000
2521
  ));
2522
+
2523
  //Check if we still have some execution time left
2524
  if( $this->execution_time() > $max_execution_time ){
2525
  //FB::log('The allotted execution time has run out');
2527
  $this->release_lock();
2528
  return;
2529
  }
2530
+
2531
  //Check if the server isn't overloaded
2532
  if ( $this->server_too_busy() ){
2533
  //FB::log('Server overloaded, bailing out.');
2546
  $containers = blcContainerHelper::get_unsynched_containers($max_containers_per_query);
2547
  $get_containers_time = microtime(true) - $start;
2548
  }
2549
+
2550
  //FB::log('No unparsed items found.');
2551
  $still_need_resynch = false;
2552
+
2553
  } else {
2554
  //FB::log('Resynch not required.');
2555
  }
2556
+
2557
  /******************************************
2558
  Resynch done?
2559
  *******************************************/
2561
  $this->conf->options['need_resynch'] = $still_need_resynch;
2562
  $this->conf->save_options();
2563
  }
2564
+
2565
  /******************************************
2566
  Remove orphaned links
2567
  *******************************************/
2568
+
2569
  if ( $orphans_possible ) {
2570
  $start = microtime(true);
2571
 
2575
  $get_links_time = microtime(true) - $start;
2576
  $this->sleep_to_maintain_ratio($get_links_time, $target_usage_fraction);
2577
  }
2578
+
2579
  //Check if we still have some execution time left
2580
  if( $this->execution_time() > $max_execution_time ){
2581
  //FB::log('The allotted execution time has run out');
2583
  $this->release_lock();
2584
  return;
2585
  }
2586
+
2587
  if ( $this->server_too_busy() ){
2588
  //FB::log('Server overloaded, bailing out.');
2589
  $blclog->info('Server load too high, stopping.');
2590
  $this->release_lock();
2591
  return;
2592
  }
2593
+
2594
  /*****************************************
2595
  Check links
2596
  ******************************************/
2602
 
2603
  while ( $links ){
2604
  $this->sleep_to_maintain_ratio($get_links_time, $target_usage_fraction);
2605
+
2606
  //Some unchecked links found
2607
  //FB::log("Checking ".count($links)." link(s)");
2608
  $blclog->info("Checking ".count($links)." link(s)");
2625
  $link->last_check_attempt = time();
2626
  $link->save();
2627
  }
2628
+
2629
  //Check if we still have some execution time left
2630
  if( $this->execution_time() > $max_execution_time ){
2631
  //FB::log('The allotted execution time has run out');
2633
  $this->release_lock();
2634
  return;
2635
  }
2636
+
2637
  //Check if the server isn't overloaded
2638
  if ( $this->server_too_busy() ){
2639
  //FB::log('Server overloaded, bailing out.');
2649
  $get_links_time = microtime(true) - $start;
2650
  }
2651
  //FB::log('No links need to be checked right now.');
2652
+
2653
  $this->release_lock();
2654
  $blclog->info('work(): All done.');
2655
  //FB::log('All done.');
2679
  usleep($sleep_time * 1000000);
2680
  }
2681
  }
2682
+
2683
  /**
2684
  * This function is called when the plugin's cron hook executes.
2685
  * Its only purpose is to invoke the worker function.
2686
  *
2687
+ * @uses wsBrokenLinkChecker::work()
2688
  *
2689
  * @return void
2690
  */
2691
  function cron_check_links(){
2692
  $this->work();
2693
  }
2694
+
2695
  /**
2696
  * Retrieve links that need to be checked or re-checked.
2697
  *
2698
  * @param integer $max_results The maximum number of links to return. Defaults to 0 = no limit.
2699
+ * @param bool $count_only If true, only the number of found links will be returned, not the links themselves.
2700
  * @return int|blcLink[]
2701
  */
2702
  function get_links_to_check($max_results = 0, $count_only = false){
2703
  global $wpdb; /* @var wpdb $wpdb */
2704
+
2705
  $check_threshold = date('Y-m-d H:i:s', strtotime('-'.$this->conf->options['check_threshold'].' hours'));
2706
  $recheck_threshold = date('Y-m-d H:i:s', time() - $this->conf->options['recheck_threshold']);
2707
+
2708
  //FB::log('Looking for links to check (threshold : '.$check_threshold.', recheck_threshold : '.$recheck_threshold.')...');
2709
+
2710
  //Select some links that haven't been checked for a long time or
2711
  //that are broken and need to be re-checked again. Links that are
2712
  //marked as "being checked" and have been that way for several minutes
2713
+ //can also be considered broken/buggy, so those will be selected
2714
  //as well.
2715
+
2716
+ //Only check links that have at least one valid instance (i.e. an instance exists and
2717
  //it corresponds to one of the currently loaded container/parser types).
2718
  $manager = blcModuleManager::getInstance();
2719
  $loaded_containers = $manager->get_escaped_ids('container');
2720
  $loaded_parsers = $manager->get_escaped_ids('parser');
2721
+
2722
  //Note : This is a slow query, but AFAIK there is no way to speed it up.
2723
+ //I could put an index on last_check_attempt, but that value is almost
2724
  //certainly unique for each row so it wouldn't be much better than a full table scan.
2725
  if ( $count_only ){
2726
  $q = "SELECT COUNT(DISTINCT links.link_id)\n";
2752
  $q .= "LIMIT " . intval($max_results);
2753
  }
2754
  }
2755
+
2756
  $link_q = $wpdb->prepare(
2757
+ $q,
2758
+ $check_threshold,
2759
+ $this->conf->options['recheck_count'],
2760
  $recheck_threshold
2761
  );
2762
  //FB::log($link_q, "Find links to check");
2763
  //$blclog->debug("Find links to check: \n" . $link_q);
2764
+
2765
  //If we just need the number of links, retrieve it and return
2766
  if ( $count_only ){
2767
  return $wpdb->get_var($link_q);
2768
  }
2769
+
2770
  //Fetch the link data
2771
  $link_data = $wpdb->get_results($link_q, ARRAY_A);
2772
  if ( empty($link_data) ){
2773
  return array();
2774
  }
2775
+
2776
  //Instantiate blcLink objects for all fetched links
2777
  $links = array();
2778
  foreach($link_data as $data){
2779
  $links[] = new blcLink($data);
2780
  }
2781
+
2782
  return $links;
2783
  }
2784
+
2785
  /**
2786
  * Output the current link checker status in JSON format.
2787
  * Ajax hook for the 'blc_full_status' action.
2791
  function ajax_full_status( ){
2792
  $status = $this->get_status();
2793
  $text = $this->status_text( $status );
2794
+
2795
  echo json_encode( array(
2796
  'text' => $text,
2797
+ 'status' => $status,
2798
  ) );
2799
+
2800
  die();
2801
  }
2802
+
2803
  /**
2804
  * Generates a status message based on the status info in $status
2805
  *
2808
  */
2809
  function status_text( $status ){
2810
  $text = '';
2811
+
2812
  if( $status['broken_links'] > 0 ){
2813
+ $text .= sprintf(
2814
+ "<a href='%s' title='" . __('View broken links', 'broken-link-checker') . "'><strong>".
2815
  _n('Found %d broken link', 'Found %d broken links', $status['broken_links'], 'broken-link-checker') .
2816
  "</strong></a>",
2817
  esc_attr(admin_url('tools.php?page=view-broken-links')),
2820
  } else {
2821
  $text .= __("No broken links found.", 'broken-link-checker');
2822
  }
2823
+
2824
  $text .= "<br/>";
2825
+
2826
  if( $status['unchecked_links'] > 0) {
2827
+ $text .= sprintf(
2828
+ _n('%d URL in the work queue', '%d URLs in the work queue', $status['unchecked_links'], 'broken-link-checker'),
2829
  $status['unchecked_links'] );
2830
  } else {
2831
  $text .= __("No URLs in the work queue.", 'broken-link-checker');
2832
  }
2833
+
2834
  $text .= "<br/>";
2835
  if ( $status['known_links'] > 0 ){
2836
  $url_count = sprintf(
2862
  $text .= __('No links detected.', 'broken-link-checker');
2863
  }
2864
  }
2865
+
2866
  return $text;
2867
  }
2868
+
2869
  /**
2870
+ * @uses wsBrokenLinkChecker::ajax_full_status()
2871
  *
2872
  * @return void
2873
  */
2875
  //Just display the full status.
2876
  $this->ajax_full_status();
2877
  }
2878
+
2879
  /**
2880
  * Output the current average server load (over the last one-minute period).
2881
  * Called via AJAX.
2887
  if ( empty($load) ){
2888
  die( _x('Unknown', 'current load', 'broken-link-checker') );
2889
  }
2890
+
2891
  $one_minute = reset($load);
2892
  printf('%.2f', $one_minute);
2893
  die();
2894
  }
2895
+
2896
  /**
2897
+ * Returns an array with various status information about the plugin. Array key reference:
2898
  * check_threshold - date/time; links checked before this threshold should be checked again.
2899
  * recheck_threshold - date/time; broken links checked before this threshold should be re-checked.
2900
  * known_links - the number of detected unique URLs (a misleading name, yes).
2901
  * known_instances - the number of detected link instances, i.e. actual link elements in posts and other places.
2902
+ * broken_links - the number of detected broken links.
2903
  * unchecked_links - the number of URLs that need to be checked ASAP; based on check_threshold and recheck_threshold.
2904
  *
2905
  * @return array
2906
  */
2907
  function get_status(){
2908
  $blc_link_query = blcLinkQuery::getInstance();
2909
+
2910
  $check_threshold=date('Y-m-d H:i:s', strtotime('-'.$this->conf->options['check_threshold'].' hours'));
2911
  $recheck_threshold=date('Y-m-d H:i:s', time() - $this->conf->options['recheck_threshold']);
2912
+
2913
  $known_links = blc_get_links(array('count_only' => true));
2914
  $known_instances = blc_get_usable_instance_count();
2915
+
2916
  $broken_links = $blc_link_query->get_filter_links('broken', array('count_only' => true));
2917
+
2918
  $unchecked_links = $this->get_links_to_check(0, true);
2919
+
2920
  return array(
2921
  'check_threshold' => $check_threshold,
2922
  'recheck_threshold' => $recheck_threshold,
2926
  'unchecked_links' => $unchecked_links,
2927
  );
2928
  }
2929
+
2930
  function ajax_work(){
2931
  check_ajax_referer('blc_work');
2932
 
2933
+ //Run the worker function
2934
  $this->work();
2935
  die();
2936
  }
2937
+
2938
  /**
2939
  * AJAX hook for the "Not broken" button. Marks a link as broken and as a likely false positive.
2940
  *
2944
  if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_discard', false, false)){
2945
  die( __("You're not allowed to do that!", 'broken-link-checker') );
2946
  }
2947
+
2948
  if ( isset($_POST['link_id']) ){
2949
  //Load the link
2950
  $link = new blcLink( intval($_POST['link_id']) );
2951
+
2952
  if ( !$link->valid() ){
2953
  printf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) );
2954
  die();
2955
  }
2956
  //Make it appear "not broken"
2957
+ $link->broken = false;
2958
  $link->warning = false;
2959
  $link->false_positive = true;
2960
  $link->last_check_attempt = time();
3009
  die( __("Error : link_id not specified", 'broken-link-checker') );
3010
  }
3011
  }
3012
+
3013
  /**
3014
+ * AJAX hook for the inline link editor on Tools -> Broken Links.
3015
  *
3016
  * @return void
3017
  */
3018
  function ajax_edit(){
3019
  if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_edit', false, false)){
3020
  die( json_encode( array(
3021
+ 'error' => __("You're not allowed to do that!", 'broken-link-checker')
3022
  )));
3023
  }
3024
 
3109
  die( json_encode($response) );
3110
  }
3111
  }
3112
+
3113
  /**
3114
+ * AJAX hook for the "Unlink" action links in Tools -> Broken Links.
3115
  * Removes the specified link from all posts and other supported items.
3116
  *
3117
  * @return void
3119
  function ajax_unlink(){
3120
  if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_unlink', false, false)){
3121
  die( json_encode( array(
3122
+ 'error' => __("You're not allowed to do that!", 'broken-link-checker')
3123
  )));
3124
  }
3125
+
3126
  if ( isset($_POST['link_id']) ){
3127
  //Load the link
3128
  $link = new blcLink( intval($_POST['link_id']) );
3129
+
3130
  if ( !$link->valid() ){
3131
  die( json_encode( array(
3132
+ 'error' => sprintf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) )
3133
  )));
3134
  }
3135
+
3136
  //Try and unlink it
3137
  $rez = $link->unlink();
3138
+
3139
  if ( $rez === false ){
3140
  die( json_encode( array(
3141
  'error' => __("An unexpected error occured!", 'broken-link-checker')
3149
  foreach($rez['errors'] as $error){ /** @var WP_Error $error */
3150
  array_push( $response['errors'], implode(', ', $error->get_error_messages()) );
3151
  }
3152
+
3153
  die( json_encode($response) );
3154
  }
3155
+
3156
  } else {
3157
  die( json_encode( array(
3158
+ 'error' => __("Error : link_id not specified", 'broken-link-checker')
3159
  )));
3160
  }
3161
  }
3263
 
3264
  die(json_encode($response));
3265
  }
3266
+
3267
  function ajax_link_details(){
3268
  global $wpdb; /* @var wpdb $wpdb */
3269
+
3270
  if (!current_user_can('edit_others_posts')){
3271
  die( __("You don't have sufficient privileges to access this information!", 'broken-link-checker') );
3272
  }
3273
+
3274
  //FB::log("Loading link details via AJAX");
3275
+
3276
  if ( isset($_GET['link_id']) ){
3277
  //FB::info("Link ID found in GET");
3278
  $link_id = intval($_GET['link_id']);
3283
  //FB::error('Link ID not specified, you hacking bastard.');
3284
  die( __('Error : link ID not specified', 'broken-link-checker') );
3285
  }
3286
+
3287
+ //Load the link.
3288
  $link = new blcLink($link_id);
3289
+
3290
  if ( !$link->is_new ){
3291
  //FB::info($link, 'Link loaded');
3292
  if ( !class_exists('blcTablePrinter') ){
3299
  die();
3300
  }
3301
  }
3302
+
3303
  /**
3304
  * Acquire an exclusive lock.
3305
  * If we already hold a lock, it will be released and a new one will be acquired.
3309
  function acquire_lock(){
3310
  return WPMutex::acquire('blc_lock');
3311
  }
3312
+
3313
  /**
3314
+ * Relese our exclusive lock.
3315
  * Does nothing if the lock has already been released.
3316
  *
3317
  * @return bool
3319
  function release_lock(){
3320
  return WPMutex::release('blc_lock');
3321
  }
3322
+
3323
  /**
3324
  * Check if server is currently too overloaded to run the link checker.
3325
  *
3329
  if ( !$this->conf->options['enable_load_limit'] || !isset($this->conf->options['server_load_limit']) ){
3330
  return false;
3331
  }
3332
+
3333
  $loads = blcUtility::get_server_load();
3334
  if ( empty($loads) ){
3335
  return false;
3336
  }
3337
  $one_minute = floatval(reset($loads));
3338
+
3339
  return $one_minute > $this->conf->options['server_load_limit'];
3340
  }
3341
+
3342
  /**
3343
  * Register BLC's Dashboard widget
3344
+ *
3345
  * @return void
3346
  */
3347
  function hook_wp_dashboard_setup(){
3348
  $show_widget = current_user_can($this->conf->get('dashboard_widget_capability', 'edit_others_posts'));
3349
  if ( function_exists( 'wp_add_dashboard_widget' ) && $show_widget ) {
3350
  wp_add_dashboard_widget(
3351
+ 'blc_dashboard_widget',
3352
+ __('Broken Link Checker', 'broken-link-checker'),
3353
  array( $this, 'dashboard_widget' ),
3354
  array( $this, 'dashboard_widget_control' )
3355
  );
3356
  }
3357
  }
3358
+
3359
  /**
3360
  * Collect various debugging information and return it in an associative array
3361
  *
3365
  /** @var wpdb $wpdb */
3366
  global $wpdb;
3367
 
3368
+ //Collect some information that's useful for debugging
3369
  $debug = array();
3370
+
3371
  //PHP version. Any one is fine as long as WP supports it.
3372
  $debug[ __('PHP version', 'broken-link-checker') ] = array(
3373
  'state' => 'ok',
3374
+ 'value' => phpversion(),
3375
  );
3376
+
3377
  //MySQL version
3378
  $debug[ __('MySQL version', 'broken-link-checker') ] = array(
3379
  'state' => 'ok',
3380
  'value' => $wpdb->db_version(),
3381
  );
3382
+
3383
  //CURL presence and version
3384
  if ( function_exists('curl_version') ){
3385
  $version = curl_version();
3386
+
3387
  if ( version_compare( $version['version'], '7.16.0', '<=' ) ){
3388
  $data = array(
3389
+ 'state' => 'warning',
3390
  'value' => $version['version'],
3391
  'message' => __('You have an old version of CURL. Redirect detection may not work properly.', 'broken-link-checker'),
3392
  );
3393
  } else {
3394
  $data = array(
3395
+ 'state' => 'ok',
3396
  'value' => $version['version'],
3397
  );
3398
  }
3399
+
3400
  } else {
3401
  $data = array(
3402
+ 'state' => 'warning',
3403
  'value' => __('Not installed', 'broken-link-checker'),
3404
  );
3405
  }
3406
  $debug[ __('CURL version', 'broken-link-checker') ] = $data;
3407
+
3408
  //Snoopy presence
3409
  if ( class_exists('Snoopy') || file_exists(ABSPATH. WPINC . '/class-snoopy.php') ){
3410
  $data = array(
3412
  'value' => __('Installed', 'broken-link-checker'),
3413
  );
3414
  } else {
3415
+ //No Snoopy? This should never happen, but if it does we *must* have CURL.
3416
  if ( function_exists('curl_init') ){
3417
  $data = array(
3418
  'state' => 'ok',
3425
  'message' => __('You must have either CURL or Snoopy installed for the plugin to work!', 'broken-link-checker'),
3426
  );
3427
  }
3428
+
3429
  }
3430
  $debug['Snoopy'] = $data;
3431
+
3432
  //Safe_mode status
3433
  if ( blcUtility::is_safe_mode() ){
3434
  $debug['Safe mode'] = array(
3442
  'value' => __('Off', 'broken-link-checker'),
3443
  );
3444
  }
3445
+
3446
  //Open_basedir status
3447
  if ( blcUtility::is_open_basedir() ){
3448
  $debug['open_basedir'] = array(
3456
  'value' => __('Off', 'broken-link-checker'),
3457
  );
3458
  }
3459
+
3460
  //Default PHP execution time limit
3461
  $debug['Default PHP execution time limit'] = array(
3462
  'state' => 'ok',
3476
  'state' => 'ok',
3477
  'value' => sprintf('%d', $this->conf->options['need_resynch'] ? '1 (resynch. required)' : '0 (resynch. not required)'),
3478
  );
3479
+
3480
  //Synch records
3481
  $synch_records = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_synch"));
3482
  $data = array(
3488
  $data['message'] = __('If this value is zero even after several page reloads you have probably encountered a bug.', 'broken-link-checker');
3489
  }
3490
  $debug['Synch. records'] = $data;
3491
+
3492
  //Total links and instances (including invalid ones)
3493
  $all_links = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_links"));
3494
  $all_instances = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_instances"));
3495
+
3496
+ //Show the number of unparsed containers. Useful for debugging. For performance,
3497
  //this is only shown when we have no links/instances yet.
3498
  if( ($all_links == 0) && ($all_instances == 0) ){
3499
  $unparsed_items = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_synch WHERE synched=0"));
3500
  $debug['Unparsed items'] = array(
3501
+ 'state' => 'warning',
3502
  'value' => $unparsed_items,
3503
  );
3504
+ }
3505
+
3506
  //Links & instances
3507
  if ( ($all_links > 0) && ($all_instances > 0) ){
3508
  $debug['Link records'] = array(
3514
  'state' => 'warning',
3515
  'value' => sprintf('%d (%d)', $all_links, $all_instances),
3516
  );
3517
+ }
3518
 
3519
  //Email notifications.
3520
  if ( $this->conf->options['last_notification_sent'] ) {
3558
  'value' => 'No installation log found found.',
3559
  );
3560
  }
3561
+
3562
  return $debug;
3563
  }
3564
 
3752
  $this->send_html_email($author->user_email, $subject, $body);
3753
  }
3754
  }
3755
+
3756
  function override_mail_content_type(/** @noinspection PhpUnusedParameterInspection */ $content_type){
3757
  return 'text/html';
3758
  }
3774
  '%d'
3775
  );
3776
  }
3777
+
3778
  /**
3779
  * Install or uninstall the plugin's Cron events based on current settings.
3780
  *
3781
+ * @uses wsBrokenLinkChecker::$conf Uses $conf->options to determine if events need to be (un)installed.
3782
  *
3783
  * @return void
3784
  */
3785
  function setup_cron_events(){
3786
+
3787
+ //Link monitor
3788
  if ( $this->conf->options['run_via_cron'] ){
3789
  if (!wp_next_scheduled('blc_cron_check_links')) {
3790
+ wp_schedule_event( time(), '10min', 'blc_cron_check_links' );
3791
  }
3792
  } else {
3793
  wp_clear_scheduled_hook('blc_cron_check_links');
3794
  }
3795
+
3796
  //Email notifications about broken links
3797
  if ( $this->conf->options['send_email_notifications'] || $this->conf->options['send_authors_email_notifications'] ){
3798
  if ( !wp_next_scheduled('blc_cron_email_notifications') ){
3801
  } else {
3802
  wp_clear_scheduled_hook('blc_cron_email_notifications');
3803
  }
3804
+
3805
  //Run database maintenance every two weeks or so
3806
  if ( !wp_next_scheduled('blc_cron_database_maintenance') ){
3807
+ wp_schedule_event(time(), 'daily', 'blc_cron_database_maintenance');
3808
  }
3809
  }
3810
+
3811
  /**
3812
  * Load the plugin's textdomain.
3813
  *
3816
  function load_language(){
3817
  $this->is_textdomain_loaded = load_plugin_textdomain( 'broken-link-checker', false, basename(dirname($this->loader)) . '/languages' );
3818
  }
3819
+
3820
  protected static function get_default_log_directory() {
3821
  $uploads = wp_upload_dir();
3822
  return $uploads['basedir'] . '/broken-link-checker';
core/init.php CHANGED
@@ -95,6 +95,7 @@ $blc_config_manager = new blcConfigurationManager(
95
  'enable_load_limit' => true, //Enable/disable load monitoring.
96
 
97
  'custom_fields' => array(), //List of custom fields that can contain URLs and should be checked.
 
98
  'enabled_post_statuses' => array('publish'), //Only check posts that match one of these statuses
99
 
100
  'autoexpand_widget' => true, //Autoexpand the Dashboard widget if broken links are detected
95
  'enable_load_limit' => true, //Enable/disable load monitoring.
96
 
97
  'custom_fields' => array(), //List of custom fields that can contain URLs and should be checked.
98
+ 'acf_fields' => array(), //List of custom fields that can contain URLs and should be checked.
99
  'enabled_post_statuses' => array('publish'), //Only check posts that match one of these statuses
100
 
101
  'autoexpand_widget' => true, //Autoexpand the Dashboard widget if broken links are detected
includes/activation.php CHANGED
@@ -46,6 +46,9 @@ if ( isset($blc_config_manager->options['check_comment_links']) ){
46
  if ( empty($blc_config_manager->options['custom_fields']) ){
47
  $moduleManager->deactivate('custom_field');
48
  }
 
 
 
49
 
50
  //Prepare the database.
51
  $blclog->info('Upgrading the database...');
46
  if ( empty($blc_config_manager->options['custom_fields']) ){
47
  $moduleManager->deactivate('custom_field');
48
  }
49
+ if ( empty($blc_config_manager->options['acf_fields']) ){
50
+ $moduleManager->deactivate('acf_field');
51
+ }
52
 
53
  //Prepare the database.
54
  $blclog->info('Upgrading the database...');
includes/admin/links-page-js.php CHANGED
@@ -436,7 +436,7 @@ jQuery(function($){
436
 
437
  //Check the Wayback Machine for an archived version of the page.
438
  $.getJSON(
439
- '//archive.org/wayback/available?callback=?',
440
  { url: url },
441
 
442
  function(data) {
@@ -456,6 +456,9 @@ jQuery(function($){
456
  '-' + snapshot.timestamp.substr(6, 2);
457
  var name = sprintf(iaSuggestionName, readableTimestamp);
458
 
 
 
 
459
  //Display the suggestion.
460
  var item = suggestionTemplate.clone();
461
  item.find('.blc-suggestion-name a').text(name).attr('href', snapshot.url);
436
 
437
  //Check the Wayback Machine for an archived version of the page.
438
  $.getJSON(
439
+ 'https://archive.org/wayback/available?callback=?',
440
  { url: url },
441
 
442
  function(data) {
456
  '-' + snapshot.timestamp.substr(6, 2);
457
  var name = sprintf(iaSuggestionName, readableTimestamp);
458
 
459
+ //Enforce HTTPS by default
460
+ snapshot.url = (snapshot.url).replace( new RegExp("^http:", "m"), "https:");
461
+
462
  //Display the suggestion.
463
  var item = suggestionTemplate.clone();
464
  item.find('.blc-suggestion-name a').text(name).attr('href', snapshot.url);
includes/admin/options-page-js.php CHANGED
@@ -111,6 +111,16 @@ jQuery(function($){
111
  $('#module-extra-settings-custom_field').removeClass('hidden');
112
  }
113
  });
 
 
 
 
 
 
 
 
 
 
114
 
115
  //Handle the "Recheck" button
116
  $('#start-recheck').click(function(){
111
  $('#module-extra-settings-custom_field').removeClass('hidden');
112
  }
113
  });
114
+
115
+ //When the user ticks the "Custom fields" box, display the field list input
116
+ //so that they notice that they need to enter the field names.
117
+ $('#module-checkbox-acf_field').click(function(){
118
+ var box = $(this);
119
+ var fieldList = $('#blc_acf_fields');
120
+ if ( box.is(':checked') && ( $.trim(fieldList.val()) == '' ) ){
121
+ $('#module-extra-settings-acf_field').removeClass('hidden');
122
+ }
123
+ });
124
 
125
  //Handle the "Recheck" button
126
  $('#start-recheck').click(function(){
includes/admin/table-printer.php CHANGED
@@ -1,904 +1,907 @@
1
- <?php
2
-
3
- if (!class_exists('blcTablePrinter')) {
4
-
5
- /**
6
- * Utility class for printing the link listing table.
7
- *
8
- * @package Broken Link Checker
9
- * @access public
10
- */
11
- class blcTablePrinter {
12
-
13
- var $current_filter; //The current search filter. Also contains the list of links to display.
14
- var $page; //The current page number
15
- var $per_page; //Max links per page
16
- /** @var wsBrokenLinkChecker */
17
- var $core; //A reference to the main plugin object
18
- var $neutral_current_url; //The "safe" version of the current URL, for use in the bulk action form.
19
-
20
- var $bulk_actions_html = '';
21
- var $pagination_html = '';
22
- var $searched_link_type = '';
23
-
24
- var $columns;
25
- var $layouts;
26
-
27
-
28
- function blcTablePrinter($core){
29
- $this->core = $core;
30
-
31
- //Initialize layout and column definitions
32
- $this->setup_columns();
33
- $this->setup_layouts();
34
-
35
- //Figure out what the "safe" URL to acccess the current page would be.
36
- //This is used by the bulk action form.
37
- $special_args = array('_wpnonce', '_wp_http_referer', 'action', 'selected_links');
38
- $this->neutral_current_url = remove_query_arg($special_args);
39
- }
40
-
41
-
42
- /**
43
- * Print the entire link table and associated navigation elements.
44
- *
45
- * @param array $current_filter
46
- * @param string $layout
47
- * @param array $visible_columns
48
- * @param bool $compact
49
- * @return void
50
- */
51
- function print_table($current_filter, $layout = 'flexible', $visible_columns = null, $compact = false){
52
- $this->current_filter = $current_filter;
53
- $this->page = $current_filter['page'];
54
- $this->per_page = $current_filter['per_page'];
55
-
56
- $current_layout = $this->layouts[$layout];
57
- if ( empty($visible_columns) ){
58
- $visible_columns = $current_layout;
59
- }
60
- //Only allow columns actually present in this layout
61
- $visible_columns = array_intersect($visible_columns, $current_layout);
62
-
63
- echo '<form id="blc-bulk-action-form" action="', esc_attr($this->neutral_current_url), '" method="post">';
64
- wp_nonce_field('bulk-action');
65
-
66
- //Top navigation
67
- $this->prepare_nav_html();
68
- $this->navigation($compact);
69
-
70
- //Table header
71
- $table_classes = array('widefat');
72
- if ( $compact ) {
73
- $table_classes[] = 'compact';
74
- };
75
- if ( $this->core->conf->options['table_color_code_status'] ) {
76
- $table_classes[] = 'color-code-link-status';
77
- };
78
- $table_classes[] = 'base-filter-' . $current_filter['base_filter'];
79
- printf(
80
- '<table class="%s" id="blc-links"><thead><tr>',
81
- implode(' ', $table_classes)
82
- );
83
-
84
- //The select-all checkbox
85
- echo '<th scope="col" class="column-checkbox check-column" id="cb"><input type="checkbox" /></th>';
86
-
87
- //Column headers
88
- foreach($current_layout as $column_id){
89
- $column = $this->columns[$column_id];
90
-
91
- $column_classes = array('column-'.$column_id);
92
- if ( isset($column['class']) ){
93
- $column_classes[] = $column['class'];
94
- }
95
- if ( !in_array($column_id, $visible_columns) ) {
96
- $column_classes[] = 'hidden';
97
- }
98
-
99
- $heading = $column['heading'];
100
- if ( isset($column['sortable']) && $column['sortable'] ) {
101
- $orderby = $column['orderby'];
102
- $current_orderby = isset($_GET['orderby']) ? $_GET['orderby'] : '';
103
- $current_order = isset($_GET['order']) ? $_GET['order'] : 'asc';
104
- if ( !in_array($current_order, array('asc', 'desc')) ) {
105
- $current_order = 'asc';
106
- }
107
-
108
- if ( $orderby == $current_orderby ) {
109
- $column_classes[] = 'sorted';
110
- $column_classes[] = $current_order;
111
- $order = ($current_order == 'asc') ? 'desc' : 'asc'; //Reverse the sort direction
112
- } else {
113
- $order = 'asc';
114
- $column_classes[] = 'desc';
115
- $column_classes[] = 'sortable';
116
- }
117
-
118
- $heading = sprintf(
119
- '<a href="%s"><span>%s</span><span class="sorting-indicator"></span></a>',
120
- esc_attr(add_query_arg(array(
121
- 'orderby' => $orderby,
122
- 'order' => $order,
123
- ))),
124
- $heading
125
- );
126
- }
127
-
128
- printf(
129
- '<th scope="col" class="%s"%s>%s</th>',
130
- implode(' ', $column_classes),
131
- isset($column['id']) ? ' id="' . $column['id'] . '"' : '',
132
- $heading
133
- );
134
- }
135
- echo '</tr></thead>';
136
-
137
- //Table body
138
- echo '<tbody id="the-list">';
139
- $this->bulk_edit_form($visible_columns);
140
- $rownum = 0;
141
- foreach ($this->current_filter['links'] as $link) {
142
- $rownum++;
143
- $this->link_row($link, $current_layout, $visible_columns, $rownum);
144
- $this->link_details_row($link, $visible_columns, $rownum);
145
- }
146
- echo '</tbody></table>';
147
-
148
- //Bottom navigation
149
- $this->navigation($compact, '2');
150
- echo '</form>';
151
-
152
- //Inline editor (hidden by default, JS will move it to the right place).
153
- $this->inline_editor($visible_columns);
154
- }
155
-
156
- /**
157
- * Print the "Bulk Actions" dropdown and navigation links
158
- *
159
- * @param bool $table_compact Whether to use the full or compact view.
160
- * @param string $suffix Optional. Appended to ID and name attributes of the bulk action dropdown.
161
- * @return void
162
- */
163
- function navigation($table_compact = false, $suffix = ''){
164
- //Display the "Bulk Actions" dropdown
165
- echo '<div class="tablenav">',
166
- '<div class="alignleft actions">',
167
- '<select name="action', $suffix ,'" id="blc-bulk-action', $suffix ,'">',
168
- $this->bulk_actions_html,
169
- '</select>',
170
- ' <input type="submit" name="doaction', $suffix ,'" id="doaction',$suffix,'" value="',
171
- esc_attr(__('Apply', 'broken-link-checker')),
172
- '" class="button-secondary action">',
173
- '</div>';
174
-
175
- //Display pagination links
176
- if ( !empty($this->pagination_html) ){
177
- echo $this->pagination_html;
178
- }
179
-
180
- //Display the view switch (only in the top nav. area)
181
- if ( empty($suffix) ){
182
- ?>
183
-
184
- <div class="view-switch">
185
- <a
186
- href="<?php echo esc_url(add_query_arg('compact', '1', $_SERVER['REQUEST_URI'])) ?>"
187
- class="view-list <?php if ( $table_compact ) echo 'current'; ?>"
188
- title="<?php echo esc_attr(__('Compact View', 'broken-link-checker')); ?>">
189
- </a>
190
- <a
191
- href="<?php echo esc_url(add_query_arg('compact', '0', $_SERVER['REQUEST_URI'])) ?>"
192
- class="view-excerpt <?php if ( !$table_compact ) echo 'current'; ?>"
193
- title="<?php echo esc_attr(__('Detailed View', 'broken-link-checker')); ?>">
194
- </a>
195
- </div>
196
-
197
- <?php
198
- }
199
-
200
- echo '</div>';
201
- }
202
-
203
- /**
204
- * Initialize the internal list of available table columns.
205
- *
206
- * @return void
207
- */
208
- function setup_columns(){
209
- $this->columns = array(
210
- 'status' => array(
211
- 'heading' => __('Status', 'broken-link-checker'),
212
- 'content' => array($this, 'column_status'),
213
- ),
214
-
215
- 'new-url' => array(
216
- 'heading' => __('URL', 'broken-link-checker'),
217
- 'content' => array($this, 'column_new_url'),
218
- 'sortable' => true,
219
- 'orderby' => 'url',
220
- ),
221
-
222
- 'used-in' => array(
223
- 'heading' => __('Source', 'broken-link-checker'),
224
- 'class' => 'column-title',
225
- 'content' => array($this, 'column_used_in'),
226
- ),
227
-
228
- 'new-link-text' => array(
229
- 'heading' => __('Link Text', 'broken-link-checker'),
230
- 'content' => array($this, 'column_new_link_text'),
231
- 'sortable' => true,
232
- 'orderby' => 'link_text',
233
- ),
234
-
235
- 'redirect-url' => array(
236
- 'heading' => __('Redirect URL', 'broken-link-checker'),
237
- 'content' => array($this, 'column_redirect_url'),
238
- 'sortable' => true,
239
- 'orderby' => 'redirect_url',
240
- ),
241
- );
242
- }
243
-
244
- /**
245
- * Initialize the list of available layouts
246
- *
247
- * @return void
248
- */
249
- function setup_layouts(){
250
- $this->layouts = array(
251
- 'classic' => array('used-in', 'new-link-text', 'new-url'),
252
- 'flexible' => array('new-url', 'status', 'new-link-text', 'redirect-url', 'used-in', ),
253
- );
254
- }
255
-
256
- /**
257
- * Get a list of columns available in a specific table layout.
258
- *
259
- * @param string $layout Layout ID.
260
- * @return array Associative array of column data indexed by column ID.
261
- */
262
- function get_layout_columns($layout){
263
- if ( isset($this->layouts[$layout]) ){
264
-
265
- $result = array();
266
- foreach($this->layouts[$layout] as $column_id){
267
- if ( isset($this->columns[$column_id]) )
268
- $result[$column_id] = $this->columns[$column_id];
269
- }
270
- return $result;
271
-
272
- } else {
273
- return null;
274
- }
275
- }
276
-
277
- /**
278
- * Pre-generate some HTML fragments used for both the top and bottom navigation/bulk action boxes.
279
- *
280
- * @return void
281
- */
282
- function prepare_nav_html(){
283
- //Generate an <option> element for each possible bulk action. The list doesn't change,
284
- //so we can do it once and reuse the generated HTML.
285
- $bulk_actions = array(
286
- '-1' => __('Bulk Actions', 'broken-link-checker'),
287
- "bulk-edit" => __('Edit URL', 'broken-link-checker'),
288
- "bulk-recheck" => __('Recheck', 'broken-link-checker'),
289
- "bulk-deredirect" => __('Fix redirects', 'broken-link-checker'),
290
- "bulk-not-broken" => __('Mark as not broken', 'broken-link-checker'),
291
- "bulk-dismiss" => __('Dismiss', 'broken-link-checker'),
292
- "bulk-unlink" => __('Unlink', 'broken-link-checker'),
293
- );
294
- if ( EMPTY_TRASH_DAYS ){
295
- $bulk_actions["bulk-trash-sources"] = __('Move sources to Trash', 'broken-link-checker');
296
- } else {
297
- $bulk_actions["bulk-delete-sources"] = __('Delete sources', 'broken-link-checker');
298
- }
299
-
300
- $bulk_actions_html = '';
301
- foreach($bulk_actions as $value => $name){
302
- $bulk_actions_html .= sprintf('<option value="%s">%s</option>', $value, $name);
303
- }
304
-
305
- $this->bulk_actions_html = $bulk_actions_html;
306
-
307
- //Pagination links can also be pre-generated.
308
- //WP has a built-in function for pagination :)
309
- $page_links = paginate_links( array(
310
- 'base' => add_query_arg( 'paged', '%#%' ),
311
- 'format' => '',
312
- 'prev_text' => __('&laquo;'),
313
- 'next_text' => __('&raquo;'),
314
- 'total' => $this->current_filter['max_pages'],
315
- 'current' => $this->page
316
- ));
317
-
318
- if ( $page_links ) {
319
- $this->pagination_html = '<div class="tablenav-pages">';
320
- $this->pagination_html .= sprintf(
321
- '<span class="displaying-num">' . __( 'Displaying %s&#8211;%s of <span class="current-link-count">%s</span>', 'broken-link-checker' ) . '</span>%s',
322
- number_format_i18n( ( $this->page - 1 ) * $this->per_page + 1 ),
323
- number_format_i18n( min( $this->page * $this->per_page, $this->current_filter['count'] ) ),
324
- number_format_i18n( $this->current_filter['count'] ),
325
- $page_links
326
- );
327
- $this->pagination_html .= '</div>';
328
- } else {
329
- $this->pagination_html = '';
330
- }
331
- }
332
-
333
- /**
334
- * Print the bulk edit form.
335
- *
336
- * @param array $visible_columns List of visible columns.
337
- * @return void
338
- */
339
- function bulk_edit_form($visible_columns){
340
- ?>
341
- <tr id="bulk-edit" class="inline-edit-rows"><td colspan="<?php echo count($visible_columns)+1; ?>">
342
- <div id="bulk-edit-wrap">
343
- <fieldset>
344
- <h4><?php _e('Bulk Edit URLs', 'broken-link-checker'); ?></h4>
345
- <label>
346
- <span class="title"><?php _e('Find', 'broken-link-checker'); ?></span>
347
- <input type="text" name="search" class="text">
348
- </label>
349
- <label>
350
- <span class="title"><?php _e('Replace with', 'broken-link-checker'); ?></span>
351
- <input type="text" name="replace" class="text">
352
- </label>
353
-
354
- <div id="bulk-edit-options">
355
- <span class="title">&nbsp;</span>
356
- <label>
357
- <input type="checkbox" name="case_sensitive">
358
- <?php _e('Case sensitive', 'broken-link-checker'); ?>
359
- </label>
360
- <label>
361
- <input type="checkbox" name="regex">
362
- <?php _e('Regular expression', 'broken-link-checker'); ?>
363
- </label>
364
- </div>
365
- </fieldset>
366
-
367
- <p class="submit inline-edit-save">
368
- <a href="#bulk-edit" class="button-secondary cancel alignleft" title="<?php echo esc_attr(__('Cancel', 'broken-link-checker')); ?>" accesskey="c"><?php _e('Cancel', 'broken-link-checker'); ?></a>
369
- <input type="submit" name="bulk_edit" class="button-primary alignright" value="<?php
370
- _e('Update', 'broken-link-checker');
371
- ?>" accesskey="s">
372
- </p>
373
- </div>
374
- </td></tr>
375
- <?php
376
- }
377
-
378
- /**
379
- * Print the link row.
380
- *
381
- * @param blcLink $link The link to display.
382
- * @param array $layout List of columns to output.
383
- * @param array $visible_columns List of visible columns.
384
- * @param integer $rownum Table row number.
385
- * @return void
386
- */
387
- function link_row($link, $layout, $visible_columns, $rownum = 0){
388
-
389
- //Figure out what CSS classes the link row should have
390
- $rowclass = ($rownum % 2)? 'alternate' : '';
391
-
392
- $excluded = $this->core->is_excluded( $link->url );
393
- if ( $excluded ) $rowclass .= ' blc-excluded-link';
394
-
395
- if ( $link->redirect_count > 0){
396
- $rowclass .= ' blc-redirect';
397
- }
398
-
399
- $days_broken = 0;
400
- if ( $link->broken ){
401
- //Add a highlight to broken links that appear to be permanently broken
402
- $days_broken = intval( (time() - $link->first_failure) / (3600*24) );
403
- if ( $days_broken >= $this->core->conf->options['failure_duration_threshold'] ){
404
- $rowclass .= ' blc-permanently-broken';
405
- if ( $this->core->conf->options['highlight_permanent_failures'] ){
406
- $rowclass .= ' blc-permanently-broken-hl';
407
- }
408
- }
409
- }
410
-
411
- $status = $link->analyse_status();
412
- $rowclass .= ' link-status-' . $status['code'];
413
-
414
- //Retrieve link instances to display in the table
415
- $instances = $link->get_instances();
416
-
417
- if ( !empty($instances) ){
418
- //Put instances that match the selected link type at the top. Makes search results look better.
419
- if ( !empty($this->current_filter['search_params']['s_link_type']) ){
420
- $s_link_type = $this->current_filter['search_params']['s_link_type'];
421
- } else {
422
- $s_link_type = '';
423
- }
424
- $instances = $this->sort_instances_for_display($instances, $s_link_type);
425
- }
426
-
427
- //For inline editing, we'll need to know if any instances have editable link text, and what it is.
428
- $can_edit_text = false;
429
- $can_edit_url = false;
430
- $editable_link_texts = $non_editable_link_texts = array();
431
- foreach($instances as $instance) {
432
- if ( $instance->is_link_text_editable() ) {
433
- $can_edit_text = true;
434
- $editable_link_texts[$instance->link_text] = true;
435
- } else {
436
- $non_editable_link_texts[$instance->link_text] = true;
437
- }
438
-
439
- if ( $instance->is_url_editable() ) {
440
- $can_edit_url = true;
441
- }
442
- }
443
-
444
- $link_texts = $can_edit_text ? $editable_link_texts : $non_editable_link_texts;
445
- $data_link_text = '';
446
- if ( count($link_texts) === 1 ) {
447
- //All instances have the same text - use it.
448
- $link_text = key($link_texts);
449
- $data_link_text = ' data-link-text="' . esc_attr($link_text) . '"';
450
- }
451
-
452
- printf(
453
- '<tr id="blc-row-%s" class="blc-row %s" data-days-broken="%d" data-can-edit-url="%d" data-can-edit-text="%d"%s>',
454
- $link->link_id,
455
- $rowclass,
456
- $days_broken,
457
- $can_edit_url ? 1 : 0,
458
- $can_edit_text ? 1 : 0,
459
- $data_link_text
460
- );
461
-
462
- //The checkbox used to select links is automatically printed in all layouts
463
- //and can't be disabled. Without it, bulk actions wouldn't work.
464
- $this->column_checkbox($link);
465
-
466
- foreach($layout as $column_id){
467
- $column = $this->columns[$column_id];
468
-
469
- printf(
470
- '<td class="column-%s%s">',
471
- $column_id,
472
- in_array($column_id, $visible_columns) ? '' : ' hidden'
473
- );
474
-
475
- if ( isset($column['content']) ){
476
- if ( is_callable($column['content']) ){
477
- call_user_func($column['content'], $link, $instances);
478
- } else {
479
- echo $column['content'];
480
- }
481
- } else {
482
- echo '[', $column_id, ']';
483
- }
484
-
485
- echo '</td>';
486
- }
487
-
488
- echo '</tr>';
489
- }
490
-
491
- /**
492
- * Print the details row for a specific link.
493
- *
494
- * @uses blcTablePrinter::details_row_contents()
495
- *
496
- * @param blcLink $link The link to display.
497
- * @param array $visible_columns List of visible columns.
498
- * @param integer $rownum Table row number.
499
- * @return void
500
- */
501
- function link_details_row($link, $visible_columns, $rownum = 0){
502
- printf(
503
- '<tr id="link-details-%d" class="blc-link-details"><td colspan="%d">',
504
- $link->link_id,
505
- count($visible_columns)+1
506
- );
507
- $this->details_row_contents($link);
508
- echo '</td></tr>';
509
- }
510
-
511
- /**
512
- * Print the contents of the details row for a specific link.
513
- *
514
- * @param blcLink $link
515
- * @return void
516
- */
517
- public static function details_row_contents($link){
518
- ?>
519
- <div class="blc-detail-container">
520
- <div class="blc-detail-block" style="float: left; width: 49%;">
521
- <ol style='list-style-type: none;'>
522
- <?php if ( !empty($link->post_date) ) { ?>
523
- <li><strong><?php _e('Post published on', 'broken-link-checker'); ?>:</strong>
524
- <span class='post_date'><?php
525
- echo date_i18n(get_option('date_format'),strtotime($link->post_date));
526
- ?></span></li>
527
- <?php } ?>
528
- <li><strong><?php _e('Link last checked', 'broken-link-checker'); ?>:</strong>
529
- <span class='check_date'><?php
530
- $last_check = $link->last_check;
531
- if ( $last_check < strtotime('-10 years') ){
532
- _e('Never', 'broken-link-checker');
533
- } else {
534
- printf(
535
- '<time datetime="%s">%s</time>',
536
- esc_attr(date('c', $last_check)),
537
- date_i18n(get_option('date_format'), $last_check)
538
- );
539
- }
540
- ?></span></li>
541
-
542
- <li><strong><?php _e('HTTP code', 'broken-link-checker'); ?>:</strong>
543
- <span class='http_code'><?php
544
- print $link->http_code;
545
- ?></span></li>
546
-
547
- <li><strong><?php _e('Response time', 'broken-link-checker'); ?>:</strong>
548
- <span class='request_duration'><?php
549
- printf( __('%2.3f seconds', 'broken-link-checker'), $link->request_duration);
550
- ?></span></li>
551
-
552
- <li><strong><?php _e('Final URL', 'broken-link-checker'); ?>:</strong>
553
- <span class='final_url'><?php
554
- print esc_html($link->final_url);
555
- ?></span></li>
556
-
557
- <li><strong><?php _e('Redirect count', 'broken-link-checker'); ?>:</strong>
558
- <span class='redirect_count'><?php
559
- print $link->redirect_count;
560
- ?></span></li>
561
-
562
- <li><strong><?php _e('Instance count', 'broken-link-checker'); ?>:</strong>
563
- <span class='instance_count'><?php
564
- print count($link->get_instances());
565
- ?></span></li>
566
-
567
- <?php if ( ($link->broken || $link->warning) && (intval( $link->check_count ) > 0) ){ ?>
568
- <li><br/>
569
- <?php
570
- printf(
571
- _n('This link has failed %d time.', 'This link has failed %d times.', $link->check_count, 'broken-link-checker'),
572
- $link->check_count
573
- );
574
-
575
- echo '<br>';
576
-
577
- $delta = time() - $link->first_failure;
578
- printf(
579
- __('This link has been broken for %s.', 'broken-link-checker'),
580
- blcUtility::fuzzy_delta($delta)
581
- );
582
- ?>
583
- </li>
584
- <?php } ?>
585
- </ol>
586
- </div>
587
-
588
- <div class="blc-detail-block" style="float: right; width: 50%;">
589
- <ol style='list-style-type: none;'>
590
- <li><strong><?php _e('Log', 'broken-link-checker'); ?>:</strong>
591
- <span class='blc_log'><?php
592
- print nl2br($link->log);
593
- ?></span></li>
594
- </ol>
595
- </div>
596
-
597
- <div style="clear:both;"> </div>
598
- </div>
599
- <?php
600
- }
601
-
602
- function column_checkbox($link){
603
- ?>
604
- <th scope="row" class="check-column"><input type="checkbox" name="selected_links[]" value="<?php echo $link->link_id; ?>" /></th>
605
- <?php
606
- }
607
-
608
- /**
609
- * @param blcLink $link
610
- * @param blcLinkInstance[] $instances
611
- */
612
- function column_status($link, $instances){
613
- printf(
614
- '<table class="mini-status" title="%s">',
615
- esc_attr(__('Show more info about this link', 'broken-link-checker'))
616
- );
617
-
618
- $status = $link->analyse_status();
619
-
620
- printf(
621
- '<tr class="link-status-row link-status-%s">
622
- <td>
623
- <span class="http-code">%s</span> <span class="status-text">%s</span>
624
- </td>
625
- </tr>',
626
- $status['code'],
627
- empty($link->http_code)?'':$link->http_code,
628
- $status['text']
629
- );
630
-
631
- //Last checked...
632
- if ( $link->last_check != 0 ){
633
- $last_check = _x('Checked', 'checked how long ago', 'broken-link-checker') . ' ';
634
- $last_check .= blcUtility::fuzzy_delta(time() - $link->last_check, 'ago');
635
-
636
- printf(
637
- '<tr class="link-last-checked"><td>%s</td></tr>',
638
- $last_check
639
- );
640
- }
641
-
642
-
643
- //Broken for...
644
- if ( $link->broken ){
645
- $delta = time() - $link->first_failure;
646
- $broken_for = blcUtility::fuzzy_delta($delta);
647
- printf(
648
- '<tr class="link-broken-for"><td>%s %s</td></tr>',
649
- __('Broken for', 'broken-link-checker'),
650
- $broken_for
651
- );
652
- }
653
-
654
- echo '</table>';
655
-
656
- //"Details" link.
657
- echo '<div class="row-actions">';
658
- printf(
659
- '<span><a href="#" class="blc-details-button" title="%s">%s</a></span>',
660
- esc_attr(__('Show more info about this link', 'broken-link-checker')),
661
- _x('Details', 'link in the "Status" column', 'broken-link-checker')
662
- );
663
- echo '</div>';
664
- }
665
-
666
-
667
- /**
668
- * @param blcLink $link
669
- */
670
- function column_new_url($link){
671
- ?>
672
- <a href="<?php print esc_url($link->url); ?>"
673
- target='_blank'
674
- class='blc-link-url'
675
- title="<?php echo esc_attr($link->url); ?>"
676
- data-editable-url="<?php echo esc_attr($link->url); ?>">
677
- <?php print esc_html($link->url); ?></a>
678
- <?php
679
- //Output inline action links for the link/URL
680
- $actions = array();
681
-
682
- $actions['edit'] = "<a href='javascript:void(0)' class='blc-edit-button' title='" . esc_attr( __('Edit this link' , 'broken-link-checker') ) . "'>". __('Edit URL' , 'broken-link-checker') ."</a>";
683
-
684
- $actions['delete'] = "<a class='submitdelete blc-unlink-button' title='" . esc_attr( __('Remove this link from all posts', 'broken-link-checker') ). "' ".
685
- "href='javascript:void(0);'>" . __('Unlink', 'broken-link-checker') . "</a>";
686
-
687
- if ( $link->broken || $link->warning ){
688
- $actions['blc-discard-action'] = sprintf(
689
- '<a href="#" title="%s" class="blc-discard-button">%s</a>',
690
- esc_attr(__('Remove this link from the list of broken links and mark it as valid', 'broken-link-checker')),
691
- __('Not broken', 'broken-link-checker')
692
- );
693
- }
694
-
695
- if ( !$link->dismissed && ($link->broken || $link->warning || ($link->redirect_count > 0)) ) {
696
- $actions['blc-dismiss-action'] = sprintf(
697
- '<a href="#" title="%s" class="blc-dismiss-button">%s</a>',
698
- esc_attr(__('Hide this link and do not report it again unless its status changes' , 'broken-link-checker')),
699
- __('Dismiss', 'broken-link-checker')
700
- );
701
- } else if ( $link->dismissed ) {
702
- $actions['blc-undismiss-action'] = sprintf(
703
- '<a href="#" title="%s" class="blc-undismiss-button">%s</a>',
704
- esc_attr(__('Undismiss this link', 'broken-link-checker')),
705
- __('Undismiss', 'broken-link-checker')
706
- );
707
- }
708
-
709
- $actions['blc-recheck-action'] = sprintf(
710
- '<a href="#" class="blc-recheck-button">%s</a>',
711
- __('Recheck', 'broken-link-checker')
712
- );
713
-
714
- if ( $link->redirect_count > 0 && !empty($link->final_url) && ($link->url != $link->final_url) ) {
715
- //TODO: Check if at least one instance has an editable URL. Otherwise this won't work.
716
- $actions['blc-deredirect-action'] = sprintf(
717
- '<a href="#" class="blc-deredirect-button" title="%s">%s</a>',
718
- __('Replace this redirect with a direct link', 'broken-link-checker'),
719
- _x('Fix redirect', 'link action; replace one redirect with a direct link', 'broken-link-checker')
720
- );
721
- }
722
-
723
- //Only show the enabled actions.
724
- $conf = blc_get_configuration();
725
- foreach($conf->get('show_link_actions', $actions) as $name => $enabled) {
726
- if ( !$enabled ) {
727
- unset($actions[$name]);
728
- }
729
- }
730
-
731
- //Wrap actions with <span></span> and separate them with | characters.
732
- //Basically, this emulates the HTML structure that WP uses for post actions under Posts -> All Posts.
733
- $spans = array();
734
- $is_first_action = true;
735
- foreach($actions as $name => $html) {
736
- $spans[] = sprintf(
737
- '<span class="%s">%s%s</span>',
738
- esc_attr($name),
739
- $is_first_action ? '' : ' | ',
740
- $html
741
- );
742
- $is_first_action = false;
743
- }
744
-
745
- echo '<div class="row-actions">';
746
- echo implode('', $spans);
747
- echo '</div>';
748
-
749
- ?>
750
- <div class="blc-url-editor-buttons">
751
- <input type="button" class="button-secondary cancel alignleft blc-cancel-button" value="<?php echo esc_attr(__('Cancel', 'broken-link-checker')); ?>" />
752
- <input type="button" class="button-primary save alignright blc-update-url-button" value="<?php echo esc_attr(__('Update URL', 'broken-link-checker')); ?>" />
753
- <img class="waiting" style="display:none;" src="<?php echo esc_url( admin_url( 'images/wpspin_light.gif' ) ); ?>" alt="" />
754
- </div>
755
- <?php
756
- }
757
-
758
- /**
759
- * @param blcLink $link
760
- * @param blcLinkInstance[] $instances
761
- */
762
- function column_used_in($link, $instances){
763
- echo '<span class="blc-link-id" style="display:none;">',
764
- $link->link_id,
765
- '</span>';
766
-
767
- if ( !empty($instances) ){
768
- /** @var $instance blcLinkInstance */
769
- $instance = reset($instances);
770
- echo $instance->ui_get_source();
771
-
772
- $actions = $instance->ui_get_action_links();
773
-
774
- echo '<div class="row-actions">';
775
- echo implode(' | </span>', $actions);
776
- echo '</div>';
777
-
778
- } else {
779
- _e("[An orphaned link! This is a bug.]", 'broken-link-checker');
780
- }
781
- }
782
-
783
- /**
784
- * @param blcLink $link
785
- * @param blcLinkInstance[] $instances
786
- */
787
- function column_new_link_text($link, $instances){
788
- if ( empty($instances) ){
789
- echo '<em>N/A</em>';
790
- } else {
791
- $instance = reset($instances); /** @var blcLinkInstance $instance */
792
- echo $instance->ui_get_link_text();
793
- }
794
- }
795
-
796
- function column_redirect_url($link, $instances) {
797
- if ( $link->redirect_count > 0 ) {
798
- printf(
799
- '<a href="%1$s" target="_blank" class="blc-redirect-url" title="%1$s">%2$s</a>',
800
- esc_attr($link->final_url),
801
- esc_html($link->final_url)
802
- );
803
- }
804
- }
805
-
806
- /**
807
- * Sort a list of link instances to be displayed in the "Broken Links" page.
808
- *
809
- * Groups instances by container type and, if $search_link_type is specified,
810
- * puts instances that have a matching container type or parser type at the
811
- * beginning.
812
- *
813
- * @param array $instances An array of blcLinkInstance objects.
814
- * @param string $searched_link_type Optional. The required container/parser type.
815
- * @return array Sorted array.
816
- */
817
- function sort_instances_for_display($instances, $searched_link_type = ''){
818
- $this->searched_link_type = $searched_link_type;
819
- usort($instances, array($this, 'compare_link_instances'));
820
- return $instances;
821
- }
822
-
823
- /**
824
- * Callback function for sorting link instances.
825
- *
826
- * @see blcTablePrinter::sort_instances_for_display()
827
- *
828
- * @param blcLinkInstance $a
829
- * @param blcLinkInstance $b
830
- * @return int
831
- */
832
- function compare_link_instances($a, $b){
833
- if ( !empty($this->searched_link_type) ){
834
- if ( ($a->container_type == $this->searched_link_type) || ($a->parser_type == $this->searched_link_type) ){
835
- if ( ($b->container_type == $this->searched_link_type) || ($b->parser_type == $this->searched_link_type) ){
836
- return 0;
837
- } else {
838
- return -1;
839
- }
840
- } else {
841
- if ( ($b->container_type == $this->searched_link_type) || ($b->parser_type == $this->searched_link_type) ){
842
- return 1;
843
- }
844
- }
845
- }
846
-
847
- return strcmp($a->container_type, $b->container_type);
848
- }
849
-
850
- protected function inline_editor($visible_columns) {
851
- ?>
852
- <table style="display: none;"><tbody>
853
- <tr id="blc-inline-edit-row" class="blc-inline-editor">
854
- <td class="blc-colspan-change" colspan="<?php echo count($visible_columns); ?>">
855
- <div class="blc-inline-editor-content">
856
- <h4><?php echo _x('Edit Link', 'inline editor title', 'broken-link-checker'); ?></h4>
857
-
858
- <label>
859
- <span class="title"><?php echo _x('Text', 'inline link editor', 'broken-link-checker'); ?></span>
860
- <span class="blc-input-text-wrap"><input type="text" name="link_text" value="" class="blc-link-text-field" /></span>
861
- </label>
862
-
863
- <label>
864
- <span class="title"><?php echo _x('URL', 'inline link editor', 'broken-link-checker'); ?></span>
865
- <span class="blc-input-text-wrap"><input type="text" name="link_url" value="" class="blc-link-url-field" /></span>
866
- </label>
867
-
868
- <div class="blc-url-replacement-suggestions" style="display: none;">
869
- <h4><?php echo _x('Suggestions', 'inline link editor', 'broken-link-checker'); ?></h4>
870
- <ul class="blc-suggestion-list">
871
- <li>...</li>
872
- </ul>
873
- </div>
874
-
875
- <div class="submit blc-inline-editor-buttons">
876
- <input type="button" class="button-secondary cancel alignleft blc-cancel-button" value="<?php echo esc_attr(__('Cancel', 'broken-link-checker')); ?>" />
877
- <input type="button" class="button-primary save alignright blc-update-link-button" value="<?php echo esc_attr(__('Update', 'broken-link-checker')); ?>" />
878
-
879
- <img class="waiting" style="display:none;" src="<?php echo esc_url( admin_url( 'images/wpspin_light.gif' ) ); ?>" alt="" />
880
- <div class="clear"></div>
881
- </div>
882
- </div>
883
- </td>
884
- </tr>
885
- </tbody></table>
886
-
887
- <ul id="blc-suggestion-template" style="display: none;">
888
- <li>
889
- <input type="button" class="button-secondary blc-use-url-button" value="<?php echo esc_attr(__('Use this URL', 'broken-link-checker')); ?>" />
890
-
891
- <div class="blc-suggestion-details">
892
- <span class="blc-suggestion-name">
893
- <a href="http://example.com/" target="_blank">Suggestion name</a>
894
- </span>
895
- <code class="blc-suggestion-url">suggestion URL</code>
896
- </div>
897
- </li>
898
- </ul>
899
- <?php
900
- }
901
-
902
- }
903
-
904
- }//class_exists
 
 
 
1
+ <?php
2
+ /**
3
+ * Utility class for printing the link listing table.
4
+ *
5
+ * @package Broken Link Checker
6
+ * @access public
7
+ */
8
+
9
+ if ( ! class_exists( 'blcTablePrinter' ) ) {
10
+
11
+ class blcTablePrinter {
12
+
13
+ var $current_filter; //The current search filter. Also contains the list of links to display.
14
+ var $page; //The current page number
15
+ var $per_page; //Max links per page
16
+ /** @var wsBrokenLinkChecker */
17
+ var $core; //A reference to the main plugin object
18
+ var $neutral_current_url; //The "safe" version of the current URL, for use in the bulk action form.
19
+
20
+ var $bulk_actions_html = '';
21
+ var $pagination_html = '';
22
+ var $searched_link_type = '';
23
+
24
+ var $columns;
25
+ var $layouts;
26
+
27
+
28
+ function __construct( $core ) {
29
+ $this->core = $core;
30
+
31
+ // Initialize layout and column definitions,
32
+ $this->setup_columns();
33
+ $this->setup_layouts();
34
+
35
+ // Figure out what the "safe" URL to acccess the current page would be.
36
+ // This is used by the bulk action form.
37
+ $special_args = array( '_wpnonce', '_wp_http_referer', 'action', 'selected_links' );
38
+ $this->neutral_current_url = remove_query_arg( $special_args );
39
+ }
40
+
41
+
42
+ /**
43
+ * Print the entire link table and associated navigation elements.
44
+ *
45
+ * @param array $current_filter
46
+ * @param string $layout
47
+ * @param array $visible_columns
48
+ * @param bool $compact
49
+ * @return void
50
+ */
51
+ function print_table( $current_filter, $layout = 'flexible', $visible_columns = null, $compact = false ) {
52
+ $this->current_filter = $current_filter;
53
+ $this->page = $current_filter['page'];
54
+ $this->per_page = $current_filter['per_page'];
55
+
56
+ $current_layout = $this->layouts[$layout];
57
+ if ( empty( $visible_columns ) ) {
58
+ $visible_columns = $current_layout;
59
+ }
60
+ // Only allow columns actually present in this layout.
61
+ $visible_columns = array_intersect( $visible_columns, $current_layout );
62
+
63
+ echo '<form id="blc-bulk-action-form" action="' . esc_attr( $this->neutral_current_url ) . '" method="post">';
64
+ wp_nonce_field( 'bulk-action' );
65
+
66
+ // Top navigation.
67
+ $this->prepare_nav_html();
68
+ $this->navigation( $compact );
69
+
70
+ // Table header.
71
+ $table_classes = array( 'widefat' );
72
+ if ( $compact ) {
73
+ $table_classes[] = 'compact';
74
+ };
75
+ if ( $this->core->conf->options['table_color_code_status'] ) {
76
+ $table_classes[] = 'color-code-link-status';
77
+ };
78
+ $table_classes[] = 'base-filter-' . $current_filter['base_filter'];
79
+ printf(
80
+ '<table class="%s" id="blc-links"><thead><tr>',
81
+ implode( ' ', $table_classes )
82
+ );
83
+
84
+ // The select-all checkbox.
85
+ echo '<th scope="col" class="column-checkbox check-column" id="cb"><input type="checkbox" /></th>';
86
+
87
+ // Column headers.
88
+ foreach ( $current_layout as $column_id ) {
89
+ $column = $this->columns[ $column_id ];
90
+
91
+ $column_classes = array( 'column-' . $column_id );
92
+ if ( isset( $column['class'] ) ) {
93
+ $column_classes[] = $column['class'];
94
+ }
95
+ if ( ! in_array( $column_id, $visible_columns ) ) {
96
+ $column_classes[] = 'hidden';
97
+ }
98
+
99
+ $heading = $column['heading'];
100
+ if ( isset( $column['sortable'] ) && $column['sortable'] ) {
101
+ $orderby = $column['orderby'];
102
+ $current_orderby = isset( $_GET['orderby'] ) ? $_GET['orderby'] : '';
103
+ $current_order = isset( $_GET['order'] ) ? $_GET['order'] : 'asc';
104
+ if ( ! in_array( $current_order, array( 'asc', 'desc') ) ) {
105
+ $current_order = 'asc';
106
+ }
107
+
108
+ if ( $orderby == $current_orderby ) {
109
+ $column_classes[] = 'sorted';
110
+ $column_classes[] = $current_order;
111
+ $order = ( 'asc' == $current_order ) ? 'desc' : 'asc'; //Reverse the sort direction
112
+ } else {
113
+ $order = 'asc';
114
+ $column_classes[] = 'desc';
115
+ $column_classes[] = 'sortable';
116
+ }
117
+
118
+ $heading = sprintf(
119
+ '<a href="%s"><span>%s</span><span class="sorting-indicator"></span></a>',
120
+ esc_attr(add_query_arg(array(
121
+ 'orderby' => $orderby,
122
+ 'order' => $order,
123
+ ))),
124
+ $heading
125
+ );
126
+ }
127
+
128
+ printf(
129
+ '<th scope="col" class="%s"%s>%s</th>',
130
+ implode( ' ', $column_classes ),
131
+ isset ( $column['id'] ) ? ' id="' . $column['id'] . '"' : '',
132
+ $heading
133
+ );
134
+ }
135
+ echo '</tr></thead>';
136
+
137
+ // Table body.
138
+ echo '<tbody id="the-list">';
139
+ $this->bulk_edit_form( $visible_columns );
140
+ $rownum = 0;
141
+ foreach ( $this->current_filter['links'] as $link ) {
142
+ $rownum++;
143
+ $this->link_row( $link, $current_layout, $visible_columns, $rownum );
144
+ $this->link_details_row( $link, $visible_columns, $rownum );
145
+ }
146
+ echo '</tbody></table>';
147
+
148
+ // Bottom navigation.
149
+ $this->navigation( $compact, '2' );
150
+ echo '</form>';
151
+
152
+ // Inline editor (hidden by default, JS will move it to the right place).
153
+ $this->inline_editor( $visible_columns );
154
+ }
155
+
156
+ /**
157
+ * Print the "Bulk Actions" dropdown and navigation links
158
+ *
159
+ * @param bool $table_compact Whether to use the full or compact view.
160
+ * @param string $suffix Optional. Appended to ID and name attributes of the bulk action dropdown.
161
+ * @return void
162
+ */
163
+ function navigation( $table_compact = false, $suffix = '' ) {
164
+ // Display the "Bulk Actions" dropdown.
165
+ echo '<div class="tablenav">
166
+ <div class="alignleft actions">
167
+ <select name="action' . $suffix . '" id="blc-bulk-action' . $suffix . '">' .
168
+ $this->bulk_actions_html .
169
+ '</select>
170
+ <input type="submit" name="doaction' . $suffix . '" id="doaction' . $suffix . '" value="',
171
+ esc_attr( __( 'Apply', 'broken-link-checker' ) ) .
172
+ '" class="button-secondary action">
173
+ </div>';
174
+
175
+ // Display pagination links.
176
+ if ( ! empty( $this->pagination_html ) ) {
177
+ echo $this->pagination_html;
178
+ }
179
+
180
+ // Display the view switch (only in the top nav. area).
181
+ if ( empty( $suffix ) ) {
182
+ ?>
183
+
184
+ <div class="view-switch">
185
+ <a
186
+ href="<?php echo esc_url( add_query_arg( 'compact', '1', $_SERVER['REQUEST_URI'] ) ) ?>"
187
+ class="view-list <?php if ( $table_compact ) echo 'current'; ?>"
188
+ title="<?php echo esc_attr( __( 'Compact View', 'broken-link-checker' ) ); ?>">
189
+ </a>
190
+ <a
191
+ href="<?php echo esc_url( add_query_arg( 'compact', '0', $_SERVER['REQUEST_URI'] ) ) ?>"
192
+ class="view-excerpt <?php if ( ! $table_compact ) echo 'current'; ?>"
193
+ title="<?php echo esc_attr( __( 'Detailed View', 'broken-link-checker' ) ); ?>">
194
+ </a>
195
+ </div>
196
+
197
+ <?php
198
+ }
199
+
200
+ echo '</div>';
201
+ }
202
+
203
+ /**
204
+ * Initialize the internal list of available table columns.
205
+ *
206
+ * @return void
207
+ */
208
+ function setup_columns(){
209
+ $this->columns = array(
210
+ 'status' => array(
211
+ 'heading' => __( 'Status', 'broken-link-checker' ),
212
+ 'content' => array( $this, 'column_status' ),
213
+ ),
214
+
215
+ 'new-url' => array(
216
+ 'heading' => __( 'URL', 'broken-link-checker' ),
217
+ 'content' => array( $this, 'column_new_url' ),
218
+ 'sortable' => true,
219
+ 'orderby' => 'url',
220
+ ),
221
+
222
+ 'used-in' => array(
223
+ 'heading' => __( 'Source', 'broken-link-checker' ),
224
+ 'class' => 'column-title',
225
+ 'content' => array( $this, 'column_used_in' ),
226
+ ),
227
+
228
+ 'new-link-text' => array(
229
+ 'heading' => __( 'Link Text', 'broken-link-checker' ),
230
+ 'content' => array( $this, 'column_new_link_text' ),
231
+ 'sortable' => true,
232
+ 'orderby' => 'link_text',
233
+ ),
234
+
235
+ 'redirect-url' => array(
236
+ 'heading' => __( 'Redirect URL', 'broken-link-checker' ),
237
+ 'content' => array( $this, 'column_redirect_url' ),
238
+ 'sortable' => true,
239
+ 'orderby' => 'redirect_url',
240
+ ),
241
+ );
242
+ }
243
+
244
+ /**
245
+ * Initialize the list of available layouts
246
+ *
247
+ * @return void
248
+ */
249
+ function setup_layouts() {
250
+ $this->layouts = array(
251
+ 'classic' => array( 'used-in', 'new-link-text', 'new-url' ),
252
+ 'flexible' => array( 'new-url', 'status', 'new-link-text', 'redirect-url', 'used-in' ),
253
+ );
254
+ }
255
+
256
+ /**
257
+ * Get a list of columns available in a specific table layout.
258
+ *
259
+ * @param string $layout Layout ID.
260
+ * @return array Associative array of column data indexed by column ID.
261
+ */
262
+ function get_layout_columns( $layout ) {
263
+ if ( isset( $this->layouts[ $layout ] ) ) {
264
+
265
+ $result = array();
266
+ foreach ( $this->layouts[ $layout ] as $column_id ) {
267
+ if ( isset( $this->columns[ $column_id ] ) ) {
268
+ $result[ $column_id ] = $this->columns[ $column_id ];
269
+ }
270
+ }
271
+ return $result;
272
+
273
+ } else {
274
+ return null;
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Pre-generate some HTML fragments used for both the top and bottom navigation/bulk action boxes.
280
+ *
281
+ * @return void
282
+ */
283
+ function prepare_nav_html(){
284
+ //Generate an <option> element for each possible bulk action. The list doesn't change,
285
+ //so we can do it once and reuse the generated HTML.
286
+ $bulk_actions = array(
287
+ '-1' => __( 'Bulk Actions', 'broken-link-checker' ),
288
+ 'bulk-edit' => __( 'Edit URL', 'broken-link-checker' ),
289
+ 'bulk-recheck' => __( 'Recheck', 'broken-link-checker' ),
290
+ 'bulk-deredirect' => __( 'Fix redirects', 'broken-link-checker' ),
291
+ 'bulk-not-broken' => __( 'Mark as not broken', 'broken-link-checker' ),
292
+ 'bulk-dismiss' => __( 'Dismiss', 'broken-link-checker' ),
293
+ 'bulk-unlink' => __( 'Unlink', 'broken-link-checker' ),
294
+ );
295
+ if ( EMPTY_TRASH_DAYS ) {
296
+ $bulk_actions['bulk-trash-sources'] = __( 'Move sources to Trash', 'broken-link-checker' );
297
+ } else {
298
+ $bulk_actions['bulk-delete-sources'] = __( 'Delete sources', 'broken-link-checker' );
299
+ }
300
+
301
+ $bulk_actions_html = '';
302
+ foreach ( $bulk_actions as $value => $name ) {
303
+ $bulk_actions_html .= sprintf( '<option value="%s">%s</option>', $value, $name );
304
+ }
305
+
306
+ $this->bulk_actions_html = $bulk_actions_html;
307
+
308
+ // Pagination links can also be pre-generated.
309
+ // WP has a built-in function for pagination :).
310
+ $page_links = paginate_links( array(
311
+ 'base' => add_query_arg( 'paged', '%#%' ),
312
+ 'format' => '',
313
+ 'prev_text' => __('&laquo;'),
314
+ 'next_text' => __('&raquo;'),
315
+ 'total' => $this->current_filter['max_pages'],
316
+ 'current' => $this->page
317
+ ));
318
+
319
+ if ( $page_links ) {
320
+ $this->pagination_html = '<div class="tablenav-pages">';
321
+ $this->pagination_html .= sprintf(
322
+ '<span class="displaying-num">' . __( 'Displaying %s&#8211;%s of <span class="current-link-count">%s</span>', 'broken-link-checker' ) . '</span>%s',
323
+ number_format_i18n( ( $this->page - 1 ) * $this->per_page + 1 ),
324
+ number_format_i18n( min( $this->page * $this->per_page, $this->current_filter['count'] ) ),
325
+ number_format_i18n( $this->current_filter['count'] ),
326
+ $page_links
327
+ );
328
+ $this->pagination_html .= '</div>';
329
+ } else {
330
+ $this->pagination_html = '';
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Print the bulk edit form.
336
+ *
337
+ * @param array $visible_columns List of visible columns.
338
+ * @return void
339
+ */
340
+ function bulk_edit_form( $visible_columns ) {
341
+ ?>
342
+ <tr id="bulk-edit" class="inline-edit-rows"><td colspan="<?php echo count( $visible_columns ) + 1; ?>">
343
+ <div id="bulk-edit-wrap">
344
+ <fieldset>
345
+ <h4><?php _e( 'Bulk Edit URLs', 'broken-link-checker' ); ?></h4>
346
+ <label>
347
+ <span class="title"><?php _e( 'Find', 'broken-link-checker' ); ?></span>
348
+ <input type="text" name="search" class="text">
349
+ </label>
350
+ <label>
351
+ <span class="title"><?php _e( 'Replace with', 'broken-link-checker' ); ?></span>
352
+ <input type="text" name="replace" class="text">
353
+ </label>
354
+
355
+ <div id="bulk-edit-options">
356
+ <span class="title">&nbsp;</span>
357
+ <label>
358
+ <input type="checkbox" name="case_sensitive">
359
+ <?php _e( 'Case sensitive', 'broken-link-checker' ); ?>
360
+ </label>
361
+ <label>
362
+ <input type="checkbox" name="regex">
363
+ <?php _e( 'Regular expression', 'broken-link-checker' ); ?>
364
+ </label>
365
+ </div>
366
+ </fieldset>
367
+
368
+ <p class="submit inline-edit-save">
369
+ <a href="#bulk-edit" class="button-secondary cancel alignleft" title="<?php echo esc_attr( __( 'Cancel', 'broken-link-checker' ) ); ?>" accesskey="c"><?php _e( 'Cancel', 'broken-link-checker' ); ?></a>
370
+ <input type="submit" name="bulk_edit" class="button-primary alignright" value="<?php
371
+ _e( 'Update', 'broken-link-checker' );
372
+ ?>" accesskey="s">
373
+ </p>
374
+ </div>
375
+ </td></tr>
376
+ <?php
377
+ }
378
+
379
+ /**
380
+ * Print the link row.
381
+ *
382
+ * @param blcLink $link The link to display.
383
+ * @param array $layout List of columns to output.
384
+ * @param array $visible_columns List of visible columns.
385
+ * @param integer $rownum Table row number.
386
+ * @return void
387
+ */
388
+ function link_row( $link, $layout, $visible_columns, $rownum = 0 ) {
389
+
390
+ //Figure out what CSS classes the link row should have
391
+ $rowclass = ($rownum % 2)? 'alternate' : '';
392
+
393
+ $excluded = $this->core->is_excluded( $link->url );
394
+ if ( $excluded ) {
395
+ $rowclass .= ' blc-excluded-link';
396
+ }
397
+
398
+ if ( $link->redirect_count > 0 ) {
399
+ $rowclass .= ' blc-redirect';
400
+ }
401
+
402
+ $days_broken = 0;
403
+ if ( $link->broken ) {
404
+ // Add a highlight to broken links that appear to be permanently broken.
405
+ $days_broken = intval( ( time() - $link->first_failure ) / ( 3600 * 24 ) );
406
+ if ( $days_broken >= $this->core->conf->options['failure_duration_threshold'] ) {
407
+ $rowclass .= ' blc-permanently-broken';
408
+ if ( $this->core->conf->options['highlight_permanent_failures'] ) {
409
+ $rowclass .= ' blc-permanently-broken-hl';
410
+ }
411
+ }
412
+ }
413
+
414
+ $status = $link->analyse_status();
415
+ $rowclass .= ' link-status-' . $status['code'];
416
+
417
+ // Retrieve link instances to display in the table.
418
+ $instances = $link->get_instances();
419
+
420
+ if ( ! empty( $instances ) ) {
421
+ // Put instances that match the selected link type at the top. Makes search results look better.
422
+ if ( ! empty( $this->current_filter['search_params']['s_link_type'] ) ) {
423
+ $s_link_type = $this->current_filter['search_params']['s_link_type'];
424
+ } else {
425
+ $s_link_type = '';
426
+ }
427
+ $instances = $this->sort_instances_for_display( $instances, $s_link_type );
428
+ }
429
+
430
+ // For inline editing, we'll need to know if any instances have editable link text, and what it is.
431
+ $can_edit_text = false;
432
+ $can_edit_url = false;
433
+ $editable_link_texts = $non_editable_link_texts = array();
434
+ foreach ( $instances as $instance ) {
435
+ if ( $instance->is_link_text_editable() ) {
436
+ $can_edit_text = true;
437
+ $editable_link_texts[ $instance->link_text ] = true;
438
+ } else {
439
+ $non_editable_link_texts[ $instance->link_text ] = true;
440
+ }
441
+
442
+ if ( $instance->is_url_editable() ) {
443
+ $can_edit_url = true;
444
+ }
445
+ }
446
+
447
+ $link_texts = $can_edit_text ? $editable_link_texts : $non_editable_link_texts;
448
+ $data_link_text = '';
449
+ if ( 1 === count( $link_texts ) ) {
450
+ // All instances have the same text - use it.
451
+ $link_text = key( $link_texts );
452
+ $data_link_text = ' data-link-text="' . esc_attr( $link_text ) . '"';
453
+ }
454
+
455
+ printf(
456
+ '<tr id="blc-row-%s" class="blc-row %s" data-days-broken="%d" data-can-edit-url="%d" data-can-edit-text="%d"%s>',
457
+ $link->link_id,
458
+ $rowclass,
459
+ $days_broken,
460
+ $can_edit_url ? 1 : 0,
461
+ $can_edit_text ? 1 : 0,
462
+ $data_link_text
463
+ );
464
+
465
+ // The checkbox used to select links is automatically printed in all layouts
466
+ // and can't be disabled. Without it, bulk actions wouldn't work.
467
+ $this->column_checkbox( $link );
468
+
469
+ foreach ( $layout as $column_id ) {
470
+ $column = $this->columns[ $column_id ];
471
+
472
+ printf(
473
+ '<td class="column-%s%s">',
474
+ $column_id,
475
+ in_array( $column_id, $visible_columns ) ? '' : ' hidden'
476
+ );
477
+
478
+ if ( isset( $column['content'] ) ) {
479
+ if ( is_callable( $column['content'] ) ) {
480
+ call_user_func( $column['content'], $link, $instances );
481
+ } else {
482
+ echo $column['content'];
483
+ }
484
+ } else {
485
+ echo '[', $column_id, ']';
486
+ }
487
+
488
+ echo '</td>';
489
+ }
490
+
491
+ echo '</tr>';
492
+ }
493
+
494
+ /**
495
+ * Print the details row for a specific link.
496
+ *
497
+ * @uses blcTablePrinter::details_row_contents()
498
+ *
499
+ * @param blcLink $link The link to display.
500
+ * @param array $visible_columns List of visible columns.
501
+ * @param integer $rownum Table row number.
502
+ * @return void
503
+ */
504
+ function link_details_row( $link, $visible_columns, $rownum = 0 ) {
505
+ printf(
506
+ '<tr id="link-details-%d" class="blc-link-details"><td colspan="%d">',
507
+ $link->link_id,
508
+ count( $visible_columns ) + 1
509
+ );
510
+ $this->details_row_contents( $link );
511
+ echo '</td></tr>';
512
+ }
513
+
514
+ /**
515
+ * Print the contents of the details row for a specific link.
516
+ *
517
+ * @param blcLink $link
518
+ * @return void
519
+ */
520
+ public static function details_row_contents( $link ) {
521
+ ?>
522
+ <div class="blc-detail-container">
523
+ <div class="blc-detail-block" style="float: left; width: 49%;">
524
+ <ol style='list-style-type: none;'>
525
+ <?php if ( ! empty( $link->post_date ) ) { ?>
526
+ <li><strong><?php _e( 'Post published on', 'broken-link-checker' ); ?>:</strong>
527
+ <span class='post_date'><?php
528
+ echo date_i18n( get_option( 'date_format' ), strtotime( $link->post_date ) );
529
+ ?></span></li>
530
+ <?php } ?>
531
+ <li><strong><?php _e( 'Link last checked', 'broken-link-checker' ); ?>:</strong>
532
+ <span class='check_date'><?php
533
+ $last_check = $link->last_check;
534
+ if ( $last_check < strtotime( '-10 years' ) ) {
535
+ _e( 'Never', 'broken-link-checker' );
536
+ } else {
537
+ printf(
538
+ '<time datetime="%s">%s</time>',
539
+ esc_attr( date( 'c', $last_check ) ),
540
+ date_i18n( get_option('date_format' ), $last_check )
541
+ );
542
+ }
543
+ ?></span></li>
544
+
545
+ <li><strong><?php _e( 'HTTP code', 'broken-link-checker' ); ?>:</strong>
546
+ <span class='http_code'><?php
547
+ print $link->http_code;
548
+ ?></span></li>
549
+
550
+ <li><strong><?php _e( 'Response time', 'broken-link-checker' ); ?>:</strong>
551
+ <span class='request_duration'><?php
552
+ printf( __( '%2.3f seconds', 'broken-link-checker' ), $link->request_duration );
553
+ ?></span></li>
554
+
555
+ <li><strong><?php _e( 'Final URL', 'broken-link-checker' ); ?>:</strong>
556
+ <span class='final_url'><?php
557
+ print esc_html( $link->final_url );
558
+ ?></span></li>
559
+
560
+ <li><strong><?php _e( 'Redirect count', 'broken-link-checker' ); ?>:</strong>
561
+ <span class='redirect_count'><?php
562
+ print $link->redirect_count;
563
+ ?></span></li>
564
+
565
+ <li><strong><?php _e( 'Instance count', 'broken-link-checker' ); ?>:</strong>
566
+ <span class='instance_count'><?php
567
+ print count( $link->get_instances() );
568
+ ?></span></li>
569
+
570
+ <?php if ( ( $link->broken || $link->warning ) && (intval( $link->check_count ) > 0) ) { ?>
571
+ <li><br/>
572
+ <?php
573
+ printf(
574
+ _n('This link has failed %d time.', 'This link has failed %d times.', $link->check_count, 'broken-link-checker'),
575
+ $link->check_count
576
+ );
577
+
578
+ echo '<br>';
579
+
580
+ $delta = time() - $link->first_failure;
581
+ printf(
582
+ __('This link has been broken for %s.', 'broken-link-checker'),
583
+ blcUtility::fuzzy_delta($delta)
584
+ );
585
+ ?>
586
+ </li>
587
+ <?php } ?>
588
+ </ol>
589
+ </div>
590
+
591
+ <div class="blc-detail-block" style="float: right; width: 50%;">
592
+ <ol style='list-style-type: none;'>
593
+ <li><strong><?php _e('Log', 'broken-link-checker'); ?>:</strong>
594
+ <span class='blc_log'><?php
595
+ print nl2br( $link->log );
596
+ ?></span></li>
597
+ </ol>
598
+ </div>
599
+
600
+ <div style="clear:both;"> </div>
601
+ </div>
602
+ <?php
603
+ }
604
+
605
+ function column_checkbox( $link ) {
606
+ ?>
607
+ <th scope="row" class="check-column"><input type="checkbox" name="selected_links[]" value="<?php echo $link->link_id; ?>" /></th>
608
+ <?php
609
+ }
610
+
611
+ /**
612
+ * @param blcLink $link
613
+ * @param blcLinkInstance[] $instances
614
+ */
615
+ function column_status( $link, $instances ) {
616
+ printf(
617
+ '<table class="mini-status" title="%s">',
618
+ esc_attr( __( 'Show more info about this link', 'broken-link-checker' ) )
619
+ );
620
+
621
+ $status = $link->analyse_status();
622
+
623
+ printf(
624
+ '<tr class="link-status-row link-status-%s">
625
+ <td>
626
+ <span class="http-code">%s</span> <span class="status-text">%s</span>
627
+ </td>
628
+ </tr>',
629
+ $status['code'],
630
+ empty( $link->http_code ) ? '' : $link->http_code,
631
+ $status['text']
632
+ );
633
+
634
+ // Last checked...
635
+ if ( $link->last_check != 0 ) {
636
+ $last_check = _x( 'Checked', 'checked how long ago', 'broken-link-checker' ) . ' ';
637
+ $last_check .= blcUtility::fuzzy_delta( time() - $link->last_check, 'ago' );
638
+
639
+ printf(
640
+ '<tr class="link-last-checked"><td>%s</td></tr>',
641
+ $last_check
642
+ );
643
+ }
644
+
645
+
646
+ // Broken for...
647
+ if ( $link->broken ) {
648
+ $delta = time() - $link->first_failure;
649
+ $broken_for = blcUtility::fuzzy_delta( $delta );
650
+ printf(
651
+ '<tr class="link-broken-for"><td>%s %s</td></tr>',
652
+ __( 'Broken for', 'broken-link-checker' ),
653
+ $broken_for
654
+ );
655
+ }
656
+
657
+ echo '</table>';
658
+
659
+ // "Details" link.
660
+ echo '<div class="row-actions">';
661
+ printf(
662
+ '<span><a href="#" class="blc-details-button" title="%s">%s</a></span>',
663
+ esc_attr( __( 'Show more info about this link', 'broken-link-checker' ) ),
664
+ _x( 'Details', 'link in the "Status" column', 'broken-link-checker' )
665
+ );
666
+ echo '</div>';
667
+ }
668
+
669
+
670
+ /**
671
+ * @param blcLink $link
672
+ */
673
+ function column_new_url( $link ) {
674
+ ?>
675
+ <a href="<?php print esc_url( $link->url ); ?>"
676
+ target='_blank'
677
+ class='blc-link-url'
678
+ title="<?php echo esc_attr( $link->url ); ?>"
679
+ data-editable-url="<?php echo esc_attr( $link->url ); ?>">
680
+ <?php print esc_html( $link->url ); ?></a>
681
+ <?php
682
+ // Output inline action links for the link/URL.
683
+ $actions = array();
684
+
685
+ $actions['edit'] = "<a href='javascript:void(0)' class='blc-edit-button' title='" . esc_attr( __('Edit this link' , 'broken-link-checker') ) . "'>". __( 'Edit URL' , 'broken-link-checker' ) . '</a>';
686
+
687
+ $actions['delete'] = "<a class='submitdelete blc-unlink-button' title='" . esc_attr( __( 'Remove this link from all posts', 'broken-link-checker') ). "' ".
688
+ "href='javascript:void(0);'>" . __( 'Unlink', 'broken-link-checker' ) . '</a>';
689
+
690
+ if ( $link->broken || $link->warning ) {
691
+ $actions['blc-discard-action'] = sprintf(
692
+ '<a href="#" title="%s" class="blc-discard-button">%s</a>',
693
+ esc_attr( __( 'Remove this link from the list of broken links and mark it as valid', 'broken-link-checker' ) ),
694
+ __( 'Not broken', 'broken-link-checker' )
695
+ );
696
+ }
697
+
698
+ if ( ! $link->dismissed && ( $link->broken || $link->warning || ( $link->redirect_count > 0 ) ) ) {
699
+ $actions['blc-dismiss-action'] = sprintf(
700
+ '<a href="#" title="%s" class="blc-dismiss-button">%s</a>',
701
+ esc_attr( __( 'Hide this link and do not report it again unless its status changes' , 'broken-link-checker' ) ),
702
+ __( 'Dismiss', 'broken-link-checker' )
703
+ );
704
+ } else if ( $link->dismissed ) {
705
+ $actions['blc-undismiss-action'] = sprintf(
706
+ '<a href="#" title="%s" class="blc-undismiss-button">%s</a>',
707
+ esc_attr( __( 'Undismiss this link', 'broken-link-checker' ) ),
708
+ __( 'Undismiss', 'broken-link-checker' )
709
+ );
710
+ }
711
+
712
+ $actions['blc-recheck-action'] = sprintf(
713
+ '<a href="#" class="blc-recheck-button">%s</a>',
714
+ __( 'Recheck', 'broken-link-checker' )
715
+ );
716
+
717
+ if ( $link->redirect_count > 0 && ! empty( $link->final_url ) && ( $link->url != $link->final_url ) ) {
718
+ //TODO: Check if at least one instance has an editable URL. Otherwise this won't work.
719
+ $actions['blc-deredirect-action'] = sprintf(
720
+ '<a href="#" class="blc-deredirect-button" title="%s">%s</a>',
721
+ __( 'Replace this redirect with a direct link', 'broken-link-checker' ),
722
+ _x( 'Fix redirect', 'link action; replace one redirect with a direct link', 'broken-link-checker' )
723
+ );
724
+ }
725
+
726
+ // Only show the enabled actions.
727
+ $conf = blc_get_configuration();
728
+ foreach ( $conf->get( 'show_link_actions', $actions ) as $name => $enabled) {
729
+ if ( ! $enabled ) {
730
+ unset( $actions[ $name ] );
731
+ }
732
+ }
733
+
734
+ // Wrap actions with <span></span> and separate them with | characters.
735
+ // Basically, this emulates the HTML structure that WP uses for post actions under Posts -> All Posts.
736
+ $spans = array();
737
+ $is_first_action = true;
738
+ foreach ( $actions as $name => $html ) {
739
+ $spans[] = sprintf(
740
+ '<span class="%s">%s%s</span>',
741
+ esc_attr($name),
742
+ $is_first_action ? '' : ' | ',
743
+ $html
744
+ );
745
+ $is_first_action = false;
746
+ }
747
+
748
+ echo '<div class="row-actions">';
749
+ echo implode( '', $spans );
750
+ echo '</div>';
751
+
752
+ ?>
753
+ <div class="blc-url-editor-buttons">
754
+ <input type="button" class="button-secondary cancel alignleft blc-cancel-button" value="<?php echo esc_attr( __( 'Cancel', 'broken-link-checker' ) ); ?>" />
755
+ <input type="button" class="button-primary save alignright blc-update-url-button" value="<?php echo esc_attr( __( 'Update URL', 'broken-link-checker' ) ); ?>" />
756
+ <img class="waiting" style="display:none;" src="<?php echo esc_url( admin_url( 'images/wpspin_light.gif' ) ); ?>" alt="" />
757
+ </div>
758
+ <?php
759
+ }
760
+
761
+ /**
762
+ * @param blcLink $link
763
+ * @param blcLinkInstance[] $instances
764
+ */
765
+ function column_used_in( $link, $instances ) {
766
+ echo '<span class="blc-link-id" style="display:none;">',
767
+ $link->link_id,
768
+ '</span>';
769
+
770
+ if ( ! empty( $instances ) ) {
771
+ /** @var $instance blcLinkInstance */
772
+ $instance = reset( $instances );
773
+ echo $instance->ui_get_source();
774
+
775
+ $actions = $instance->ui_get_action_links();
776
+
777
+ echo '<div class="row-actions">';
778
+ echo implode( ' | </span>', $actions );
779
+ echo '</div>';
780
+
781
+ } else {
782
+ _e( '[An orphaned link! This is a bug.]', 'broken-link-checker' );
783
+ }
784
+ }
785
+
786
+ /**
787
+ * @param blcLink $link
788
+ * @param blcLinkInstance[] $instances
789
+ */
790
+ function column_new_link_text( $link, $instances ) {
791
+ if ( empty( $instances ) ) {
792
+ echo '<em>N/A</em>';
793
+ } else {
794
+ $instance = reset( $instances ); /** @var blcLinkInstance $instance */
795
+ echo $instance->ui_get_link_text();
796
+ }
797
+ }
798
+
799
+ function column_redirect_url( $link, $instances ) {
800
+ if ( $link->redirect_count > 0 ) {
801
+ printf(
802
+ '<a href="%1$s" target="_blank" class="blc-redirect-url" title="%1$s">%2$s</a>',
803
+ esc_attr( $link->final_url ),
804
+ esc_html( $link->final_url )
805
+ );
806
+ }
807
+ }
808
+
809
+ /**
810
+ * Sort a list of link instances to be displayed in the "Broken Links" page.
811
+ *
812
+ * Groups instances by container type and, if $search_link_type is specified,
813
+ * puts instances that have a matching container type or parser type at the
814
+ * beginning.
815
+ *
816
+ * @param array $instances An array of blcLinkInstance objects.
817
+ * @param string $searched_link_type Optional. The required container/parser type.
818
+ * @return array Sorted array.
819
+ */
820
+ function sort_instances_for_display( $instances, $searched_link_type = '' ) {
821
+ $this->searched_link_type = $searched_link_type;
822
+ usort( $instances, array( $this, 'compare_link_instances' ) );
823
+ return $instances;
824
+ }
825
+
826
+ /**
827
+ * Callback function for sorting link instances.
828
+ *
829
+ * @see blcTablePrinter::sort_instances_for_display()
830
+ *
831
+ * @param blcLinkInstance $a
832
+ * @param blcLinkInstance $b
833
+ * @return int
834
+ */
835
+ function compare_link_instances( $a, $b ) {
836
+ if ( ! empty( $this->searched_link_type ) ) {
837
+ if ( ( $a->container_type == $this->searched_link_type ) || ( $a->parser_type == $this->searched_link_type ) ) {
838
+ if ( ( $b->container_type == $this->searched_link_type ) || ( $b->parser_type == $this->searched_link_type ) ) {
839
+ return 0;
840
+ } else {
841
+ return -1;
842
+ }
843
+ } else {
844
+ if ( ( $b->container_type == $this->searched_link_type ) || ( $b->parser_type == $this->searched_link_type ) ) {
845
+ return 1;
846
+ }
847
+ }
848
+ }
849
+
850
+ return strcmp( $a->container_type, $b->container_type );
851
+ }
852
+
853
+ protected function inline_editor( $visible_columns ) {
854
+ ?>
855
+ <table style="display: none;"><tbody>
856
+ <tr id="blc-inline-edit-row" class="blc-inline-editor">
857
+ <td class="blc-colspan-change" colspan="<?php echo count( $visible_columns ); ?>">
858
+ <div class="blc-inline-editor-content">
859
+ <h4><?php echo _x( 'Edit Link', 'inline editor title', 'broken-link-checker' ); ?></h4>
860
+
861
+ <label>
862
+ <span class="title"><?php echo _x( 'Text', 'inline link editor', 'broken-link-checker' ); ?></span>
863
+ <span class="blc-input-text-wrap"><input type="text" name="link_text" value="" class="blc-link-text-field" /></span>
864
+ </label>
865
+
866
+ <label>
867
+ <span class="title"><?php echo _x( 'URL', 'inline link editor', 'broken-link-checker' ); ?></span>
868
+ <span class="blc-input-text-wrap"><input type="text" name="link_url" value="" class="blc-link-url-field" /></span>
869
+ </label>
870
+
871
+ <div class="blc-url-replacement-suggestions" style="display: none;">
872
+ <h4><?php echo _x( 'Suggestions', 'inline link editor', 'broken-link-checker' ); ?></h4>
873
+ <ul class="blc-suggestion-list">
874
+ <li>...</li>
875
+ </ul>
876
+ </div>
877
+
878
+ <div class="submit blc-inline-editor-buttons">
879
+ <input type="button" class="button-secondary cancel alignleft blc-cancel-button" value="<?php echo esc_attr( __( 'Cancel', 'broken-link-checker' ) ); ?>" />
880
+ <input type="button" class="button-primary save alignright blc-update-link-button" value="<?php echo esc_attr( __( 'Update', 'broken-link-checker' ) ); ?>" />
881
+
882
+ <img class="waiting" style="display:none;" src="<?php echo esc_url( admin_url( 'images/wpspin_light.gif' ) ); ?>" alt="" />
883
+ <div class="clear"></div>
884
+ </div>
885
+ </div>
886
+ </td>
887
+ </tr>
888
+ </tbody></table>
889
+
890
+ <ul id="blc-suggestion-template" style="display: none;">
891
+ <li>
892
+ <input type="button" class="button-secondary blc-use-url-button" value="<?php echo esc_attr( __( 'Use this URL', 'broken-link-checker' ) ); ?>" />
893
+
894
+ <div class="blc-suggestion-details">
895
+ <span class="blc-suggestion-name">
896
+ <a href="http://example.com/" target="_blank">Suggestion name</a>
897
+ </span>
898
+ <code class="blc-suggestion-url">suggestion URL</code>
899
+ </div>
900
+ </li>
901
+ </ul>
902
+ <?php
903
+ }
904
+
905
+ }
906
+
907
+ } // class_exists.
includes/modules.php CHANGED
@@ -20,6 +20,8 @@ $blc_module_manager = blcModuleManager::getInstance(array(
20
  'url_field', //URL field parser
21
  'comment', //Comment container
22
  'custom_field', //Post metadata container (aka custom fields)
 
 
23
  'post', //Post content container
24
  'page', //Page content container
25
  'youtube-checker', //Video checker using the YouTube API
20
  'url_field', //URL field parser
21
  'comment', //Comment container
22
  'custom_field', //Post metadata container (aka custom fields)
23
+ 'acf_field', //Post acf container (aka advanced custom fields)
24
+ 'acf', //acf parser
25
  'post', //Post content container
26
  'page', //Page content container
27
  'youtube-checker', //Video checker using the YouTube API
includes/utility-class.php CHANGED
@@ -4,414 +4,414 @@
4
  * @author W-Shadow
5
  * @copyright 2010
6
  */
7
-
8
- if ( !function_exists('sys_get_temp_dir')) {
9
- function sys_get_temp_dir() {
10
- if (!empty($_ENV['TMP'])) { return realpath($_ENV['TMP']); }
11
- if (!empty($_ENV['TMPDIR'])) { return realpath( $_ENV['TMPDIR']); }
12
- if (!empty($_ENV['TEMP'])) { return realpath( $_ENV['TEMP']); }
13
- $tempfile = tempnam(uniqid(rand(),TRUE),'');
14
- if (@file_exists($tempfile)) {
15
- unlink($tempfile);
16
- return realpath(dirname($tempfile));
17
- }
18
- return '';
19
- }
20
  }
21
 
22
  //Include the internationalized domain name converter (requires PHP 5)
23
- if ( version_compare(phpversion(), '5.0.0', '>=') && !class_exists('idna_convert') ){
24
  include BLC_DIRECTORY . '/idn/idna_convert.class.php';
25
- if ( !function_exists('encode_utf8') ){
26
  include BLC_DIRECTORY . '/idn/transcode_wrapper.php';
27
  }
28
  }
29
 
30
 
31
- if ( !class_exists('blcUtility') ){
32
-
33
- class blcUtility {
34
-
35
- /**
36
- * blcUtility::is_safe_mode()
37
- * Checks if PHP is running in safe mode
38
- *
39
- * @return bool
40
- */
41
- static function is_safe_mode(){
42
- $safe_mode = ini_get('safe_mode');
43
- //Null, 0, '', '0' and so on count as false
44
- if ( !$safe_mode ) return false;
45
- //Test for some textual true/false variations
46
- switch ( strtolower($safe_mode) ){
47
- case 'on':
48
- case 'true':
49
- case 'yes':
50
- return true;
51
-
52
- case 'off':
53
- case 'false':
54
- case 'no':
55
  return false;
56
-
57
- default: //Let PHP handle anything else
58
- return (bool)(int)$safe_mode;
59
- }
60
- }
61
-
62
- /**
63
- * blcUtility::is_open_basedir()
64
- * Checks if open_basedir is enabled
65
- *
66
- * @return bool
67
- */
68
- static function is_open_basedir(){
69
- $open_basedir = ini_get('open_basedir');
70
- return $open_basedir && ( strtolower($open_basedir) != 'none' );
71
- }
72
-
73
- /**
74
- * Truncate a string on a specified boundary character.
75
- *
76
- * @param string $text The text to truncate.
77
- * @param integer $max_characters Return no more than $max_characters
78
- * @param string $break Break on this character. Defaults to space.
79
- * @param string $pad Pad the truncated string with this string. Defaults to an HTML ellipsis.
80
- * @return string
81
- */
82
- static function truncate($text, $max_characters = 0, $break = ' ', $pad = '&hellip;'){
83
- if ( strlen($text) <= $max_characters ){
84
- return $text;
85
- }
86
-
87
- $text = substr($text, 0, $max_characters);
88
- $break_pos = strrpos($text, $break);
89
- if ( $break_pos !== false ){
90
- $text = substr($text, 0, $break_pos);
91
  }
92
-
93
- return $text.$pad;
94
- }
95
-
96
  /**
97
- * extract_tags()
98
- * Extract specific HTML tags and their attributes from a string.
99
- *
100
- * You can either specify one tag, an array of tag names, or a regular expression that matches the tag name(s).
101
- * If multiple tags are specified you must also set the $selfclosing parameter and it must be the same for
102
- * all specified tags (so you can't extract both normal and self-closing tags in one go).
103
- *
104
- * The function returns a numerically indexed array of extracted tags. Each entry is an associative array
105
- * with these keys :
106
- * tag_name - the name of the extracted tag, e.g. "a" or "img".
107
- * offset - the numberic offset of the first character of the tag within the HTML source.
108
- * contents - the inner HTML of the tag. This is always empty for self-closing tags.
109
- * attributes - a name -> value array of the tag's attributes, or an empty array if the tag has none.
110
- * full_tag - the entire matched tag, e.g. '<a href="http://example.com">example.com</a>'. This key
111
- * will only be present if you set $return_the_entire_tag to true.
112
- *
113
- * @param string $html The HTML code to search for tags.
114
- * @param string|array $tag The tag(s) to extract.
115
- * @param bool $selfclosing Whether the tag is self-closing or not. Setting it to null will force the script to try and make an educated guess.
116
- * @param bool $return_the_entire_tag Return the entire matched tag in 'full_tag' key of the results array.
117
- * @param string $charset The character set of the HTML code. Defaults to ISO-8859-1.
118
- *
119
- * @return array An array of extracted tags, or an empty array if no matching tags were found.
120
- */
121
- static function extract_tags( $html, $tag, $selfclosing = null, $return_the_entire_tag = false, $charset = 'ISO-8859-1' ){
122
-
123
- if ( is_array($tag) ){
124
- $tag = implode('|', $tag);
125
  }
126
-
127
- //If the user didn't specify if $tag is a self-closing tag we try to auto-detect it
128
- //by checking against a list of known self-closing tags.
129
- $selfclosing_tags = array( 'area', 'base', 'basefont', 'br', 'hr', 'input', 'img', 'link', 'meta', 'col', 'param' );
130
- if ( is_null($selfclosing) ){
131
- $selfclosing = in_array( $tag, $selfclosing_tags );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  }
133
-
134
- //The regexp is different for normal and self-closing tags because I can't figure out
135
- //how to make a sufficiently robust unified one.
136
- if ( $selfclosing ){
137
- $tag_pattern =
138
- '@<(?P<tag>'.$tag.') # <tag
139
- (?P<attributes>\s[^>]+)? # attributes, if any
140
- \s*/?> # /> or just >, being lenient here
141
- @xsi';
142
- } else {
143
- $tag_pattern =
144
- '@<(?P<tag>'.$tag.') # <tag
145
- (?P<attributes>\s[^>]+)? # attributes, if any
146
- \s*> # >
147
- (?P<contents>.*?) # tag contents
148
- </(?P=tag)> # the closing </tag>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  @xsi';
150
- }
151
-
152
- $attribute_pattern =
153
- '@
154
- (?P<name>\w+) # attribute name
155
- \s*=\s*
156
- (
157
- (?P<quote>[\"\'])(?P<value_quoted>.*?)(?P=quote) # a quoted value
158
- | # or
159
- (?P<value_unquoted>[^\s"\']+?)(?:\s+|$) # an unquoted value (terminated by whitespace or EOF)
160
- )
161
- @xsi';
162
-
163
- //Find all tags
164
- if ( !preg_match_all($tag_pattern, $html, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE ) ){
165
- //Return an empty array if we didn't find anything
166
- return array();
167
- }
168
-
169
- $tags = array();
170
- foreach ($matches as $match){
171
-
172
- //Parse tag attributes, if any
173
- $attributes = array();
174
- if ( !empty($match['attributes'][0]) ){
175
-
176
- if ( preg_match_all( $attribute_pattern, $match['attributes'][0], $attribute_data, PREG_SET_ORDER ) ){
177
- //Turn the attribute data into a name->value array
178
- foreach($attribute_data as $attr){
179
- if( !empty($attr['value_quoted']) ){
180
- $value = $attr['value_quoted'];
181
- } else if( !empty($attr['value_unquoted']) ){
182
- $value = $attr['value_unquoted'];
183
- } else {
184
- $value = '';
185
  }
186
-
187
- //Passing the value through html_entity_decode is handy when you want
188
- //to extract link URLs or something like that. You might want to remove
189
- //or modify this call if it doesn't fit your situation.
190
- $value = html_entity_decode( $value, ENT_QUOTES, $charset );
191
-
192
- $attributes[$attr['name']] = $value;
193
  }
 
194
  }
195
-
 
 
 
 
 
 
 
 
 
 
 
196
  }
197
-
198
- $tag = array(
199
- 'tag_name' => $match['tag'][0],
200
- 'offset' => $match[0][1],
201
- 'contents' => !empty($match['contents'])?$match['contents'][0]:'', //empty for self-closing tags
202
- 'attributes' => $attributes,
203
- );
204
- if ( $return_the_entire_tag ){
205
- $tag['full_tag'] = $match[0][0];
 
 
 
 
 
 
 
206
  }
207
-
208
- $tags[] = $tag;
209
  }
210
-
211
- return $tags;
212
- }
213
-
214
  /**
215
- * Get the value of a cookie.
216
- *
217
- * @param string $cookie_name The name of the cookie to return.
218
- * @param string $default_value Optional. If the cookie is not set, this value will be returned instead. Defaults to an empty string.
219
- * @return mixed Either the value of the requested cookie, or $default_value.
220
- */
221
- static function get_cookie($cookie_name, $default_value = ''){
222
- if ( isset($_COOKIE[$cookie_name]) ){
223
- return $_COOKIE[$cookie_name];
224
- } else {
225
- return $default_value;
226
- }
227
- }
228
-
229
- /**
230
- * Format a time delta using a fuzzy format, e.g. '2 minutes ago', '2 days', etc.
231
- *
232
- * @param int $delta Time period in seconds.
233
- * @param string $type Optional. The output template to use.
234
- * @return string
235
- */
236
- static function fuzzy_delta($delta, $template = 'default'){
237
- $ONE_MINUTE = 60;
238
- $ONE_HOUR = 60 * $ONE_MINUTE;
239
- $ONE_DAY = 24 * $ONE_HOUR;
240
- $ONE_MONTH = $ONE_DAY * 3652425 / 120000;
241
- $ONE_YEAR = $ONE_DAY * 3652425 / 10000;
242
-
243
- $templates = array(
244
- 'seconds' => array(
245
- 'default' => _n_noop('%d second', '%d seconds'),
246
- 'ago' => _n_noop('%d second ago', '%d seconds ago'),
247
- ),
248
- 'minutes' => array(
249
- 'default' => _n_noop('%d minute', '%d minutes'),
250
- 'ago' => _n_noop('%d minute ago', '%d minutes ago'),
251
- ),
252
- 'hours' => array(
253
- 'default' => _n_noop('%d hour', '%d hours'),
254
- 'ago' => _n_noop('%d hour ago', '%d hours ago'),
255
- ),
256
- 'days' => array(
257
- 'default' => _n_noop('%d day', '%d days'),
258
- 'ago' => _n_noop('%d day ago', '%d days ago'),
259
- ),
260
- 'months' => array(
261
- 'default' => _n_noop('%d month', '%d months'),
262
- 'ago' => _n_noop('%d month ago', '%d months ago'),
263
- ),
264
- );
265
-
266
- if ( $delta < 1 ) {
267
- $delta = 1;
 
 
 
 
 
 
 
268
  }
269
-
270
- if ( $delta < $ONE_MINUTE ){
271
- $units = 'seconds';
272
- } elseif ( $delta < $ONE_HOUR ){
273
- $delta = intval($delta / $ONE_MINUTE);
274
- $units = 'minutes';
275
- } elseif ( $delta < $ONE_DAY ){
276
- $delta = intval($delta / $ONE_HOUR);
277
- $units = 'hours';
278
- } elseif ( $delta < $ONE_MONTH ){
279
- $delta = intval($delta / $ONE_DAY);
280
- $units = 'days';
281
- } else {
282
- $delta = intval( $delta / $ONE_MONTH );
283
- $units = 'months';
284
  }
285
-
286
- return sprintf(
287
- _n(
288
- $templates[$units][$template][0],
289
- $templates[$units][$template][1],
290
- $delta,
291
- 'broken-link-checker'
292
- ),
293
- $delta
294
- );
295
- }
296
-
297
- /**
298
- * Optimize the plugin's tables
299
- *
300
- * @return void
301
- */
302
- static function optimize_database(){
303
- global $wpdb; /** @var wpdb $wpdb */
304
-
305
- $wpdb->query("OPTIMIZE TABLE {$wpdb->prefix}blc_links, {$wpdb->prefix}blc_instances, {$wpdb->prefix}blc_synch");
306
- }
307
-
308
- /**
309
- * Get the server's load averages.
310
- *
311
- * Returns an array with three samples - the 1 minute avg, the 5 minute avg, and the 15 minute avg.
312
- *
313
- * @param integer $cache How long the load averages may be cached, in seconds. Set to 0 to get maximally up-to-date data.
314
- * @return array|null Array, or NULL if retrieving load data is impossible (e.g. when running on a Windows box).
315
- */
316
- static function get_server_load($cache = 5){
317
- static $cached_load = null;
318
- static $cached_when = 0;
319
-
320
- if ( !empty($cache) && ((time() - $cached_when) <= $cache) ){
321
- return $cached_load;
322
  }
323
-
324
- $load = null;
325
-
326
- if ( function_exists('sys_getloadavg') ){
327
- $load = sys_getloadavg();
328
- } else {
329
- $loadavg_file = '/proc/loadavg';
330
- if (@is_readable($loadavg_file)) {
331
- $load = explode(' ',file_get_contents($loadavg_file));
332
- $load = array_map('floatval', $load);
333
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
  }
335
-
336
- $cached_load = $load;
337
- $cached_when = time();
338
- return $load;
339
- }
340
-
341
- /**
342
- * Convert an internationalized domain name or URL to ASCII-compatible encoding.
343
- *
344
- * @param string $url Either a domain name or a complete URL.
345
- * @param string $charset The character encoding of the $url parameter. Defaults to the encoding set in Settings -> Reading.
346
- * @return string
347
- */
348
- static function idn_to_ascii($url, $charset = ''){
349
- $idn = blcUtility::get_idna_converter();
350
- if ( $idn != null ){
351
- if ( empty($charset) ){
352
- $charset = get_bloginfo('charset');
353
  }
354
-
355
- //Encode only the host
356
- if ( preg_match('@(\w+:/*)?([^/:]+)(.*$)?@s', $url, $matches) ){
357
- $host = $matches[2];
358
- if ( (strtoupper($charset) != 'UTF-8') && (strtoupper($charset) != 'UTF8') ){
359
- $host = encode_utf8($host, $charset, true);
360
- }
361
- $host = $idn->encode($host);
362
- $url = $matches[1] . $host . $matches[3];
363
- }
364
  }
365
-
366
- return $url;
367
- }
368
 
369
- /**
370
- * Convert an internationalized domain name (or URL) from ASCII-compatible encoding to UTF8.
371
- *
372
- * @param string $url
373
- * @return string
374
- */
375
- static function idn_to_utf8($url){
376
- $idn = blcUtility::get_idna_converter();
377
- if ( $idn != null ){
378
- $url = $idn->decode($url);
 
379
  }
380
-
381
- return $url;
382
- }
383
-
384
- /**
385
- * Get an instance of idna_converter
386
- *
387
- * @return idna_convert|null Either an instance of IDNA converter, or NULL if the converter class is not available
388
- */
389
- static function get_idna_converter(){
390
- static $idn = null;
391
- if ( ($idn == null) && class_exists('idna_convert') ){
392
- $idn = new idna_convert();
 
 
 
393
  }
394
- return $idn;
395
- }
396
 
397
- /**
398
- * Generate a numeric hash from a string. The result will be constrained to the specified interval.
399
- *
400
- * @static
401
- * @param string $input
402
- * @param int $min
403
- * @param int $max
404
- * @return float
405
- */
406
- public static function constrained_hash($input, $min = 0, $max = 1) {
407
- $bytes_to_use = 3;
408
- $md5_char_count = 32;
409
- $hash = substr(md5($input), $md5_char_count - $bytes_to_use*2);
410
- $hash = intval(hexdec($hash));
411
- return $min + (($max - $min) * ($hash / (pow(2, $bytes_to_use * 8) - 1)));
412
- }
413
-
414
- }//class
415
 
416
  }//class_exists
417
-
4
  * @author W-Shadow
5
  * @copyright 2010
6
  */
7
+
8
+ if ( ! function_exists( 'sys_get_temp_dir' ) ) {
9
+ function sys_get_temp_dir() {
10
+ if ( ! empty( $_ENV['TMP'] ) ) { return realpath( $_ENV['TMP'] ); }
11
+ if ( ! empty( $_ENV['TMPDIR'] ) ) { return realpath( $_ENV['TMPDIR'] ); }
12
+ if ( ! empty( $_ENV['TEMP'] ) ) { return realpath( $_ENV['TEMP'] ); }
13
+ $tempfile = tempnam( uniqid( rand(),TRUE ),'' );
14
+ if ( @file_exists( $tempfile ) ) {
15
+ unlink( $tempfile );
16
+ return realpath( dirname( $tempfile ) );
17
+ }
18
+ return '';
19
+ }
20
  }
21
 
22
  //Include the internationalized domain name converter (requires PHP 5)
23
+ if ( version_compare( phpversion(), '5.0.0', '>=' ) && ! class_exists( 'idna_convert' ) ) {
24
  include BLC_DIRECTORY . '/idn/idna_convert.class.php';
25
+ if ( ! function_exists( 'encode_utf8' ) ) {
26
  include BLC_DIRECTORY . '/idn/transcode_wrapper.php';
27
  }
28
  }
29
 
30
 
31
+ if ( ! class_exists( 'blcUtility' ) ) {
32
+ class blcUtility {
33
+ /**
34
+ * Checks if PHP is running in safe mode
35
+ * blcUtility::is_safe_mode()
36
+ *
37
+ * @return bool
38
+ */
39
+ static function is_safe_mode() {
40
+ // Check php.ini safe_mode only if PHP version is lower than 5.3.0, else set to false.
41
+ if ( version_compare( phpversion(), '5.3.0', '<' ) ) {
42
+ $safe_mode = ini_get( 'safe_mode' );
43
+ } else {
44
+ $safe_mode = false;
45
+ }
46
+
47
+ // Null, 0, '', '0' and so on count as false.
48
+ if ( ! $safe_mode ) {
 
 
 
 
 
 
49
  return false;
50
+ }
51
+ // Test for some textual true/false variations.
52
+ switch ( strtolower( $safe_mode ) ) {
53
+ case 'on':
54
+ case 'true':
55
+ case 'yes':
56
+ return true;
57
+
58
+ case 'off':
59
+ case 'false':
60
+ case 'no':
61
+ return false;
62
+
63
+ default: // Let PHP handle anything else.
64
+ return (bool) (int) $safe_mode;
65
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  }
67
+
 
 
 
68
  /**
69
+ * blcUtility::is_open_basedir()
70
+ * Checks if open_basedir is enabled
71
+ *
72
+ * @return bool
73
+ */
74
+ static function is_open_basedir(){
75
+ $open_basedir = ini_get( 'open_basedir' );
76
+ return $open_basedir && ( strtolower( $open_basedir ) != 'none' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  }
78
+
79
+ /**
80
+ * Truncate a string on a specified boundary character.
81
+ *
82
+ * @param string $text The text to truncate.
83
+ * @param integer $max_characters Return no more than $max_characters
84
+ * @param string $break Break on this character. Defaults to space.
85
+ * @param string $pad Pad the truncated string with this string. Defaults to an HTML ellipsis.
86
+ * @return string
87
+ */
88
+ static function truncate( $text, $max_characters = 0, $break = ' ', $pad = '&hellip;' ) {
89
+ if ( strlen( $text ) <= $max_characters ) {
90
+ return $text;
91
+ }
92
+
93
+ $text = substr( $text, 0, $max_characters );
94
+ $break_pos = strrpos( $text, $break );
95
+ if ( false !== $break_pos ) {
96
+ $text = substr( $text, 0, $break_pos );
97
+ }
98
+
99
+ return $text.$pad;
100
  }
101
+
102
+ /**
103
+ * extract_tags()
104
+ * Extract specific HTML tags and their attributes from a string.
105
+ *
106
+ * You can either specify one tag, an array of tag names, or a regular expression that matches the tag name(s).
107
+ * If multiple tags are specified you must also set the $selfclosing parameter and it must be the same for
108
+ * all specified tags (so you can't extract both normal and self-closing tags in one go).
109
+ *
110
+ * The function returns a numerically indexed array of extracted tags. Each entry is an associative array
111
+ * with these keys :
112
+ * tag_name - the name of the extracted tag, e.g. "a" or "img".
113
+ * offset - the numberic offset of the first character of the tag within the HTML source.
114
+ * contents - the inner HTML of the tag. This is always empty for self-closing tags.
115
+ * attributes - a name -> value array of the tag's attributes, or an empty array if the tag has none.
116
+ * full_tag - the entire matched tag, e.g. '<a href="http://example.com">example.com</a>'. This key
117
+ * will only be present if you set $return_the_entire_tag to true.
118
+ *
119
+ * @param string $html The HTML code to search for tags.
120
+ * @param string|array $tag The tag(s) to extract.
121
+ * @param bool $selfclosing Whether the tag is self-closing or not. Setting it to null will force the script to try and make an educated guess.
122
+ * @param bool $return_the_entire_tag Return the entire matched tag in 'full_tag' key of the results array.
123
+ * @param string $charset The character set of the HTML code. Defaults to ISO-8859-1.
124
+ *
125
+ * @return array An array of extracted tags, or an empty array if no matching tags were found.
126
+ */
127
+ static function extract_tags( $html, $tag, $selfclosing = null, $return_the_entire_tag = false, $charset = 'ISO-8859-1' ) {
128
+
129
+ if ( is_array( $tag ) ) {
130
+ $tag = implode( '|', $tag );
131
+ }
132
+
133
+ //If the user didn't specify if $tag is a self-closing tag we try to auto-detect it
134
+ //by checking against a list of known self-closing tags.
135
+ $selfclosing_tags = array( 'area', 'base', 'basefont', 'br', 'hr', 'input', 'img', 'link', 'meta', 'col', 'param' );
136
+ if ( is_null( $selfclosing ) ) {
137
+ $selfclosing = in_array( $tag, $selfclosing_tags );
138
+ }
139
+
140
+ //The regexp is different for normal and self-closing tags because I can't figure out
141
+ //how to make a sufficiently robust unified one.
142
+ if ( $selfclosing ) {
143
+ $tag_pattern =
144
+ '@<(?P<tag>' . $tag . ') # <tag
145
+ (?P<attributes>\s[^>]+)? # attributes, if any
146
+ \s*/?> # /> or just >, being lenient here
147
+ @xsi';
148
+ } else {
149
+ $tag_pattern =
150
+ '@<(?P<tag>' . $tag . ') # <tag
151
+ (?P<attributes>\s[^>]+)? # attributes, if any
152
+ \s*> # >
153
+ (?P<contents>.*?) # tag contents
154
+ </(?P=tag)> # the closing </tag>
155
+ @xsi';
156
+ }
157
+
158
+ $attribute_pattern =
159
+ '@
160
+ (?P<name>\w+) # attribute name
161
+ \s*=\s*
162
+ (
163
+ (?P<quote>[\"\'])(?P<value_quoted>.*?)(?P=quote) # a quoted value
164
+ | # or
165
+ (?P<value_unquoted>[^\s"\']+?)(?:\s+|$) # an unquoted value (terminated by whitespace or EOF)
166
+ )
167
  @xsi';
168
+
169
+ //Find all tags
170
+ if ( ! preg_match_all( $tag_pattern, $html, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE ) ) {
171
+ //Return an empty array if we didn't find anything
172
+ return array();
173
+ }
174
+
175
+ $tags = array();
176
+ foreach ( $matches as $match ) {
177
+
178
+ // Parse tag attributes, if any.
179
+ $attributes = array();
180
+ if ( ! empty( $match['attributes'][0] ) ) {
181
+
182
+ if ( preg_match_all( $attribute_pattern, $match['attributes'][0], $attribute_data, PREG_SET_ORDER ) ) {
183
+ //Turn the attribute data into a name->value array
184
+ foreach ( $attribute_data as $attr ) {
185
+ if( ! empty( $attr['value_quoted'] ) ) {
186
+ $value = $attr['value_quoted'];
187
+ } else if( ! empty( $attr['value_unquoted'] ) ) {
188
+ $value = $attr['value_unquoted'];
189
+ } else {
190
+ $value = '';
191
+ }
192
+
193
+ // Passing the value through html_entity_decode is handy when you want
194
+ // to extract link URLs or something like that. You might want to remove
195
+ // or modify this call if it doesn't fit your situation.
196
+ $value = html_entity_decode( $value, ENT_QUOTES, $charset );
197
+
198
+ $attributes[ $attr['name'] ] = $value;
 
 
 
 
199
  }
 
 
 
 
 
 
 
200
  }
201
+
202
  }
203
+
204
+ $tag = array(
205
+ 'tag_name' => $match['tag'][0],
206
+ 'offset' => $match[0][1],
207
+ 'contents' => ! empty( $match['contents'] ) ? $match['contents'][0] : '', // Empty for self-closing tags.
208
+ 'attributes' => $attributes,
209
+ );
210
+ if ( $return_the_entire_tag ) {
211
+ $tag['full_tag'] = $match[0][0];
212
+ }
213
+
214
+ $tags[] = $tag;
215
  }
216
+
217
+ return $tags;
218
+ }
219
+
220
+ /**
221
+ * Get the value of a cookie.
222
+ *
223
+ * @param string $cookie_name The name of the cookie to return.
224
+ * @param string $default_value Optional. If the cookie is not set, this value will be returned instead. Defaults to an empty string.
225
+ * @return mixed Either the value of the requested cookie, or $default_value.
226
+ */
227
+ static function get_cookie( $cookie_name, $default_value = '' ) {
228
+ if ( isset( $_COOKIE[$cookie_name] ) ) {
229
+ return $_COOKIE[$cookie_name];
230
+ } else {
231
+ return $default_value;
232
  }
 
 
233
  }
234
+
 
 
 
235
  /**
236
+ * Format a time delta using a fuzzy format, e.g. '2 minutes ago', '2 days', etc.
237
+ *
238
+ * @param int $delta Time period in seconds.
239
+ * @param string $type Optional. The output template to use.
240
+ * @return string
241
+ */
242
+ static function fuzzy_delta( $delta, $template = 'default' ) {
243
+
244
+ $templates = array(
245
+ 'seconds' => array(
246
+ 'default' => _n_noop('%d second', '%d seconds'),
247
+ 'ago' => _n_noop('%d second ago', '%d seconds ago'),
248
+ ),
249
+ 'minutes' => array(
250
+ 'default' => _n_noop('%d minute', '%d minutes'),
251
+ 'ago' => _n_noop('%d minute ago', '%d minutes ago'),
252
+ ),
253
+ 'hours' => array(
254
+ 'default' => _n_noop('%d hour', '%d hours'),
255
+ 'ago' => _n_noop('%d hour ago', '%d hours ago'),
256
+ ),
257
+ 'days' => array(
258
+ 'default' => _n_noop('%d day', '%d days'),
259
+ 'ago' => _n_noop('%d day ago', '%d days ago'),
260
+ ),
261
+ 'months' => array(
262
+ 'default' => _n_noop('%d month', '%d months'),
263
+ 'ago' => _n_noop('%d month ago', '%d months ago'),
264
+ ),
265
+ );
266
+
267
+ if ( $delta < 1 ) {
268
+ $delta = 1;
269
+ }
270
+
271
+ if ( $delta < MINUTE_IN_SECONDS ) {
272
+ $units = 'seconds';
273
+ } elseif ( $delta < HOUR_IN_SECONDS ) {
274
+ $delta = intval( $delta / MINUTE_IN_SECONDS );
275
+ $units = 'minutes';
276
+ } elseif ( $delta < DAY_IN_SECONDS ) {
277
+ $delta = intval( $delta / HOUR_IN_SECONDS );
278
+ $units = 'hours';
279
+ } elseif ( $delta < MONTH_IN_SECONDS ) {
280
+ $delta = intval( $delta / DAY_IN_SECONDS );
281
+ $units = 'days';
282
+ } else {
283
+ $delta = intval( $delta / MONTH_IN_SECONDS );
284
+ $units = 'months';
285
+ }
286
+
287
+ return sprintf(
288
+ _n(
289
+ $templates[$units][$template][0],
290
+ $templates[$units][$template][1],
291
+ $delta,
292
+ 'broken-link-checker'
293
+ ),
294
+ $delta
295
+ );
296
  }
297
+
298
+ /**
299
+ * Optimize the plugin's tables
300
+ *
301
+ * @return void
302
+ */
303
+ static function optimize_database(){
304
+ global $wpdb; /** @var wpdb $wpdb */
305
+
306
+ $wpdb->query( "OPTIMIZE TABLE {$wpdb->prefix}blc_links, {$wpdb->prefix}blc_instances, {$wpdb->prefix}blc_synch" );
 
 
 
 
 
307
  }
308
+
309
+ /**
310
+ * Get the server's load averages.
311
+ *
312
+ * Returns an array with three samples - the 1 minute avg, the 5 minute avg, and the 15 minute avg.
313
+ *
314
+ * @param integer $cache How long the load averages may be cached, in seconds. Set to 0 to get maximally up-to-date data.
315
+ * @return array|null Array, or NULL if retrieving load data is impossible (e.g. when running on a Windows box).
316
+ */
317
+ static function get_server_load( $cache = 5 ) {
318
+ static $cached_load = null;
319
+ static $cached_when = 0;
320
+
321
+ if ( ! empty( $cache ) && ((time() - $cached_when) <= $cache) ) {
322
+ return $cached_load;
323
+ }
324
+
325
+ $load = null;
326
+
327
+ if ( function_exists( 'sys_getloadavg' ) ) {
328
+ $load = sys_getloadavg();
329
+ } else {
330
+ $loadavg_file = '/proc/loadavg';
331
+ if ( @is_readable( $loadavg_file ) ) {
332
+ $load = explode( ' ', file_get_contents( $loadavg_file ) );
333
+ $load = array_map( 'floatval', $load );
334
+ }
335
+ }
336
+
337
+ $cached_load = $load;
338
+ $cached_when = time();
339
+ return $load;
 
 
 
 
 
340
  }
341
+
342
+ /**
343
+ * Convert an internationalized domain name or URL to ASCII-compatible encoding.
344
+ *
345
+ * @param string $url Either a domain name or a complete URL.
346
+ * @param string $charset The character encoding of the $url parameter. Defaults to the encoding set in Settings -> Reading.
347
+ * @return string
348
+ */
349
+ static function idn_to_ascii( $url, $charset = '' ) {
350
+ $idn = blcUtility::get_idna_converter();
351
+ if ( $idn != null ) {
352
+ if ( empty( $charset ) ) {
353
+ $charset = get_bloginfo( 'charset' );
354
+ }
355
+
356
+ // Encode only the host.
357
+ if ( preg_match( '@(\w+:/*)?([^/:]+)(.*$)?@s', $url, $matches ) ) {
358
+ $host = $matches[2];
359
+ if ( ( strtoupper( $charset ) != 'UTF-8') && ( strtoupper( $charset ) != 'UTF8') ) {
360
+ $host = encode_utf8( $host, $charset, true );
361
+ }
362
+ $host = $idn->encode( $host );
363
+ $url = $matches[1] . $host . $matches[3];
364
+ }
365
+ }
366
+
367
+ return $url;
368
  }
369
+
370
+ /**
371
+ * Convert an internationalized domain name (or URL) from ASCII-compatible encoding to UTF8.
372
+ *
373
+ * @param string $url
374
+ * @return string
375
+ */
376
+ static function idn_to_utf8( $url ) {
377
+ $idn = blcUtility::get_idna_converter();
378
+ if ( null !== $idn ) {
379
+ $url = $idn->decode( $url );
 
 
 
 
 
 
 
380
  }
381
+
382
+ return $url;
 
 
 
 
 
 
 
 
383
  }
 
 
 
384
 
385
+ /**
386
+ * Get an instance of idna_converter
387
+ *
388
+ * @return idna_convert|null Either an instance of IDNA converter, or NULL if the converter class is not available
389
+ */
390
+ static function get_idna_converter() {
391
+ static $idn = null;
392
+ if ( ( null === $idn ) && class_exists( 'idna_convert' ) ) {
393
+ $idn = new idna_convert();
394
+ }
395
+ return $idn;
396
  }
397
+
398
+ /**
399
+ * Generate a numeric hash from a string. The result will be constrained to the specified interval.
400
+ *
401
+ * @static
402
+ * @param string $input
403
+ * @param int $min
404
+ * @param int $max
405
+ * @return float
406
+ */
407
+ public static function constrained_hash( $input, $min = 0, $max = 1 ) {
408
+ $bytes_to_use = 3;
409
+ $md5_char_count = 32;
410
+ $hash = substr( md5( $input ), $md5_char_count - $bytes_to_use * 2 );
411
+ $hash = intval( hexdec( $hash ) );
412
+ return $min + ( ( $max - $min ) * ( $hash / ( pow( 2, $bytes_to_use * 8 ) - 1) ) );
413
  }
 
 
414
 
415
+ }//class
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
 
417
  }//class_exists
 
modules/checkers/http.php CHANGED
@@ -254,6 +254,9 @@ class blcCurlHttp extends blcHttpCheckerBase {
254
  curl_setopt($ch, CURLINFO_HEADER_OUT, true);
255
  }
256
 
 
 
 
257
  //Execute the request
258
  $start_time = microtime_float();
259
  $content = curl_exec($ch);
@@ -316,6 +319,11 @@ class blcCurlHttp extends blcHttpCheckerBase {
316
  } else {
317
  $result['broken'] = $this->is_error_code($result['http_code']);
318
  }
 
 
 
 
 
319
  curl_close($ch);
320
 
321
  $blclog->info(sprintf(
254
  curl_setopt($ch, CURLINFO_HEADER_OUT, true);
255
  }
256
 
257
+ // Apply filter for additional options
258
+ curl_setopt_array($ch, apply_filters('broken-link-checker-curl-options', array()) );
259
+
260
  //Execute the request
261
  $start_time = microtime_float();
262
  $content = curl_exec($ch);
319
  } else {
320
  $result['broken'] = $this->is_error_code($result['http_code']);
321
  }
322
+
323
+
324
+ // Apply filter before curl closes
325
+ apply_filters('broken-link-checker-curl-before-close', $ch, $content, $this->last_headers);
326
+
327
  curl_close($ch);
328
 
329
  $blclog->info(sprintf(
modules/containers/acf_field.php ADDED
@@ -0,0 +1,710 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Plugin Name: Acf fields
5
+ Description: Container module for acf fields.
6
+ Version: 1.0
7
+ Author: Janne Aalto
8
+
9
+ ModuleID: acf_field
10
+ ModuleCategory: container
11
+ ModuleClassName: blcAcfMetaManager
12
+ */
13
+
14
+ //Note : If it ever becomes necessary to check metadata on objects other than posts, it will
15
+ //be fairly easy to extract a more general metadata container class from blcAcfMeta.
16
+
17
+ /**
18
+ * blcAcfMeta - A link container class for post metadata (AKA custom fields).
19
+ *
20
+ * Due to the way metadata works, this container differs significantly from other containers :
21
+ * - container_field is equal to meta name, and container_id holds the ID of the post.
22
+ * - There is one synch. record per post that determines the synch. state of all metadata fields of that post.
23
+ * - Unlinking simply deletes the meta entry in question without involving the parser.
24
+ * - The list of parse-able $fields is not fixed. Instead, it's initialized based on the
25
+ * custom field list defined in Settings -> Link Checker.
26
+ * - The $wrapped_object is an array (and isn't really used for anything).
27
+ * - update_wrapped_object() does nothing.
28
+ *
29
+ * @package Broken Link Checker
30
+ * @access public
31
+ */
32
+ class blcAcfMeta extends blcContainer {
33
+
34
+ var $meta_type = 'post';
35
+
36
+ /**
37
+ * Retrieve all metadata fields of the post associated with this container.
38
+ * The results are cached in the internal $wrapped_object variable.
39
+ *
40
+ * @param bool $ensure_consistency
41
+ *
42
+ * @return object The wrapped object.
43
+ */
44
+ function get_wrapped_object($ensure_consistency = false) {
45
+ if (is_null($this->wrapped_object) || $ensure_consistency) {
46
+ $this->wrapped_object = get_metadata($this->meta_type, $this->container_id);
47
+ }
48
+
49
+ return $this->wrapped_object;
50
+ }
51
+
52
+ function update_wrapped_object() {
53
+ trigger_error('Function blcAcfMeta::update_wrapped_object() does nothing and should not be used.', E_USER_WARNING);
54
+ }
55
+
56
+ /**
57
+ * Get the value of the specified metadata field of the object wrapped by this container.
58
+ *
59
+ * @access protected
60
+ *
61
+ * @param string $field Field name. If omitted, the value of the default field will be returned.
62
+ *
63
+ * @return array
64
+ */
65
+ function get_field($field = '') {
66
+ global $wpdb;
67
+
68
+ $meta = get_metadata('post', $this->container_id, '_' . $field, true);
69
+ $key = explode('|', str_replace('_field', '|field', $meta));
70
+
71
+ if (is_array($key)) {
72
+ $key = $key[ count($key) - 1 ];
73
+ } else {
74
+ $key = $meta;
75
+ }
76
+
77
+ if (!isset($this->fields[ $key ])) {
78
+ $key = $field;
79
+ }
80
+
81
+ $get_only_first_field = ($this->fields[ $key ] !== 'acf_field');
82
+
83
+ return get_metadata($this->meta_type, $this->container_id, $field, $get_only_first_field);
84
+ }
85
+
86
+ /**
87
+ * Update the value of the specified metadata field of the object wrapped by this container.
88
+ *
89
+ * @access protected
90
+ *
91
+ * @param string $field Meta name.
92
+ * @param string $new_value New meta value.
93
+ * @param string $old_value old meta value.
94
+ *
95
+ * @return bool|WP_Error True on success, an error object if something went wrong.
96
+ */
97
+ function update_field($field, $new_value, $old_value = '') {
98
+ $rez = update_metadata($this->meta_type, $this->container_id, $field, $new_value, $old_value);
99
+ if ($rez) {
100
+ return true;
101
+ } else {
102
+ return new WP_Error('metadata_update_failed', sprintf(__("Failed to update the meta field '%s' on %s [%d]", 'broken-link-checker'), $field, $this->meta_type, $this->container_id));
103
+ }
104
+ }
105
+
106
+ /**
107
+ * "Unlink"-ing a custom fields removes all metadata fields that contain the specified URL.
108
+ *
109
+ * @param string $field_name
110
+ * @param blcParser $parser
111
+ * @param string $url
112
+ * @param string $raw_url
113
+ *
114
+ * @return bool|WP_Error True on success, or an error object if something went wrong.
115
+ */
116
+ function unlink($field_name, $parser, $url, $raw_url = '') {
117
+ // error_log(print_r('unlink', true));
118
+ $meta = get_metadata('post', $this->container_id, '_' . $field_name, true);
119
+ $key = explode('|', str_replace('_field', '|field', $meta));
120
+
121
+ if (is_array($key)) {
122
+ $key = $key[ count($key) - 1 ];
123
+ }
124
+ if ($this->fields[ $key ] !== 'acf_field') {
125
+ return parent::unlink($field_name, $parser, $url, $raw_url);
126
+ }
127
+
128
+ $rez = update_metadata($this->meta_type, $this->container_id, $field_name, '');
129
+ if ($rez) {
130
+ return true;
131
+ } else {
132
+ return new WP_Error('metadata_delete_failed', sprintf(__("Failed to delete the meta field '%s' on %s [%d]", 'broken-link-checker'), $field_name, $this->meta_type, $this->container_id));
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Change a meta field containing the specified URL to a new URL.
138
+ *
139
+ * @param string $field_name Meta name
140
+ * @param blcParser $parser
141
+ * @param string $new_url New URL.
142
+ * @param string $old_url
143
+ * @param string $old_raw_url Old meta value.
144
+ * @param null $new_text
145
+ *
146
+ * @return string|WP_Error The new value of raw_url on success, or an error object if something went wrong.
147
+ */
148
+ function edit_link($field_name, $parser, $new_url, $old_url = '', $old_raw_url = '', $new_text = null) {
149
+ // error_log(print_r('edit_link', true));
150
+
151
+ /*
152
+ FB::log(sprintf(
153
+ 'Editing %s[%d]:%s - %s to %s',
154
+ $this->container_type,
155
+ $this->container_id,
156
+ $field_name,
157
+ $old_url,
158
+ $new_url
159
+ ));
160
+ */
161
+
162
+ $meta = get_metadata('post', $this->container_id, '_' . $field_name, true);
163
+ $key = explode('|', str_replace('_field', '|field', $meta));
164
+
165
+ if (is_array($key)) {
166
+ $key = $key[ count($key) - 1 ];
167
+ }
168
+
169
+ if ($this->fields[ $key ] !== 'acf_field') {
170
+ return parent::edit_link($field_name, $parser, $new_url, $old_url, $old_raw_url, $new_text);
171
+ }
172
+
173
+ if (empty($old_raw_url)) {
174
+ $old_raw_url = $old_url;
175
+ }
176
+
177
+ //Get the current values of the field that needs to be edited.
178
+ //The default metadata parser ignores them, but we're still going
179
+ //to set this argument to a valid value in case someone writes a
180
+ //custom meta parser that needs it.
181
+ $old_value = $this->get_field($field_name);
182
+
183
+ //Get the new field value (a string).
184
+ $edit_result = $parser->edit($old_value, $new_url, $old_url, $old_raw_url);
185
+ if (is_wp_error($edit_result)) {
186
+ return $edit_result;
187
+ }
188
+
189
+ //Update the field with the new value returned by the parser.
190
+ //Notice how $old_raw_url is used instead of $old_value. $old_raw_url contains the entire old
191
+ //value of the metadata field (see blcMetadataParser::parse()) and thus can be used to
192
+ //differentiate between multiple meta fields with identical names.
193
+ $update_result = $this->update_field($field_name, $edit_result['content'], $old_raw_url);
194
+ if (is_wp_error($update_result)) {
195
+ return $update_result;
196
+ }
197
+
198
+ //Return the new "raw" URL.
199
+ return $edit_result['raw_url'];
200
+ }
201
+
202
+ /**
203
+ * Get the default link text to use for links found in a specific container field.
204
+ *
205
+ * @param string $field
206
+ *
207
+ * @return string
208
+ */
209
+ function default_link_text($field = '') {
210
+ // error_log(print_r('default_link_text', true));
211
+
212
+ //Just use the field name. There's no way to know how the links inside custom fields are
213
+ //used, so no way to know the "real" link text. Displaying the field name at least gives
214
+ //the user a clue where to look if they want to find/modify the field.
215
+ return $field;
216
+ }
217
+
218
+ function ui_get_source($container_field = '', $context = 'display') {
219
+ // error_log(print_r('ui_get_source', true));
220
+
221
+ if (!post_type_exists(get_post_type($this->container_id))) {
222
+ //Error: Invalid post type. The user probably removed a CPT without removing the actual posts.
223
+ $post_html = '';
224
+
225
+ $post = get_post($this->container_id);
226
+ if ($post) {
227
+ $post_html .= sprintf('<span class="row-title">%s</span><br>', get_the_title($post));
228
+ }
229
+ $post_html .= sprintf('Invalid post type "%s"', htmlentities($this->container_type));
230
+
231
+ return $post_html;
232
+ }
233
+
234
+ $post_html = sprintf('<a class="row-title" href="%s" title="%s">%s</a>', esc_url($this->get_edit_url()), esc_attr(__('Edit this post')), get_the_title($this->container_id));
235
+
236
+ return $post_html;
237
+ }
238
+
239
+ function ui_get_action_links($container_field) {
240
+ // error_log(print_r('ui_get_action_links', true));
241
+
242
+ $actions = [];
243
+ if (!post_type_exists(get_post_type($this->container_id))) {
244
+ return $actions;
245
+ }
246
+
247
+ if (current_user_can('edit_post', $this->container_id)) {
248
+ $actions['edit'] = '<span class="edit"><a href="' . $this->get_edit_url() . '" title="' . esc_attr(__('Edit this item')) . '">' . __('Edit') . '</a>';
249
+
250
+ if ($this->current_user_can_delete()) {
251
+ if ($this->can_be_trashed()) {
252
+ $actions['trash'] = sprintf("<span><a class='submitdelete' title='%s' href='%s'>%s</a>", esc_attr(__('Move this item to the Trash')), get_delete_post_link($this->container_id, '', false), __('Trash'));
253
+ } else {
254
+ $actions['delete'] = sprintf("<span><a class='submitdelete' title='%s' href='%s'>%s</a>", esc_attr(__('Delete this item permanently')), get_delete_post_link($this->container_id, '', true), __('Delete'));
255
+ }
256
+ }
257
+ }
258
+ $actions['view'] = '<span class="view"><a href="' . esc_url(get_permalink($this->container_id)) . '" title="' . esc_attr(sprintf(__('View "%s"', 'broken-link-checker'), get_the_title($this->container_id))) . '" rel="permalink">' . __('View') . '</a>';
259
+
260
+ return $actions;
261
+ }
262
+
263
+ /**
264
+ * Get edit URL for this container. Returns the URL of the Dashboard page where the item
265
+ * associated with this container can be edited.
266
+ *
267
+ * @access protected
268
+ *
269
+ * @return string
270
+ */
271
+ function get_edit_url() {
272
+ // error_log(print_r('get_edit_url', true));
273
+
274
+ /*
275
+ The below is a near-exact copy of the get_post_edit_link() function.
276
+ Unfortunately we can't just call that function because it has a hardcoded
277
+ caps-check which fails when called from the email notification script
278
+ executed by Cron.
279
+ */
280
+
281
+ if (!($post = get_post($this->container_id))) {
282
+ return '';
283
+ }
284
+
285
+ $context = 'display';
286
+
287
+ //WP 3.0
288
+ if ('display' == $context) {
289
+ $action = '&amp;action=edit';
290
+ } else {
291
+ $action = '&action=edit';
292
+ }
293
+
294
+ $post_type_object = get_post_type_object($post->post_type);
295
+ if (!$post_type_object) {
296
+ return '';
297
+ }
298
+
299
+ return apply_filters('get_edit_post_link', admin_url(sprintf($post_type_object->_edit_link . $action, $post->ID)), $post->ID, $context);
300
+ }
301
+
302
+ /**
303
+ * Get the base URL of the container. For custom fields, the base URL is the permalink of
304
+ * the post that the field is attached to.
305
+ *
306
+ * @return string
307
+ */
308
+ function base_url() {
309
+ return get_permalink($this->container_id);
310
+ }
311
+
312
+ /**
313
+ * Delete or trash the post corresponding to this container. If trash is enabled,
314
+ * will always move the post to the trash instead of deleting.
315
+ *
316
+ * @return bool|WP_error
317
+ */
318
+ function delete_wrapped_object() {
319
+ // error_log(print_r('delete_wrapped_object', true));
320
+
321
+ if (EMPTY_TRASH_DAYS) {
322
+ return $this->trash_wrapped_object();
323
+ } else {
324
+ if (wp_delete_post($this->container_id)) {
325
+ return true;
326
+ } else {
327
+ return new WP_Error('delete_failed', sprintf(__('Failed to delete post "%s" (%d)', 'broken-link-checker'), get_the_title($this->container_id), $this->container_id));
328
+ }
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Move the post corresponding to this custom field to the Trash.
334
+ *
335
+ * @return bool|WP_Error
336
+ */
337
+ function trash_wrapped_object() {
338
+ // error_log(print_r('trash_wrapped_object', true));
339
+
340
+ if (!EMPTY_TRASH_DAYS) {
341
+ return new WP_Error('trash_disabled', sprintf(__('Can\'t move post "%s" (%d) to the trash because the trash feature is disabled', 'broken-link-checker'), get_the_title($this->container_id), $this->container_id));
342
+ }
343
+
344
+ $post = &get_post($this->container_id);
345
+ if ($post->post_status == 'trash') {
346
+ //Prevent conflicts between post and custom field containers trying to trash the same post.
347
+ return true;
348
+ }
349
+
350
+ if (wp_trash_post($this->container_id)) {
351
+ return true;
352
+ } else {
353
+ return new WP_Error('trash_failed', sprintf(__('Failed to move post "%s" (%d) to the trash', 'broken-link-checker'), get_the_title($this->container_id), $this->container_id));
354
+ }
355
+ }
356
+
357
+ function current_user_can_delete() {
358
+ $post = get_post($this->container_id);
359
+ $post_type_object = get_post_type_object($post->post_type);
360
+
361
+ return current_user_can($post_type_object->cap->delete_post, $this->container_id);
362
+ }
363
+
364
+ function can_be_trashed() {
365
+ return defined('EMPTY_TRASH_DAYS') && EMPTY_TRASH_DAYS;
366
+ }
367
+ }
368
+
369
+ class blcAcfMetaManager extends blcContainerManager {
370
+
371
+ var $container_class_name = 'blcAcfMeta';
372
+
373
+ protected $selected_fields = [];
374
+
375
+ function init() {
376
+ parent::init();
377
+
378
+ //Figure out which custom fields we're interested in.
379
+ if (is_array($this->plugin_conf->options['acf_fields'])) {
380
+ $prefix_formats = [
381
+ 'html' => 'html',
382
+ 'acf_field' => 'acf_field',
383
+ ];
384
+ foreach ($this->plugin_conf->options['acf_fields'] as $meta_name) {
385
+ //The user can add an optional "format:" prefix to specify the format of the custom field.
386
+ $parts = explode(':', $meta_name, 2);
387
+ if ((count($parts) == 2) && in_array($parts[0], $prefix_formats)) {
388
+ $this->selected_fields[ $parts[1] ] = $prefix_formats[ $parts[0] ];
389
+ } else {
390
+ $this->selected_fields[ $meta_name ] = 'acf_field';
391
+ }
392
+ }
393
+
394
+ }
395
+
396
+ //Intercept 2.9+ style metadata modification actions
397
+ add_action("acf/save_post", [$this, 'acf_save'], 10, 4);
398
+
399
+ //When a post is deleted, also delete the custom field container associated with it.
400
+ add_action('delete_post', [$this, 'post_deleted']);
401
+ add_action('trash_post', [$this, 'post_deleted']);
402
+
403
+ //Re-parse custom fields when a post is restored from trash
404
+ add_action('untrashed_post', [$this, 'post_untrashed']);
405
+
406
+ }
407
+
408
+ /**
409
+ * Get a list of parseable fields.
410
+ *
411
+ * @return array
412
+ */
413
+ function get_parseable_fields() {
414
+ return $this->selected_fields;
415
+ }
416
+
417
+ /**
418
+ * Instantiate multiple containers of the container type managed by this class.
419
+ *
420
+ * @param array $containers Array of assoc. arrays containing container data.
421
+ * @param string $purpose An optional code indicating how the retrieved containers will be used.
422
+ * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose.
423
+ *
424
+ * @return array of blcAcfMeta indexed by "container_type|container_id"
425
+ */
426
+ function get_containers($containers, $purpose = '', $load_wrapped_objects = false) {
427
+
428
+ $containers = $this->make_containers($containers);
429
+
430
+ /*
431
+ When links from custom fields are displayed in Tools -> Broken Links,
432
+ each one also shows the title of the post that the custom field(s)
433
+ belong to. Thus it makes sense to pre-cache the posts beforehand - it's
434
+ faster to load them all at once than to make a separate query for each
435
+ one later.
436
+
437
+ So make a list of involved post IDs and load them.
438
+
439
+ Calling get_posts() will automatically populate the post cache, so we
440
+ don't need to actually store the results anywhere in the container object().
441
+ */
442
+
443
+ $preload = $load_wrapped_objects || in_array($purpose, [BLC_FOR_DISPLAY]);
444
+ if ($preload) {
445
+ $post_ids = [];
446
+ foreach ($containers as $container) {
447
+ $post_ids[] = $container->container_id;
448
+ }
449
+
450
+ $args = ['include' => implode(',', $post_ids)];
451
+ get_posts($args);
452
+ }
453
+
454
+ $selected_fields = $this->selected_fields;
455
+
456
+ $html_fields = array_filter($selected_fields, function ($value) {
457
+ if ($value == 'html') {
458
+ return true;
459
+ }
460
+ return false;
461
+ });
462
+
463
+ $url_fields = array_keys(array_diff($selected_fields, $html_fields));
464
+ $html_fields = array_keys($html_fields);
465
+
466
+ foreach ($containers as $key => $container) {
467
+
468
+ $meta = get_metadata('post', $container->container_id);
469
+ $fields = [];
470
+
471
+ foreach ($meta as $field => $value) {
472
+
473
+ $value = explode('|', str_replace('_field', '|field', $value[0]));
474
+
475
+ if (!is_array($value)) {
476
+ continue;
477
+ } else {
478
+
479
+ $value = $value[ count($value) - 1 ];
480
+
481
+ if (in_array($value, $url_fields)) {
482
+ $field = ltrim($field, '_');
483
+ if (!filter_var($meta[ $field ][0], FILTER_VALIDATE_URL) === false) {
484
+ $fields[ $field ] = 'acf_field';
485
+ }
486
+ }
487
+
488
+ if (in_array($value, $html_fields)) {
489
+ $field = ltrim($field, '_');
490
+ if ($meta[ $field ][0] != '') {
491
+ $fields[ $field ] = 'html';
492
+ }
493
+ }
494
+
495
+ }
496
+ }
497
+
498
+ $containers[ $key ]->fields = $fields;
499
+
500
+ }
501
+
502
+ return $containers;
503
+ }
504
+
505
+ /**
506
+ * Create or update synchronization records for all containers managed by this class.
507
+ *
508
+ * @param bool $forced If true, assume that all synch. records are gone and will need to be recreated from scratch.
509
+ *
510
+ * @return void
511
+ */
512
+ function resynch($forced = false) {
513
+ // error_log(print_r('resynch', true));
514
+
515
+ global $wpdb;
516
+ /** @var wpdb $wpdb */
517
+ global $blclog;
518
+
519
+ //Only check custom fields on selected post types. By default, that's "post" and "page".
520
+ $post_types = ['post', 'page'];
521
+ if (class_exists('blcPostTypeOverlord')) {
522
+ $overlord = blcPostTypeOverlord::getInstance();
523
+ $post_types = array_merge($post_types, $overlord->enabled_post_types);
524
+ $post_types = array_unique($post_types);
525
+ }
526
+
527
+ $escaped_post_types = "'" . implode("', '", array_map('esc_sql', $post_types)) . "'";
528
+
529
+ if ($forced) {
530
+ //Create new synchronization records for all posts.
531
+ $blclog->log('...... Creating synch records for all custom fields on ' . $escaped_post_types);
532
+ $start = microtime(true);
533
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch(container_id, container_type, synched)
534
+ SELECT id, '{$this->container_type}', 0
535
+ FROM {$wpdb->posts}
536
+ WHERE
537
+ {$wpdb->posts}.post_status = 'publish'
538
+ AND {$wpdb->posts}.post_type IN ({$escaped_post_types})";
539
+ $wpdb->query($q);
540
+ $blclog->log(sprintf('...... %d rows inserted in %.3f seconds', $wpdb->rows_affected, microtime(true) - $start));
541
+ } else {
542
+ //Delete synch records corresponding to posts that no longer exist.
543
+ $blclog->log('...... Deleting custom field synch records corresponding to deleted posts');
544
+ $start = microtime(true);
545
+ $q = "DELETE synch.*
546
+ FROM
547
+ {$wpdb->prefix}blc_synch AS synch LEFT JOIN {$wpdb->posts} AS posts
548
+ ON posts.ID = synch.container_id
549
+ WHERE
550
+ synch.container_type = '{$this->container_type}' AND posts.ID IS NULL";
551
+ $wpdb->query($q);
552
+ $blclog->log(sprintf('...... %d rows deleted in %.3f seconds', $wpdb->rows_affected, microtime(true) - $start));
553
+
554
+ //Remove the 'synched' flag from all posts that have been updated
555
+ //since the last time they were parsed/synchronized.
556
+ $blclog->log('...... Marking custom fields on changed posts as unsynched');
557
+ $start = microtime(true);
558
+ $q = "UPDATE
559
+ {$wpdb->prefix}blc_synch AS synch
560
+ JOIN {$wpdb->posts} AS posts ON (synch.container_id = posts.ID and synch.container_type='{$this->container_type}')
561
+ SET
562
+ synched = 0
563
+ WHERE
564
+ synch.last_synch < posts.post_modified";
565
+ $wpdb->query($q);
566
+ $blclog->log(sprintf('...... %d rows updated in %.3f seconds', $wpdb->rows_affected, microtime(true) - $start));
567
+
568
+ //Create synch. records for posts that don't have them.
569
+ $blclog->log('...... Creating custom field synch records for new ' . $escaped_post_types);
570
+ $start = microtime(true);
571
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch(container_id, container_type, synched)
572
+ SELECT id, '{$this->container_type}', 0
573
+ FROM
574
+ {$wpdb->posts} AS posts LEFT JOIN {$wpdb->prefix}blc_synch AS synch
575
+ ON (synch.container_id = posts.ID and synch.container_type='{$this->container_type}')
576
+ WHERE
577
+ posts.post_status = 'publish'
578
+ AND posts.post_type IN ({$escaped_post_types})
579
+ AND synch.container_id IS NULL";
580
+ $wpdb->query($q);
581
+ $blclog->log(sprintf('...... %d rows inserted in %.3f seconds', $wpdb->rows_affected, microtime(true) - $start));
582
+ }
583
+ }
584
+
585
+ /**
586
+ * Mark custom fields as unsynched when they're modified or deleted.
587
+ *
588
+ * @param $post_id
589
+ *
590
+ * @return void
591
+ *
592
+ */
593
+ function acf_save($post_id) {
594
+ global $wpdb;
595
+ /** @var wpdb $wpdb */
596
+
597
+ if (empty($this->selected_fields)) {
598
+ return;
599
+ }
600
+
601
+ if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id) || !is_int($post_id)) {
602
+ return;
603
+ }
604
+
605
+ $selected_fields = $this->selected_fields;
606
+
607
+ $html_fields = array_filter($selected_fields, function ($value) {
608
+ if ($value == 'html') {
609
+ return true;
610
+ }
611
+
612
+ return false;
613
+ });
614
+
615
+ $url_fields = array_keys(array_diff($selected_fields, $html_fields));
616
+
617
+ $html_fields = array_keys($html_fields);
618
+ $selected_fields = array_keys($selected_fields);
619
+
620
+ $keys = [];
621
+ $fields = $_POST['acf'];
622
+
623
+ array_walk_recursive($fields, function ($item, $key) use (&$keys, $selected_fields, $url_fields, $html_fields) {
624
+
625
+ $key = explode('|', str_replace('_field', '|field', $key));
626
+
627
+ if (is_array($key)) {
628
+ $key = $key[ count($key) - 1 ];
629
+ }
630
+
631
+ if (in_array($key, $url_fields)) {
632
+ if (!filter_var($item, FILTER_VALIDATE_URL) === false) {
633
+ $keys[] = $key;
634
+ }
635
+ }
636
+
637
+ if (in_array($key, $html_fields)) {
638
+ if ($item != '') {
639
+ $keys[] = $key;
640
+ }
641
+ }
642
+ });
643
+
644
+ if (empty($keys)) {
645
+ return;
646
+ }
647
+
648
+ $container = blcContainerHelper::get_container([$this->container_type, $post_id]);
649
+ $container->mark_as_unsynched();
650
+
651
+ }
652
+
653
+ /**
654
+ * Delete custom field synch. records when the post that they belong to is deleted.
655
+ *
656
+ * @param int $post_id
657
+ *
658
+ * @return void
659
+ */
660
+ function post_deleted($post_id) {
661
+ //Get the associated container object
662
+
663
+ $container = blcContainerHelper::get_container([$this->container_type, intval($post_id) ]);
664
+
665
+ if ($container != null) {
666
+ //Delete it
667
+ $container->delete();
668
+ //Clean up any dangling links
669
+ blc_cleanup_links();
670
+ }
671
+ }
672
+
673
+ /**
674
+ * When a post is restored, mark all of its custom fields as unparsed.
675
+ * Called via the 'untrashed_post' action.
676
+ *
677
+ * @param int $post_id
678
+ *
679
+ * @return void
680
+ */
681
+ function post_untrashed($post_id) {
682
+ //Get the associated container object
683
+ $container = blcContainerHelper::get_container([$this->container_type, intval($post_id)]);
684
+ $container->mark_as_unsynched();
685
+ }
686
+
687
+ /**
688
+ * Get the message to display after $n posts have been deleted.
689
+ *
690
+ * @uses blcAnyPostContainerManager::ui_bulk_delete_message()
691
+ *
692
+ * @param int $n Number of deleted posts.
693
+ *
694
+ * @return string A delete confirmation message, e.g. "5 posts were moved to the trash"
695
+ */
696
+ function ui_bulk_delete_message($n) {
697
+ return blcAnyPostContainerManager::ui_bulk_delete_message($n);
698
+ }
699
+
700
+ /**
701
+ * Get the message to display after $n posts have been trashed.
702
+ *
703
+ * @param int $n Number of deleted posts.
704
+ *
705
+ * @return string A confirmation message, e.g. "5 posts were moved to trash"
706
+ */
707
+ function ui_bulk_trash_message($n) {
708
+ return blcAnyPostContainerManager::ui_bulk_trash_message($n);
709
+ }
710
+ }
modules/containers/custom_field.php CHANGED
@@ -12,32 +12,32 @@ ModuleClassName: blcPostMetaManager
12
  */
13
 
14
  //Note : If it ever becomes necessary to check metadata on objects other than posts, it will
15
- //be fairly easy to extract a more general metadata container class from blcPostMeta.
16
 
17
  /**
18
  * blcPostMeta - A link container class for post metadata (AKA custom fields).
19
  *
20
  * Due to the way metadata works, this container differs significantly from other containers :
21
  * - container_field is equal to meta name, and container_id holds the ID of the post.
22
- * - There is one synch. record per post that determines the synch. state of all metadata fields of that post.
23
  * - Unlinking simply deletes the meta entry in question without involving the parser.
24
- * - The list of parse-able $fields is not fixed. Instead, it's initialized based on the
25
- * custom field list defined in Settings -> Link Checker.
26
  * - The $wrapped_object is an array (and isn't really used for anything).
27
  * - update_wrapped_object() does nothing.
28
- *
29
  * @package Broken Link Checker
30
  * @access public
31
  */
32
  class blcPostMeta extends blcContainer {
33
-
34
  var $meta_type = 'post';
35
-
36
  /**
37
  * Retrieve all metadata fields of the post associated with this container.
38
  * The results are cached in the internal $wrapped_object variable.
39
- *
40
- * @param bool $ensure_consistency
41
  * @return object The wrapped object.
42
  */
43
  function get_wrapped_object($ensure_consistency = false){
@@ -45,33 +45,33 @@ class blcPostMeta extends blcContainer {
45
  $this->wrapped_object = get_metadata($this->meta_type, $this->container_id);
46
  }
47
  return $this->wrapped_object;
48
- }
49
-
50
  function update_wrapped_object(){
51
  trigger_error('Function blcPostMeta::update_wrapped_object() does nothing and should not be used.', E_USER_WARNING);
52
  }
53
-
54
  /**
55
  * Get the value of the specified metadata field of the object wrapped by this container.
56
- *
57
  * @access protected
58
  *
59
- * @param string $field Field name. If omitted, the value of the default field will be returned.
60
  * @return array
61
  */
62
  function get_field($field = ''){
63
  $get_only_first_field = ($this->fields[$field] !== 'metadata');
64
  return get_metadata($this->meta_type, $this->container_id, $field, $get_only_first_field);
65
  }
66
-
67
  /**
68
- * Update the value of the specified metadata field of the object wrapped by this container.
69
  *
70
  * @access protected
71
  *
72
  * @param string $field Meta name.
73
- * @param string $new_value New meta value.
74
- * @param string $old_value old meta value.
75
  * @return bool|WP_Error True on success, an error object if something went wrong.
76
  */
77
  function update_field($field, $new_value, $old_value = ''){
@@ -82,9 +82,9 @@ class blcPostMeta extends blcContainer {
82
  return new WP_Error(
83
  'metadata_update_failed',
84
  sprintf(
85
- __("Failed to update the meta field '%s' on %s [%d]", 'broken-link-checker'),
86
- $field,
87
- $this->meta_type,
88
  $this->container_id
89
  )
90
  );
@@ -112,9 +112,9 @@ class blcPostMeta extends blcContainer {
112
  return new WP_Error(
113
  'metadata_delete_failed',
114
  sprintf(
115
- __("Failed to delete the meta field '%s' on %s [%d]", 'broken-link-checker'),
116
  $field_name,
117
- $this->meta_type,
118
  $this->container_id
119
  )
120
  );
@@ -138,7 +138,7 @@ class blcPostMeta extends blcContainer {
138
  'Editing %s[%d]:%s - %s to %s',
139
  $this->container_type,
140
  $this->container_id,
141
- $field_name,
142
  $old_url,
143
  $new_url
144
  ));
@@ -147,36 +147,36 @@ class blcPostMeta extends blcContainer {
147
  if ( $this->fields[$field_name] !== 'metadata' ) {
148
  return parent::edit_link($field_name, $parser, $new_url, $old_url, $old_raw_url, $new_text);
149
  }
150
-
151
  if ( empty($old_raw_url) ){
152
  $old_raw_url = $old_url;
153
  }
154
-
155
  //Get the current values of the field that needs to be edited.
156
  //The default metadata parser ignores them, but we're still going
157
- //to set this argument to a valid value in case someone writes a
158
  //custom meta parser that needs it.
159
  $old_value = $this->get_field($field_name);
160
-
161
  //Get the new field value (a string).
162
  $edit_result = $parser->edit($old_value, $new_url, $old_url, $old_raw_url);
163
  if ( is_wp_error($edit_result) ){
164
  return $edit_result;
165
  }
166
-
167
  //Update the field with the new value returned by the parser.
168
  //Notice how $old_raw_url is used instead of $old_value. $old_raw_url contains the entire old
169
  //value of the metadata field (see blcMetadataParser::parse()) and thus can be used to
170
- //differentiate between multiple meta fields with identical names.
171
  $update_result = $this->update_field( $field_name, $edit_result['content'], $old_raw_url );
172
  if ( is_wp_error($update_result) ){
173
  return $update_result;
174
  }
175
-
176
  //Return the new "raw" URL.
177
  return $edit_result['raw_url'];
178
  }
179
-
180
  /**
181
  * Get the default link text to use for links found in a specific container field.
182
  *
@@ -189,7 +189,7 @@ class blcPostMeta extends blcContainer {
189
  //the user a clue where to look if they want to find/modify the field.
190
  return $field;
191
  }
192
-
193
  function ui_get_source($container_field = '', $context = 'display'){
194
  if ( !post_type_exists(get_post_type($this->container_id)) ) {
195
  //Error: Invalid post type. The user probably removed a CPT without removing the actual posts.
@@ -216,10 +216,10 @@ class blcPostMeta extends blcContainer {
216
  esc_attr(__('Edit this post')),
217
  get_the_title($this->container_id)
218
  );
219
-
220
  return $post_html;
221
  }
222
-
223
  function ui_get_action_links($container_field){
224
  $actions = array();
225
  if ( !post_type_exists(get_post_type($this->container_id)) ) {
@@ -228,7 +228,7 @@ class blcPostMeta extends blcContainer {
228
 
229
  if ( current_user_can('edit_post', $this->container_id) ) {
230
  $actions['edit'] = '<span class="edit"><a href="' . $this->get_edit_url() . '" title="' . esc_attr(__('Edit this item')) . '">' . __('Edit') . '</a>';
231
-
232
  if ( $this->current_user_can_delete() ){
233
  if ( $this->can_be_trashed() ) {
234
  $actions['trash'] = sprintf(
@@ -248,48 +248,48 @@ class blcPostMeta extends blcContainer {
248
  }
249
  }
250
  $actions['view'] = '<span class="view"><a href="' . esc_url(get_permalink($this->container_id)) . '" title="' . esc_attr(sprintf(__('View "%s"', 'broken-link-checker'), get_the_title($this->container_id))) . '" rel="permalink">' . __('View') . '</a>';
251
-
252
  return $actions;
253
  }
254
-
255
  /**
256
- * Get edit URL for this container. Returns the URL of the Dashboard page where the item
257
  * associated with this container can be edited.
258
  *
259
- * @access protected
260
  *
261
  * @return string
262
  */
263
  function get_edit_url(){
264
  /*
265
- The below is a near-exact copy of the get_post_edit_link() function.
266
- Unfortunately we can't just call that function because it has a hardcoded
267
- caps-check which fails when called from the email notification script
268
  executed by Cron.
269
- */
270
-
271
  if ( !($post = get_post( $this->container_id )) ){
272
  return '';
273
  }
274
-
275
  $context = 'display';
276
-
277
  //WP 3.0
278
  if ( 'display' == $context )
279
  $action = '&amp;action=edit';
280
  else
281
  $action = '&action=edit';
282
-
283
  $post_type_object = get_post_type_object( $post->post_type );
284
  if ( !$post_type_object ){
285
  return '';
286
  }
287
-
288
  return apply_filters( 'get_edit_post_link', admin_url( sprintf($post_type_object->_edit_link . $action, $post->ID) ), $post->ID, $context );
289
  }
290
-
291
  /**
292
- * Get the base URL of the container. For custom fields, the base URL is the permalink of
293
  * the post that the field is attached to.
294
  *
295
  * @return string
@@ -297,7 +297,7 @@ class blcPostMeta extends blcContainer {
297
  function base_url(){
298
  return get_permalink($this->container_id);
299
  }
300
-
301
  /**
302
  * Delete or trash the post corresponding to this container. If trash is enabled,
303
  * will always move the post to the trash instead of deleting.
@@ -322,10 +322,10 @@ class blcPostMeta extends blcContainer {
322
  }
323
  }
324
  }
325
-
326
  /**
327
  * Move the post corresponding to this custom field to the Trash.
328
- *
329
  * @return bool|WP_Error
330
  */
331
  function trash_wrapped_object(){
@@ -339,13 +339,13 @@ class blcPostMeta extends blcContainer {
339
  )
340
  );
341
  }
342
-
343
  $post = &get_post($this->container_id);
344
  if ( $post->post_status == 'trash' ){
345
  //Prevent conflicts between post and custom field containers trying to trash the same post.
346
  return true;
347
  }
348
-
349
  if ( wp_trash_post($this->container_id) ){
350
  return true;
351
  } else {
@@ -359,13 +359,13 @@ class blcPostMeta extends blcContainer {
359
  );
360
  }
361
  }
362
-
363
  function current_user_can_delete(){
364
  $post = get_post($this->container_id);
365
  $post_type_object = get_post_type_object($post->post_type);
366
  return current_user_can( $post_type_object->cap->delete_post, $this->container_id );
367
  }
368
-
369
  function can_be_trashed(){
370
  return defined('EMPTY_TRASH_DAYS') && EMPTY_TRASH_DAYS;
371
  }
@@ -375,7 +375,7 @@ class blcPostMetaManager extends blcContainerManager {
375
  var $container_class_name = 'blcPostMeta';
376
  var $meta_type = 'post';
377
  protected $selected_fields = array();
378
-
379
  function init(){
380
  parent::init();
381
 
@@ -395,7 +395,7 @@ class blcPostMetaManager extends blcContainerManager {
395
  }
396
  }
397
  }
398
-
399
  //Intercept 2.9+ style metadata modification actions
400
  add_action( "added_{$this->meta_type}_meta", array($this, 'meta_modified'), 10, 4 );
401
  add_action( "updated_{$this->meta_type}_meta", array($this, 'meta_modified'), 10, 4 );
@@ -404,63 +404,63 @@ class blcPostMetaManager extends blcContainerManager {
404
  //When a post is deleted, also delete the custom field container associated with it.
405
  add_action('delete_post', array($this,'post_deleted'));
406
  add_action('trash_post', array($this,'post_deleted'));
407
-
408
  //Re-parse custom fields when a post is restored from trash
409
  add_action('untrashed_post', array($this,'post_untrashed'));
410
  }
411
 
412
-
413
  /**
414
  * Get a list of parseable fields.
415
- *
416
  * @return array
417
  */
418
  function get_parseable_fields(){
419
  return $this->selected_fields;
420
  }
421
-
422
  /**
423
  * Instantiate multiple containers of the container type managed by this class.
424
  *
425
  * @param array $containers Array of assoc. arrays containing container data.
426
  * @param string $purpose An optional code indicating how the retrieved containers will be used.
427
- * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose.
428
- *
429
  * @return array of blcPostMeta indexed by "container_type|container_id"
430
  */
431
  function get_containers($containers, $purpose = '', $load_wrapped_objects = false){
432
  $containers = $this->make_containers($containers);
433
-
434
  /*
435
  When links from custom fields are displayed in Tools -> Broken Links,
436
  each one also shows the title of the post that the custom field(s)
437
  belong to. Thus it makes sense to pre-cache the posts beforehand - it's
438
  faster to load them all at once than to make a separate query for each
439
  one later.
440
-
441
  So make a list of involved post IDs and load them.
442
-
443
- Calling get_posts() will automatically populate the post cache, so we
444
  don't need to actually store the results anywhere in the container object().
445
- */
446
  $preload = $load_wrapped_objects || in_array($purpose, array(BLC_FOR_DISPLAY));
447
  if ( $preload ){
448
  $post_ids = array();
449
  foreach($containers as $container){
450
  $post_ids[] = $container->container_id;
451
  }
452
-
453
  $args = array('include' => implode(',', $post_ids));
454
  get_posts($args);
455
  }
456
-
457
  return $containers;
458
  }
459
-
460
  /**
461
  * Create or update synchronization records for all containers managed by this class.
462
  *
463
- * @param bool $forced If true, assume that all synch. records are gone and will need to be recreated from scratch.
464
  * @return void
465
  */
466
  function resynch($forced = false){
@@ -478,7 +478,7 @@ class blcPostMetaManager extends blcContainerManager {
478
  $escaped_post_types = "'" . implode("', '", array_map('esc_sql', $post_types)) . "'";
479
 
480
  if ( $forced ){
481
- //Create new synchronization records for all posts.
482
  $blclog->log('...... Creating synch records for all custom fields on ' . $escaped_post_types);
483
  $start = microtime(true);
484
  $q = "INSERT INTO {$wpdb->prefix}blc_synch(container_id, container_type, synched)
@@ -501,7 +501,7 @@ class blcPostMetaManager extends blcContainerManager {
501
  synch.container_type = '{$this->container_type}' AND posts.ID IS NULL";
502
  $wpdb->query( $q );
503
  $blclog->log(sprintf('...... %d rows deleted in %.3f seconds', $wpdb->rows_affected, microtime(true) - $start));
504
-
505
  //Remove the 'synched' flag from all posts that have been updated
506
  //since the last time they were parsed/synchronized.
507
  $blclog->log('...... Marking custom fields on changed posts as unsynched');
@@ -515,7 +515,7 @@ class blcPostMetaManager extends blcContainerManager {
515
  synch.last_synch < posts.post_modified";
516
  $wpdb->query( $q );
517
  $blclog->log(sprintf('...... %d rows updated in %.3f seconds', $wpdb->rows_affected, microtime(true) - $start));
518
-
519
  //Create synch. records for posts that don't have them.
520
  $blclog->log('...... Creating custom field synch records for new ' . $escaped_post_types);
521
  $start = microtime(true);
@@ -532,7 +532,7 @@ class blcPostMetaManager extends blcContainerManager {
532
  $blclog->log(sprintf('...... %d rows inserted in %.3f seconds', $wpdb->rows_affected, microtime(true) - $start));
533
  }
534
  }
535
-
536
  /**
537
  * Mark custom fields as unsynched when they're modified or deleted.
538
  *
@@ -544,27 +544,27 @@ class blcPostMetaManager extends blcContainerManager {
544
  */
545
  function meta_modified($meta_id, $object_id = 0, $meta_key= '', $meta_value = ''){
546
  global $wpdb; /** @var wpdb $wpdb */
547
-
548
- //If object_id isn't specified then the hook was probably called from the
549
  //stupidly inconsistent delete_meta() function in /wp-admin/includes/post.php.
550
  if ( empty($object_id) ){
551
  //We must manually retrieve object_id and meta_key from the DB.
552
  if ( is_array($meta_id) ){
553
  $meta_id = array_shift($meta_id);
554
  }
555
-
556
  $meta = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $wpdb->postmeta WHERE meta_id = %d", $meta_id), ARRAY_A );
557
  if ( empty($meta) ){
558
  return;
559
  }
560
-
561
  $object_id = $meta['post_id'];
562
  $meta_key = $meta['meta_key'];
563
  }
564
-
565
-
566
- //Metadata changes only matter to us if the modified key
567
- //is one that the user wants checked.
568
  if ( empty($this->selected_fields) ){
569
  return;
570
  }
@@ -581,7 +581,7 @@ class blcPostMetaManager extends blcContainerManager {
581
  $container = blcContainerHelper::get_container( array($this->container_type, intval($object_id)) );
582
  $container->mark_as_unsynched();
583
  }
584
-
585
  /**
586
  * Delete custom field synch. records when the post that they belong to is deleted.
587
  *
@@ -590,14 +590,17 @@ class blcPostMetaManager extends blcContainerManager {
590
  */
591
  function post_deleted($post_id){
592
  //Get the associated container object
593
- $container = blcContainerHelper::get_container( array($this->container_type, intval($post_id)) );
594
- //Delete it
595
- $container->delete();
596
- //Clean up any dangling links
597
- blc_cleanup_links();
598
- }
599
-
600
- /**
 
 
 
601
  * When a post is restored, mark all of its custom fields as unparsed.
602
  * Called via the 'untrashed_post' action.
603
  *
@@ -609,11 +612,11 @@ class blcPostMetaManager extends blcContainerManager {
609
  $container = blcContainerHelper::get_container( array($this->container_type, intval($post_id)) );
610
  $container->mark_as_unsynched();
611
  }
612
-
613
  /**
614
  * Get the message to display after $n posts have been deleted.
615
  *
616
- * @uses blcAnyPostContainerManager::ui_bulk_delete_message()
617
  *
618
  * @param int $n Number of deleted posts.
619
  * @return string A delete confirmation message, e.g. "5 posts were moved to the trash"
@@ -621,7 +624,7 @@ class blcPostMetaManager extends blcContainerManager {
621
  function ui_bulk_delete_message($n){
622
  return blcAnyPostContainerManager::ui_bulk_delete_message($n);
623
  }
624
-
625
  /**
626
  * Get the message to display after $n posts have been trashed.
627
  *
12
  */
13
 
14
  //Note : If it ever becomes necessary to check metadata on objects other than posts, it will
15
+ //be fairly easy to extract a more general metadata container class from blcPostMeta.
16
 
17
  /**
18
  * blcPostMeta - A link container class for post metadata (AKA custom fields).
19
  *
20
  * Due to the way metadata works, this container differs significantly from other containers :
21
  * - container_field is equal to meta name, and container_id holds the ID of the post.
22
+ * - There is one synch. record per post that determines the synch. state of all metadata fields of that post.
23
  * - Unlinking simply deletes the meta entry in question without involving the parser.
24
+ * - The list of parse-able $fields is not fixed. Instead, it's initialized based on the
25
+ * custom field list defined in Settings -> Link Checker.
26
  * - The $wrapped_object is an array (and isn't really used for anything).
27
  * - update_wrapped_object() does nothing.
28
+ *
29
  * @package Broken Link Checker
30
  * @access public
31
  */
32
  class blcPostMeta extends blcContainer {
33
+
34
  var $meta_type = 'post';
35
+
36
  /**
37
  * Retrieve all metadata fields of the post associated with this container.
38
  * The results are cached in the internal $wrapped_object variable.
39
+ *
40
+ * @param bool $ensure_consistency
41
  * @return object The wrapped object.
42
  */
43
  function get_wrapped_object($ensure_consistency = false){
45
  $this->wrapped_object = get_metadata($this->meta_type, $this->container_id);
46
  }
47
  return $this->wrapped_object;
48
+ }
49
+
50
  function update_wrapped_object(){
51
  trigger_error('Function blcPostMeta::update_wrapped_object() does nothing and should not be used.', E_USER_WARNING);
52
  }
53
+
54
  /**
55
  * Get the value of the specified metadata field of the object wrapped by this container.
56
+ *
57
  * @access protected
58
  *
59
+ * @param string $field Field name. If omitted, the value of the default field will be returned.
60
  * @return array
61
  */
62
  function get_field($field = ''){
63
  $get_only_first_field = ($this->fields[$field] !== 'metadata');
64
  return get_metadata($this->meta_type, $this->container_id, $field, $get_only_first_field);
65
  }
66
+
67
  /**
68
+ * Update the value of the specified metadata field of the object wrapped by this container.
69
  *
70
  * @access protected
71
  *
72
  * @param string $field Meta name.
73
+ * @param string $new_value New meta value.
74
+ * @param string $old_value old meta value.
75
  * @return bool|WP_Error True on success, an error object if something went wrong.
76
  */
77
  function update_field($field, $new_value, $old_value = ''){
82
  return new WP_Error(
83
  'metadata_update_failed',
84
  sprintf(
85
+ __("Failed to update the meta field '%s' on %s [%d]", 'broken-link-checker'),
86
+ $field,
87
+ $this->meta_type,
88
  $this->container_id
89
  )
90
  );
112
  return new WP_Error(
113
  'metadata_delete_failed',
114
  sprintf(
115
+ __("Failed to delete the meta field '%s' on %s [%d]", 'broken-link-checker'),
116
  $field_name,
117
+ $this->meta_type,
118
  $this->container_id
119
  )
120
  );
138
  'Editing %s[%d]:%s - %s to %s',
139
  $this->container_type,
140
  $this->container_id,
141
+ $field_name,
142
  $old_url,
143
  $new_url
144
  ));
147
  if ( $this->fields[$field_name] !== 'metadata' ) {
148
  return parent::edit_link($field_name, $parser, $new_url, $old_url, $old_raw_url, $new_text);
149
  }
150
+
151
  if ( empty($old_raw_url) ){
152
  $old_raw_url = $old_url;
153
  }
154
+
155
  //Get the current values of the field that needs to be edited.
156
  //The default metadata parser ignores them, but we're still going
157
+ //to set this argument to a valid value in case someone writes a
158
  //custom meta parser that needs it.
159
  $old_value = $this->get_field($field_name);
160
+
161
  //Get the new field value (a string).
162
  $edit_result = $parser->edit($old_value, $new_url, $old_url, $old_raw_url);
163
  if ( is_wp_error($edit_result) ){
164
  return $edit_result;
165
  }
166
+
167
  //Update the field with the new value returned by the parser.
168
  //Notice how $old_raw_url is used instead of $old_value. $old_raw_url contains the entire old
169
  //value of the metadata field (see blcMetadataParser::parse()) and thus can be used to
170
+ //differentiate between multiple meta fields with identical names.
171
  $update_result = $this->update_field( $field_name, $edit_result['content'], $old_raw_url );
172
  if ( is_wp_error($update_result) ){
173
  return $update_result;
174
  }
175
+
176
  //Return the new "raw" URL.
177
  return $edit_result['raw_url'];
178
  }
179
+
180
  /**
181
  * Get the default link text to use for links found in a specific container field.
182
  *
189
  //the user a clue where to look if they want to find/modify the field.
190
  return $field;
191
  }
192
+
193
  function ui_get_source($container_field = '', $context = 'display'){
194
  if ( !post_type_exists(get_post_type($this->container_id)) ) {
195
  //Error: Invalid post type. The user probably removed a CPT without removing the actual posts.
216
  esc_attr(__('Edit this post')),
217
  get_the_title($this->container_id)
218
  );
219
+
220
  return $post_html;
221
  }
222
+
223
  function ui_get_action_links($container_field){
224
  $actions = array();
225
  if ( !post_type_exists(get_post_type($this->container_id)) ) {
228
 
229
  if ( current_user_can('edit_post', $this->container_id) ) {
230
  $actions['edit'] = '<span class="edit"><a href="' . $this->get_edit_url() . '" title="' . esc_attr(__('Edit this item')) . '">' . __('Edit') . '</a>';
231
+
232
  if ( $this->current_user_can_delete() ){
233
  if ( $this->can_be_trashed() ) {
234
  $actions['trash'] = sprintf(
248
  }
249
  }
250
  $actions['view'] = '<span class="view"><a href="' . esc_url(get_permalink($this->container_id)) . '" title="' . esc_attr(sprintf(__('View "%s"', 'broken-link-checker'), get_the_title($this->container_id))) . '" rel="permalink">' . __('View') . '</a>';
251
+
252
  return $actions;
253
  }
254
+
255
  /**
256
+ * Get edit URL for this container. Returns the URL of the Dashboard page where the item
257
  * associated with this container can be edited.
258
  *
259
+ * @access protected
260
  *
261
  * @return string
262
  */
263
  function get_edit_url(){
264
  /*
265
+ The below is a near-exact copy of the get_post_edit_link() function.
266
+ Unfortunately we can't just call that function because it has a hardcoded
267
+ caps-check which fails when called from the email notification script
268
  executed by Cron.
269
+ */
270
+
271
  if ( !($post = get_post( $this->container_id )) ){
272
  return '';
273
  }
274
+
275
  $context = 'display';
276
+
277
  //WP 3.0
278
  if ( 'display' == $context )
279
  $action = '&amp;action=edit';
280
  else
281
  $action = '&action=edit';
282
+
283
  $post_type_object = get_post_type_object( $post->post_type );
284
  if ( !$post_type_object ){
285
  return '';
286
  }
287
+
288
  return apply_filters( 'get_edit_post_link', admin_url( sprintf($post_type_object->_edit_link . $action, $post->ID) ), $post->ID, $context );
289
  }
290
+
291
  /**
292
+ * Get the base URL of the container. For custom fields, the base URL is the permalink of
293
  * the post that the field is attached to.
294
  *
295
  * @return string
297
  function base_url(){
298
  return get_permalink($this->container_id);
299
  }
300
+
301
  /**
302
  * Delete or trash the post corresponding to this container. If trash is enabled,
303
  * will always move the post to the trash instead of deleting.
322
  }
323
  }
324
  }
325
+
326
  /**
327
  * Move the post corresponding to this custom field to the Trash.
328
+ *
329
  * @return bool|WP_Error
330
  */
331
  function trash_wrapped_object(){
339
  )
340
  );
341
  }
342
+
343
  $post = &get_post($this->container_id);
344
  if ( $post->post_status == 'trash' ){
345
  //Prevent conflicts between post and custom field containers trying to trash the same post.
346
  return true;
347
  }
348
+
349
  if ( wp_trash_post($this->container_id) ){
350
  return true;
351
  } else {
359
  );
360
  }
361
  }
362
+
363
  function current_user_can_delete(){
364
  $post = get_post($this->container_id);
365
  $post_type_object = get_post_type_object($post->post_type);
366
  return current_user_can( $post_type_object->cap->delete_post, $this->container_id );
367
  }
368
+
369
  function can_be_trashed(){
370
  return defined('EMPTY_TRASH_DAYS') && EMPTY_TRASH_DAYS;
371
  }
375
  var $container_class_name = 'blcPostMeta';
376
  var $meta_type = 'post';
377
  protected $selected_fields = array();
378
+
379
  function init(){
380
  parent::init();
381
 
395
  }
396
  }
397
  }
398
+
399
  //Intercept 2.9+ style metadata modification actions
400
  add_action( "added_{$this->meta_type}_meta", array($this, 'meta_modified'), 10, 4 );
401
  add_action( "updated_{$this->meta_type}_meta", array($this, 'meta_modified'), 10, 4 );
404
  //When a post is deleted, also delete the custom field container associated with it.
405
  add_action('delete_post', array($this,'post_deleted'));
406
  add_action('trash_post', array($this,'post_deleted'));
407
+
408
  //Re-parse custom fields when a post is restored from trash
409
  add_action('untrashed_post', array($this,'post_untrashed'));
410
  }
411
 
412
+
413
  /**
414
  * Get a list of parseable fields.
415
+ *
416
  * @return array
417
  */
418
  function get_parseable_fields(){
419
  return $this->selected_fields;
420
  }
421
+
422
  /**
423
  * Instantiate multiple containers of the container type managed by this class.
424
  *
425
  * @param array $containers Array of assoc. arrays containing container data.
426
  * @param string $purpose An optional code indicating how the retrieved containers will be used.
427
+ * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose.
428
+ *
429
  * @return array of blcPostMeta indexed by "container_type|container_id"
430
  */
431
  function get_containers($containers, $purpose = '', $load_wrapped_objects = false){
432
  $containers = $this->make_containers($containers);
433
+
434
  /*
435
  When links from custom fields are displayed in Tools -> Broken Links,
436
  each one also shows the title of the post that the custom field(s)
437
  belong to. Thus it makes sense to pre-cache the posts beforehand - it's
438
  faster to load them all at once than to make a separate query for each
439
  one later.
440
+
441
  So make a list of involved post IDs and load them.
442
+
443
+ Calling get_posts() will automatically populate the post cache, so we
444
  don't need to actually store the results anywhere in the container object().
445
+ */
446
  $preload = $load_wrapped_objects || in_array($purpose, array(BLC_FOR_DISPLAY));
447
  if ( $preload ){
448
  $post_ids = array();
449
  foreach($containers as $container){
450
  $post_ids[] = $container->container_id;
451
  }
452
+
453
  $args = array('include' => implode(',', $post_ids));
454
  get_posts($args);
455
  }
456
+
457
  return $containers;
458
  }
459
+
460
  /**
461
  * Create or update synchronization records for all containers managed by this class.
462
  *
463
+ * @param bool $forced If true, assume that all synch. records are gone and will need to be recreated from scratch.
464
  * @return void
465
  */
466
  function resynch($forced = false){
478
  $escaped_post_types = "'" . implode("', '", array_map('esc_sql', $post_types)) . "'";
479
 
480
  if ( $forced ){
481
+ //Create new synchronization records for all posts.
482
  $blclog->log('...... Creating synch records for all custom fields on ' . $escaped_post_types);
483
  $start = microtime(true);
484
  $q = "INSERT INTO {$wpdb->prefix}blc_synch(container_id, container_type, synched)
501
  synch.container_type = '{$this->container_type}' AND posts.ID IS NULL";
502
  $wpdb->query( $q );
503
  $blclog->log(sprintf('...... %d rows deleted in %.3f seconds', $wpdb->rows_affected, microtime(true) - $start));
504
+
505
  //Remove the 'synched' flag from all posts that have been updated
506
  //since the last time they were parsed/synchronized.
507
  $blclog->log('...... Marking custom fields on changed posts as unsynched');
515
  synch.last_synch < posts.post_modified";
516
  $wpdb->query( $q );
517
  $blclog->log(sprintf('...... %d rows updated in %.3f seconds', $wpdb->rows_affected, microtime(true) - $start));
518
+
519
  //Create synch. records for posts that don't have them.
520
  $blclog->log('...... Creating custom field synch records for new ' . $escaped_post_types);
521
  $start = microtime(true);
532
  $blclog->log(sprintf('...... %d rows inserted in %.3f seconds', $wpdb->rows_affected, microtime(true) - $start));
533
  }
534
  }
535
+
536
  /**
537
  * Mark custom fields as unsynched when they're modified or deleted.
538
  *
544
  */
545
  function meta_modified($meta_id, $object_id = 0, $meta_key= '', $meta_value = ''){
546
  global $wpdb; /** @var wpdb $wpdb */
547
+
548
+ //If object_id isn't specified then the hook was probably called from the
549
  //stupidly inconsistent delete_meta() function in /wp-admin/includes/post.php.
550
  if ( empty($object_id) ){
551
  //We must manually retrieve object_id and meta_key from the DB.
552
  if ( is_array($meta_id) ){
553
  $meta_id = array_shift($meta_id);
554
  }
555
+
556
  $meta = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $wpdb->postmeta WHERE meta_id = %d", $meta_id), ARRAY_A );
557
  if ( empty($meta) ){
558
  return;
559
  }
560
+
561
  $object_id = $meta['post_id'];
562
  $meta_key = $meta['meta_key'];
563
  }
564
+
565
+
566
+ //Metadata changes only matter to us if the modified key
567
+ //is one that the user wants checked.
568
  if ( empty($this->selected_fields) ){
569
  return;
570
  }
581
  $container = blcContainerHelper::get_container( array($this->container_type, intval($object_id)) );
582
  $container->mark_as_unsynched();
583
  }
584
+
585
  /**
586
  * Delete custom field synch. records when the post that they belong to is deleted.
587
  *
590
  */
591
  function post_deleted($post_id){
592
  //Get the associated container object
593
+
594
+ $container = blcContainerHelper::get_container([$this->container_type, intval($post_id)]);
595
+ if ($container != null) {
596
+ //Delete it
597
+ $container->delete();
598
+ //Clean up any dangling links
599
+ blc_cleanup_links();
600
+ }
601
+ }
602
+
603
+ /**
604
  * When a post is restored, mark all of its custom fields as unparsed.
605
  * Called via the 'untrashed_post' action.
606
  *
612
  $container = blcContainerHelper::get_container( array($this->container_type, intval($post_id)) );
613
  $container->mark_as_unsynched();
614
  }
615
+
616
  /**
617
  * Get the message to display after $n posts have been deleted.
618
  *
619
+ * @uses blcAnyPostContainerManager::ui_bulk_delete_message()
620
  *
621
  * @param int $n Number of deleted posts.
622
  * @return string A delete confirmation message, e.g. "5 posts were moved to the trash"
624
  function ui_bulk_delete_message($n){
625
  return blcAnyPostContainerManager::ui_bulk_delete_message($n);
626
  }
627
+
628
  /**
629
  * Get the message to display after $n posts have been trashed.
630
  *
modules/parsers/acf_field.php ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Plugin Name: ACF
4
+ Description: Parses acf fields (AKA custom fields)
5
+ Version: 1.0
6
+ Author: Janne Aalto
7
+
8
+ ModuleID: acf
9
+ ModuleCategory: parser
10
+ ModuleClassName: blcACFParser
11
+ ModuleContext: on-demand
12
+ ModuleLazyInit: true
13
+ ModuleAlwaysActive: true
14
+ ModuleHidden: true
15
+ */
16
+
17
+ class blcACFParser extends blcParser {
18
+
19
+ var $supported_formats = ['acf_field'];
20
+ var $supported_containers = [];
21
+
22
+ /**
23
+ * Parse a acf value.
24
+ *
25
+ * @param string|array $content ACF value(s).
26
+ * @param string $base_url The base URL to use for normalizing relative URLs. If ommitted, the blog's root URL will be used.
27
+ * @param string $default_link_text
28
+ * @return array An array of new blcLinkInstance objects.
29
+ */
30
+ function parse($content, $base_url = '', $default_link_text = ''){
31
+ $instances = [];
32
+
33
+ if ( !is_array($content) ){
34
+ $content = [$content];
35
+ }
36
+
37
+ foreach($content as $value){
38
+ //The complete contents of the meta field are stored in raw_url.
39
+ //This is useful for editing/unlinking, when one may need to
40
+ //distinguish between multiple fields with the same name.
41
+ $raw_url = $value;
42
+
43
+ //If this is a multiline acf field take only the first line (workaround for the 'enclosure' field).
44
+ $lines = explode("\n", $value);
45
+ $url = trim(reset($lines));
46
+
47
+ //Attempt to parse the URL
48
+ $parts = @parse_url($url);
49
+ if(!$parts) {
50
+ return $instances; //Ignore invalid URLs
51
+ };
52
+
53
+ if ( !isset($parts['scheme']) ){
54
+ //No scheme - likely a relative URL. Turn it into an absolute one.
55
+ $url = $this->relative2absolute($url, $base_url);
56
+
57
+ //Skip invalid URLs (again)
58
+ if ( !$url || (strlen($url)<6) ) {
59
+ return $instances;
60
+ }
61
+ }
62
+
63
+ //The URL is okay, create and populate a new link instance.
64
+ $instance = new blcLinkInstance();
65
+
66
+ $instance->set_parser($this);
67
+ $instance->raw_url = $raw_url;
68
+ $instance->link_text = $default_link_text;
69
+
70
+ $link_obj = new blcLink($url); //Creates or loads the link
71
+ $instance->set_link($link_obj);
72
+
73
+ $instances[] = $instance;
74
+ }
75
+
76
+ return $instances;
77
+ }
78
+
79
+ /**
80
+ * Change the URL in a acf field to another one.
81
+ *
82
+ * This is tricky because there can be multiple acf fields with the same name
83
+ * but different values. So we ignore $content (which might be an array of multiple
84
+ * acf values) and use the old raw_url that we stored when parsing the field(s)
85
+ * instead.
86
+ *
87
+ * @see blcACFParser::parse()
88
+ *
89
+ * @param string $content Ignored.
90
+ * @param string $new_url The new URL.
91
+ * @param string $old_url Ignored.
92
+ * @param string $old_raw_url The current meta value.
93
+ *
94
+ * @return array|WP_Error
95
+ */
96
+ function edit($content, $new_url, $old_url, $old_raw_url){
97
+ //For multiline fields (like 'enclosure') we only want to change the first line.
98
+ $lines = explode("\n", $old_raw_url);
99
+ array_shift($lines); //Discard the old first line
100
+ array_unshift($lines, $new_url); //Insert the new URL in its place.
101
+ $content = implode("\n", $lines);
102
+
103
+ return array(
104
+ 'content' => $content,
105
+ 'raw_url' => $new_url,
106
+ );
107
+ }
108
+
109
+ /**
110
+ * Get the link text for printing in the "Broken Links" table.
111
+ *
112
+ * @param blcLinkInstance $instance
113
+ * @param string $context
114
+ * @return string HTML
115
+ */
116
+ function ui_get_link_text($instance, $context = 'display'){
117
+ $image_html = sprintf(
118
+ '<img src="%s" class="blc-small-image" title="%2$s" alt="%2$s"> ',
119
+ esc_attr( plugins_url('/images/font-awesome/font-awesome-code.png', BLC_PLUGIN_FILE) ),
120
+ __('Custom field', 'broken-link-checker')
121
+ );
122
+
123
+ $field_html = sprintf(
124
+ '<code>%s</code>',
125
+ $instance->container_field
126
+ );
127
+
128
+ if ( $context != 'email' ){
129
+ $field_html = $image_html . $field_html;
130
+ }
131
+
132
+ return $field_html;
133
+ }
134
+ }
readme.txt CHANGED
@@ -4,7 +4,7 @@ Donate link:
4
  Tags: links, broken, maintenance, blogroll, custom fields, admin, comments, posts
5
  Requires at least: 3.2
6
  Tested up to: 4.8
7
- Stable tag: 1.11.3
8
 
9
  This plugin will check your posts, comments and other content for broken links and missing images, and notify you if any are found.
10
 
@@ -101,6 +101,9 @@ To upgrade your installation
101
 
102
  == Changelog ==
103
 
 
 
 
104
  = 1.11.3 =
105
  * Updated plugin information
106
 
4
  Tags: links, broken, maintenance, blogroll, custom fields, admin, comments, posts
5
  Requires at least: 3.2
6
  Tested up to: 4.8
7
+ Stable tag: 1.11.4
8
 
9
  This plugin will check your posts, comments and other content for broken links and missing images, and notify you if any are found.
10
 
101
 
102
  == Changelog ==
103
 
104
+ = 1.11.4 =
105
+ * Fixed a few more PHP 7.x/5.6 compatibility issues
106
+
107
  = 1.11.3 =
108
  * Updated plugin information
109
 
uninstall.php CHANGED
@@ -1,22 +1,21 @@
1
- <?php
2
-
3
- /**
4
- * @author Janis Elsts
5
- * @copyright 2010
6
- *
7
- * The terrifying uninstallation script.
8
- */
9
-
10
- if( defined( 'ABSPATH') && defined('WP_UNINSTALL_PLUGIN') ) {
11
-
12
- //Remove the plugin's settings & installation log
13
- delete_option('wsblc_options');
14
- delete_option('blc_installation_log');
15
-
16
- //Remove the database tables
17
- $mywpdb = $GLOBALS['wpdb'];
18
- if( isset($mywpdb) ) { /** @var wpdb $mywpdb */
19
- //EXTERMINATE!
20
- $mywpdb->query( "DROP TABLE IF EXISTS {$mywpdb->prefix}blc_linkdata, {$mywpdb->prefix}blc_postdata, {$mywpdb->prefix}blc_instances, {$mywpdb->prefix}blc_links, {$mywpdb->prefix}blc_synch, {$mywpdb->prefix}blc_filters" );
21
- }
22
- }
1
+ <?php
2
+ /**
3
+ * @author Janis Elsts
4
+ * @copyright 2010
5
+ *
6
+ * The terrifying uninstallation script.
7
+ */
8
+
9
+ if ( defined( 'ABSPATH' ) && defined( 'WP_UNINSTALL_PLUGIN' ) ) {
10
+
11
+ // Remove the plugin's settings & installation log.
12
+ delete_option( 'wsblc_options' );
13
+ delete_option( 'blc_installation_log' );
14
+
15
+ // Remove the database tables.
16
+ $mywpdb = $GLOBALS['wpdb'];
17
+ if( isset( $mywpdb ) ) { /** @var wpdb $mywpdb */
18
+ // EXTERMINATE!
19
+ $mywpdb->query( "DROP TABLE IF EXISTS {$mywpdb->prefix}blc_linkdata, {$mywpdb->prefix}blc_postdata, {$mywpdb->prefix}blc_instances, {$mywpdb->prefix}blc_links, {$mywpdb->prefix}blc_synch, {$mywpdb->prefix}blc_filters" );
20
+ }
21
+ }